Rebuilding Winamp's skin engine natively for the Mac
A Winamp 2 skin is a zip file full of Windows bitmaps. MAIN.BMP is the 275 by 116 pixel main window. CBUTTONS.BMP holds every transport button in its normal and pressed state. TEXT.BMP is a 5 by 6 pixel font. NUMBERS.BMP is the clock. There is no layout file: the positions of every sprite were hard coded into Winamp itself in the late 1990s, and every skin artist since then has drawn into those exact rectangles.
Amp98 is a native macOS player that reads those files and draws them exactly as their artists made them. This post is about what that took.
The coordinates are the spec
Because the layout lives in the player, not in the skin, getting the coordinates right is the whole job. I did not guess them. The Webamp project (MIT licensed) has spent years documenting every sprite rectangle, so I used its table as the reference and then rendered real skins to check the result.
Each window is a borderless NSWindow that blits sprites with nearest neighbour sampling, so a 1 pixel line in a 1999 skin is still exactly 1 pixel, or exactly a 2 by 2 block on a Retina screen. Double size mode makes it a 4 by 4 block. Nothing is smoothed.
Old files are weirder than the format
The format is simple. The files are not.
- Some bitmaps are RLE compressed and leave gaps that the RLE stream never writes. ImageIO and ffmpeg fill those with palette entry 0, so Amp98 does too, otherwise parts of the balance slider and the font come out as garbage.
- Some 20 year old zips are malformed in ways the macOS
dittotool refuses to read. Amp98 falls back tounzip, which is more forgiving. One skin in my test set only loads because of that fallback. - File names inside the zip come in every possible case:
Main.bmp,MAIN.BMP,main.BMP. Lookups are case insensitive. - A skin can ship a
REGION.TXTthat describes a polygon outline for the window. Those are the odd shaped skins people remember, and Amp98 cuts the window to that outline, so they float on the desktop with transparent corners.
My test set is 142 popular classic skins: 138 of the most downloaded skins in the Internet Archive's Winamp collection plus the four demo skins that ship with Webamp. All 142 load and render.
Modern skins have a programming language
Winamp 3 and 5 introduced "modern" .wal skins: XML layouts, PNG layers, and scripts compiled to bytecode called MAKI. A modern skin can be any shape, slide drawers out of a head, or rebuild its layout when you click a button. Without the scripts, a modern skin is a pile of overlapping layers.
So Amp98 includes a MAKI virtual machine written in Swift: a bytecode parser, a stack interpreter, and enough of Winamp's standard object library (groups, layers, buttons, sliders, text, timers, animated targets) to run real skins. Scripts are sandboxed by a step budget, so a broken skin cannot hang the player.
Of the 27 modern skins in my test set, 26 load. The one that does not depends on plugin files that were never inside the skin. Modern skin support is labelled beta, because the long tail of that library is long.
Audio
Amp98 plays your own files through AVAudioEngine, with a 10 band equalizer and an FFT spectrum analyzer modelled on the original.
A small surprise from the audio side: macOS happily decodes Ogg Vorbis and Opus, but AVURLAsset reports the wrong duration for them. A 60 second Opus file came back as 75 seconds. Reading the frame count from AVAudioFile gives the exact length, so the playlist uses that instead.
A skin museum in the app
There are more than 100,000 classic skins in the Winamp Skin Museum at skins.webamp.org. Amp98 has a browser for it built in: search, click a skin, and it downloads and applies. Skins belong to the people who drew them, so the app only fetches them from the museum when you ask.
Try it
Amp98 is $9.99, one-time, for macOS 14 and later on Apple silicon and Intel: https://amp98.com
Thanks to Jordan Eldredge and the Webamp contributors, whose years of reverse engineering made the coordinate tables and the MAKI format documentation possible.