On October 12th, 2024, a few friends and I met at our buddy Taggsta's home arcade, where he was eager to show off his gorgeous Afterburner cabinet that had been recently refurbished; however, we had to appreciate it with our eyes only as it was awaiting a new harness to make it playable. Beside it was his Outrun cab and a couple of cute LAI low-boys which always have a special place in my heart.
It had been a while since seeing these guys. We'd typically meet a couple of times a year at an arcade bar, or the annual Aussie Kong Off at BPAC — the same event where I'd originally met most of these guys, tormenting ourselves through many hours of Donkey Kong over multiple days. The evening went mostly as usual catch-ups would, kicking back and talking shit whilst adoring Taggsta's various nerdy collectibles and displays around the shed.
On the far end of the arcade room, there was a projector pointing at the wall plugged into a tiny, scaled-down arcade cabinet.
The Taito Egret II Mini
The Egret II Mini is a mini arcade cabinet with 40 emulated Taito titles pre-installed on it, and I wasn't aware of its existence until then.

Usually I'd turn my nose up at emulator consoles like it, but the build quality stood out from many other modern emulator consoles that often feel and appear clunky or unpolished. Before I could get too eager to buy one myself however, I learned the price tag — too much for my liking. It was hooked up to a wireless 8bitdo arcade controller that allowed us to comfortably sit back on the couch and take turns playing various games relatively known to most of us — popular titles like Bubble Bobble, Qix, Rastan and Elevator Action. There were also even more games available that were included on an "Arcade Memories" expansion card (SD card) that could be purchased separately to add even more games.

Enter Adventure Canoe
At some point in the night, one of the guys selected a play on a game that didn't ring a bell to any of us — Adventure Canoe (1982 - Taito SJ System). The game we didn't think too much of initially remained on the projector screen for the rest of the night as we took turns beating each other's PB's. The gameplay was a straight-foward and had similarities to Speed Race CL-5, another Taito game that has eaten many of our coins at our local Bartronica in Melbourne.

As the night went on, I did a search on Google to learn more about the game. It was odd that in a room full of arcade nerds, none of us had heard anything about it before! Nearly all of the information we found was dated 2021-onwards; strange for a game that was stamped as being released in 1982.
Scrolling a bit further, we found this gem:

The only pre-2021 mention of "Adventure Canoe" that I could find was the above post on the KLOV forum from @solvalou68, asking about it in 2019, only to be met with "are you sure it was a taito release, and not a bootleg?" A follow-up response from the OP recalled "...the Arcade was owned by Taito, and you never really saw the clones there from what I could remember..."
Users commented theorising which game it could be, suggesting a few other titles but nobody could quite determine its title. After 4 years of silence in the thread, the mystery was revived with a comment from @TremiRodomi, mentioning the game's inclusion on the newly released Taito Egret II Mini:

Somehow, this game was mostly undiscovered and unreleased for 40 years since its creation in 1982. It became clear to me after more digging that at the time in 2024 (and even until very recently in early 2026), the only way to play Adventure Canoe was to own this miniature $400 emulator console. There are no photographs of any original Adventure Canoe cabinets, nor any records of art or PCBs for the console having ever existed!
We kept playing for another hour or so before we dispersed, but stuck with an overwhelming desire to learn more about the game, I grabbed Taggsta before leaving and asked, "how would you feel about me borrowing the console for a week or so to poke around at it?" With a promise not to break it, and no clear idea where to start, I tucked the Egret II Mini into my passenger seat and headed home.
Poking around the machine
When I woke up the next day, I jumped online to do a deep dive and see what else I could find about the console and games within; still finding only articles and videos about the Egret II Mini with no particular focus on Adventure Canoe. Finding little about the hardware online, nor anything particularly useful for cracking into it, I decided to pull out the screwdriver and poke around.
Removing the screws from the bottom of the machine revealed the PCB within, sporting a bunch of discrete components, various I/O connectors, an RF shield and a cute mascot on the silk screen.

Removing the RF shield didn't reveal anything else of interest, so I removed the remaining screws and pulled out the board to reveal the back side.

The rear side of the board was much more interesting. A couple of IC's I didn't recognise but thankfully their designators made my search easy:
IC2: AXP223 - The USB-C power management chip
IC6: EP952 - HDMI controller
IC3: K4B4G16 - 4GB DRAM
IC5: FS33ND04G68TF10 - 4GB NAND flash
IC1: Z7213 - Zuiki-branded SoC.
The SoC was my first focus. Google pulled up a bunch of pages mentioning other modern emulator consoles: the NES Classic, Genesis Mini and the Astro City Mini V (a similar style, equally endearing mini cab). The search told me that the Zuiki SoCs were ARM-based chips that were used mostly for these common emulator consoles.
Not wanting to start de-soldering chips and risking a $400 purchase to replace my buddy's console, I took my time over the next few days and searched intermittently until I came across a gold-mine of a thread on X featuring user @Caralynx unboxing and tearing down the console, before removing the NAND flash and dumping it.
My Egret II Mini has arrived pic.twitter.com/poFO92MAsR
— Caralynx (@GMMan_BZFlag) March 4, 2022
One of the posts in particular grabbed my attention:

The board has exposed UART ports! That would be my next target. The rest of the thread showed them removing and dumping the NAND flash, gaining access to the root filesystem and showing off some of the resources within.
A later post in the thread pointed out that the main binary that runs the frontend of the emulator was encrypted in a .zip file, stating that the password "is not hard to find, especially when you leave the symbols in..."

The thread continued as they successfully managed to load the Egret software on an ARM-based RK3288 to run it, without comment on whether they were able to dump the ROMs themselves. The thread ultimately wrapped up with no trace of a password, meaning I'd have to figure it out myself.
Getting UART access
My next logical step was to get access to that UART. With the earlier screenshot pointing out the TX & RX pads, finding GND and connecting the Egret to a USB-to-TTL adapter was trivial. I soldered up some jumper wires I had on-hand to the respective pads and dodgily hooked them up to the TTL adapter.

