Skip to content

Instantly share code, notes, and snippets.

@applecuckoo
Last active March 1, 2024 08:07
Show Gist options
  • Save applecuckoo/3943b8d00508e2e78a4a31d3c683747b to your computer and use it in GitHub Desktop.
Save applecuckoo/3943b8d00508e2e78a4a31d3c683747b to your computer and use it in GitHub Desktop.
applecuckoo's adventures in Dolphin, vgmstream and... the beast known as ffmpeg

applecuckoo's ripping notes

Hello! As of initial writing, I've gone through these games with relative success:

  • Spore Hero
  • LEGO Batman: The Video Game
  • Family Game Night 4 - Uploaded sfx, now pending on sounds-resource.org
  • Destroy All Humans: Big Willy Unleashed
  • Epic Mickey
  • Toy Story 3
  • Toy Story Mania

Template commands

Before we get into the odd stuff, here are some generic template commands that you can throw at the terminal:

find -type f -name "*.spd" -exec vgmstream-cli {} \; # Passes files with a certain extension to a command

find . -name '*.vp6' -exec bash -c 'ffmpeg -i {} -i ../audio/en-gb/$(basename {} .vp6).asf -c:v libx264 -c:a aac -vf scale=iw*3:ih*3 $(basename {} .vp6).mp4' \;

Spore Hero

Argh, this one was quite the pain with the opening cutscenes. Someone at EA decided that they should master said cutscenes in quadraphonic sound, which is not all that common these days. ffmpeg seems to think that this sound is in '4.0' layout i.e. front left, front right, front centre, back centre. That is not good, since quadraphonic sound is designated by ffmpeg as the 'quad' layout which covers all four corners of the room.

Alright, to the actual decoding. The video came in On2's VP6 format but was anamorphically squeezed from 16:9 to 4:3. You might recognise the prefix since Google ended up buying out On2, and in that same family is the VP9 codec that is used on YouTube. Meanwhile, the sound is in a .asf file with EA's custom ADPCM format, which was decoded using vgmstream. Ultimately, I settled for an MP4 with AVC for video and AC3 for audio. I used the following command to combine the video and audio, stretch the 4:3 video to 16:9 and force the audio layout in one fell swoop:
ffmpeg -i EABrand.vp6 -i EABrand.wav -aspect 16:9 -filter_complex "channelmap=channel_layout=quad" -c:v libx264 -c:a ac3 EABrandWideSurround.mp4

The -i bits are the source video and audio. I put the -aspect flag to stretch the clip back to 16:9 and used the channelmap filter to apply the quad sound layout. The quad layout is L R SL SR, however it's important to note that this doesn't work with YouTube. You'll need to create dummy centre and LFE channels for that.

I mapped the sound channels in Audacity, exported a .ac3, and just piped it into the existing process like this: ffmpeg -i EABrand.vp6 -i EA_5.1_test.ac3 -aspect 16:9 -c:v libx264 -c:a copy EABrandWide5.1.mp4

Except that it's not satisfying to use Audacity to do all the heavy lifting, especially for larger files. That meant more sitting down in front of bash thinking of crafty ways to one-line this task! I got stuck on this command:

ffmpeg -i EABrand.wav -f lavfi -i anullsrc=channel_layout=mono -filter_complex \
"join=inputs=6:channel_layout=5.1:map=0.0-FL|0.1-FR|1.0-FC|1.0-LFE|0.2-BL|0.3-BR" \
EA_5.1_test.wav # this was what I posted on the #ffmpeg irc channel

ffmpeg -i EABrand.wav -f lavfi -i anullsrc=channel_layout=FC+LFE \
-filter_complex "join=inputs=2:channel_layout=5.1" \
EA_5.1_test.wav # and then it turned into this. Thanks furc!

furc on the #ffmpeg channel at libera.chat pointed out that I forgot to change 6 inputs into 2, and also pointed out that by changing the channel layout for anullsrc you could toss out the map. Now comes trying to toss it into an mp4 file that's YouTube friendly. That was pretty easy, sso I ended up settling for this one-liner (now edited for upscaling):

ffmpeg -i EABrand.vp6 -i EABrand.wav -f lavfi \
-i anullsrc=channel_layout=FC+LFE \
-aspect 16:9 \
-filter_complex "join=inputs=2:channel_layout=5.1" \
-vf scale=1920:1080 \
-c:v libx264 -c:a ac3 EA_NTSC_5.1_test.mp4 # Final rip command

All the other soundbites are stored in .abk files which can be extracted with vgmstream when supplied with a .ast file.

Pathfinder files (.mpf + .mus)

On a random evening I managed to solve the problem that prevented me from ripping the Pathfinder files in Spore Hero, with Pathfinder being the interactive audio engine used by EA, similar to Audiokinetic's Wwise.

Lego Batman: The Videogame

This was a pretty easy cutscene extraction, albeit an extra command was needed. The cutscenes are in the HVQM4 format, which was jointly developed by the Chaos Lab at the University of Tsukuba along with Hudson Soft. All patents for HVQM4 have expired. This includes the South Korean patent (doi:10.8080/1020000076886?urlappend=en) which appears to be valid on Google Patents. However, according to KIPRIS (linked previously via the DOI), it expired in July of 2020 since it wasn't renewed. The video can be decoded using https://github.com/mbcgh/h4m-video-decoder along with vgmstream. The video decoder spits out a bunch of .ppm frames while vgmstream spits out a stereo .wav. Here's the commands to transcode them into an MP4:

/path/to/decoder test.h4m dummy.wav
vgmstream-cli test.h4m -o /sound.wav
ffmpeg -pattern_type glob -framerate 29.97 -i "output/video_rgb_*.ppm" -i ../sound.wav -c:v libx264 -c:a aac -vf scale=iw*3:ih*3 video.mp4

Toy Story 3

Sounds are stored in the FMOD Studio bank format (.fsb) using Nintendo's 4-bit ADPCM, which is easily decoded with vgmstream. Cutscenes are in Bink format which I can transcode with this command: ffmpeg -i input_16x9.BIK -c:v libx264 -c:a aac -aspect 16:9 -vf scale=1920:1080 output_16x9.mp4

Toy Story Mania

Basically the same as TS3, but with the opening FMV stored in a multilingual Bink file! The Bink file consists of a single mono 22 kHz background track and 10 different dub tracks in 44 kHz stereo, which is optionally pre-panned to match the camera movements. The Wii then combines the background and language tracks on the fly for the final result.

ffmpeg -i BNK_TSM_Intro_w.bik -filter_complex \
"[0:1][0:2] amerge=inputs=2; \
[0:1][0:3] amerge=inputs=2; \
[0:1][0:4] amerge=inputs=2; \
[0:1][0:5] amerge=inputs=2; \
[0:1][0:6] amerge=inputs=2; \
[0:1][0:7] amerge=inputs=2; \
[0:1][0:8] amerge=inputs=2; \
[0:1][0:9] amerge=inputs=2; \
[0:1][0:10] amerge=inputs=2; \
[0:1][0:11] amerge=inputs=2" \
-metadata:s:a:0 language=eng \
-metadata:s:a:1 language=deu \
-metadata:s:a:2 language=spa \
-metadata:s:a:3 language=fra \
-metadata:s:a:4 language=ita \
-metadata:s:a:5 language=nld \
-metadata:s:a:9 language=rus \
-ac 2 -ar 44100 -c:v libx264 \
-c:a aac -aspect 16:9 \
tsm_test.mp4

By the way, the missing three lines of metadata are for the Scandinavian languages (Danish, Swedish and Norwegian) which I cannot tell apart.

Another Bink feature used is its transparency function. This is used for compositing the pre-rendered characters onto the 'live' 3D set.

Personally, I'd recommend transcoding it to the open-source FFV1 codec. It's compatible with software like DaVinci Resolve, Adobe Premiere and Kdenlive, which is what I use. Here's a transcoding command for those transparent sections. It reuses most of the bits from the opening FMV command, but it doesn't mess with the dimensions...

ffmpeg -i filename.bik -filter_complex \
"[0:1][0:2] amerge=inputs=2; \
[0:1][0:3] amerge=inputs=2; \
[0:1][0:4] amerge=inputs=2; \
[0:1][0:5] amerge=inputs=2; \
[0:1][0:6] amerge=inputs=2; \
[0:1][0:7] amerge=inputs=2; \
[0:1][0:8] amerge=inputs=2; \
[0:1][0:9] amerge=inputs=2; \
[0:1][0:10] amerge=inputs=2; \
[0:1][0:11] amerge=inputs=2" \
-metadata:s:a:0 language=eng \
-metadata:s:a:1 language=deu \
-metadata:s:a:2 language=spa \
-metadata:s:a:3 language=fra \
-metadata:s:a:4 language=ita \
-metadata:s:a:5 language=nld \
-metadata:s:a:9 language=rus \
-ac 2 -ar 44100 -c:v ffv1 \
-c:a aac filename.mkv
ffmpeg -i filename.bik -filter_complex "[0:1][0:2] amerge=inputs=2; [0:1][0:3] amerge=inputs=2; [0:1][0:4] amerge=inputs=2; [0:1][0:5] amerge=inputs=2; [0:1][0:6] amerge=inputs=2; [0:1][0:7] amerge=inputs=2; [0:1][0:8] amerge=inputs=2; [0:1][0:9] amerge=inputs=2; [0:1][0:10] amerge=inputs=2; [0:1][0:11] amerge=inputs=2" \
-metadata:s:a:0 language=eng \
-metadata:s:a:1 language=deu \
-metadata:s:a:2 language=spa \
-metadata:s:a:3 language=fra \
-metadata:s:a:4 language=ita \
-metadata:s:a:5 language=nld \
-metadata:s:a:9 language=rus \
-ac 2 -ar 44100 -c:v ffv1 \
-c:a aac filename.mkv

FGM4

The sounds from this game are in the review queue for the excellent website https://www.sounds-resource.com and will pop up at some point.

They were extracted using vgmstream from .spd/.spt files (you run the command on the .spd file and it won't work without its corresponding .spt)

The FMV/opening logos are stored in .wme format using the codecs from Nintendo's THP library they reused from the GameCube.

There don't seem to be any known ways to extract the 3D files from the game into a usable format. I've uploaded a sample in my Google Drive, so if you find anything, leave a comment below this gist!

Nerf N-Strike

All the data is stored in .big format, a custom EA archive (like a .zip file) that can be extracted using a simple Python script. There were two .big files for cutscene storage, with the second containing the rest of the Spanish video tracks.

Like the Spore Hero FMVs/logos, these were easily transcoded with ffmpeg. They were stored in the exact same codecs as before.

The Wii U-era LEGO games

The TT-made LEGO games are stored in parts, comprised of .DAT files.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment