Skip to content

Instantly share code, notes, and snippets.

@deckarep
Created June 28, 2024 17:34
Show Gist options
  • Save deckarep/d6e0f0884a22c8a4a4b9392155f9dad0 to your computer and use it in GitHub Desktop.
Save deckarep/d6e0f0884a22c8a4a4b9392155f9dad0 to your computer and use it in GitHub Desktop.
Extracts all .wav audio from Leisure Suit Larry Casino - originally written by @Doomlazer
# Original credit: @doomlazer
# https://github.com/Doomlazer/TrivialQuest/blob/main/audio/jokes/ExtractWavs.py
# Extract wav files from Larry's Casino audio.vol
# Sound effects can also be dumped from resources.vol
# jokes are 71.wav - 183.wav
#
# command to convert wav to mp3:
# for f in *.wav; do ffmpeg -i "$f" "${f%.wav}.mp3"; done
import struct
AUDIO_DEST_FOLDER = "extracted_audio/"
def dump_audio(from_file):
fnum = 0
with open(from_file, "rb") as f:
while (byte := f.read(1)):
if byte == b'\x52' and f.read(1) == b'\x49' and f.read(1) == b'\x46' and f.read(1) == b'\x46':
print("Found RIFF starting at: ", f.tell()-4)
size = struct.unpack('<i', f.read(4))[0]
print("wav size: ", size)
f.seek(-8, 1)
wav = f.read(size+8)
s = AUDIO_DEST_FOLDER + str(fnum) + "_" + from_file + ".wav"
fnum=fnum+1
nf = open(s, 'bw+')
nf.write(wav)
nf.close()
# Extract music + sfx.
dump_audio("resource.vol")
# Extract cast voice audio.
dump_audio("audio.vol")
@deckarep
Copy link
Author

deckarep commented Jul 18, 2024

After figuring out how to do some tracing with Wine (I'm not sure if you're familiar with the Wine project but basically it allows you to run older Windows applications on Mac or Linux as if they were native applications). Not everything works well with Wine, and I got the game to run but it doesn't render correctly.

Anyways, I feel like I'm a step closer, here is some interesting tracing from the Wine logs while the game is running:

0024:trace:bitmap:NtGdiCreateBitmap 16x16, bpp 32 planes 1: returning 0x4090069
0024:trace:bitmap:NtGdiCreateBitmap 16x16, bpp 1 planes 1: returning 0x4090068
0024:trace:bitmap:NtGdiCreateDIBSection format (16,-16), planes 1, bpp 32, BI_RGB, size 1024 RGB
0024:trace:bitmap:NtGdiCreateBitmap 32x32, bpp 1 planes 1: returning 0x409006a
0024:trace:bitmap:NtGdiCreateCompatibleBitmap (0x101003c,32,32)
0024:trace:bitmap:NtGdiCreateBitmap 32x32, bpp 32 planes 1: returning 0x209006c
0024:trace:bitmap:nulldrv_StretchDIBits 0 0 32 32 <- 0 0 32 32 rop 00cc0020
0024:trace:bitmap:NtGdiCreateDIBSection format (32,-32), planes 1, bpp 32, BI_RGB, size 4096 RGB
0024:trace:bitmap:nulldrv_StretchDIBits 0 0 32 32 <- 0 0 32 32 rop 00cc0020
0024:trace:bitmap:nulldrv_StretchDIBits 0 0 32 32 <- 0 0 32 32 rop 00cc0020
0024:trace:bitmap:NtGdiCreateCompatibleBitmap (0x27010066,1,1)
0024:trace:bitmap:NtGdiCreateBitmap 1x1, bpp 32 planes 1: returning 0xc09006b
0024:trace:bitmap:NtGdiCreateCompatibleBitmap (0x3010071,1,1)
0024:trace:bitmap:NtGdiCreateBitmap 1x1, bpp 32 planes 1: returning 0x1090073
0024:trace:bitmap:NtGdiCreateCompatibleBitmap (0x1601005d,1,1)
0024:trace:bitmap:NtGdiCreateBitmap 1x1, bpp 32 planes 1: returning 0x1090075
0024:trace:bitmap:NtGdiCreateCompatibleBitmap (0x5010078,1,1)
0024:trace:bitmap:NtGdiCreateBitmap 1x1, bpp 32 planes 1: returning 0x2090079
0024:trace:bitmap:NtGdiCreateCompatibleBitmap (0x6010072,1,1)
0024:trace:bitmap:NtGdiCreateBitmap 1x1, bpp 32 planes 1: returning 0x109007b
0024:trace:bitmap:NtGdiCreateCompatibleBitmap (0x501007e,1,1)
0024:trace:bitmap:NtGdiCreateBitmap 1x1, bpp 32 planes 1: returning 0x209007f
0024:trace:bitmap:NtGdiCreateCompatibleBitmap (0x8010077,1,1)
0024:trace:bitmap:NtGdiCreateBitmap 1x1, bpp 32 planes 1: returning 0x1090081
0024:trace:bitmap:NtGdiCreateCompatibleBitmap (0xe41007d,32,32)
0024:trace:bitmap:NtGdiCreateBitmap 32x32, bpp 32 planes 1: returning 0x5090084
0024:trace:bitmap:NtGdiCreateCompatibleBitmap (0xf41007d,32,32)
0024:trace:bitmap:NtGdiCreateBitmap 32x32, bpp 1 planes 1: returning 0x1090085
0024:trace:bitmap:NtGdiCreateCompatibleBitmap (0x18010044,1,1)
0024:trace:bitmap:NtGdiCreateBitmap 1x1, bpp 32 planes 1: returning 0x909004c
0024:trace:bitmap:NtGdiCreateDIBSection format (640,480), planes 1, bpp 32, BI_BITFIELDS, size 1228800 RGB
0024:trace:bitmap:NtGdiCreateCompatibleBitmap (0x14410070,32,64)
0024:trace:bitmap:NtGdiCreateBitmap 32x64, bpp 1 planes 1: returning 0x6090083