I plugged the adapter into my laptop and pulled up minicom. After seeing some garbled nonsense and switching the Baud rate a couple of times, text began filling the screen:
▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒!▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒%▒▒[ 0.3
U-Boot 2011.09-rc1 (Dec 02 2021 - 06:57:41) Allwinner Technology
[ 0.312]version: 1.1.0
[ 0.314]uboot commit : 8
ready
normal dc exist, limit to dc
no key input
dram_para_set start
dram_para_set end
In: Out: Err: Net: Warning: failed to set MAC address
out of usb burn from boot: without usb
Verifying Checksum ... OK
OK
OK
Uncompressing Linux... done, booting the kernel.
init started: BusyBox v1.30.1 (2023-06-29 03:28:53 UTC)
<truncated>
host_chose finished!
User Mode:policy=0
[joypad-1] fd is not valid.
[joypad-2] fd is not valid.
start game.
numid=12,iface=MIXER,name='Lineout volume control'
; type=INTEGER,access=rw------,values=1,min=0,max=63,step=0
: values=40
Configuring network interfaces... cat: write error: No space left on device
ip: SIOCGIFFLAGS: No such device
main 176 fb
main 180
main 184
System time was Thu Jan 1 00:00:53 UTC 1970.
Setting the System Clock using the Hardware Clock as reference...
System Clock set. System local time is now Thu Jan 1 00:00:54 UTC 1970.
Starting syslogd/klogd: value is: 0
done
Sweet! But sadly the bootloader logs didn't provide as much value as I'd hoped — there wasn't any particularly relevant or new information there, and I didn't have any luck getting an interactive shell. I wasn't willing to desolder the NAND and didn't have the tools to read or dump it without doing so. @Caralynx had mentioned their success membooting the console to gain access, but that was a little over my head so I left it be, nearly resigned having had the console for about a week by this point. Not wanting to deprive Taggsta of his lovely toy, it appeared time to kiss it goodbye and return it. I removed the jumper wires, cleaned up the board and put it back together.
Poking around the firmware
Returning to the x.com thread I'd found earlier, there was an off-hand comment about a recent firmware update for the Egret Mini on Taito's site. Conveniently, I'd recently watched a couple of oddly relevant videos about using software tools such as Ghidra to exfiltrate secrets from binaries and firmware files.
I downloaded Ghidra from Github, as well as the Egret II firmware .zip file. Before even loading up Ghidra, I was surprised to find that the egret firmware zip file contained a single .bin file that could simply be extracted. Contained within it was another file called rootfs that could once again be extracted to expose what looked to be a complete Linux filesystem.

Poking around within the filesystem, it wasn't long before I found the egret binary that was mentioned in the post from earlier. I grep'd a few keywords — canoe, arcade, egret etc., and landed on a folder /usr/egret2. Inside it was a file also named egret2, alongside two other files named resource1 and resource2.
D:\git\egret\filesystem\usr\egret2\ $> ls | xargs file
data: directory
egret2: Zip archive data, at least v?[0x333] to extract
font: directory
resource1: Zip archive data, at least v?[0x314] to extract
resource2: Zip archive data, at least v?[0x314] to extractMore Zip archives! Attempting to extract any of them yielded a password prompt — a sign I was on the right track, as this was the same prompt mentioned in the x.com thread above.

I'd been confident up until this point, but with a password-prompt staring me down and no password to provide it, I wasn't really sure of how I was going to get it, or whether I'd have any luck at all.
The earlier grep command yielded a few other hits in /usr/bin which I started poking around. One caught my eye in particular:
/etc/init.d/gameapp
Sitting in init.d, I figured it fair to assume that this was the entrypoint of the emulator that started the egret service on boot. Inside was a simple bash script that checks for updates on the SD card before running a secondary game_launcher process, logging its output to a log file in /tmp/.game.log.
I pulled up game_launcher to see what it did internally, upset to find that it wasn't a simple bash script like the last one. Running file on it told me that it was a compiled ARM32 binary. Unreadable in this state, I'd need to disassemble it to make sense of what it was doing.
D:\git\egret $> file filesystem\usr\bin\game_launcher
filesystem\usr\bin\game_launcher: ELF 32-bit LSB pie executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-armhf.so.3, BuildID[sha1]=b1050c75c6ab444f9a5d700a82624f03f3257337, for GNU/Linux 3.2.0, strippedAnalysing the firmware with Ghidra
Booting up Ghidra for the first time was a bit overwhelming. I spent some time researching how to find my way around the tool and set my target on finding the entrypoint (main) of the game_launcher binary to see what it does upon start-up. I had Ghidra auto-analyse the file and it was able to determine the main function automatically. Ghidra analysed the file: it was much more legible than I'd expected.
It didn't take long to click for me that this is what Caralynx was referring to when they'd commented about the password being easy to find "when you leave the symbols in". Reading the disassembled code, it seemed not everything was stripped, a lot of valuable context was retained in the file.

The contents of main were somewhat straight forward after digesting the disassembled syntax. The code checks for the presence of a legitimate Taito SD card. If one isn't present, the console uses the internal egret2 binary to run the games. If a card is present, it ensures that it gets mounted properly and uses it instead. Strangely, this tells us that the Egret II Mini's expansion cards actually contain their own binary that handles the console's frontend, and this file is encrypted using a key derived from the SD card's own CID (for copy protection).

Scrolling to the code that handles booting without an SD card present, I'd expected to be at this for a while longer yet; but was baffled to find this:

The encryption key for the internal egret2 binary was simply... hardcoded? In hindsight, the same outcome could have been reached a lot faster with a simple strings call, as the password sticks out like a sore thumb:

strings on the binary yields the password front-and-centre
Heading back to the egret2 archive and pulling it up in 7zip, I was relieved to find that entering the 64-character password yielded a lovely decrypted binary:
D:\git\egret\filesystem\usr\egret2\egret2 $> file egret2
egret2: ELF 32-bit LSB pie executable, ARM, EABI5 version 1 (GNU/Linux), dynamically linked, interpreter /lib/ld-linux-armhf.so.3, BuildID[sha1]=f474e2e54a48c5f268659a01a8fdc9a0898df90c, for GNU/Linux 3.2.0, strippedWhen pivoting to the other two zips, resource1 and resource2, I was soon humbled by an "invalid password" error, but excited to see that the empty structure of the archive was still revealed, along with a child folder titled roms.

I spent a bit of time sifting further through the game_launcher code, before punching the newly extracted egret2 binary into Ghidra to see what was going on behind the scenes.
The egret2 binary
Following a same approach as I did for the game_launcher script, I started by looking for a main function. What I noticed immediately is how slow the auto-analyse feature was taking compared to the earlier file. Taking a quick look at the size of the files, game_launcher's size (69.6kb) was dwarfed by egret2's (11.5mb) — by a little under 17,000%.
After a few minutes, Ghidra had mostly finished auto-analysing, and presented me with an entry function with did little but invoke __libc_start_main. From the use of std::string and operator.new, it looked to be originally written in C++ rather than C as I'd assumed game_launcher was.
Clicking through to the first argument LAB_0002bdc0+1, the de-compilation window showed a greyed window with a bunch of unruly code, starting with byte UndefinedFunction_0002bdc0(void). After clicking f in the listing view to mark it as a Function, I hit l and labeled it main. A quick perusal of the main function revealed a few strings that were being printed by the program (as well as a lot of unintelligible garbage), which I bookmarked so that I could easily find them later:

resource1 and roms.zip!I went on to spend many (many) hours sifting through the decompiled code, marking various points of interest. This was a gruelling process, as anything useful to me was polluted by countless nested functions and variables with unknown types. I tried searching the program text for a few strings like canoe, zip and extract etc., finding some interesting snippets — including a list of games on the console! But after a day or so of intermittent digging, I couldn't find any code responsible for unzipping the archive.
A few days later, I was chatting to a few people at a bar after Ruxmon (a security/IT meetup in Melbourne) and found myself explaining the project to someone who suggested trying a tool like angr to emulate the binary and inspect the memory addresses at the point right before it unzips the file. It sounded like black magic to me at that point — I had naively assumed that debugging it in real-time wouldn't be possible or practical, but he encouraged me to give it a crack and see what I could come up with.
Dynamic Analysis with angr & gdb
I got home late that night, but couldn't help myself. I jumped on my PC and installed angr. I watched a few videos about it and struggled to get it to run against my binary in any useful way. From what I'd gathered, angr calculates the conditions required to reach a specific in-code location. The problem was that I still wasn't sure where in-code the zip extraction was happening. Hearing birds chirping outside, I temporarily accepted defeat and got some rest.
Returning to my PC the next day and reading up a bit more, I came across gdb — a tool that fit a similar purpose, but was made for interactively debugging binaries, rather than "solving" them. I fired it up and loaded in the egret2 binary — but didn't get very far before realising that it wouldn't be that easy. Running & debugging the arm32 binary on my x64 machine wasn't going to work without some massaging; but I had an idea!
I grabbed a Raspberry Pi 4 from my drawer and flashed an SD with a fresh copy of 32-bit PiOS. Once I had a shell via SSH, I copied over the important files from the Egret filesystem onto the Pi's, namely the libs, the egret2 binary (which I placed in /tmp/egret2) and the archives containing the goods.
I then spent an hour or so making sense of gdb, primarily struggling with the awful monochrome output and (lack of) UI. Google introduced me to gef — a toolkit for gdb which solves most of the pain-points and adds some additional QoL features. I imported it into gdb and was relieved to find it significantly more usable.
As with earlier, my goal at this point was to simply find the entrypoint and main function of the program. I already had the address for main from Ghidra, so I set a breakpoint with break *0x2bdc0 and then typed run. The script seemingly ran fine, but blew past the breakpoint I'd set and fell over when trying to initialise graphics. I knew that this was way past the code that I was interested in, as I'd come across the same code whilst digging through Ghidra earlier — well beyond the ROM-related logic that I cared about.

A bit more fumbling with various gdb commands, I ran info auxv to get some more information and received the following output:
gef➤ info auxv
...
9 AT_ENTRY Entry point of program 0x427735
...Nice! It seemed like what I was looking for, so I set a breakpoint with break *0x427735, typed run and tried again. It paused execution elsewhere this time! But I wasn't sure where I'd landed as it didn't resemble the same logic I could see for the main function in Ghidra. I poked and researched a little more until finding the x (examine) tool which would allow me to print n instructions from a specified address. I cast my net wide and ran x/50i $pc to print the next 50 instructions after the current program counter.

__libc_start_main has joined the party!I set my breakpoint at that address and continued along with ni (next instruction), cross-checking Ghidra and looking for any instructions that looked similar, before landing at the following address:

0x41bdc0 is the same as the first instruction of main in ghidra!Checking against Ghidra, I confirmed the instruction at these two addresses (Ghidra's main at 0x2bdc0) and gdb's 0x41bdc0 were the same, bar the syntax; with the latter being offset by exactly 0x3F0000. I googled roughly, "different addresses between Ghidra and gdb" and it appeared this was due to ASLR which randomly offsets an executable's data to make it more difficult for known memory addresses to be exploited. Good to know! I continued on, assuming the other locations I could see in Ghidra would be offset by the same amount.
I set another breakpoint with b *(0x3F0000 + 0x2c0a8), the location of the "resource1" string I'd bookmarked in Ghidra, and typed continue.

0x2c0a8 in GhidraAs hoped, the "resource1" string was sitting in the register $r1. A few rows down, there was another string sitting in register $r9 which stood out. The register UI had truncated it, so I spent longer than I'd care to admit poking at gdb to make it show me the full thing.

After reading up a bit more on the examine command from earlier, I ran x/s $r9 to examine the string value from $r9:

Not wanting to get too excited, I copied it and threw it at the resource1 archive...

No errors. The archive extracted quietly and revealed its gorgeous innards:

Making the game playable
I could immediately see the files weren't in a format runnable within MAME; which would expect the rom files to be split into smaller parts, per the respective arcade board's individual rom IC's on it. A fairly trivial obstacle compared to the work that led up to this point.
Throwing a couple of the rom files into my hex editor of choice (ImHex) for a quick sanity check, I could see some exciting text:

With the hard part over, I was tasked with two final challenges: splitting the ROM files into their correct chunks, and getting the game running on MAME.
Splitting the ROM
Funnily enough, this is the part of the entire endeavour that took the longest amount of time; not due to difficulty, but simply due to minutiae and various other projects. I returned to the project around a year later and decided to polish it off.
The Adventure Canoe files were in a folder titled SRSGIJ07. None of the folders had any identifiable information initially, but the other archive — resource2 — contained bezel artwork for each game, each with a filename that corresponded to the SRS* folder that the respective roms lived in. Matching the bezel graphic to my photos of Adventure Canoe running on the egret, I found the filename SRSGIJ07_wallpaper.png which pointed me to Adventure Canoe's folder.

As I'd expected, splitting the rom was relatively trivial. Knowing that Adventure Canoe was written to run on the Taito SJ hardware, I felt it was fair to assume that it would have a similar IC configuration as the other Taito SJ games. Alpine Ski was aesthetically similar, and had a similar year of release (1981/1982) — I searched it on Arcade Italia and had a look at the romset info, which detailed the size, offset and purpose of each individual rom IC.
The SRSGIJ07 folder contained three files:
- maincpu.rom - 32kb
- sound.rom - 4kb
- tiles.rom - 20kb
Comparing that to the romset of Alpine ski:
- prom - 256b
- cpu - 32kb (split into 8 x 4kb files)
- sound - 4kb
- gfx (tiles) - 16kb (split into 4 x 4kb files)
The only noticeable difference is the extra 4kb of tile data and the missing eb16.22 prom file, the latter of which I could acquire from another Taito SJ board.
I went ahead and split the Adventure Canoe ROMs into similar chunks, but wouldn't know whether it was correct until I could run it on MAME or similar.
Running Adventure Canoe on MAME
MAME is a well known open-source emulator for running games from almost any system, including various Taito SJ System games like Jungle King, Alpine Ski, and (soon) Adventure Canoe. Since MAME uses CRC and SHA1 checksums to verify the roms, it wouldn't be as simple as just loading the roms in and hitting play — I'd have to update the MAME codebase to handle the new game. I checked out the MAME repo from git and opened the taitosj.cpp file which was responsible for the emulation of all of the other Taito SJ titles.
The controls for Adventure Canoe were simple enough — a 4-way joystick and a "Speed up" button and a "Fire" button. MAME also features a DIP switch config which allows you to configure the parameters for the game as you would on a real arcade board. Since I didn't have the console on-hand any more, I found a couple of videos on Youtube which briefly showed the DIP options for the game and added them.
The final part of the puzzle was adding the checksums. I ran crc32 and sha1sum against the files and added them to the code. I built MAME from source (with a quick intermission reinstalling msys2 as I'd broken it at some point) and after a few minutes, it had built successfully.
The only thing left to do was to run it and cross my fingers. I moved my new adcanoe ROMs into the MAME folder, ran mame adcanoe -video gdi -window, and hoped for the best...

Wasting no time at all, I plugged in a controller and gave it a spin. It ran without a hitch. No glitches, no audio issues. I pulled out my phone, captured a video to send to and celebrate with the group of buddies I'd first discovered it with.
Bonus chapter: Running Adcanoe on my Analogue Pocket
A week after I got the game running on MAME, I decided to look into getting the game running on the Analogue Pocket, my favourite handheld. I found a Taito SJ core for the pocket and cloned it locally.
GitHub - antongale/arcade-taitosj: Taito System SJ Analogue Pocket Core
Taito System SJ Analogue Pocket Core. Contribute to antongale/arcade-taitosj development by creating an account on GitHub.
antongale
Implementing an FPGA core itself is a bit outside of my area of expertise, but the hard work had already been done. I opened the repo and looked at how the other ROMs had been implemented in it. Since the core itself was already implemented, it should theoretically be simple to copy & modify a configuration for Adventure Canoe and get it running, right....?

If you made it all this way, thanks for reading!
Reach out to me on bsky and share what you're working on (or generally geek out) with me!
fippi (@fippi.bsky.social)
i like to make stuff and tinker
Bluesky Social
And check out my tabletop board game "Sudden Conflict" which we're launching on Gamefound in a few weeks! (it's largely to blame for taking so long on this project & post).

¹ all em-dashes, as well as the rest of this post was written by myself (a verified human). LLMs have not been (and will be) used in the writing of posts on my site.