Convert images into Forza Horizon vinyl groups and Assetto Corsa liveries using ForzaDesigner6
---
name: forzadesigner6-vinyl-converter
description: Convert images into Forza Horizon vinyl groups and Assetto Corsa liveries using ForzaDesigner6
triggers:
- convert image to forza vinyl
- create forza horizon livery
- generate vinyl group for forza
- inject vinyl into forza horizon
- export assetto corsa livery
- use forzadesigner6
- forza designer vinyl conversion
- automate forza vinyl creation
---
# ForzaDesigner6 Vinyl Converter
> Skill by [ara.so](https://ara.so) — Design Skills collection
## Overview
ForzaDesigner6 (FD6) is a Python-based tool that converts any image (JPEG/PNG) into:
- **Vinyl groups** for Forza Horizon 3, 4, 5, and 6 via live memory injection
- **Liveries** for Assetto Corsa Competizione via file export
The tool uses geometric shape approximation algorithms to reconstruct images using in-game primitives (spheres for Forza, texture sheets for ACC), then either injects the shape data directly into the running game's memory or writes livery files to disk.
## Installation
### Pre-built Binary (Windows 10/11 x64)
```powershell
# Download FD6.exe from releases
# https://github.com/tokyubevoxelverse/ForzaDesigner6/releases
# Run directly - no installation required
.\FD6.exe
```
### From Source
```powershell
git clone https://github.com/tokyubevoxelverse/ForzaDesigner6.git
cd ForzaDesigner6
# Create virtual environment
python -m venv .venv
.\.venv\Scripts\Activate.ps1
# Install dependencies
pip install -r requirements.txt
# Run from source
python -m fd6
# Build executable
.\build_exe.bat # outputs to dist/FD6.exe
```
**Requirements:**
- Python 3.10+
- Microsoft Visual C++ Redistributable (usually pre-installed)
- Windows 10/11 x64
## Core Workflow
### 1. Generate Vinyl JSON from Image
```python
# Typical programmatic usage (if building on FD6 as a library)
from fd6.generator import VinylGenerator
from fd6.profiles import Profile
# Initialize generator
generator = VinylGenerator()
# Load image and configure
image_path = "my_logo.png"
profile = Profile.BALANCED # or FAST, QUALITY, ULTRA
max_shapes = 1500 # Must match in-game template size
# Generate vinyl JSON
vinyl_data = generator.generate(
image_path=image_path,
profile=profile,
max_shapes=max_shapes,
output_json="my_logo.json"
)
```
### 2. Prepare In-Game Template (Forza)
**Critical:** Before injection, create sphere templates in-game:
1. Open Forza Horizon (3/4/5/6)
2. Go to Vinyl Editor → Create New Group
3. Add exactly N spheres (1500 or 3000 recommended)
4. Save as template (e.g., "Template_1500_Spheres")
5. Load this template before each injection
### 3. Inject Vinyl into Forza
```python
# Memory injection workflow
from fd6.inject import ForzaInjector
from fd6.inject.game_profiles import GameTarget
# Initialize injector
injector = ForzaInjector()
# Select target game
target = GameTarget.FORZA_HORIZON_6 # or FH3, FH4, FH5
# Inject vinyl data
result = injector.inject(
json_path="my_logo.json",
target=target,
validate=True # Strict 5/5 + 95% validation
)
if result.success:
print(f"✅ Injected {result.shapes_written} shapes")
else:
print(f"❌ Failed: {result.error_message}")
```
**Injection Process:**
1. Locates the game process in memory
2. Finds the vinyl group via fingerprint (fresh spheres) or RTTI scan (re-injection)
3. Overwrites sphere data (position, scale, color, opacity)
4. Validates write integrity
### 4. Export Assetto Corsa Competizione Livery
```python
# ACC file-based export
from fd6.acc import ACCExporter
# Initialize exporter
exporter = ACCExporter()
# Export livery
livery = exporter.export(
image_path="racing_stripe.png",
car_model="porsche_991ii_gt3_r", # Use ACC car catalog
livery_name="MyCustomLivery",
output_dir=None # Auto-detects Documents/Assetto Corsa Competizione
)
print(f"Livery exported to: {livery.json_path}")
# Creates:
# - Documents/Assetto Corsa Competizione/Customs/Cars/<car>/<livery>.json
# - Documents/Assetto Corsa Competizione/Customs/Liveries/<livery>/decals.png
```
## Configuration
### Profiles
FD6 ships with pre-tuned generation profiles:
| Profile | Shapes/Iteration | Quality | Speed | Use Case |
|---------|------------------|---------|-------|----------|
| `FAST` | Low mutation rate | Low | Fast | Quick previews |
| `BALANCED` | Medium mutation | Medium | Medium | **Recommended default** |
| `QUALITY` | High mutation | High | Slow | Detailed logos |
| `ULTRA` | Maximum iterations | Very High | Very Slow | Professional work |
```python
from fd6.profiles import Profile
# Access profile settings
profile = Profile.BALANCED
print(profile.max_iterations) # 5000
print(profile.mutation_rate) # 0.5
print(profile.alpha_threshold) # 128
```
### Game-Specific Struct Offsets
FD6 maintains per-game memory layouts in `fd6/inject/game_profiles.py`:
```python
# Example: Forza Horizon 6 struct offsets
FH6_PROFILE = {
"struct_size": 0x48, # CLiveryGroup size
"offsets": {
"position_x": 0x00,
"position_y": 0x04,
"scale_x": 0x08,
"scale_y": 0x0C,
"rotation": 0x10,
"color_r": 0x14,
"color_g": 0x15,
"color_b": 0x16,
"opacity": 0x17,
"shape_type": 0x18,
},
"rtti_class": ".?AVCLiveryGroup@@",
}
```
**When game patches break injection:** Update these offsets by reverse-engineering the new build.
## CLI Usage (GUI Wrapper)
While FD6 is primarily GUI-based, you can script the underlying modules:
```python
# Automated batch conversion script
import os
from pathlib import Path
from fd6.generator import VinylGenerator
from fd6.profiles import Profile
def batch_convert(image_dir: str, output_dir: str, max_shapes: int = 1500):
"""Convert all images in directory to vinyl JSON"""
generator = VinylGenerator()
for img_path in Path(image_dir).glob("*.png"):
output_json = Path(output_dir) / f"{img_path.stem}.json"
print(f"Converting {img_path.name}...")
generator.generate(
image_path=str(img_path),
profile=Profile.BALANCED,
max_shapes=max_shapes,
output_json=str(output_json)
)
print(f"✅ Saved to {output_json}")
# Usage
batch_convert("./logos", "./vinyls", max_shapes=1500)
```
## Common Patterns
### Pattern: Progressive Shape Count Testing
Start small, scale up to avoid long re-injection scans:
```python
from fd6.generator import VinylGenerator
from fd6.inject import ForzaInjector
generator = VinylGenerator()
injector = ForzaInjector()
# Test with low shape count first
for shape_count in [500, 1000, 1500, 3000]:
vinyl_json = f"test_{shape_count}.json"
generator.generate(
image_path="complex_logo.png",
max_shapes=shape_count,
output_json=vinyl_json
)
# Load matching template in-game before this step
input(f"Load {shape_count}-sphere template, then press Enter...")
result = injector.inject(vinyl_json, target="FH6")
if result.success:
print(f"✅ {shape_count} shapes: Success")
else:
print(f"❌ {shape_count} shapes: {result.error_message}")
```
### Pattern: Re-injection on Painted Template
```python
# First injection uses fingerprint (fast)
injector.inject("design_v1.json", target="FH6") # ~10s
# Subsequent re-injections fall back to RTTI scan (slower first time)
injector.inject("design_v2.json", target="FH6") # ~2-5 min first scan
injector.inject("design_v3.json", target="FH6") # ~10s (cached)
```
### Pattern: ACC Multi-Car Export
```python
from fd6.acc import ACCExporter
exporter = ACCExporter()
cars = [
"porsche_991ii_gt3_r",
"ferrari_488_gt3",
"lamborghini_huracan_gt3",
]
for car in cars:
exporter.export(
image_path="team_logo.png",
car_model=car,
livery_name=f"TeamLivery_{car}",
)
```
## Troubleshooting
### Error: "No confident match found"
**Cause:** Vinyl editor not open, or template has fewer spheres than JSON requires.
```python
# Verify shape count before injection
import json
with open("design.json") as f:
data = json.load(f)
required_shapes = len(data["shapes"])
print(f"This design needs {required_shapes} spheres in-game")
# Ensure in-game template matches or exceeds this count
```
### Error: "RTTI scan stalled"
**Cause:** RTTI fallback can take 2-5 minutes on large games (FH6 ~120GB).
```python
# Add timeout and progress callback
result = injector.inject(
"design.json",
target="FH6",
timeout=600, # 10-minute timeout
progress_callback=lambda pct: print(f"Scan: {pct}%")
)
```
### Error: "Shapes offset or wrong scale"
**Cause:** Game struct offsets changed after patch.
```python
# Diagnostic: dump detected memory layout
from fd6.inject.diagnostics import dump_vinyl_group
dump_vinyl_group(
process_name="ForzaHorizon6.exe",
output_file="struct_dump.txt"
)
# Compare output against known-good offsets in game_profiles.py
# Update offsets if mismatched
```
### Warning: "Windows SmartScreen blocked FD6.exe"
**Solution:**
1. Click "More info"
2. Click "Run anyway"
3. Or build from source to avoid code-signing warnings
### Issue: Injection fails silently
```python
# Enable debug logging
import logging
logging.basicConfig(level=logging.DEBUG)
from fd6.inject import ForzaInjector
injector = ForzaInjector(debug=True)
result = injector.inject("design.json", target="FH6")
# Check detailed error log
print(result.debug_log)
```
## Safety & Risk Warnings
**Memory injection modifies running game processes.** This may violate:
- Microsoft Services Agreement
- Xbox Community Standards
- Forza titles' Terms of Use
**Potential consequences:**
- Temporary or permanent account suspension
- Loss of online access, achievements, or purchased content
**ACC file export does NOT involve memory modification** and carries no ban risk.
**Mitigation:**
- Never run FD6 as administrator
- Don't click anything during injection
- Only inject into offline/non-competitive sessions
- Accept all risks before use
## Advanced: Custom Shape Algorithms
FD6's generation engine can be extended with custom shape primitives:
```python
from fd6.shapes import ShapeBase, ShapeType
class CustomTriangle(ShapeBase):
shape_type = ShapeType.TRIANGLE
def to_forza_bytes(self) -> bytes:
"""Convert to Forza CLiveryGroup struct bytes"""
return struct.pack(
"<ffHHHBBBB",
self.x, # Position X (float)
-self.y, # Position Y (float, negated)
int(self.w / 63), # Scale X (uint16, div 63)
int(self.h / 127), # Scale Y (uint16, div 127)
self.rotation, # Rotation (uint16)
self.color[0], # R (uint8)
self.color[1], # G (uint8)
self.color[2], # B (uint8)
self.opacity, # Alpha (uint8)
)
# Register custom shape
from fd6.generator import VinylGenerator
generator = VinylGenerator(custom_shapes=[CustomTriangle])
```
## Testing
```powershell
# Run test suite
pytest
# Test specific module
pytest tests/test_generator.py
# Test injection (requires game running)
pytest tests/test_inject.py --game=FH6
# Benchmark generation performance
python -m fd6.benchmark --image test.png --shapes 3000
```
## Project Structure
```
ForzaDesigner6/
├── fd6/
│ ├── generator.py # Image → vinyl JSON conversion
│ ├── inject/
│ │ ├── injector.py # Memory injection engine
│ │ ├── game_profiles.py # Per-game struct offsets
│ │ └── rtti_locator.py # RTTI vtable scanner
│ ├── acc/
│ │ └── exporter.py # ACC livery file writer
│ ├── shapes.py # Shape primitives
│ └── profiles.py # Generation profiles
├── tools/
│ └── SplashScreen.gif
├── requirements.txt
├── build_exe.bat
└── README.md
```
## References
- **Forza vinyl-group struct layout:** Reverse-engineered from FH6 build 354.221, cross-validated with bvzrays' [forza-painter-fh6](https://github.com/bvzrays/forza-painter-fh6)
- **RTTI locator:** Searches for `.?AVCLiveryGroup@@` MSVC type descriptor
- **Shape algorithms:** Based on [geometrize-lib](https://github.com/Tw1ddle/geometrize-lib) (MIT) and [Primitive](https://github.com/fogleman/primitive) (MIT)
---
**License:** MIT
**Repository:** https://github.com/tokyubevoxelverse/ForzaDesigner6
**Video Tutorial:** https://youtu.be/8LGvE7O9aeg
Creator's repository · aradotso/design-skills