Reverse-Engineering Premier Manager 2’s VGA Graphics

About once every five years I fire up Premier Manager 2 — THE soccer management game before Championship Manager monopolised the genre. (These days, you can even play PM2 in your browser.)
Before starting to play, this time I noticed an interesting mix of files in the root directory — thingslike contract.gnd
, icons.vga
, anim.bin
, goal.voc
, and others—and wondered how hard it would be to decode them with modern tooling. Surely the .gnd
and .vga
files were graphic assets, the .voc
files audio, and so on. A little reverse‑engineering later, here’s what I found about the image formats…
The PM2 .vga
Format
Every PM2 graphic needs two files:
- Image file (e.g.
icons.vga
,font16c.vga
) – stores pixel indices. - Palette file (
paldata.vga
for in‑game screens,paltitle.vga
for the title screen) – stores the actual colours.
The PM2 palette is just a basic look‑up table: 256 entries x 3 bytes (red, green, blue) = 768 bytes that follow a 0x100‑byte header. Each pixel in the image is an index (0‑255) into that palette table, keeping files tiny and guaranteeing that every sprite, font, and icon shares the exact same colour set.
Why only 256 colours? Classic DOS VGA modes (320 × 200, 8‑bit), such as this masterpiece, were limited to a 256‑entry palette. SVGA later raised the bar on resolution and, eventually, colour depth, but PM2 shipped pretty much as SVGA was getting started.
File layout
0x000–0x0FF File header (256 bytes)
0x100–0x3FF Palette data (only in palette files: 256 × RGB)
0x400–EOF Image blocks
Each image block starts with an 8‑byte header:
Offset | Size (Bytes) | Purpose |
---|---|---|
0 | 4 | unused / reserved |
4 | 2 | height (y_size ) |
6 | 2 | width (x_size ) |
Pixel data immediately follows and is exactly width x height
bytes. Because of that regularity, you can iterate through a file like so:
block_size = x_size * y_size + 8
num_images = len(raw_data) // block_size
Tool 1 - Batch Image Extractor
I wrote two small Python scripts that validate all assets, walk through a .vga
file, pulls out each bitmap, and saves it as a BMP. Grab the PM2 game files (the easiest source is My Abandonware), drop them in an assets/
folder, then run:
python3 verifyAssets.py # validates file hashes
python3 vgaToBmp.py # dumps every icon/font/sprite to BMP
…But I don’t want to run your janky script.
Fine… Here are the extracted assets images:

Fax screen (fax_merged.vga)

Font16c glyphs (font16c_merged.vga)

Font55 glyphs (font55_merged.vga)

Font57 glyphs (font57_merged.vga)

Font57b glyphs (font57b_merged.vga)

Font77 glyphs (font77_merged.vga)

Font77b glyphs (font77b_merged.vga)

Font77c glyphs (font77c_merged.vga)

FontF9 glyphs (fontf9_merged.vga)

Score background (gndscore_merged.vga)

Stadium seats (gndseats_merged.vga)

Ground index (groundix_merged.vga)

UI icons (icons_merged.vga)

Impulse bar (impslbar_merged.vga)

Match ball icon (matball_merged.vga)

Match buttons (matbtn_merged.vga)

Match speed bar (matspd_merged.vga)

Phone UI 1 (phone2_merged.vga)

Phone UI 2 (phonem_merged.vga)

Pitch sprites (pitch_merged.vga)

Pitch bitmap (pitchbit_merged.vga)

Position graph (posgraph_merged.vga)

Report screen (report_merged.vga)

Result screen (result_merged.vga)

Security screen 2 (sec2_merged.vga)

Shareholders screen (sh_merged.vga)

Sponsors screen (sponsors_merged.vga)

Ticket window (ticket_merged.vga)

Validation buttons (validbtn_merged.vga)
Tool 2 - Dynamic Font Display
With the fonts extracted, why not type in glorious mid‑90 s style?
dynamicFontDisplay.py
is a quick Pygame demo that does exactly that:
python3 dynamicFontDisplay.py assets/font16c.vga assets/paldata.vga --scale 4
Where --scale
enlarges each glyph by an integer factor. Here’s a sample output:
Next Steps …
The image background .gnd
files are tougher: they appear to be compressed with a custom Lempel‑Ziv–Huffman variant. I’m pretty sure I’ll have to dive into the main executable to see how its compressed. Next time.