Extract unit counter images from VASSAL game modules (.vmod files) for roster generation. Use when adding counter images to a game, setting up image mappings for units, or troubleshooting missing/mismatched unit images. Handles both individual pre-rendered images and template-based composite images with automatic counter type detection.
VASSAL modules are ZIP files containing game piece definitions and images. This workflow covers:
detect_counter_type.py)A .vmod file is a ZIP archive containing:
module.vmod/
├── buildFile.xml # Piece definitions, prototypes, image references
├── moduledata # Module metadata
├── images/ # All image files (jpg, gif, png)
│ ├── counters...
│ ├── markers...
│ └── maps...
└── *.vsav # Saved game files for scenarios
This XML file defines all game pieces. Key elements:
<!-- PieceSlot defines a placeable piece -->
<VASSAL.build.widget.PieceSlot entryName="UnitName" gpid="123">
+/null/prototype;USA Infantry Division\
piece;;;ImageFile.jpg;UnitName/
</VASSAL.build.widget.PieceSlot>
<!-- The piece definition format: -->
<!-- piece;;;IMAGE_FILE;PIECE_NAME/ -->
Each unit has a dedicated pre-rendered image with the unit name already on it:
C_Lee.jpg - Confederate unit "Lee"U_Meade.jpg - Union unit "Meade"CL_Stuart.jpg - Confederate leaderUL_Hooker.jpg - Union leaderExtraction: Use extract_images.py with the game code.
Units use generic background images based on corps/type, with unit names overlaid via VASSAL's label mechanism:
I-P-2-4.jpg, UII_2_3.jpg, USA_I_24.jpg)Only leaders have dedicated images. Brigade/division counters are composited.
Background image naming patterns:
I-P-2-4.jpg, UII_2_3.jpg, UV_2_2.jpgJ-3-4.jpg (Jackson's Wing), CIII_3_4.jpgUSA_I_24.jpg, CSA_M_20.jpgExtraction: Use the unified generator generate_counters.py with the appropriate game_id:
Important: For HYBRID systems where VASSAL adds text labels dynamically (detected by detect_counter_type.py), use --no-text flag to avoid double text overlay:
cd parser && uv run python image_extraction/generate_counters.py tom ~/Documents/vasl/gcacw/TOM_3_17.vmod --no-text
Environment Variables: Configure VASSAL module paths in parser/.env:
# Example from parser/.env
GTC2_VASSAL_PATH=~/Documents/vasl/gcacw/GTC2_3_10.vmod
OTR2_VASSAL_PATH=~/Documents/vasl/gcacw/OTR2_3_11.vmod
VMOD Files Location: ~/Documents/vasl/gcacw/ (store all GCACW .vmod files here)
For a streamlined extraction workflow, see:
parser/EXTRACT_IMAGES_QUICKSTART.md - 30-second workflow cheat sheetparser/image_extraction/integrate_game_images.py - Automates post-extraction setupUse detect_counter_type.py to automatically determine the counter image system:
cd parser && uv run python image_extraction/detect_counter_type.py /path/to/GAME.vmod
This analyzes buildFile.xml and image naming patterns to determine:
To analyze all modules in a directory:
cd parser && uv run python image_extraction/detect_counter_type.py --all /path/to/vmods/
mkdir /tmp/game_vmod
unzip "/path/to/GAME.vmod" -d /tmp/game_vmod
# List all images
ls /tmp/game_vmod/images/
# Find unit counters (filter out maps, charts, markers)
ls /tmp/game_vmod/images/ | grep -vE "Map-|Chart|VP|Turn|Control"
# Find piece definitions
grep -o 'entryName="[^"]*".*piece;;;[^;]*;[^/]*/' buildFile.xml | head -50
# Look for prototype definitions (counter composition)
grep "PrototypeDefinition" buildFile.xml | head -20
For INDIVIDUAL (RTG2-style) games:
cd parser
uv run python image_extraction/extract_images.py GAME /path/to/GAME.vmod
For TEMPLATE_COMPOSITE games:
# HCR
cd parser && uv run python image_extraction/generate_counters.py hcr ~/Documents/vasl/gcacw/HCR.vmod
# OTR2
cd parser && uv run python image_extraction/generate_counters.py otr2 ~/Documents/vasl/gcacw/OTR2.vmod
# GTC2
cd parser && uv run python image_extraction/generate_counters.py gtc2 ~/Documents/vasl/gcacw/GTC2.vmod
# TOM (HYBRID - uses --no-text because VASSAL adds labels)
cd parser && uv run python image_extraction/generate_counters.py tom ~/Documents/vasl/gcacw/TOM_3_17.vmod --no-text
For new games, add game-specific configuration to image_extraction/generate_counters.py.
Use the integration helper script to automate the post-extraction setup:
cd parser && uv run python image_extraction/integrate_game_images.py GAME
This automatically:
image_mappings/game_images.json to web/src/data/web/src/data/imageMap.ts with necessary imports and registrationsThen verify the build:
make build
Unit names often differ between VASSAL and parsed data:
| VASSAL | Parsed | Issue |
|---|---|---|
Heitzelman | Heintzelman | Typo |
A.P.Hill | A.P. Hill | Spacing |
Rodes | Rodes-A | Suffix |
Wilcox | Willcox-A | Spelling + suffix |
D'Utassy | D'Utassy | Curly apostrophe |
The extractors include name normalization to handle these variations.
Location: parser/image_mappings/{game}_images.json
{
"game": "hcr",
"matched": {
"C:Jackson": "Jackson",
"U:Meade": "U_Meade"
},
"matched_with_ext": {
"C:Jackson": "Jackson.jpg",
"U:Meade": "U_Meade.jpg"
},
"unmatched": ["Union (Cav): IL", ...],
"unused_images": [...]
}
Location: web/src/data/imageMap.ts
This file is auto-generated and maps unit keys to image filenames for the web app.
Location: web/public/images/counters/{game}/
detect_counter_type.pyextract_images.pygenerate_counters.pyimageMap.ts if not auto-generatedThe VASSAL module may not have all referenced images. Check if images exist:
ls /tmp/game_vmod/images/ | grep -i "imagename"
Usually indicates:
Check the Pillow font loading - the script uses system fonts with fallback to default.
If integrate_game_images.py or the extraction creates files in the wrong location (e.g., parser/web/ instead of project root web/), the path calculations in the scripts may be incorrect. After adding a new game configuration:
web/public/images/counters/{game}/ at the project rootparser/image_extraction/ need .parent.parent.parent to reach project root (up from parser/image_extraction/ → parser/ → project root)web/src/data/imageMap.ts:
import {game}Data from "./{game}_images.json";imageMap: {game}: {game}Data.matched_with_ext,counterTypeMap: {game}: ({game}Data as {{ counterType?: CounterType }}).counterType ?? 'template',parser/image_extraction/detect_counter_type.py - Automatic counter type detectionparser/image_extraction/extract_images.py - INDIVIDUAL (RTG2-style) extractorparser/image_extraction/generate_counters.py - Unified TEMPLATE_COMPOSITE generator (HCR, OTR2, GTC2, HSN)parser/image_extraction/integrate_game_images.py - Post-extraction integration automationparser/image_mappings/*.json - Generated mappingsparser/EXTRACT_IMAGES_QUICKSTART.md - Quick reference cheat sheetweb/src/data/imageMap.ts - TypeScript image map