CARVIEW |
Setting Discord status from physical GameCube console
Have you ever seen one of your friends playing a game or listening to music in their Discord “status”? That feature is called “Rich Presence”.
What if you want to show your Discord friends that you're playing your GameCube? and I don't mean an emulator like Dolphin, I'm talking about a physical console from 2001.
We can do just that with a simple Python script and a Memcard Pro GC.
The Memcard Pro GC is an internet-connected GameCube Memory Card virtualizer. The Memcard Pro GC automatically switches
to a virtual Memory Card (VMC) for specific games when launched with
a
We can use this automatic VMC switching feature to “detect” when a game is being played on a physical GameCube and update Discord with a new Rich Presence.
Configuring the Memcard Pro GC§
You can buy a Black or White Memcard Pro GC from 8BitMods for ~$45 USD plus shipping. They are oftentimes sold out, so you may need to be patient for a re-stock.
There is a documented first-time setup page. If you're on
Ubuntu or Linux like me, you can use mkfs
to format the microSD filesystem to exFAT:
# Find your microSD card. Make sure it's the right size
# and device so you don't accidentally overwrite your
# storage drive(s) / boot drive. You want the /dev directory
sudo fdisk -l
# Might need to fiddle with microSD card, un-and-remount,
# make sure it's not in read-only mode on the adapter.
sudo apt-get install exfat-fuse
sudo mkfs.exfat -n memcardprogc </dev/...>
You should see something like this when you're done:
Writing volume boot record: done
Writing backup volume boot record: done
Fat table creation: done
Allocation bitmap creation: done
Upcase table creation: done
Writing root directory entry: done
Synchronizing...
exFAT format complete!
After that put all the files on the new filesystem and plug the card into a GameCube or Wii, power the console on, and let the device install the firmware.
NOTE: When I set up the Memcard Pro GC I wasn't able to get the latest firmware at the time (v2.0.4) to work without the device endlessly boot-looping. I tried downgrading to an earlier version of the firmware (v2.0.0) and the device worked flawlessly. Maybe I'll try again if the firmware is updated again and see what happens.
Once the device is online do the setup for connecting the device to WiFi. From here you can access the “Settings” page on your phone or computer.
For automatic detection to work as expected we need to create a default
memory card that isn't associated with a Game ID (I used MemoryCard1
) and to disable
the “Load Last Card when MemCard Boots” feature. Now on boot-up the Memcard Pro GC will
use the MemoryCard1
instead of whatever your last played game was by default.
Now we can create VMCs for every game which the Memcard Pro GC will automatically do when we launch a new game through our Game ID launcher. This can be done quickly in CubeBoot on the FlippyDrive by selecting games in your library one-by-one but not launching into the game completely. This is enough to trigger the Memcard Pro GC to load the VMC for a given Game ID.
NOTE: If you plan to do anything with the FTP server with the Memcard Pro GC you need to configure a username and password, enable the server, and crucially completely power off and power on the console for the FTP server to start on boot. I lost around 30 minutes trying to get FTP to work to this oversight.
Downloading assets§
So we'll need two different assets from the GameTDB. First we'll need a list of valid Game IDs so our program can distinguish between a VMC not associated with an actual game and the cover artwork for the games we own.
Download the wiidb.txt
database in your preferred language (I am using English)
and “Covers” for each region of games that your collection contains.
I own games from the USA (NSTC) and Japan (NSTC-J) regions, so I downloaded those
covers for those regions.
The covers from GameTDB are 160x224 pixels which is below the minimums that Discord requires for Art Assets (512x512) so we can scale them up with ImageMagick after we've unzipped the images into a directory.
First we need to isolate only the cover artworks for games in our library.
For this we can query the VMCs on our Memcard Pro GC after we've setup
VMCs for each game. Replace the IP address (192.168.0.43
) with the one used for your own Memcard Pro GC:
$ curl -X POST \
--data '{"limit":100,"start":0}' \
https://192.168.0.43/api/query | \
pcregrep -o1 'gameID":\s*"([A-Z0-9]{6})'
Example list of Game IDs
GPVE01
GC6E01
G8MJ01
G4SE01
GAFE01
GEZE8P
GKYE01
GLME01
GM4E01
GP5E01
GP6E01
GP7E01
GFTE01
GMPE01
G8ME01
GPIE01
GSNE8P
GZLE01
GALE01
GMSE01
GS8E7D
GXSE8P
GSOE8P
G9SE8P
G2XE8P
PZLE01
GPVJ01
Save this list into a file and make sure there's a trailing newline. Now we can use the list we generated to
select and resize only the game cover artworks we plan to use.
Assuming we have a directory of the original images named covers/
and an empty directory named discord-icons/
sudo apt-get install imagemagick
cat gameids.txt | while read gameid
do
convert covers/$gameid.png -scale 400% discord-icons/$gameid.png
done
We don't want ImageMagick to blur or interpolate the images, so we use -scale
instead of -resize
.
At this point you should have a directory full of upscaled images to use with your Discord application.
Creating the Discord Application§
To use “Rich Presence” you need a Discord application.
There are plenty of tutorials on how to create one of these. I named
the application “GameCube” so the Discord status will say “Seth is playing GameCube”.
You'll need to upload all the newly resized game cover artwork images under the
Rich Presence > Art Assets
section.
NOTE: Uploading tons of Art Assets to Discord applications is kinda annoying. Super aggressive rate-limiting, upload them in small batches and take your time. Duplicates aren't a huge issue so don't sweat it!
Copy your Discord application ID into the script below.
Querying the Memcard Pro GC§
The Memcard Pro GC provides a simple JSON HTTP API
that can be queried for the current state of the VMC.
Requesting GET /api/currentState
returns a JSON
body including the current Game ID and game name
for the VMC:
{
"gameName": "Paper Mario: The Thousand-Year Door",
"gameID": "G8ME0100",
"currentChannel": 1,
"rssi": -40
}
We can periodically call this API and then, using the
pypresence
library, we can update our Discord Rich
Status. We need to have fairly lax timeouts and retries
to avoid unnecessarily setting and clearing the Rich
Status due to things like the memory card not responding
fast enough, the Memcard Pro GC is a fairly low powered device:
Full Python script source code
import urllib3
import pypresence
import time
import re
import pathlib
ROOT_DIR = pathlib.Path(__file__).absolute().parent
with (ROOT_DIR / "wiitdb.txt").open(mode="r") as f:
GAME_ID_TO_NAMES = {
gid: name
for gid, name in re.findall(
r"^([A-Z0-9]{6}) = (.+?)$",
f.read(),
re.DOTALL | re.MULTILINE,
)
}
class MemcardGCPresence:
def __init__(
self, memcardgc_host: str, discord_app_id: str
):
self.host = memcardgc_host
self.http = urllib3.HTTPConnectionPool(
memcardgc_host
)
self.discord = pypresence.Presence(
client_id=discord_app_id
)
self.discord.connect()
self.active_game_id: str | None = None
self.consecutive_errors = 0
def poll_forever(self):
try:
while True:
poll_start = time.time()
self.poll_once()
# Only set Discord status every 15 seconds.
poll_duration = time.time() - poll_start
time.sleep(max([0, 15 - poll_duration]))
finally:
self.reset()
self.close()
def poll_once(self) -> None:
try:
resp = self.http.request(
"GET",
"/api/currentState",
timeout=3,
redirect=False,
retries=False,
)
if resp.status != 200:
raise ValueError("Invalid HTTP status")
data = resp.json()
game_id_with_revision = data["gameID"]
# We use the GameID without the revision
# to determine the game cover artwork.
game_id = game_id_with_revision[:6]
game_name = data["gameName"]
except (
urllib3.exceptions.HTTPError,
ValueError,
KeyError,
):
self.consecutive_errors += 1
if (
self.active_game_id
and self.consecutive_errors > 3
):
self.reset()
return
# Game ID isn't a known game. Might be the default
# memory card or a ROM hack that we don't know about.
if game_id not in GAME_ID_TO_NAMES:
self.reset()
return
# New game, set Rich Presence.
if game_id_with_revision != self.active_game_id:
self.discord.update(
activity_type=pypresence.ActivityType.PLAYING,
state=game_name,
start=int(time.time()),
# Discord lowercases all filenames.
large_image=game_id.lower(),
)
self.active_game_id = game_id_with_revision
def reset(self) -> None:
if self.active_game_id is not None:
print(f"Stopped playing {self.active_game_id}")
self.consecutive_errors = 0
self.active_game_id = None
self.discord.clear()
def close(self) -> None:
self.http.close()
self.http = None
try:
self.discord.clear()
except Exception:
pass
self.discord.close()
self.discord = None
if __name__ == "__main__":
memcardgc = MemcardGCPresence(
# Default IP address for the Memcard Pro GC.
# Update if necessary. Include your own
# Discord App ID.
memcardgc_host="192.168.0.43",
discord_app_id="<Discord App ID>",
)
memcardgc.poll_forever()
The above script is open source under the MIT license.
So now when you're playing your GameCube at home you can run this script, and you should see your Discord status change to the game you are playing. Magic!
Let me know what games you still play on your GameCube! :)
Wow, you made it to the end! ...and you're thinking, what now?
- Share your thoughts on Mastodon, email, or Bluesky.
- Follow this blog on RSS or the email newsletter.
- Browse this blog’s archive of 140 entries.
- Check out this list of cool stuff I found on the internet.
- Go outside (best option)