Relevant code that loads these images is here:

https://github.com/wine-mirror/wine/blob/88a28aa5757ae74d9997b470d70216f10974247f/dlls/win32u/dib.c

and here:

https://github.com/wine-mirror/wine/blob/88a28aa5757ae74d9997b470d70216f10974247f/dlls/win32u/bitmap.c

More specifically check out this RLE encoding routine:

https://github.com/wine-mirror/wine/blob/88a28aa5757ae74d9997b470d70216f10974247f/dlls/win32u/dib.c#L333

@Doomlazer
Copy link

Doomlazer commented Jul 19, 2024

Well, that certainly gives me a lot to investigate. I never really learned C++ so it's difficult for me parse. I can usually do it though if I study the code slowly. The code mentions RLE4, so I might start by tracking down some info on BMP structures and compression.

Unfortunately, work has consumed my weekdays. I'm hoping I'm not completely exhausted again this weekend so I can follow up on these leads. Saturday will likely be a recovery day, but I'm hopeful to find some time Sunday. I'd really like to solve this if possible considering all the time we've put into it.

@deckarep
Copy link
Author

All good @Doomlazer - I'm updating these notes so I don't lose track of my findings and for you to also know when you feel up to the task but please tackle this at your discretion.

@deckarep
Copy link
Author

This has admittedly turned into a full blown obsession for me...still have stuff to work out that I don't fully understand but i've managed to get Peter looking a lot more like Peter. 😁

0

@Doomlazer
Copy link

Damn, that is nearly there! Keep going!

@deckarep
Copy link
Author

Thank you for the encouragement, I think i'm almost there...this is insane as I've never cracked something like this before...there's no magic to it other than me tediously counting pixels, offsets and trying to figure out these damn patterns. Another thing I've been doing is reading all the variations of RLE encoding in the wild but so far nothing really seems to match up with how this game does it.

I wish there was a better way.

0

@Doomlazer
Copy link

AFAIK, that is the way all reverse engineering is done. It's a combination of research and mostly trial and error based on hunches. If it were easy, it would have already been done.

It's extremely satisfying once you crack it though. It's like finding the WOPR backdoor, only 100x better because you didn't just guess a password. Based on that last pic you are very close, though there might be more to learn about the other images in the resource.

I have a feeling you'll get it this weekend, which is very exciting!

@deckarep
Copy link
Author

Time for a celebration: 🎊🔥⚡️🍾🚀

Still left to do:

  • Try other sprites and hopefully eliminate any further edge cases
  • Work on the sub-sprites (rows/columns theory) to get them to render out
  • Upload to Github this code so it can be improved and shared
  • Primary goal is to extract the character sprites during LC gameplay
  • Secondary goals would be get all room backgrounds and GUI sprites, but I don't really need this for my poker game

LarryCasinoSpritesReversedBanner

I credited you because you helped get the scripts started and you were instrumental in bouncing ideas off of, so a big thank you @Doomlazer!

@Doomlazer
Copy link

Doomlazer commented Jul 20, 2024

Grats, that's awesome! I'm excited to see the RLE algorithm.

You have to list yourself first in the credits for finally breaking it.

@deckarep
Copy link
Author

Thanks, here's the not-so-secret-sauce: https://github.com/deckarep/laffer-casino-extractor

@deckarep
Copy link
Author

@Doomlazer - would you be okay with also getting your .wav/jokes extractor in this repo for completion?

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