Jekyll2022-10-08T14:27:54+00:00https://blog.gbplay.io/feed.xmlGBPlay BlogGBPlay aims to enable Game Boy and Game Boy Color multiplayer over the internet using original hardware. It is currently in an early/exploratory stage. Stay tuned for more updates as the project progresses.Building a Link Cable Server2022-09-04T00:00:00+00:002022-09-04T00:00:00+00:00https://blog.gbplay.io/2022/09/04/Building-a-Link-Cable-Server<figure class="centered">
<a href="/images/tetris_link_nodejs.jpg" title="Our prior analysis now allows us to build a stable, future-proof server
" target="_blank">
<img src="/images/tetris_link_nodejs.jpg" />
</a>
<figcaption><p>Our prior analysis now allows us to build a stable, future-proof server</p>
</figcaption>
</figure>
<h2 id="learning-from-experience">Learning from experience</h2>
<p>Until this point, our networking code has relied on several assumptions such as
the idea that naive byte forwarding would be sufficient to enable games to
operate properly after slave mode initialization. After
<a href="/2022/07/24/Multi-Game-Link-Cable-Protocol-Analysis.html">research and experimentation</a>, these assumptions have
proven to be incorrect. We <a href="/2021/05/10/An-8-Bit-Idea_The-Internet-of-Game-Boys.html
#next-steps">knew</a> we
<a href="/2021/06/13/Beyond-Serial_Linking-Game-Boys-the-Hard-Way.html
#connecting-game-boys-through-a-pc">could be</a> wrong,
and we were. This is why we didn’t invest too much time in creating the existing
test scripts. However, we now have a much better idea of the different kinds of
usage patterns we must support and therefore more confidence going forward. It’s
time to replace our experimental TCP serial bridge script with a fully-featured
backend server.</p>
<p>Due to the multitude of ways games can use the link cable, the new server cannot
be game-agnostic. To compensate, it must provide high-level abstractions and be
as flexible as possible. Even though we have a relatively thorough understanding
of link cable usage, the server should still be easily extensible to enable new
features and refactoring as the project evolves or new edge cases are
discovered. It’s better to err on the side of caution than try to get away with
shortcuts like we’ve been doing until now – they won’t apply to all games and
will make the code harder to maintain.</p>
<p>After building the necessary server infrastructure
<a href="/2022/07/24/Multi-Game-Link-Cable-Protocol-Analysis.html
#conclusion">identified</a> in our prior analysis, we will add
support for the simplest and most latency-tolerant game we have studied so far:
<a href="/2022/07/24/Multi-Game-Link-Cable-Protocol-Analysis.html
#tetris">Tetris</a>. Its protocol is very forgiving and does
not require every feature we have seen. Instead, it will serve as a solid
foundation to build an end-to-end vertical slice around (server, client,
front-end, etc.). More complexity (and games) can then be added once everything
is working at all layers of the stack.</p>
<h2 id="server-technologies-and-architecture">Server technologies and architecture</h2>
<p>We chose to write the server in TypeScript and run on Node.js. Node’s
asynchronous IO is perfect for a high-latency application like this. We will be
sending many small packets with minimal logic in between. The primary bottleneck
is going to be network latency. Lower-level technologies would offer little to
no performance benefit in practice at the cost of having to re-implement useful
abstractions and features that we get here for free (e.g., event loop, async IO,
easy networking, etc.). On the language side TypeScript is concise and flexible
yet strict, reducing boilerplate and facilitating rapid iteration with
confidence. In summary, our chosen backend stack is robust and scalable. It
allows us to focus on project-related logic rather than unnecessary low-level
details. Using web-friendly technologies from the start will also make it easier
to eventually add a front-end when the time comes.</p>
<p>With the stack decided upon, the next step is implementation. Due to the fact
that server-side logic will be required for most (if not all) games, it is
important to abstract away as much as possible so that per-game code can be
simple and only need to read and write bytes. We started by creating 2 classes
to handle network connections and game sessions:</p>
<table>
<thead>
<tr>
<th>Class</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code class="language-plaintext highlighter-rouge">GameBoyClient</code></td>
<td>This <a href="https://github.com/mwpenny/gbplay/blob/1dd98eac7a0c297391e6b9775c5308194eb90866/server/src/client.ts">class</a> manages the network connection for a single player (i.e., Game Boy). It is able to send and receive bytes, detect dropped connections, control the transfer delay, and forward data between itself and another client (naive byte forwarding) with the option to monitor the transmission.</td>
</tr>
<tr>
<td><code class="language-plaintext highlighter-rouge">GameSession</code></td>
<td>This is an abstract <a href="https://github.com/mwpenny/gbplay/blob/1dd98eac7a0c297391e6b9775c5308194eb90866/server/src/game-session.ts">class</a> which corresponds to a single game that multiple <code class="language-plaintext highlighter-rouge">GameBoyClient</code>s are connected to. Each supported game requires a subclass which implements any game-specific logic. The base class logic keeps track of whether the game is joinable and is responsible for ending itself when there is an error or a client disconnects. It also runs a state machine which handles calling the appropriate subclass function depending on game state.</td>
</tr>
</tbody>
</table>
<p>When the
<a href="https://github.com/mwpenny/gbplay/blob/1dd98eac7a0c297391e6b9775c5308194eb90866/server/src/server.ts">server</a>
is started, it listens for incoming TCP connections. Since this is still just
using TCP, our existing tools – namely the
<a href="/2021/06/13/Beyond-Serial_Linking-Game-Boys-the-Hard-Way.html
#networking-game-boys">TCP serial client script</a>
used to connect to real hardware – will work here with no modifications needed.
Upon receiving a connection, a new <code class="language-plaintext highlighter-rouge">GameBoyClient</code> instance is created to manage
it and the server looks for an open <code class="language-plaintext highlighter-rouge">GameSession</code> for the client to join. A new
session is created if none exist. Sessions know when they’re full and will wait
until enough players have joined before starting their main loop. For now,
players cannot configure the game being played or the specific session to join.
The game is hard-coded to Tetris and clients will join the first session
available. Choice of game and session will come later after more of the stack
has been built up.</p>
<p>Although the server is currently small, we are already able to take advantage of
some of the benefits of Node.js and TypeScript. Detection of dropped client
connections and ended games is accomplished using Node’s
<a href="https://nodejs.org/api/events.html#class-eventemitter">EventEmitter</a> class. A
<code class="language-plaintext highlighter-rouge">GameBoyClient</code> emits a <code class="language-plaintext highlighter-rouge">disconnect</code> event when relevant, which its
<code class="language-plaintext highlighter-rouge">GameSession</code> listens for and reacts to by ending the game. When this happens,
the session emits its own <code class="language-plaintext highlighter-rouge">end</code> event which the top-level session management
code uses to update the list of active sessions. Node’s event loop takes care of
dispatching these events, allowing us to easily react asynchronously with only a
small amount of code.</p>
<p>When it comes to the <code class="language-plaintext highlighter-rouge">GameSession</code> subclasses, we want to be able to create each
new game-specific implementation quickly and easily with minimal duplication.
There will be many of these classes and so it is important to limit them to pure
game protocol logic, removing infrastructure-related code whenever possible.
TypeScript’s
<a href="https://www.typescriptlang.org/docs/handbook/decorators.html">decorator</a>
feature allows us to do just that. Specifically, we use
<a href="https://www.typescriptlang.org/docs/handbook/decorators.html#decorator-factories">decorator factories</a>
to add each state handler function to a static lookup map so the base
<code class="language-plaintext highlighter-rouge">GameSession</code> logic knows when and how to call them. With this, all that is
needed to implement a new game is a class like the following:</p>
<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">enum</span> <span class="nx">MyGameState</span> <span class="p">{</span>
<span class="nx">Menus</span><span class="p">,</span>
<span class="nx">InGame</span>
<span class="p">};</span>
<span class="kd">class</span> <span class="nx">MyGame</span> <span class="kd">extends</span> <span class="nx">GameSession</span>
<span class="p">{</span>
<span class="p">@</span><span class="nd">stateHandler</span><span class="p">(</span><span class="nx">MyGameState</span><span class="p">.</span><span class="nx">Menus</span><span class="p">)</span>
<span class="nx">handleMenus</span><span class="p">()</span> <span class="p">{</span>
<span class="c1">// Menu logic...</span>
<span class="k">this</span><span class="p">.</span><span class="nx">state</span> <span class="o">=</span> <span class="nx">MyGameState</span><span class="p">.</span><span class="nx">InGame</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">@</span><span class="nd">stateHandler</span><span class="p">(</span><span class="nx">MyGameState</span><span class="p">.</span><span class="nx">InGame</span><span class="p">)</span>
<span class="nx">handleInGame</span><span class="p">()</span> <span class="p">{</span>
<span class="c1">// Gameplay logic...</span>
<span class="k">this</span><span class="p">.</span><span class="nx">state</span> <span class="o">=</span> <span class="nx">MyGameState</span><span class="p">.</span><span class="nx">Menus</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">};</span>
</code></pre></div></div>
<p>The <code class="language-plaintext highlighter-rouge">stateHandler</code> decorator automatically sets up the function dispatch
mechanism and nothing else needs to be done. The <code class="language-plaintext highlighter-rouge">GameSession</code>’s main loop will
call <code class="language-plaintext highlighter-rouge">handleMenus()</code> when the game is in the <code class="language-plaintext highlighter-rouge">Menus</code> state and <code class="language-plaintext highlighter-rouge">handleInGame()</code>
when in the <code class="language-plaintext highlighter-rouge">InGame</code> state. This keeps all management-related code (except state
changes) out of the game logic classes which makes it simple to create new ones.
It is also very readable and easy to see what each function is for.</p>
<h2 id="game-specific-logic">Game-specific logic</h2>
<p>With overall server structure and state handling taken care of, what remains is
the game state logic itself and how it interacts with clients. For example,
we’ve seen that many games require synchronization transfers: polling with a
particular value until a specific byte is received to indicate the connected
game is in a known state. Code and effort to implement common operations like
this should not be duplicated. To achieve this we abstracted as many of them as
possible. There are helper functions for:</p>
<ul>
<li>Waiting until a client has responded with a specified value</li>
<li>Transferring a buffer to a client</li>
<li>Adjusting the delay between each data transfer</li>
<li>Performing the same operations for all clients</li>
<li>Byte forwarding between clients, with the ability to monitor the data</li>
</ul>
<p>These helpers allow the communication code for each state handler to be reduced
to a combination of primitive operations. For example, here is how Tetris’
pre-round initialization is implemented:</p>
<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">@</span><span class="nd">stateHandler</span><span class="p">(</span><span class="nx">TetrisGameState</span><span class="p">.</span><span class="nx">SendingInitializationData</span><span class="p">)</span>
<span class="k">async</span> <span class="nx">handleSendingInitializationData</span><span class="p">()</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">garbageLineData</span> <span class="o">=</span> <span class="nx">generateGarbageLines</span><span class="p">();</span>
<span class="kd">const</span> <span class="nx">pieceData</span> <span class="o">=</span> <span class="nx">generatePieces</span><span class="p">();</span>
<span class="c1">// Send global data</span>
<span class="k">await</span> <span class="k">this</span><span class="p">.</span><span class="nx">forAllClients</span><span class="p">(</span><span class="k">async</span> <span class="nx">c</span> <span class="o">=></span> <span class="p">{</span>
<span class="c1">// This is a lot of data, and timing requirements aren't as strict</span>
<span class="nx">c</span><span class="p">.</span><span class="nx">setSendDelayMs</span><span class="p">(</span><span class="mi">5</span><span class="p">);</span>
<span class="k">await</span> <span class="nx">c</span><span class="p">.</span><span class="nx">waitForByte</span><span class="p">(</span><span class="nx">TetrisCtrlByte</span><span class="p">.</span><span class="nx">Master</span><span class="p">,</span> <span class="nx">TetrisCtrlByte</span><span class="p">.</span><span class="nx">Slave</span><span class="p">);</span>
<span class="k">await</span> <span class="nx">c</span><span class="p">.</span><span class="nx">sendBuffer</span><span class="p">(</span><span class="nx">garbageLineData</span><span class="p">);</span>
<span class="k">await</span> <span class="nx">c</span><span class="p">.</span><span class="nx">waitForByte</span><span class="p">(</span><span class="nx">TetrisCtrlByte</span><span class="p">.</span><span class="nx">Master</span><span class="p">,</span> <span class="nx">TetrisCtrlByte</span><span class="p">.</span><span class="nx">Slave</span><span class="p">);</span>
<span class="k">return</span> <span class="nx">c</span><span class="p">.</span><span class="nx">sendBuffer</span><span class="p">(</span><span class="nx">pieceData</span><span class="p">);</span>
<span class="p">});</span>
<span class="c1">// Start the game</span>
<span class="k">await</span> <span class="k">this</span><span class="p">.</span><span class="nx">forAllClients</span><span class="p">(</span><span class="nx">c</span> <span class="o">=></span> <span class="p">{</span>
<span class="c1">// The main game loop needs some time for each transfer</span>
<span class="nx">c</span><span class="p">.</span><span class="nx">setSendDelayMs</span><span class="p">(</span><span class="mi">30</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">startSequence</span> <span class="o">=</span> <span class="p">[</span><span class="mh">0x30</span><span class="p">,</span> <span class="mh">0x00</span><span class="p">,</span> <span class="mh">0x02</span><span class="p">,</span> <span class="mh">0x02</span><span class="p">,</span> <span class="mh">0x20</span><span class="p">];</span>
<span class="k">return</span> <span class="nx">c</span><span class="p">.</span><span class="nx">sendBuffer</span><span class="p">(</span><span class="nx">startSequence</span><span class="p">);</span>
<span class="p">});</span>
<span class="k">this</span><span class="p">.</span><span class="nx">state</span> <span class="o">=</span> <span class="nx">TetrisGameState</span><span class="p">.</span><span class="nx">Playing</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>First, the initialization data (garbage line and piece buffers) is generated and
sent using <code class="language-plaintext highlighter-rouge">sendBuffer()</code>. This still takes place one byte at a time, but the
individual transfers are handled by the server to simplify the calling code.
Synchronization occurs before each buffer is sent – the <code class="language-plaintext highlighter-rouge">waitForByte()</code> helper
function ensures the main data is only transmitted to a client after it has
indicated it is ready. <code class="language-plaintext highlighter-rouge">forAllClients()</code> repeats this logic for each connected
game and prevents moving on until they have reached the same point in lockstep.
Once all clients have been initialized, the round is started by sending the
magic bytes required by the game. Note that the two phases of this process have
different timing requirements. <code class="language-plaintext highlighter-rouge">setSendDelayMs()</code> ensures we are taking full
advantage and sending data as fast as possible at each step. It also takes
latency into account and will shorten or eliminate the delay based on the
connection.</p>
<p>These abstractions greatly reduce the complexity of server-side game logic,
which is important considering the number of potential games to support. If
needed, there is also room for further improvement through the creation of
higher-level helper functions for common <em>combinations</em> of the existing
functions. For instance, forwarding bytes until the values do not change as
required by the menus of Tetris, F-1 Race, and Wave Race. Performance-wise,
<code class="language-plaintext highlighter-rouge">sendBuffer()</code> could be improved with packetization. If games do not send any
meaningful data while receiving a buffer, the server can send it all at once in
its entirety to avoid the latency overhead of transferring one byte at a time.
The client can then quickly feed each byte to the game individually. These
improvements are not needed right now but are straightforward to add if/when
required in the future. This also applies to unimplemented features like
keepalive packets and configurable master-only options.</p>
<h2 id="tetris-implementation">Tetris implementation</h2>
<p>With the server’s flexibility and all of the abstractions, the challenges of
implementing Tetris had more to do with game logic than networking or any other
GBPlay-specific functionality. For example, organizing end of game checks to
properly handle edge cases like draws. The goal of the server’s architecture is
to provide everything necessary except game logic and so we view this as a
success. Our time was primarily spent on problems the original developers of the
game would have had to face as well.</p>
<p>What’s interesting about the way the protocol works and the control we have here
is that we can generate garbage lines and random pieces however we want with
completely different algorithms (e.g., using a
<a href="https://harddrop.com/wiki/Random_Generator">bag randomizer</a> like modern
Tetris). This is fun to think about, but to keep the experience as original as
possible we re-implemented the original algorithms.</p>
<p>Below is a video of Tetris running with the new backend. For the most part, it
behaves exactly as it would when running with two directly connected Game Boys.
Notably, it supports difficulty selection and multiple rounds due to the
server-side state machine. All of the work under the hood gives the
<a href="https://www.youtube.com/watch?v=edCqF_NtpOQ">appearance of none</a> – a success.</p>
<figure class="centered">
<iframe src="https://www.youtube-nocookie.com/embed/HI1r6OjBdxs" width="560" height="315" frameborder="0" allowfullscreen="">
</iframe>
<figcaption><p>Multiplayer Tetris running with the new backend server</p>
</figcaption>
</figure>
<p>One noticeable quirk is that both players appear as Luigi on the menus and round
end screen. This is because both games are running in slave mode (effectively
“player 2”), which is <a href="/2021/05/10/An-8-Bit-Idea_The-Internet-of-Game-Boys.html
#gb-spi-and-latency">required</a>
to work over the internet. However, it is purely cosmetic. The important part is
that each game knows the difference between its own state and the state of the
opposing player, which they do. This is worth revisiting once keepalive
transfers are implemented. For lenient protocols like Tetris’, we could allow
one game to operate in master mode and supply it with fake data until the slave
responds – handling latency without the graphical oddity. The complexity of
this approach needs to be investigated further.</p>
<p>The full Tetris code is available
<a href="https://github.com/mwpenny/gbplay/blob/fa368a6ea0672940ef443a37bd3e1cd8e4e3000b/server/src/games/tetris.ts">here</a>.
The game’s link cable protocol details are documented
<a href="/game-protocols/Tetris.html">here</a>.</p>
<h2 id="looking-forward">Looking forward</h2>
<p>The knowledge gained through analyzing a wide variety of link cable protocols
has allowed us to design and implement a server architecture that makes it easy
to support different kinds of games. This new backend is lightweight and easily
extensible as we learn more, while also stable and good for testing. This is in
contrast to the existing TCP serial bridge script which is based on invalid
assumptions and is now obsolete. The fact that our server requires game-specific
logic is not ideal, but the logic itself is not very complex and abstractions
simplify its creation.</p>
<p>Implementing the Tetris protocol has helped validate the server design as well
as provide an internet-tolerant testbed to use while getting the full GBPlay
stack up and running end-to-end. With a working core we can now turn our focus
to building other areas that rely on it as well as adding missing features to
the server itself. With that in mind, our next step will be to use what we have
learned to improve upon our
<a href="/2021/05/29/Connecting-to-a-Game-Boy-Link-Cable-From-a-PC.html">USB link cable adapter</a>
and create a Wi-Fi enabled adapter that can connect to this new server!</p>Matt PennyWith a better understanding of how different games use the link cable and what is required for them to work properly online, we have enough information to create a stable, future-proof backend server.Multi-Game Link Cable Protocol Analysis2022-07-24T00:00:00+00:002022-07-24T00:00:00+00:00https://blog.gbplay.io/2022/07/24/Multi-Game-Link-Cable-Protocol-Analysis<figure class="centered">
<a href="/images/multi_game_analysis.jpg" title="Different games will help show different ways the link cable can be used
" target="_blank">
<img src="/images/multi_game_analysis.jpg" />
</a>
<figcaption><p>Different games will help show different ways the link cable can be used</p>
</figcaption>
</figure>
<h2 id="expanding-horizons">Expanding horizons</h2>
<p>The research stage of this project has progressed nicely. We understand the
<a href="/2021/05/10/An-8-Bit-Idea_The-Internet-of-Game-Boys.html
#understanding-the-link-cable-protocol">Game Boy link cable protocol</a>,
have created tools for interfacing with
<a href="/2021/05/11/Emulating-a-Pokemon-Trade-with-Generated-Link-Cable-Data.html">emulators</a>
and <a href="/2021/05/29/Connecting-to-a-Game-Boy-Link-Cable-From-a-PC.html">real hardware</a>,
and have even conducted tests
<a href="/2021/06/13/Beyond-Serial_Linking-Game-Boys-the-Hard-Way.html
#networking-game-boys">over the internet</a>.
It’s almost time to start on the full implementation but there are a few open
questions to answer first.</p>
<p>So far, the only game’s protocol we have studied in depth is Pokemon generation
1, which turned out to be relatively straightforward. The protocol is symmetric
once each game has been put into slave mode: both games contain the exact same
state and send the same type of data at the same time. This allows our
experimental TCP serial bridge script to be implemented as a naive byte
forwarder. Pokemon also fared well over a high-latency unstable internet
connection due to its turn-based gameplay.</p>
<p>However, there are a wide variety of Game Boy game genres and it is doubtful
that their protocols will all be so simple and latency-tolerant. We want to
create a robust implementation to use in place of the test script and hardware
but there are several unknowns. How do timing requirements differ across game
types? How common are symmetric protocols? How much server-side logic will be
needed for this project? Can we keep up with transfer sizes and speeds while
maintaining good performance? In order to design and create our full backend
server and Wi-Fi adapter, we need to answer these questions. To do so, we will
analyze a cross-section of different multiplayer game types in the Game Boy’s
library:</p>
<ul>
<li>A puzzle game (Tetris)</li>
<li>Two racing games (F-1 Race and Wave Race), and</li>
<li>A fighting game (Street Fighter II)</li>
</ul>
<p>In addition to using an emulator, we can use our previously created tools to
monitor link cable transfers on real hardware. The chosen games are all
real-time, unlike Pokemon. The level of player interaction differs significantly
between them which should give us a good overall idea of the kinds of game
protocols that exist (assuming similar games use similar protocols).</p>
<h2 id="protocol-documentation">Protocol documentation</h2>
<p>While looking for information about how different Game Boy games communicate
we noticed that not many of them have been documented online before (at least
publicly). Generally only the most popular are documented (e.g., Pokemon). We
saw this as an opportunity to build a helpful resource for future projects and
have created a <a href="/game-protocols/">dedicated section</a> of this blog to store
the inner-workings of game protocols. As we reverse-engineer games, we will
create corresponding pages in this new section. Blog posts will still contain
protocol information when relevant but may omit low-level details and link to
the documentation instead for the sake of brevity and to avoid duplication.</p>
<h2 id="game-analysis">Game analysis</h2>
<h3 id="tetris">Tetris</h3>
<figure class="centered">
<a href="/images/games/tetris/tetris_gameplay.png" title="Not the first game to support the link cable, but the first for many
" target="_blank">
<img src="/images/games/tetris/tetris_gameplay.png" />
</a>
<figcaption><p>Not the first game to support the link cable, but the first <a href="https://consolevariations.com/variation/console/nintendo-game-boy-tetris-bundle">for many</a></p>
</figcaption>
</figure>
<p>We already know that it’s possible to support Tetris over the internet because
that’s exactly what the
<a href="/2021/05/10/An-8-Bit-Idea_The-Internet-of-Game-Boys.html
#the-spark">project</a>
that inspired this one does. Stacksmashing’s original, however, has some
limitations: it doesn’t allow difficulty selection or multiple rounds (the game
must be reset). It’s possible that the missing features are simply unimplemented
due to the proof-of-concept nature of that project, but their omission could
also be necessitated by technical restrictions. As such, it’s still valuable to
study Tetris’s protocol. At the very least it can serve as a stable “hello
world” of sorts as we build the rest of GBPlay, since it is guaranteed to mostly
work and we want to support it anyway.</p>
<h4 id="tetris-link-cable-protocol">Tetris link cable protocol</h4>
<blockquote>
<p>Tetris’ full link cable protocol details are documented
<a href="/game-protocols/Tetris.html">here</a>.</p>
<p>This section will only discuss notable aspects and their implications.</p>
</blockquote>
<p>It doesn’t take long for our
<a href="/2021/06/13/Beyond-Serial_Linking-Game-Boys-the-Hard-Way.html
#connecting-game-boys-through-a-pc">symmetric protocol</a>
assumptions to be violated. Immediately after a connection is established, the
music selection screen is shown. Here, the master (and only the master) chooses
which track will play during each round. The second player only observes the
choice as it is being made and has no input. The transmission is not symmetric.
In other words, the two games behave differently and send different data. This
is problematic for our project because both Game Boys need to be in slave mode
to <a href="/2021/05/10/An-8-Bit-Idea_The-Internet-of-Game-Boys.html
#gb-spi-and-latency">tolerate latency</a> and thereby
work over the internet, which means neither will have a way progress in this
case. Difficulty selection – which comes after music selection – is only a
little better. While selecting, both games send the same data (their current
choice) and so naive forwarding of bytes will allow each to properly see what
the other is doing. But when it comes time to confirm, only the master can
choose to move on and we are again stuck.</p>
<p>To work around the issue, we will instead need to allow configuration of
master-only choices ahead of time (i.e., when creating a game lobby) and have
our server send the necessary bytes to both games to simulate a confirmation by
the master Game Boy. In effect, the server <em>is</em> the master. This explains some
of the limitations of stacksmashing’s project, as it solves these problems the
same way (music selection is configurable and difficulty is hard-coded to 0).
It’s disappointing that not all setup actions will be possible in the game
itself, but it isn’t that noticeable at the end of the day since the music is
only ever chosen once at the very beginning of the connection (not even if the
game ends and a new one is started). There is room for improvement with the
difficulty screen though. We can inspect the bytes being transmitted and
auto-confirm if there have been no selection changes for a predetermined amount
of time –
<a href="https://mortalkombat.fandom.com/wiki/Kombat_Kodes">Mortal Kombat cheat code</a>
style.</p>
<p>The menus aren’t the only non-symmetric part of Tetris’ protocol. To ensure
fairness, both players receive the same set of random tetrominoes and starting
garbage lines (depending on difficulty) before each round begins. This data is
supplied by the master game and so our server will be required to generate it
for online play to work. This is simple enough to implement. Notably, this
transfer can handle much shorter delays between bytes compared to those in the
rest of the game, which makes sense since the engine does not need spare time
to drive the graphics and music here. To take full advantage of this for optimal
performance, our server will need to be able to adjust the transfer delay based
on game state so it doesn’t wait longer than needed.</p>
<p>When it comes to actual gameplay, the protocol is straightforward and symmetric.
Each game repeatedly sends the height of its stack, which is used to update the
opponent height indicator and determine when “attack” garbage lines should be
added. Like difficulty selection, naive byte forwarding will <em>mostly</em> work here.
That is, until the very end, which involves the master game signaling it’s
time to move to the round end screen. With both games in slave mode, our server
will need to monitor the transmission for the end of the round and trigger the
next screen when that happens – acting as the master. The same applies to
deciding when to leave the round end screen.</p>
<p>There is one last hurdle with the round end screen itself. The game state – and
therefore data transmission requirements – after this screen depends on whether
a player has won 4 rounds or not. If yes, both games return to difficulty
selection. If no, another round begins. I can see why stacksmashing chose not to
implement multiple rounds. Doing so requires keeping track of the score on the
server, and his project supports more than 2 players which makes this
complicated. In our case, we will be keeping things as vanilla as possible (at
least initially) and so supporting multiple rounds is very doable.</p>
<h4 id="tetris-takeaways">Tetris takeaways</h4>
<p>What’s interesting about multiplayer Tetris is that player interaction is really
not central to the main game. The two players are essentially just competing to
see who can win first, with occasional “attacks” between them. The timing of
these attacks is not critical as they are not the game’s main focus. Further,
no attempt is made to detect a dropped connection. Both games will continue
running if the link cable is unplugged – their view of the other frozen in
time.</p>
<p>These qualities make Tetris an ideal end-to-end test game for online play since
lag or delayed transfers will not have a significant impact on gameplay. With
that said, its protocol certainly breaks the assumptions our current TCP serial
bridge tool is based on. It is both symmetric and asymmetric depending on the
game’s state and there is data and logic which only exists on the master side.
This is quite different from Pokemon’s protocol, which is to be expected because
it is a very different game.</p>
<p>Examining Tetris has uncovered several gaps in our understanding, but as we’ve
seen, all of them are able to be crossed (albeit some more cleanly than others).
Just this one game has made it clear that our backend server will need to be
extremely flexible. To work with both symmetric, asymmetric, and hybrid
protocols it must be capable of naively forwarding bytes while optionally
observing the data being exchanged and injecting bytes of its own. It must
support switching between these modes and executing any necessary master-only
logic dynamically, depending on the current game state. For settings outside of
the slave game’s control, the server must also allow configuration on lobby
creation (i.e., via a front-end UI). Finally, to make the most of timing
affordances, it will need a way to adjust the data transfer delay when possible.</p>
<h3 id="f-1-race--wave-race">F-1 Race & Wave Race</h3>
<figure class="centered">
<a href="/images/games/f1-race/f1-race_gameplay.png" title="These two racing games (F-1 Race, left; Wave Race, right) are much more complex than they appear
" target="_blank">
<img src="/images/games/f1-race/f1-race_gameplay.png" />
</a>
<a href="/images/games/wave-race/wave-race_gameplay.png" title="These two racing games (F-1 Race, left; Wave Race, right) are much more complex than they appear
" target="_blank">
<img src="/images/games/wave-race/wave-race_gameplay.png" />
</a>
<figcaption><p>These two racing games (F-1 Race, left; Wave Race, right) are much more complex than they appear</p>
</figcaption>
</figure>
<p>Tetris necessitates a complex backend server on its own but its protocol keeps
transfers to a minimum and is very lenient. The two players only occasionally
interact. Dropped or delayed data in the middle of a round is tolerated and
fairly low-impact. It is important to also consider games with more frequent,
high-priority communication. Racing games are good examples. Players can see
(and usually drive into) each other, meaning their positions must be constantly
exchanged. Analyzing such games will give us a better idea of the
latency-tolerance and timing requirements of chattier protocols.</p>
<p>We chose two similar games – F-1 Race and Wave Race – because we noticed early
on that data transfer timing was very important, and wanted to ensure this trait
was the rule and not the exception.</p>
<h4 id="f-1-race--wave-race-link-cable-protocols">F-1 Race & Wave Race link cable protocols</h4>
<blockquote>
<p>We have not yet fully documented the link cable protocols of F-1 Race and Wave
Race since their relevant qualities have to do with how data is sent and
received in general, rather than the data itself.</p>
<p>Full documentation will be available when support for these games is added to
GBPlay.</p>
</blockquote>
<p>Both F-1 Race and Wave Race support the
<a href="https://shonumi.github.io/articles/art9.html">4-player adapter</a>.
For simplicity, we will only consider 2 players for now (incidentally, the
4-player adapter works by communicating separately with each game).</p>
<p>The menu screens of both games have similar restrictions to those of Tetris:
only the master Game Boy can confirm, and in some cases select at all. These can
be dealt with in the same way as before by allowing configuration of one-sided
choices ahead of time and using timeouts to auto-confirm all others (i.e., the
name entry menu). Also similar to Tetris is the start position screen. In both
racing games, player starting positions are randomized and shown on each Game
Boy with a slot machine animation. To ensure synchronization, the actual
decision happens on the master game before being transmitted and displayed. This
can also be solved relatively easily by generating the start positions
server-side.</p>
<p>The largest challenge, <em>unlike</em> Tetris, is that the slave game needs to be
constantly receiving data in order to run. This applies not just during menus
but also during races themselves. In F-1 Race, everything appears to be driven
by the speed of the link cable connection. Delays in communication mean delays
in gameplay, and sending data too quickly makes the game run faster than it
should. Wave Race is even worse: if nothing is received in ~200 ms then it will
return to the main menu! As a result, internet latency will render both games
unplayable. However, as we saw with Tetris, player interaction is not actually
key to the outcome. Since these are racing games, the core element that matters
is detecting the winner and loser, and so supporting them is still possible with
some compromises.</p>
<p>Our current network protocol operates at the byte level. A byte sent from the
server to the client is interpreted as a byte to send over the link cable to the
connected Game Boy, and vice versa. There are no higher-level constructs or
abstractions. To address the requirement of fast, constant data transfer we can
introduce the concept of command packets, starting with a keepalive packet. This
new type of packet can instruct the client hardware connected to the Game Boy to
repeatedly send configurable data to a game in between regular transfers. This
will allow us to ensure data is received without it having to make a round trip
to the backend server every time, satisfying the tight timing window. The server
can then periodically poll the client for all of the bytes received from the
Game Boy since the last poll (with timestamps), allowing games like F-1 Race and
Wave Race to operate asynchronously.</p>
<p>With this scheme, we can solve the slowdown and reset problems. However, there
is still the matter of the opponent’s position. This is where the compromising
comes in. There is no way to keep these games perfectly in sync over the
internet. There is too much latency to meet their timing needs. Keepalive
transfers will allow us to send the real location data at slower speeds or even
drop it entirely, but that means the opponent’s vehicle on the screen won’t
always be up to date. It may appear to suddenly jump to its next position and
driving into it could behave erratically. We view this as an acceptable
trade-off given that these elements are not the main focus of the games and they
are unplayable otherwise. What is important is having a rough idea of where the
opponent is located on the course, which will still be the case. One potential
improvement is predicting where the vehicle will be on the client-side and
sending that data to the game until the true location is received (e.g., assume
the player will keep moving straight and not go off-course).</p>
<h4 id="f-1-race--wave-race-takeaways">F-1 Race & Wave Race takeaways</h4>
<p>I was surprised by the amount of data these games actually send. To keep tightly
in sync and also leave time for game logic they do small transfers, but a lot of
them – a worst-case scenario for an internet connection. Their protocols are
extremely timing dependent, but luckily most of the data (opponent position) is
not relevant to the outcome of the game and this problem can be worked around
with some concessions such as janky or out of sync opponent movement.</p>
<p>Again, our assumptions were violated. We built the TCP serial bridge script in
a very asynchronous way, when in reality some games are highly synchronous.
These games highlight the need for client-side logic on our link cable adapter
to send keepalive data and for a higher-level network protocol to configure
that logic. None of this is possible with our current setup. We can’t blindly
forward bytes or drive the connection with data alone anymore.</p>
<p>Although there are workarounds for the problems these kinds of protocols
present, they are just that – workarounds. We now know that GBPlay can’t be
perfectly seamless for every game. There will be some which “mostly” work in
exchange for functioning online at all. The next game to examine will serve as
a stress test to gauge the extent of this: a fighting game.</p>
<h3 id="street-fighter-ii">Street Fighter II</h3>
<figure class="centered">
<a href="/images/games/street-fighter-2/sf2_gameplay.png" title="While not the most ideal way to play, this port is still impressive (and Guile’s theme still goes with everything)
" target="_blank">
<img src="/images/games/street-fighter-2/sf2_gameplay.png" />
</a>
<figcaption><p>While not the most ideal way to play, this port is still impressive (and Guile’s theme still <a href="https://www.youtube.com/watch?v=0qzWOfZGxeE">goes with everything</a>)</p>
</figcaption>
</figure>
<p>Tetris and the racing games have already taught us quite a lot, but they are
fundamentally very indirect. Players compete in parallel to be the first to
achieve an objective, only interacting superficially (garbage lines in Tetris,
view of the other vehicle in racing games). Their interactions are not central
to the main goal. Latency issues can be worked around in these types of games
because delaying data transfers is tolerable, but what about games where <em>all</em>
data is relevant to the outcome?</p>
<p>In fighting games such as Street Fighter II, the end result is solely dependent
on the position and state of the other player. Every jump, punch, and block
matters. If the two games get out of sync then they may disagree on whether a
hit has landed or who the winner is. In the previously analyzed games,
interaction is incidental. Here, interaction is the entire point. Analyzing a
truly real-time game like this will be the last piece of the puzzle needed to
understand what is and isn’t possible with this project.</p>
<h4 id="street-fighter-ii-link-cable-protocol">Street Fighter II link cable protocol</h4>
<blockquote>
<p>Street Fighter II’s full link cable protocol details are documented
<a href="/game-protocols/Street-Fighter-2.html">here</a>.</p>
<p>This section will only discuss notable aspects and their implications.</p>
</blockquote>
<p>Like the racing games, Street Fighter II needs to constantly receive data in
order to run. It is most similar to F-1 Race in this regard: if it receives
nothing it will freeze rather than reset. Other than this, the menus are
surprisingly simple. Both games behave mostly identically and are allowed to
select <em>and</em> confirm! The only departure from this is the occasional need for
the master game to initiate synchronization transfers when entering the first
menu and starting a round. These constraints can be worked around as with the
other games analyzed by using keepalive transfers and monitoring the connection
to inject bytes when necessary. Other than that, naive byte forwarding will work
in theory. In practice, it is not so easy.</p>
<p>The reason why byte forwarding is all that is needed for most of the game is
because Street Fighter II’s protocol operates by constantly exchanging joypad
input. This keeps things extremely simple. It is as if each player is using a
second controller connected to the opponent’s Game Boy, allowing the game to
re-use much of the single-player code. Unfortunately, this also introduces the
same problem as the racing games: the internet is too high-latency to send the
joypad input at playable speeds. The game will work, but it will be very slow.
<em>Unlike</em> before, we cannot delay, drop, or generate data to get around this
since every button press is integral to the game’s outcome. Both simulations
must be exactly in sync.</p>
<p>Modern online games
<a href="https://arstechnica.com/gaming/2019/10/explaining-how-fighting-games-use-delay-based-and-rollback-netcode/4/">handle this problem</a>
and compensate for lag by either delaying inputs or, more commonly, using
client-side prediction and rewinding. Each game makes educated guesses about
what other players will do and runs normally to avoid lag. If the predictions
turn out to be incorrect when the opponent inputs are received, the game
re-simulates the corresponding period of time using the real actions that
actually occurred instead of the predicted ones. This is all invisible to
players, who only see the final result. Neither delay nor rollback techniques
are feasible for Street Fighter II because there is no way to modify or rewind
the state of the game without using hardware mods or creating a custom,
latency-tolerant version of the game itself. The hardware and software are
fixed.</p>
<p>Hypothetically, even if an internet connection had low enough latency to
transfer the joypad data fast enough, there would still be problems due to the
protocol’s simplicity. For instance, each game detects when the round is over on
its own. There is no “round end” byte sent over the link cable because there
doesn’t need to be – each game is simulating the exact same thing. If we wanted
to support multiple rounds in this scenario we’d have to emulate the game on the
server and read its memory to detect when it’s over and who won. This problem
could technically exist for any type of game. For Street Fighter II we could
compromise and only allow one round, but this is all quickly getting away from
our goal of seamless online play. With this and the more fundamental issues in
mind, it’s fair to say that properly supporting games like Street Fighter II at
reasonable speeds – let alone over the internet at all – is well and truly
infeasible for this project. It is easy in theory, but unplayable in practice.</p>
<h4 id="street-fighter-ii-takeaways">Street Fighter II takeaways</h4>
<p>Unfortunately, truly real-time games like Street Fighter II have too much
overhead to work well online. The sheer number of transfers cannot be supported
effectively given the latency of the internet. Lag matters. The game will run
far too slowly and there is no way to implement lag compensation strategies
without modifying the hardware or the game. Going beyond a plug and play
solution that works on an unmodified Game Boy is not the goal of this project.
We want to build something that is compatible with 1989 hardware out of the box.
The impact of lag is not entirely surprising given that quality online play is a
problem fighting games face
<a href="https://www.polygon.com/2020/3/25/21192522/netcode-samurai-showdown-fighting-games-rollback-delay">even today</a>.</p>
<p>These issues will exist for any real-time game where interaction is the main
focus. If the key element to the outcome is player action and it needs to be
synchronized often without the possibility to delay, then an internet connection
is simply too slow. Consequently, GBPlay will not be able to support the entire
Game Boy library – even partially with keepalive workarounds like the racing
games. Truly real-time games are infeasible. This is unfortunate but somewhat
expected given the anachronistic nature of this project. Multiplayer Game Boy
games weren’t designed for the internet, especially the internet of the era.
They were designed for the fast, real-time communication that a hard-wired
physical connection delivers.</p>
<p>On a more positive note, we have seen that many Game Boy games are <em>not</em> this
demanding when it comes to timing needs. Part of the reason is hardware
limitations. The serial link and CPU are slow and game code cannot run while
link cable data is being prepared or processed. Larger and more frequent
transfers mean less time to run game logic. In general, we expect that we will
still be able to support a large number of games based on what we have seen so
far.</p>
<h2 id="conclusion">Conclusion</h2>
<p>To answer the questions posed at the beginning of this post: timing requirements
can differ both within and across games, a given protocol may use any
combination of symmetric and asymmetric transfers depending on state,
server-side logic will absolutely be needed for a majority of games, and
workarounds (including client-side logic) are sometimes required to maintain
playable speeds.</p>
<p>We also learned the extent to which different games will work online. They fall
into three broad categories. Turn-based games such as Pokemon are ideal due to
their symmetric nature and because the natural delays between actions make lag
less noticeable. Indirect real-time games like Tetris, F-1 Race, and Wave Race
– where players are merely competing in parallel – will generally work well
enough since interaction is incidental and data can be delayed or dropped
without much impact. Sometimes these will require compromises. Finally, fully
real-time games such as Street Fighter II use all player activity to determine
the outcome and are therefore infeasible to support due to the amount of
transfers and the impact of latency. It is also not always possible to determine
their complete state.</p>
<p>Our server cannot be fully game-agnostic like we hoped, but we can make writing
game-specific code easier with high-level abstractions. Through our multi-genre
analysis we now have a much clearer picture of the different usage patterns we
must handle, and consequently the features our server needs. Investigating a
variety of protocols also has us confident that new assumption-breaking
surprises will be few and far between. With this in mind we can now create a
stable, future-proof backend server and Wi-Fi adapter to use instead of the
existing TCP serial bridge script and Arduino-based adapter.</p>Matt PennyThere are some unknowns to investigate before creating a full version of this project. Let's analyze different games to learn more!Beyond Serial: Linking Game Boys the Hard Way2021-06-13T00:00:00+00:002021-06-13T00:00:00+00:00https://blog.gbplay.io/2021/06/13/Beyond-Serial_Linking-Game-Boys-the-Hard-Way<figure class="centered">
<a href="/images/serial_bridge_link.jpg" title="Linking Pokemon games through a PC
" target="_blank">
<img src="/images/serial_bridge_link.jpg" />
</a>
<figcaption><p>Linking Pokemon games through a PC</p>
</figcaption>
</figure>
<h2 id="putting-all-the-pieces-together">Putting all the pieces together</h2>
<p>In the <a href="/2021/05/29/Connecting-to-a-Game-Boy-Link-Cable-From-a-PC.html">last post</a>, we talked about building a USB to link
cable adapter and using it to send data to an original Game Boy via a PC. With
the adapter, data can be sent both programmatically (to implement game protocols
ourselves) and using an emulator (to link with arbitrary games for testing).
However, there’s still one major piece missing: a second Game Boy!</p>
<p>Our existing tools allow testing via emulation, and with <em>one</em> real Game Boy.
The former is useful for gaining a high-level understanding of game protocols,
and the latter can help verify timing requirements and hardware behavior under
non-standard conditions. But the entire point of this project is to link two
real devices together. Now that we have the ability to communicate with Game
Boys individually using the USB adapter, it’s a good time to try forwarding data
between them.</p>
<p>We’ll start with a hard-wired connection through the same PC, which should be
around the same speed as previous one-sided tests. After that, we’ll attempt a
LAN connection, where there may be some noticeable latency. Finally, we can try
it over the internet – the first full GBPlay proof of concept!</p>
<h2 id="connecting-game-boys-through-a-pc">Connecting Game Boys through a PC</h2>
<p>Connecting two Game Boys directly is actually very straightforward: just use the
link cable! Joking aside, building on our current one-Game Boy communication
setup to mediate data transfer between two devices is simple in theory. We just
need to use two adapters to receive a byte from one console, send it to the
other, then repeat the process in the opposite direction.</p>
<p>There’s one caveat: since both Game Boys
<a href="/2021/05/10/An-8-Bit-Idea_The-Internet-of-Game-Boys.html
#gb-spi-and-latency">need to be in slave mode</a> in
order to tolerate latency, forwarding bytes blindly from the start won’t work.
Both games will be trying to determine which mode to operate in, so we’ll have
to first send the proper game-specific initialization data to get each to accept
an external clock signal from the USB adapter. This wasn’t required for the
<a href="/2021/05/29/Connecting-to-a-Game-Boy-Link-Cable-From-a-PC.html
#connecting-an-emulator-to-a-game-boy">emulator to Game Boy connection</a>
because the emulator could act as the master device and slow down or speed up as
necessary to maintain the connection. Once both Game Boys are in slave mode,
naive forwarding should work and the games themselves should take care of the
rest. Sending bytes one at a time will make any connection latency more
noticeable due to the increased per-byte overhead. However, doing so allows the
large majority of link cable communication to be treated as a black box,
simplifying the implementation. We want to do the bare minimum to get the
connection set up and then just act as a messenger.</p>
<figure class="centered">
<a href="/images/2gbs_1pc.jpg" title="Connecting two Game Boys through a PC using our Arduino-based USB to link cable adapters
" target="_blank">
<img src="/images/2gbs_1pc.jpg" />
</a>
<figcaption><p>Connecting two Game Boys through a PC using our Arduino-based USB to link cable adapters</p>
</figcaption>
</figure>
<p>For these initial two-Game Boy connection tests, Pokemon Generation 1 was used
since we <a href="/2021/05/11/Emulating-a-Pokemon-Trade-with-Generated-Link-Cable-Data.html
#pokemon-link-cable-protocol">already understood</a>
its protocol well. A new “serial bridge” script based on our existing
<a href="https://github.com/mwpenny/gbplay/blob/8a8303c38790254442ceff6d3c58d969b00bb614/tools/python/common/serial_link_cable.py#L39">serial link cable client</a>
class was created. It takes as input the serial port for each link cable
adapter, as well as the game that’s being played (so that the appropriate
initialization data can be used). Only Pokemon is supported for now. The script
instantiates a client for each Game Boy and waits until the connections are
opened before continuing. After both handhelds are connected, a setup function
corresponding to the game that was specified at launch is called for each one.
This function takes the last byte received from a Game Boy and returns the next
initialization byte to send to it, or null when setup is complete (i.e., the
device is in slave mode). This is similar to the way the callbacks in our other
test scripts work. The logic of the setup code is decoupled from the mechanism
by which the data is sent, allowing the code to be simpler and the data to
potentially be sent anywhere (an emulator instead of real hardware, for
instance).</p>
<p>Once each Game Boy is in slave mode, the script forwards data between the two
handhelds in a loop, acting as an intermediary. Since one transfer both
<a href="/2021/05/10/An-8-Bit-Idea_The-Internet-of-Game-Boys.html
#understanding-the-link-cable-protocol">sends and receives</a>
a byte at the same time, the very first byte to send needs to be generated by
us. For this, the script just uses the last byte received during initialization,
although it could have also faked it because the beginning of the connection is
protocol-specific anyway. During this time, we can also easily sniff the
connection and log the data. Intercepting communication was already possible
when using an emulator but now we’re be able to spy on the original hardware,
which will be useful as we adjust the timing to attempt to minimize latency and
push the limits of the link. If garbage data is being sent, it’ll be easy to
see. In fact, the capability was used when debugging the new script.</p>
<p>Doing things this way works well and there’s minimal overhead. For comparison,
with a direct connection between Game Boys it takes about 4 seconds to transfer
<a href="/2021/05/11/Emulating-a-Pokemon-Trade-with-Generated-Link-Cable-Data.html
#trainer-data-exchange">Pokemon trainer data</a> (424
bytes). When sending the same data using 2 USB link cable adapters and a PC it
took 6 seconds. Not too shabby. The code for the serial bridge script is
available
<a href="https://github.com/mwpenny/gbplay/blob/a70dde6a41f0fc7e96ba3e791b161734825f2c6e/tools/python/serial-bridge/serial_bridge.py">here</a>.
Below is a video demonstration of a Pokemon battle between two Game Boys connected through a PC.</p>
<figure class="centered">
<iframe src="https://www.youtube-nocookie.com/embed/yZ3_5nhSzi0" width="560" height="315" frameborder="0" allowfullscreen="">
</iframe>
<figcaption><p>A Pokemon link cable battle between two indirectly connected Game Boys</p>
</figcaption>
</figure>
<p>This isn’t a general solution. The fact that game-specific data is needed is
unfortunate, but with Pokemon at least, it doesn’t take much to get the games
into the proper mode (just
<a href="/2021/05/11/Emulating-a-Pokemon-Trade-with-Generated-Link-Cable-Data.html
#role-negotiation">one byte</a>). This method also
assumes that once games are properly initialized, they’ll be able to operate
correctly with byte forwarding alone and no further intervention. That is, it
assumes that each game has the same state information and sends the same type of
data at the same time – a so-called “symmetric” protocol. Things may not be so
easy across the board, so more testing is required with different types of
games. Depending on the conventions used by other games and how similar they are
to Pokemon’s, we may be able to get away with a semi-generic approach which uses
a common implementation with configurable initialization steps for each specific
game.</p>
<h2 id="networking-game-boys">Networking Game Boys</h2>
<p>Connecting two Game Boys together through the same PC worked well with minimal
tweaking, which was very encouraging. The logical next step was extending it to
work through two different PCs. At a basic level, the serial bridge script is
just sending and receiving bytes. It uses our link cable client helper class and
doesn’t directly depend on where bytes come from or go. Supporting a link across
different PCs was therefore a matter of just transferring bytes over the network
rather than a link cable.</p>
<p>To do this, the serial bridge script was split into two parts: a client and a
server. The server handles the initialization and forwarding logic. Instead of
sending and receiving data to and from serial ports, it does so over TCP
sockets. Clients handle the actual Game Boy communication. They simply wait for
a byte from the server, send it to the USB link cable adapter’s serial port, and
then send the response back to the server. Very little has changed from what was
there before aside from the extra layer of abstraction. If a server process and
two client processes are run on the same PC, there’s no visible difference at
all (which is very convenient; no separate script needs to be created for local
testing). The updated TCP serial bridge script is
<a href="https://github.com/mwpenny/gbplay/tree/d24267c2d6a9b29be14067f460bdcb7ca07a84be/tools/python/tcp-serial-bridge">here</a>.</p>
<h3 id="lan-connection">LAN connection</h3>
<p>With the updated script in hand, the server and one client were run on one PC
connected to the first Game Boy, and another client was run on a different PC
connected to the second Game Boy. The server PC was connected to the network via
an Ethernet cable, and the other used 802.11ac Wi-Fi. Below is a video of a
Pokemon battle over my home network.</p>
<figure class="centered">
<iframe src="https://www.youtube-nocookie.com/embed/4KE-TgG9hPU" width="560" height="315" frameborder="0" allowfullscreen="">
</iframe>
<figcaption><p>A Pokemon link cable battle between two Game Boys connected over LAN</p>
</figcaption>
</figure>
<p>Instead of taking 4 seconds like real hardware, transferring Pokemon trainer
data took 13 seconds. Not great, but not terrible either. While acceptable, the
~2x increase in time compared to the single-PC connection had me worried about
how slow an internet-connected link would be. I was concerned that games would
be effectively unplayable online.</p>
<h3 id="internet-connection">Internet connection</h3>
<p>At the time of these experiments, I didn’t have someone else to test with over
the internet (Aidan was in the middle of a move), so for simplicity I connected
the second PC to the internet via my phone’s mobile data connection instead of
Wi-Fi. Cell service isn’t great in my area, and I was also doing all of this in
my basement (which seriously degrades the signal). I tested the connection
latency by pinging Google and the round trip time was about 500 ms or so on
average, and sometimes climbed as high as 2000+ ms. For reference, on my home
network I can get a round trip time of 15-20 ms when using Wi-Fi, and about
10 ms when directly connected to the router.</p>
<p>So, with one Game Boy on my terrible mobile data connection I tried again and
the Pokemon trainer data transfer took 77 seconds – just under 13x slower than
the single-PC link. This is about how long it took for the
<a href="/2021/05/11/Emulating-a-Pokemon-Trade-with-Generated-Link-Cable-Data.html
#mock-pokemon-trader">mock trader</a> script to run with
real hardware before the USB adapter’s send delay was reduced from 100 ms to 5
ms. The data size is known (424 bytes), and so the speed can be calculated:
about 5.5 bytes per second. While a little disappointing, there’s a silver
lining: my mobile data connection is likely a <em>worst-case</em> scenario, and with
that in mind this was a good stress test. The connection was unstable and had
frequent latency spikes during the transfer but no data was lost or corrupted.
The two games handled the
<a href="https://www.ietf.org/rfc/rfc1149.txt">carrier pigeon-grade</a> delays with no
issues and after the data was transferred, they behaved as normal. So while the
numbers aren’t ideal, they provide reassurance that GBPlay isn’t completely
infeasible. Further, regardless of connection quality I suspect that many games
won’t need to send this much data all at once (real-time games, for example). If
they do, it’ll likely be at the very beginning of the session since there isn’t
much CPU time for game logic when sending large amounts of data at decent
speeds. In fact, all subsequent transfers in Pokemon after the trainer data are
only a handful of bytes long and don’t happen nearly as frequently, so the
slowness wasn’t noticeable when battling or confirming a trade. This is another
reason to analyze the link cable protocols of different types of games now that
we know the connection works online.</p>
<p>In terms of speed, bandwidth isn’t the killer, latency is. Even particularly bad
internet connections should perform better than my mobile data connection. They
generally have lower latency and regardless of bandwidth, the amount of data
being sent is dwarfed by what the modern web demands. Once Aidan is set up,
re-running this experiment will give us insight into the performance
characteristics of a more typical connection. For now we have a likely upper
bound.</p>
<h2 id="takeaways">Takeaways</h2>
<p>Connecting two original Game Boys together in several different configurations
was very motivating! It showed that this project is viable and also gave us an
idea of backend implementation requirements. There will definitely need to be at
least a little server-side logic for each supported game in order to put both
Game Boys into slave mode. What remains to be seen is how much else. The answer
to that question will affect the generalizability of our approach. If it can’t
be easily generalized, we can attempt to minimize the amount of per-game code by
creating common primitives that can be re-used. Analysis of more games –
particularly games of different genres – will help make things more clear. The
ability to log the data sent between devices will allow us to form a very
confident answer compared to emulation alone, especially in non-standard
situations like this.</p>
<p>We also learned valuable information about performance and stability. The
internet test wasn’t ideal but it served as a good stress test and gave us an
idea of what a worst-case scenario looks like. In the future we’ll test with a
more typical connection and compare the results to the other data we’ve
gathered. Hopefully it’ll be much closer to the LAN link speed than the mobile
data link speed.</p>
<p>We’re now at a point where we have a fairly solid set of tools for prototyping.
We can use the TCP serial bridge script to experiment with networking different
games on real Game Boy hardware, which is what we’ll do next in order to get a
better idea of network requirements and the amount of game-specific code that’s
needed for the connection to work at all. We can also start looking into the
hardware side of this project and how it will connect to what exists currently.</p>Matt PennyUsing our USB to link cable adapter, it's time to test out connecting two Game Boys together through a PC, and then over the internet!Connecting to a Game Boy Link Cable From a PC2021-05-29T00:00:00+00:002021-05-29T00:00:00+00:00https://blog.gbplay.io/2021/05/29/Connecting-to-a-Game-Boy-Link-Cable-From-a-PC<figure class="centered">
<a href="/images/pokered_arduino_connection.jpg" title="Trading Pokemon to a Game Boy from a PC via an Arduino
" target="_blank">
<img src="/images/pokered_arduino_connection.jpg" />
</a>
<figcaption><p>Trading Pokemon to a Game Boy from a PC via an Arduino</p>
</figcaption>
</figure>
<h2 id="putting-theory-into-practice">Putting theory into practice</h2>
<p>Previous posts have described our process of learning about the Game Boy
hardware and how it’s used. At this point, we have a good level of familiarity
with <a href="/2021/05/10/An-8-Bit-Idea_The-Internet-of-Game-Boys.html">how the link cable works</a>, some
<a href="/2021/05/11/Emulating-a-Pokemon-Trade-with-Generated-Link-Cable-Data.html">useful tools</a> for experimenting with the connection,
and some known good data to send. There’s been a lot of research, and until now
we’ve only talked about how we can use our software tools with an emulator. It’s
time to dive into the meat of this project: interfacing with original Game Boy
hardware!</p>
<p>This involves building an adapter to send data between a PC and Game Boy via the
link cable. Once constructed, the
<a href="/2021/05/11/Emulating-a-Pokemon-Trade-with-Generated-Link-Cable-Data.html
#mock-pokemon-trader">mock Pokemon trade data</a> from
last time can be sent to verify the connection. From there, we can take it a
step further and try connecting a real Game Boy to an emulated one. Let’s get
started!</p>
<h2 id="building-a-game-boy-link-cable-adapter">Building a Game Boy link cable adapter</h2>
<p>The eventual goal of this project is a plug and play solution in the form of a
nondescript dongle that connects to a Game Boy via the link cable and to a TCP
server over Wi-Fi. However, testing is much easier with a direct connection to
the console. So with this in mind, a good first step is a barebones USB to link
cable adapter. An Arduino Uno clone (i.e., ATmega328P-based development board)
was used, although any decent microcontroller would do the job just fine and the
final hardware will almost certainly use a different MCU. The Arduino is easy to
use to make a prototype quickly and has plenty of GPIO pins available.</p>
<p>To access the individual wires inside of the link cable without having to cut
into it, a small breakout board was used. Helpfully, one already existed with
<a href="https://github.com/Palmr/gb-link-cable">open-source CAD files</a> available,
meaning it was very cost-effective to have a good number of them fabricated for
this project (100 boards for ~$10 – more than enough for two people).</p>
<figure class="centered">
<a href="/images/breakout_boards.jpg" title="Game Boy link cable breakout boards. We have a few extra.
" target="_blank">
<img src="/images/breakout_boards.jpg" />
</a>
<figcaption><p>Game Boy link cable breakout boards. We have a few extra.</p>
</figcaption>
</figure>
<p>With a way to access the individual wires, it’s straightforward to connect
them to the Arduino’s GPIO pins and manipulate the GB SPI signals
programmatically. Bits must be sent one at a time. Per the
<a href="/2021/05/10/An-8-Bit-Idea_The-Internet-of-Game-Boys.html
#understanding-the-link-cable-protocol">protocol’s specification</a>,
the microcontroller sets the output line high or low, according to the value of
the bit to send, and then pulses the clock line to indicate to the Game Boy that
a transfer is occurring. At the same time, it reads the input line to retrieve
the bit being sent by the Game Boy. After 8 bits have been shifted in and out in
this way, an entire byte will have been sent and received.</p>
<figure class="centered">
<a href="/images/gb_arduino_connection.jpg" title="Connecting a Game Boy’s link cable to an Arduino via a breakout board
" target="_blank">
<img src="/images/gb_arduino_connection.jpg" />
</a>
<figcaption><p>Connecting a Game Boy’s link cable to an Arduino via a breakout board</p>
</figcaption>
</figure>
<p>Since the Game Boy’s link cable protocol is effectively SPI mode 3, existing SPI
libraries actually work fine for interfacing with it. But the protocol really
isn’t that complex to implement, and part of the goal of this project is to
learn, so no libraries were used here. This way, we also have more control over
the precise timing if we need to tweak it later for compatibility. Below is our
code for exchanging a byte.</p>
<figure class="highlight"><pre><code class="language-c" data-lang="c"><span class="n">byte</span> <span class="nf">transfer_byte</span><span class="p">(</span><span class="n">byte</span> <span class="n">tx</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">byte</span> <span class="n">rx</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o"><</span> <span class="mi">8</span><span class="p">;</span> <span class="o">++</span><span class="n">i</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">digitalWrite</span><span class="p">(</span><span class="n">PIN_SO</span><span class="p">,</span> <span class="p">(</span><span class="n">tx</span> <span class="o">&</span> <span class="mh">0x80</span><span class="p">)</span> <span class="o">?</span> <span class="n">HIGH</span> <span class="o">:</span> <span class="n">LOW</span><span class="p">);</span>
<span class="n">tx</span> <span class="o"><<=</span> <span class="mi">1</span><span class="p">;</span>
<span class="c1">// http://www.devrs.com/gb/files/gblpof.gif</span>
<span class="c1">// 120 us/bit</span>
<span class="n">digitalWrite</span><span class="p">(</span><span class="n">PIN_CLK</span><span class="p">,</span> <span class="n">LOW</span><span class="p">);</span>
<span class="n">delayMicroseconds</span><span class="p">(</span><span class="mi">60</span><span class="p">);</span>
<span class="n">byte</span> <span class="n">rx_bit</span> <span class="o">=</span> <span class="p">(</span><span class="n">digitalRead</span><span class="p">(</span><span class="n">PIN_SI</span><span class="p">)</span> <span class="o">==</span> <span class="n">HIGH</span><span class="p">)</span> <span class="o">?</span> <span class="mi">1</span> <span class="o">:</span> <span class="mi">0</span><span class="p">;</span>
<span class="n">rx</span> <span class="o">=</span> <span class="p">(</span><span class="n">rx</span> <span class="o"><<</span> <span class="mi">1</span><span class="p">)</span> <span class="o">|</span> <span class="n">rx_bit</span><span class="p">;</span>
<span class="n">digitalWrite</span><span class="p">(</span><span class="n">PIN_CLK</span><span class="p">,</span> <span class="n">HIGH</span><span class="p">);</span>
<span class="n">delayMicroseconds</span><span class="p">(</span><span class="mi">60</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">return</span> <span class="n">rx</span><span class="p">;</span>
<span class="p">}</span></code></pre></figure>
<p>As a sanity check, if we hard-code the Arduino to constantly send the value <code class="language-plaintext highlighter-rouge">1</code>
and then visit the Cable Club in Pokemon Red, the game displays the same message
as when it’s connected to another copy! This confirms that the adapter is able
to successfully send bytes to the Game Boy. To validate the other direction of
data flow, we can send the received bytes to a PC over the Arduino’s USB serial
interface. Doing so shows that the Arduino is reading the value <code class="language-plaintext highlighter-rouge">2</code> from the
Game Boy, which is correct! For details on the game’s link cable protocol, see
the <a href="/2021/05/11/Emulating-a-Pokemon-Trade-with-Generated-Link-Cable-Data.html
#pokemon-link-cable-protocol">previous post</a>.</p>
<figure class="centered">
<a href="/images/pokered_arduino_sanity_check.jpg" title="Tricking Pokemon Red into behaving as if another game is connected by sending the first byte of its link cable protocol. Received bytes are shown in the serial monitor window.
" target="_blank">
<img src="/images/pokered_arduino_sanity_check.jpg" />
</a>
<figcaption><p>Tricking Pokemon Red into behaving as if another game is connected by sending the first byte of its link cable protocol. Received bytes are shown in the serial monitor window.</p>
</figcaption>
</figure>
<p>For flexibility, it’s preferable to read and write link cable bytes entirely
from a PC rather than reprogramming the Arduino every time. This is simple
enough: we can continue to send the received bytes over USB serial to the PC as
we are now and read the next byte to send from USB serial as well. The code
running on the Arduino will block until it receives a byte from the PC, send
that byte to the connected Game Boy, read the byte sent by the Game Boy and
forward it to the PC, then repeat the process indefinitely. Although the final
dongle will use Wi-Fi, it will likely retain this USB serial capability as well
for debugging and troubleshooting purposes.</p>
<p>The adapter is purposefully as low-level as possible, only providing a way to
send and receive single bytes at a time and nothing more. Keeping it close to
the hardware like this with no additional logic allows higher-level abstractions
to be built entirely in software on the PC side, which will be much easier than
doing it on the Arduino. Rapid iteration of tools which use the adapter is
important since this project is not very well-defined yet. As we go, if
assumptions are wrong, we don’t want to have to throw away a ton of work. The
full source code for the adapter is available
<a href="https://github.com/mwpenny/gbplay/blob/bc178aff79c9f9f7f8a14a57cd9b1daa8080bc1d/arduino/gb_to_serial/gb_to_serial.ino">here</a>.</p>
<h2 id="sending-data-to-a-game-boy">Sending data to a Game Boy</h2>
<p>With the hardware connection set up, it’s time to send real data! We can make
use of the
<a href="/2021/05/11/Emulating-a-Pokemon-Trade-with-Generated-Link-Cable-Data.html
#mock-pokemon-trader">mock Pokemon trader script</a> from
last time, which is confirmed to work with an emulated Game Boy. First, a helper
class was created for interfacing with the USB link cable adapter. In a loop, it
calls a configurable callback function with the previously-received byte from
the Game Boy as input (or a null value, for the first call). It then takes the
byte returned by the function, sends it to the specified serial device (in this
case, our adapter), and saves the response for the next iteration of the loop.
The code for this serial link cable server is available
<a href="https://github.com/mwpenny/gbplay/blob/8a8303c38790254442ceff6d3c58d969b00bb614/tools/python/common/serial_link_cable.py#L17">here</a>.
It’s modeled after the
<a href="/2021/05/11/Emulating-a-Pokemon-Trade-with-Generated-Link-Cable-Data.html
#bgb-link-cable-tcp-server">BGB link cable server</a>
described in the previous post, and in fact the callback function doesn’t need
to know which server it’s running within. This allows the same function to work
with both an emulator and real hardware.</p>
<p>We do, however, need to make two small modifications to the mock trader code
itself. Firstly, it must be able to handle null data as input (for the initial
call). Second, the script was originally written to mimic a copy of Pokemon
operating in slave mode (since our BGB link cable server only allows the
connected emulator to operate in master mode). However, for our use case a real
Game Boy can only operate in slave mode due to latency, and so the script needs
the ability to behave like a copy of Pokemon running in master mode. This isn’t
too big of a change. In the
<a href="/2021/05/11/Emulating-a-Pokemon-Trade-with-Generated-Link-Cable-Data.html
#role-negotiation">role negotiation</a> step, the script
needs to send a <code class="language-plaintext highlighter-rouge">1</code> instead of a <code class="language-plaintext highlighter-rouge">2</code>.</p>
<p>Putting all of the pieces together, a new argument was added to the mock trader
script: the type of connection – BGB or serial. If serial is chosen, the
state machine management class is instantiated in master mode and the serial
link cable server is used instead of the BGB link cable server. The updated code
is
<a href="https://github.com/mwpenny/gbplay/blob/c679bf398207365e9dc5b9f5895d8dd54848d88c/tools/python/pokered-mock-trade/trader.py">here</a>.
Running the script with a Game Boy-connected Arduino plugged into the PC works
as expected and a fake trade can be executed! Check out the video below for an
example.</p>
<figure class="centered">
<iframe src="https://www.youtube-nocookie.com/embed/09wOyIk_DU4" width="560" height="315" frameborder="0" allowfullscreen="">
</iframe>
<figcaption><p>Conducting a Pokemon trade by sending data to a Game Boy from a PC</p>
</figcaption>
</figure>
<p>At first, this was quite slow but it turned out that was because the adapter
code was waiting for more time than was necessary between each transferred byte.
It’s not possible to know when the slave mode Game Boy is ready to send data.
Since everything is running on the bare CPU, games typically just count
cycles until “enough” time has passed to be sure that the other game is ready.
If the next byte is sent too early, the slave device could send bad or
incomplete data in response or get caught in a loop (preparing response,
received a byte, preparing response, etc.). If our adapter was operating in
slave mode this wouldn’t be a problem since the second Game Boy would tell <em>it</em>
when to send the next byte via the clock signal. The delay between bytes was
initially set at a very conservative 100 ms. On its own, that’s not much time at
all. But multiply it by the hundreds of bytes that Pokemon sends and it really
adds up. It turns out that the game will work with a delay as low as 5 ms,
lowering the time it takes to send trainer data from a little over a minute to
around 5 seconds – similar to how long it normally takes with two Game Boys!</p>
<p>Different games will certainly have different delay requirements. We don’t want
to choose an overly-large delay value that’s safe in general, since it will slow
down games which don’t need it. The server side will have to know how long to
wait at a minimum on a game-by-game basis. Although, it’s likely that many games
will have similar timing needs. Further, the latency of the internet will
presumably be more than enough. More testing is required in this area with
different games.</p>
<h2 id="connecting-an-emulator-to-a-game-boy">Connecting an emulator to a Game Boy</h2>
<p>We’ve now verified that our adapter works and that real data can be sent and
received from a PC! There’s one last tool at our disposal which still needs to
be tested with real hardware: an emulator connection. Connecting BGB to a real
Game Boy will be useful for testing how different games deal with latency and
unstable connections since the emulator has numerous debugging features that
allow changing and inspecting the game in real-time as it’s running.</p>
<p>Using the server + callback function approach doesn’t lend itself well to
connecting BGB to our adapter since the helper classes we’ve written so far for
each <em>both</em> act as servers. So, another one was written – a serial link cable
client, rather than a server. This class has one responsibility: to enable
sending a byte to the Game Boy and receiving its response. There’s no loop or
server functionality, and in fact most of its code is related to connection
management since the <a href="https://pyserial.readthedocs.io/en/latest/">pyserial</a>
library takes care of the actual USB serial communication. It’s simply a light
abstraction on top of our serial adapter protocol. Its code is available
<a href="https://github.com/mwpenny/gbplay/blob/8a8303c38790254442ceff6d3c58d969b00bb614/tools/python/common/serial_link_cable.py#L39">here</a>.</p>
<p>Putting it all together, a script was written which starts the
<a href="/2021/05/11/Emulating-a-Pokemon-Trade-with-Generated-Link-Cable-Data.html
#bgb-link-cable-tcp-server">BGB link cable server</a>
with the callback set to a function that uses the serial link cable
client class to send the BGB byte to the Game Boy and the Game Boy byte back to
BGB. Its code is
<a href="https://github.com/mwpenny/gbplay/blob/8a8303c38790254442ceff6d3c58d969b00bb614/tools/python/bgb-serial-link/bgb_serial_link.py">here</a>.
Connecting to Pokemon works flawlessly. I can even battle! Below is a video of
two very mismatched teams facing off in this way.</p>
<figure class="centered">
<iframe src="https://www.youtube-nocookie.com/embed/MDmD3VmwcZw" width="560" height="315" frameborder="0" allowfullscreen="">
</iframe>
<figcaption><p>A Pokemon battle between real hardware and an emulator</p>
</figcaption>
</figure>
<p>Beyond Pokemon, in theory it should “just work” for any two games, allowing us
to connect them without having to write any additional code. This is because
the emulator is operating in master mode and the Game Boy is operating in slave
mode – just like two real Game Boys. There’s no need for double-slave latency
workarounds here since the emulator/master can slow down or pause as necessary,
and therefore simply forwarding bytes back and forth is sufficient. The two
games are operating as they were designed to.</p>
<p>After Pokemon, I tried Tetris – a game I’ve written no specific code to
support – and it works too. See the video below.</p>
<figure class="centered">
<iframe src="https://www.youtube-nocookie.com/embed/gddp-j2685Y" width="560" height="315" frameborder="0" allowfullscreen="">
</iframe>
<figcaption><p>Multiplayer Tetris between BGB and a Game Boy “just works” out of the box</p>
</figcaption>
</figure>
<h2 id="future-work">Future work</h2>
<p>This was some great progress! We’re able to send arbitrary data between a PC and
Game Boy – both programmatically and from an emulator. We can use these new
tools to pry deeper into how the link cable protocols of specific games work and
to test assumptions and theories about the connection using real hardware.</p>
<p>With a hardware connection like this, it’s also possible to start building a
backend multiplayer server, although until we have a better understanding of
what different games will require (e.g., timing, game-specific protocols, etc.)
it’s best to stick with a lightweight prototype for now. A good next step will
be to use the USB link cable adapter to connect two Game Boys together through a
PC. From there we can try a LAN connection using two different PCs, and then a
link over the internet!</p>
<p>A whole bunch of doors have just opened up!</p>Matt PennyUntil now, we've only talked about how we can use our software tools with an emulator. It's time to dive into the meat of this project and interface with original Game Boy hardware by building a USB link cable adapter!Emulating a Pokemon Trade with Generated Link Cable Data2021-05-11T00:00:00+00:002021-05-11T00:00:00+00:00https://blog.gbplay.io/2021/05/11/Emulating-a-Pokemon-Trade-with-Generated-Link-Cable-Data<figure class="centered">
<a href="/images/pokered_mock_trade.png" title="Trading Pokemon with a test script. If only I could have done this when I was 10.
" target="_blank">
<img src="/images/pokered_mock_trade.png" />
</a>
<figcaption><p>Trading Pokemon with a test script. If only I could have done this when I was 10.</p>
</figcaption>
</figure>
<h2 id="the-need-to-test">The need to test</h2>
<p><a href="/2021/05/10/An-8-Bit-Idea_The-Internet-of-Game-Boys.html">Last time</a>, we talked about the online Game Boy link cable
project we started. After some preliminary research, we ordered all of the
necessary components (and more) to start experimenting. In order to minimize
mistakes and confirm that everything continues to operate correctly when changes
are made, we’ll develop small self-contained tools and pieces of functionality
incrementally and continually build on that foundation. The goal is to have
something that works at all times, no matter how small or limited it may be.</p>
<p>So, after ordering the necessary hardware for serial communication, a good first
step is coming up with some information to communicate! This can be done without
any real hardware by using emulation. The goal is to create some known good data
which can be used to verify the eventual hardware setup.</p>
<h2 id="how-to-test">How to test</h2>
<p>The obvious choice of test data is game data – sending some bytes that an
actual Game Boy game <em>would have</em> sent and making sure that a real copy of
the game behaves as expected in response. We decided on mimicking a Pokemon
Generation 1 trade since Pokemon is a game we definitely want to support, we
have it on hand for testing with real hardware, and it’s not Tetris. The
<a href="https://www.youtube.com/watch?v=KtHu693wE9o">existing project</a> that inspired
this one is based around Tetris, and understanding the inner-workings of as many
different games as possible (such as Pokemon) is important for getting a sense
of how to design our project.</p>
<p>Spoofing a Pokemon trade requires a thorough understanding of how the game’s
link cable protocol works. Emulation can be used to validate the generated data
without a hardware setup. <a href="https://bgb.bircd.org/">BGB</a> is a well-known Game
Boy/Game Boy Color emulator that supports “linking” multiple instances. It can
also log all of the bytes sent over the emulated link cable connection, which is
invaluable for understanding the protocol of any game of interest. For when
that’s not enough, BGB features a solid debugger for reverse-engineering. The
emulator’s link cable interface is exposed over TCP, making it straightforward
to send arbitrary data without having to modify any emulator internals. It also
means that in theory the final version of GBPlay could support the BGB network
protocol – allowing emulated games to link up with real ones.</p>
<p>Once the fake Pokemon trade data has been confirmed to work with BGB, we can be
reasonably confident that it will work with an original Game Boy as well. Then,
the same data can be sent to a real Game Boy instead of the emulator, providing
an easy way to test the connection.</p>
<h2 id="pokemon-link-cable-protocol">Pokemon link cable protocol</h2>
<p>In order to generate data that mimics a second copy of Pokemon, we’ll first need
to understand how the game communicates over the link cable. As mentioned, BGB
can log link cable traffic. However, since this is Pokemon we’re talking about,
we can benefit from the hard work of those who came before us. There exists a
<a href="https://github.com/pret/pokered">comprehensive disassembly</a> of the first
generation Pokemon games (and beyond), and
<a href="http://www.adanscotney.com/2014/01/spoofing-pokemon-trades-with-stellaris.html">others</a>
have <a href="http://pepijndevos.nl/2015/02/12/grep-your-way-into-pokemon-red.html">already</a>
worked out the link cable protocol. Below is a detailed description of the
various steps involved in a Pokemon trade.</p>
<h3 id="role-negotiation">Role negotiation</h3>
<figure class="centered">
<a href="/images/pokered_role_negotiation.png" title="Initiating a connection. Choosing roles requires only a few bytes.
" target="_blank">
<img src="/images/pokered_role_negotiation.png" />
</a>
<figcaption><p>Initiating a connection. Choosing roles requires only a few bytes.</p>
</figcaption>
</figure>
<p>As described in the <a href="/2021/05/10/An-8-Bit-Idea_The-Internet-of-Game-Boys.html
#understanding-the-link-cable-protocol">previous post</a>,
when communicating, one Game Boy acts as the master device and provides the
link’s clock signal. Since both consoles will be running identical code, games
need to include a mechanism for determining which instance will provide the
clock signal and consequently initiate all data transfers. The way Pokemon
implements role negotiation is extremely simple.</p>
<p>When visiting the Cable Club at a Pokemon Center, the game will first operate
in slave mode and wait for a serial transfer to be initiated by the other Game
Boy (i.e., try to use an external clock signal). If this happens, it will
respond with the value <code class="language-plaintext highlighter-rouge">2</code>. If instead no such transfer occurs, then the game
will switch to master mode and send the value <code class="language-plaintext highlighter-rouge">1</code> to the other Game Boy (i.e.,
using an internal clock signal). This is done in a loop until the connection is
successful or enough failed attempts take place. If the master game receives a
<code class="language-plaintext highlighter-rouge">2</code> and the slave game receives a <code class="language-plaintext highlighter-rouge">1</code> then both instances will send two <code class="language-plaintext highlighter-rouge">0</code>
bytes, save the game, exchange a <code class="language-plaintext highlighter-rouge">0x60</code> byte for synchronization, and then
display the in-game link type selection menu.</p>
<h3 id="link-type-selection">Link type selection</h3>
<figure class="centered">
<a href="/images/pokered_link_type_selection.png" title="Choosing a destination. Bytes are sent continuously to allow either player to decide.
" target="_blank">
<img src="/images/pokered_link_type_selection.png" />
</a>
<figcaption><p>Choosing a destination. Bytes are sent continuously to allow either player to decide.</p>
</figcaption>
</figure>
<p>After the link has been established, both players are asked where they would
like to go – either the Trade Center (for trades) or Colosseum (for battles)
– or they can choose to cancel and close the link. All link cable transfers
must be initiated by the master, but the game allows either player to select
the destination. So while on this screen, the master will continually send
values of the form <code class="language-plaintext highlighter-rouge">0xDx</code> (where the bottom 2 bits of <code class="language-plaintext highlighter-rouge">x</code> store the index of the
highlighted option) to poll the other game, which will respond with a value
following the same format. Whichever side the selection is made on first will
indicate it by setting bit 2 of its value: <code class="language-plaintext highlighter-rouge">0xD4</code> for the Trade Center, <code class="language-plaintext highlighter-rouge">0xD5</code>
for the Colosseum, or <code class="language-plaintext highlighter-rouge">0xD6</code> to cancel. A cancel can also be signaled by setting
bit 3, which indicates that the B button was pressed on the associated menu
entry.</p>
<p>For our purposes, we will be faking a trade and will not go into detail on
how battles work.</p>
<h3 id="trade-center">Trade Center</h3>
<figure class="centered">
<a href="/images/pokered_trade_center.png" title="The Trade Center. Player 1 has already interacted with the trade machine and must wait until player 2 does the same.
" target="_blank">
<img src="/images/pokered_trade_center.png" />
</a>
<figcaption><p>The Trade Center. Player 1 has already interacted with the trade machine and must wait until player 2 does the same.</p>
</figcaption>
</figure>
<p>After the selection is made to go to the Trade Center, both players are
brought there but nothing else happens yet. No further transfers occur until
both players interact with the trade machine, which is signaled by sending
the value <code class="language-plaintext highlighter-rouge">0x60</code>. After this, the main transfer can begin. Interestingly
there’s no way to cancel or otherwise exit the Trade Center. To leave, the game
must be reset (saves the original developers and us some work).</p>
<h3 id="trainer-data-exchange">Trainer data exchange</h3>
<figure class="centered">
<a href="/images/pokered_trainer_data_exchange.png" title="Sending trainer data before displaying the trade menu. 0x8C 0x80 0x93 0x93 0x87 0x84 is MATTHE in Pokemon’s character encoding – the first 6 letters of my/player 2’s name.
" target="_blank">
<img src="/images/pokered_trainer_data_exchange.png" />
</a>
<figcaption><p>Sending trainer data before displaying the trade menu. <code class="language-plaintext highlighter-rouge">0x8C</code> <code class="language-plaintext highlighter-rouge">0x80</code> <code class="language-plaintext highlighter-rouge">0x93</code> <code class="language-plaintext highlighter-rouge">0x93</code> <code class="language-plaintext highlighter-rouge">0x87</code> <code class="language-plaintext highlighter-rouge">0x84</code> is <code class="language-plaintext highlighter-rouge">MATTHE</code> in Pokemon’s <a href="https://bulbapedia.bulbagarden.net/wiki/Character_encoding_(Generation_I)">character encoding</a> – the first 6 letters of my/player 2’s name.</p>
</figcaption>
</figure>
<p>Before the actual trainer data is sent, some random bytes are exchanged.
These are used to ensure consistency in link cable battles. For example, to
guarantee that the outcome of attacks is the same for both players. For
simplicity, the game uses the same code regardless of link cable connection
type. Since we’re doing a trade, these bytes are irrelevant and don’t need to
be understood further.</p>
<p>After the random bytes, Pokemon trainer data is exchanged. The 424-byte data
structure contains the name of the trainer, as well as detailed information for
every Pokemon in their party (level, moves, stats, etc.). The party Pokemon
portion is helpfully
<a href="https://bulbapedia.bulbagarden.net/wiki/Pok%C3%A9mon_data_structure_in_Generation_I">documented</a>
on Bulbapedia.</p>
<p>Sending this all up-front greatly simplifies the trade process. Players can view
the stats of every Pokemon in their friend’s party and change their mind about
which one they want without having to send additional information. When a trade
is confirmed, the game simply needs to copy the appropriate Pokemon data from
the already-received block of memory into the proper location in the player’s
save file and then remove the Pokemon they traded away.</p>
<h3 id="trade-selection-and-confirmation">Trade selection and confirmation</h3>
<p>Once the trainer data has been exchanged, the trade menu is shown and players
can view Pokemon stats and select which one they want to trade away. During
this time, the master polls the slave by repeatedly sending <code class="language-plaintext highlighter-rouge">0</code> similarly to
what happened during the link type selection step. Either player can exit the
trade menu by sending <code class="language-plaintext highlighter-rouge">0x6F</code> which will cause the players to go back to the
Trade Center. To trade, each side first indicates the selected Pokemon by
sending its index number in the party Pokemon list (ranging from <code class="language-plaintext highlighter-rouge">0x60</code> to
<code class="language-plaintext highlighter-rouge">0x65</code>). The game then displays a confirmation dialog stating which two Pokemon
are about to be traded. If a player cancels the trade at this point, <code class="language-plaintext highlighter-rouge">0x61</code> is
sent and the selection process is repeated.</p>
<figure class="centered">
<a href="/images/pokered_trade_confirmation.png" title="Player 1 selects index 4 (0x64; Mewtwo) and player 2 selects index 5 (0x65; Oddish) – a pretty bad deal. Player 1 has confirmed the trade and is waiting for player 2 to confirm as well.
" target="_blank">
<img src="/images/pokered_trade_confirmation.png" />
</a>
<figcaption><p>Player 1 selects index 4 (<code class="language-plaintext highlighter-rouge">0x64</code>; Mewtwo) and player 2 selects index 5 (<code class="language-plaintext highlighter-rouge">0x65</code>; Oddish) – a pretty bad deal. Player 1 has confirmed the trade and is waiting for player 2 to confirm as well.</p>
</figcaption>
</figure>
<p>If both players accept, <code class="language-plaintext highlighter-rouge">0x62</code> is exchanged as a confirmation and the trade
takes place. Since each game already has all of the necessary information about
the connected player’s Pokemon in memory, they already have everything they need
in order to trade and can copy the relevant memory right away. No further
transfers occur and the long-winded trade animation is played on both devices
purely for show. Both games are also saved during this time. When the sequence
is done, both players are taken back to the trade menu (“trainer data exchange”
step) and can trade more Pokemon if they wish.</p>
<figure class="centered">
<a href="/images/pokered_trade_finished.png" title="A successful trade
" target="_blank">
<img src="/images/pokered_trade_finished.png" />
</a>
<figcaption><p>A successful trade</p>
</figcaption>
</figure>
<h2 id="implementing-a-mock-trader">Implementing a mock trader</h2>
<p>With an understanding of the communication details behind Pokemon trades, it’s
time to simulate it with a script that produces output adhering to the same
protocol. This will be done using one module to interface with the BGB emulator
over TCP, and another to actually generate the data and send it over that
interface.</p>
<h3 id="bgb-link-cable-tcp-server">BGB link cable TCP server</h3>
<p>Not only does BGB use a simple TCP protocol for its link cable
functionality – it’s <a href="https://bgb.bircd.org/bgblink.html">well-documented</a> too!
All data is encapsulated into 8-byte packets. Four of these bytes always store
the time at which the packet was sent. This is to enable two instances of the
emulator to stay in sync. If one is running too far behind, the other can pause
to allow catch-up (a luxury that’s <a href="/2021/05/10/An-8-Bit-Idea_The-Internet-of-Game-Boys.html
#gb-spi-and-latency">not possible</a>
on real hardware).</p>
<p>The packet structure is as follows:</p>
<table>
<thead>
<tr>
<th>Offset</th>
<th>Size</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>0</td>
<td>1</td>
<td>Packet type</td>
</tr>
<tr>
<td>1</td>
<td>1</td>
<td>Packet data 1</td>
</tr>
<tr>
<td>2</td>
<td>1</td>
<td>Packet data 2</td>
</tr>
<tr>
<td>3</td>
<td>1</td>
<td>Packet data 3</td>
</tr>
<tr>
<td>4</td>
<td>4</td>
<td>Timestamp</td>
</tr>
</tbody>
</table>
<p>There are 7 types of packets:</p>
<table>
<thead>
<tr>
<th>Type</th>
<th>Name</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>version</td>
<td>Contains the version of the protocol the emulator is using</td>
</tr>
<tr>
<td>101</td>
<td>joypad</td>
<td>Notification of controller input changes (used for remotely controlling the emulator)</td>
</tr>
<tr>
<td>104</td>
<td>sync1</td>
<td>Indicates a byte sent from the master Game Boy</td>
</tr>
<tr>
<td>105</td>
<td>sync2</td>
<td>Indicates a byte sent from the slave Game Boy; sent in response to a <code class="language-plaintext highlighter-rouge">sync1</code> packet</td>
</tr>
<tr>
<td>106</td>
<td>sync3</td>
<td>Either an acknowledgment of a <code class="language-plaintext highlighter-rouge">sync1</code> packet or a timestamp update, depending on the value of the first data byte</td>
</tr>
<tr>
<td>108</td>
<td>status</td>
<td>Contains information about the state of the emulator (paused or running, supported protocol features)</td>
</tr>
<tr>
<td>109</td>
<td>wantdisconnect</td>
<td>Indicates a voluntary closing of the connection (avoids auto-reconnect logic)</td>
</tr>
</tbody>
</table>
<p>When the connection is established, each endpoint must send a <code class="language-plaintext highlighter-rouge">version</code> packet,
and if the version is supported then a <code class="language-plaintext highlighter-rouge">status</code> packet is sent in response to
complete the handshake.</p>
<p>The protocol isn’t that complex to begin with, but it can be further simplified
for this use case. We won’t be implementing an entire emulator, so we can send
dummy data for the <code class="language-plaintext highlighter-rouge">status</code> packet (i.e., never paused) and ignore sending and
receiving <code class="language-plaintext highlighter-rouge">joypad</code> and <code class="language-plaintext highlighter-rouge">wantdisconnect</code> packets altogether. For simplicity we
also will only ever allow the connected emulator to operate in master mode,
meaning incoming <code class="language-plaintext highlighter-rouge">sync2</code> messages and outgoing <code class="language-plaintext highlighter-rouge">sync1</code> messages don’t need to be
supported either. This will also allow us to eventually connect the emulator to
a real Game Boy via our TCP server, since real hardware will always need to
use an external clock signal due to the inherent latency of TCP.</p>
<p>Finally, the time of sending (measured in 2 MiHz Game Boy clock cycles) must be
included in every message, but we don’t have to worry about that either. We can
just lie and say that our server is always exactly in sync with the emulator
it’s communicating with by returning the same timestamp value that was in the
most recently received packet.</p>
<p>With all of this in mind, the high-level logic of our BGB link cable TCP server
is as follows:</p>
<ol>
<li>Wait for a connection from BGB</li>
<li>Send the <code class="language-plaintext highlighter-rouge">version</code> packet</li>
<li>Wait for packets from BGB in a loop
<ul>
<li>For <code class="language-plaintext highlighter-rouge">version</code> packets, respond with a <code class="language-plaintext highlighter-rouge">status</code> packet containing dummy data</li>
<li>For <code class="language-plaintext highlighter-rouge">sync1</code> packets, call a configurable callback function and send its
return value to the emulator via a <code class="language-plaintext highlighter-rouge">sync2</code> packet</li>
<li>For <code class="language-plaintext highlighter-rouge">sync3</code> packets, respond with a <code class="language-plaintext highlighter-rouge">sync3</code> packet</li>
<li>For <code class="language-plaintext highlighter-rouge">status</code> packets, respond with a <code class="language-plaintext highlighter-rouge">status</code> packet containing dummy data</li>
</ul>
</li>
</ol>
<p>The server also keeps track of the most recently received timestamp, which is
used in all response packets to simulate being in sync.</p>
<p>And that’s it! The whole thing works out to less than 150 lines of Python. You
can check out the code
<a href="https://github.com/mwpenny/gbplay/blob/fd707bf061460e9b0853770a8a2d8a42573b2be4/tools/pokered-mock-trade/bgb_link_cable_server.py">here</a>.
The server is generic and allows sending arbitrary data via the configurable
callback function. Whenever a link cable byte is received from BGB (<code class="language-plaintext highlighter-rouge">sync1</code>
packet), the server will call the function to get the response. This will
allow us to easily re-use the server for anything we want. Right now, the
callback we supply will implement the Pokemon link cable protocol, but in the
future we could have it communicate with a real Game Boy! Doing things this way
also keeps the mock trader code self-contained, which it will need to be so it
can work with our eventual hardware setup.</p>
<p>One implementation quirk worth noting is that although the BGB documentation
says not to send <code class="language-plaintext highlighter-rouge">status</code> packets in response to <code class="language-plaintext highlighter-rouge">status</code> packets, not doing so
causes instability in the emulator’s link cable connection (sometimes the entire
application becomes unresponsive). It seems that if BGB doesn’t receive
timestamps for a while, it assumes the peer has gone out of sync and pauses to
give it some time. Since <code class="language-plaintext highlighter-rouge">status</code> packets contain timestamps (like all packets),
sending them like we do helps keep the emulator’s connection state up to date
and allows it to continue happily along. The problem likely could have also been
solved by sending <code class="language-plaintext highlighter-rouge">sync3</code> packets on a regular basis, but this non-standard
approach keeps the code simpler (the server only responds to incoming messages
and never sends its own otherwise).</p>
<p>Now let’s test it out. We can supply a callback function that prints out the
bytes we receive.</p>
<figure class="centered">
<a href="/images/bgb_link_test.png" title="Testing our BGB link cable TCP server by printing out received data
" target="_blank">
<img src="/images/bgb_link_test.png" />
</a>
<figcaption><p>Testing our BGB link cable TCP server by printing out received data</p>
</figcaption>
</figure>
<p>After “linking” the emulator to our server and going to the Cable Club, a <code class="language-plaintext highlighter-rouge">1</code>
is received – success! This is the game trying to operate in master mode per
its role negotiation scheme. Further, if we don’t respond for a while an error
is shown in-game. Now that our infrastructure is working, we can implement the
actual logic.</p>
<h3 id="mock-pokemon-trader">Mock Pokemon trader</h3>
<p>With the generic BGB link cable TCP server written, implementing the Pokemon
trade state machine described earlier was relatively straightforward. The most
error-prone parts were handling synchronization (e.g., making sure that the
script receives the proper confirmation bytes before changing states) and the
serialization of the Pokemon data structures. It only supports trades and will
always trade the first Pokemon in the mock party. The party data is configurable
via Pokemon data structure helper classes.</p>
<p>A single class keeps track of the state information and provides a member
function which, given an input byte, will update the state and return the next
byte to send. The mock trader script creates an instance of the state machine
class using the specified fake Pokemon party. It then creates an instance of the
BGB link cable server class using the state machine’s update function as the
callback and runs the server. Once we have a way of communicating with real
hardware, we can use this same callback function to generate data based on bytes
received from a Game Boy.</p>
<p>Like the link cable server, this ended up not being overly-complex either. The
code is available
<a href="https://github.com/mwpenny/gbplay/blob/fd707bf061460e9b0853770a8a2d8a42573b2be4/tools/pokered-mock-trade/trader.py">here</a>
and the final result is shown in the video below.</p>
<figure class="centered">
<iframe src="https://www.youtube-nocookie.com/embed/PsPIXxGxTOE" width="560" height="315" frameborder="0" allowfullscreen="">
</iframe>
<figcaption><p>The link cable server and mock trader in action</p>
</figcaption>
</figure>
<h2 id="conclusion">Conclusion</h2>
<p>Going through this process required lots of learning and was very satisfying. We
now know how Pokemon’s link cable protocol works and it’s very simple. Only a
few bytes are needed at the beginning to put the game into slave mode and then
the rest of the communication is symmetric – that is, each instance of the game
sends the same type of data at the same time. This means that if we can get the
two games into this mode, it should be possible for the rest to “just work” by
naively forwarding bytes between them with no further intervention. Hopefully
this is common since it will make the project easier overall. We’ll need to dig
into more games to be completely sure. However, the implementation of the
state machine wasn’t very complex, so if we end up having to write game-specific
code then it doesn’t look like that will involve a substantial amount of work
per game.</p>
<p>Regardless of how different games work, we now have two very valuable tools in
our toolbox: the BGB link cable server will allow us to easily create other
game-mocking scripts if need be, and to connect an instance of the emulator to
real hardware! The Pokemon mock trader script – the goal of this
experiment – provides us with known good data that we can use to verify our
eventual hardware setup. The two are modular and self-contained, meaning they
can be mixed and matched with other tools we develop as necessary.</p>
<p>Overall, I consider this a success in many ways! Next time we’ll look at
connecting these new tools to real hardware.</p>Matt PennyBefore building an interface to communicate with an original Game Boy, we need something to send! So we created tools to mimic a Pokemon trade and to feed the data to an emulator for testing.An 8-bit Idea: The Internet of Game Boys2021-05-10T00:00:00+00:002021-05-10T00:00:00+00:00https://blog.gbplay.io/2021/05/10/An-8-Bit-Idea_The-Internet-of-Game-Boys<figure class="centered">
<a href="/images/dmg_link_cable_port.jpg" title="The side of an original DMG-01 Game Boy
" target="_blank">
<img src="/images/dmg_link_cable_port.jpg" />
</a>
<figcaption><p>The side of an original DMG-01 Game Boy</p>
</figcaption>
</figure>
<h2 id="the-spark">The spark</h2>
<p>In early May, my friend <a href="https://github.com/aidancrowther">@aidancrowther</a>
and I came across an incredibly cool video by hardware hacking YouTuber
<a href="https://www.youtube.com/c/stacksmashing">stacksmashing</a>. For his latest project
he’d created an adapter to connect an original Game Boy to a PC via the link cable
peripheral, along with a web server which could host multiplayer Tetris games by
bridging that connection. This combination allows players to face off in
competitive block stacking match-ups over the internet with original hardware!</p>
<p>As someone who loves 80s and 90s-era Nintendo and has
<a href="https://github.com/mwpenny/GameDroid">dabbled</a> in
<a href="https://github.com/mwpenny/pureNES">emulation</a>, this is awesome!
However Aidan and I both saw room for improvement and a chance to learn a lot
along the way. So we decided to start our own online link cable project.
Before diving into more detail, check out stacksmashing’s original video below.</p>
<figure class="centered">
<iframe src="https://www.youtube-nocookie.com/embed/KtHu693wE9o" width="560" height="315" frameborder="0" allowfullscreen="">
</iframe>
<figcaption><p>Stacksmashing’s original video</p>
</figcaption>
</figure>
<h2 id="how-it-works">How it works</h2>
<p>To summarize the video, there are three main components involved in making this work:</p>
<ol>
<li>
<p>A Raspberry Pi Pico, which implements the Game Boy’s link cable protocol and mediates
data transfer between the PC and handheld</p>
</li>
<li>
<p>A website, which utilizes <a href="https://developer.mozilla.org/en-US/docs/Web/API/WebUSB_API">WebUSB</a>
to communicate with the Pi and send data to and from the backend server</p>
</li>
<li>
<p>A backend server, which manages players and host-side game logic such as game over,
sending garbage lines to other players when lines are cleared, etc.</p>
</li>
</ol>
<p>Under normal conditions (before we started living in the future) one Tetris
instance acts as the game host and controls certain aspects of play such as the
music and when the game ends. Stacksmashing moved that logic to the server side
which allows each instance of the game to act as a client, thereby supporting a
theoretically infinite number of players! The server broadcasts received messages
to all connected clients so everyone is effectively part of the same game.</p>
<p>In Tetris, the only substantial interaction one player has with another is
attacking them by sending garbage lines to add some challenge. This makes the
game well-suited to a “battle royale” style like this (in fact, that’s exactly
what the officially-licensed
<a href="https://en.wikipedia.org/wiki/Tetris_99">Tetris 99</a> is).</p>
<h2 id="improvements-and-goals">Improvements and goals</h2>
<p>This is all very impressive but there are a few limitations: it only supports
Tetris and in order to play you need to be sitting at your computer using a
hard-wired connection. This is a handheld console! Furthermore, stacksmashing
didn’t flesh out the hardware too much beyond the proof of concept stage, which
isn’t the easiest to share with non-technical friends.</p>
<p>Being on the lookout for new side project ideas, we couldn’t resist.</p>
<p>Our goal is to create a solution that supports as much of the Game Boy library
as possible (ideally generic/game-agnostic) and is plug and play, such that
everything can be done entirely from the Game Boy. This will take the form of
a small dongle with a link cable connector and Wi-Fi connectivity. Configuration
will be done via a custom Game Boy cartridge, with a mobile-friendly web page as
a backup option – much more portable.</p>
<p>Overly-ambitious? Probably. But a fantastic opportunity for both of us to learn
more about hardware projects and stretch those low-level programming muscles. We
both love seeing how far old technology can be pushed beyond what it was ever
intended to do, and creating custom hardware to go with it will add a whole other
layer of depth and fun. We’re extremely excited to start.</p>
<h2 id="hardware-details-and-feasibility">Hardware details and feasibility</h2>
<p>With our goals and ideas in mind, how realistic is this project? Clearly,
something along these lines is possible – stacksmashing’s project exists. But
before any work can begin we need to understand as many of the hardware details
as possible. Tetris works, but can other games cope with the latency of the
internet? We don’t want to come to a disappointing conclusion after putting in
hours of time and effort.</p>
<h3 id="understanding-the-link-cable-protocol">Understanding the link cable protocol</h3>
<p>First, let’s look at the link cable protocol. It’s essentially
<a href="https://en.wikipedia.org/wiki/Serial_Peripheral_Interface">SPI</a>, which is still
in wide use today in embedded devices ranging from SD cards to LCD screens. That
means there are many ready-to-use libraries for interfacing with it. Doing so
manually isn’t particularly difficult either (by design). It’s a very simple
serial protocol in which one master device communicates with one or more slave
devices. A minimum of three signals are needed:</p>
<ol>
<li>
<p>A clock signal, to indicate when a bit is being transferred (SCLK; serial clock)</p>
</li>
<li>
<p>Output to slave device(s) (MOSI; master out, slave in)</p>
</li>
<li>
<p>Input from slave device(s) (MISO; master in, slave out)</p>
</li>
</ol>
<p>The Game Boy exposes these signals on its link cable port.</p>
<figure class="centered">
<a href="/images/dmg_link_port_pinout.png" title="Game Boy link port pinout. The link cable swaps the wires for "out" and "in" on each end.
" target="_blank">
<img src="/images/dmg_link_port_pinout.png" />
</a>
<figcaption><p>Game Boy link port pinout. The link cable swaps the wires for "out" and "in" on each end.</p>
</figcaption>
</figure>
<p>In SPI configurations with multiple devices, there are also slave select (SS)
signals to indicate which device the master is communicating with at a given
time. However that’s not the case with the Game Boy – only 2 devices can ever
be directly involved in a transfer. This is true even when using the
<a href="https://shonumi.github.io/articles/art9.html">4-player adapter</a>, which
communicates separately with each connected Game Boy to get around the hardware
limitation.</p>
<p>With SPI, when the master device generates a clock pulse, both the master and
slave send a bit on their output lines and receive a bit on their input lines.
In other words, the protocol is bidirectional and synchronous. On the Game Boy,
games store the next byte to send in an on-board shift register. When a transfer
is initiated or a clock pulse is received, the next bit to send is shifted out
of the register and the received bit is simultaneously shifted in. After 8 of
these 1-bit transfers have occurred, an interrupt is generated on each Game Boy
to signal completion and that it’s safe for the game to read the value.</p>
<p>GB SPI holds the clock line high when idle and indicates a transfer by pulsing
it low. Data is shifted out (most significant bit first) on each falling clock
edge and sampled on each rising edge. This configuration is known as SPI mode 3.</p>
<figure class="centered">
<a href="/images/gb_spi.png" title="An example GB SPI transfer. Here, the master sends 0xD9 (217) and the slave sends 0x45 (69).
" target="_blank">
<img src="/images/gb_spi.png" />
</a>
<figcaption><p>An example GB SPI transfer. Here, the master sends <code class="language-plaintext highlighter-rouge">0xD9</code> (<code class="language-plaintext highlighter-rouge">217</code>) and the slave sends <code class="language-plaintext highlighter-rouge">0x45</code> (<code class="language-plaintext highlighter-rouge">69</code>).</p>
</figcaption>
</figure>
<h3 id="gb-spi-and-latency">GB SPI and latency</h3>
<p>Since the link protocol is synchronous and bits are sent and received
simultaneously, that means the master device requires the slave to send its
response at a rate equal to the clock speed. In non-Game Boy Color mode, the
master Game Boy supplies an 8KHz clock (data transfer speed of 1KB/s). This
means that there is only a ~120μs window to respond! The Game Boy Color can
operate at even higher speeds. No internet connection could possibly satisfy
this latency requirement. However, the slave device has no such constraints.
It just responds when it receives data!</p>
<p>According to the excellent
<a href="https://gbdev.io/pandocs/Serial_Data_Transfer_(Link_Cable).html#external-clock">Pan Docs</a>,
the Game Boy can operate with link cable clock speeds of up to 500KHz (62.5KB/s
transfer speed) and importantly there is no lower bound. By default, the slave
will wait forever until it receives data from the master. In fact, the clock
pulses don’t even need to be sent at a consistent speed and there can be large
gaps in between.</p>
<p>Of course, some games may still implement timeouts but this sounds perfect for
our use-case. If we can somehow force both Game Boys to operate in slave mode
then the latency can be theoretically infinite, supporting even the roughest of
connections. As it turns out, this is exactly what stacksmashing did for Tetris
and what another project called <a href="http://pepijndevos.nl/TCPoke/">TCPoke</a>
did for Pokemon Generation 1 and 2. Bingo! This is a viable approach and has
already been proven to work with another game.</p>
<h2 id="next-steps">Next steps</h2>
<p>Understanding the link cable protocol and seeing similar projects gave us
confidence that what we want to do is possible. So, we did what anybody else
in this situation would: ordered an obscene amount of hardware! Link cables,
breakout boards, Game Boy games, microcontrollers, flash cartridges, and more.
With all of this in hand, the plan is to get a basic prototype PC to Game Boy
interface working and then build on top of that. Once proven, we’ll be able
to design the hardware and software to run it all.</p>
<p>There are still some unknowns that will need to be delved into through
experimentation – namely, testing compatibility with different games and
finding a game-agnostic way to force both Game Boys to operate in slave mode.
In the worst case, we’ll need to write game-specific code. But since we don’t
plan on supporting massive game lobbies like stacksmashing (as of now), once
the Game Boys are initialized properly the rest should just be a matter of
forwarding bytes back and forth between them as normal. We could be wrong, but
that’s the fun part!</p>
<p>Stay tuned for further updates and more deep dives.</p>Matt PennyAfter coming across an incredibly cool project involving online Game Boy multiplayer, we saw room for several improvements and lots of learning. So we decided to start our own project!