16 KiB
🎨 RR3 Asset Download System - Community Server Support
Overview
Real Racing 3 downloads game assets (cars, tracks, textures, etc.) from EA's CDN after installation. We can redirect these downloads to community servers for game preservation.
🔍 How RR3 Asset Downloads Work
Director Pattern
When the game starts, it calls the /director endpoint which returns URLs for various services:
{
"serverUrls": {
"synergy.account": "http://your-server:5001/synergy/account",
"synergy.commerce": "http://your-server:5001/synergy/commerce",
"synergy.content": "http://your-server:5001/synergy/content", // <-- ASSETS!
"synergy.rewards": "http://your-server:5001/synergy/rewards",
"synergy.progression": "http://your-server:5001/synergy/progression"
}
}
Key Service: synergy.content
This service handles all asset downloads:
- Car models (.pak files)
- Track data
- Textures
- Audio files
- Updates
URL Structure
From SynergyEnvironmentImpl.java, we can see:
// Line 109-116
public String getServerUrlWithKey(String str) {
// Returns URL for service key like "synergy.content"
return environmentDataContainer.getServerUrlWithKey(str);
}
The game asks for URLs by key, and the Director response tells it where to download content.
🎯 Solution: Community Asset Server
Phase 1: Intercept Asset Requests
Update DirectorController.cs to include content service:
[HttpGet("director")]
public IActionResult GetDirector()
{
var baseUrl = $"{Request.Scheme}://{Request.Host}";
return Ok(new
{
serverUrls = new Dictionary<string, string>
{
["synergy.account"] = $"{baseUrl}/synergy/account",
["synergy.commerce"] = $"{baseUrl}/synergy/commerce",
["synergy.rewards"] = $"{baseUrl}/synergy/rewards",
["synergy.progression"] = $"{baseUrl}/synergy/progression",
["synergy.content"] = $"{baseUrl}/synergy/content" // NEW!
},
// ... rest of response
});
}
Phase 2: Create Content Controller
Handle asset download requests:
[ApiController]
[Route("synergy/content")]
public class ContentController : ControllerBase
{
private readonly IWebHostEnvironment _env;
private readonly string _assetsPath;
public ContentController(IWebHostEnvironment env)
{
_env = env;
_assetsPath = Path.Combine(_env.ContentRootPath, "Assets");
// Create assets directory if not exists
Directory.CreateDirectory(_assetsPath);
}
// Get asset manifest (list of available content)
[HttpGet("manifest")]
public IActionResult GetManifest()
{
return Ok(new
{
version = "1.0.0",
assets = GetAssetList()
});
}
// Download specific asset
[HttpGet("download/{assetType}/{assetId}")]
public IActionResult DownloadAsset(string assetType, string assetId)
{
var assetPath = Path.Combine(_assetsPath, assetType, $"{assetId}.pak");
if (!System.IO.File.Exists(assetPath))
{
return NotFound(new { error = $"Asset not found: {assetType}/{assetId}" });
}
var fileBytes = System.IO.File.ReadAllBytes(assetPath);
return File(fileBytes, "application/octet-stream", $"{assetId}.pak");
}
// Get asset metadata
[HttpGet("info/{assetType}/{assetId}")]
public IActionResult GetAssetInfo(string assetType, string assetId)
{
var assetPath = Path.Combine(_assetsPath, assetType, $"{assetId}.pak");
if (!System.IO.File.Exists(assetPath))
{
return NotFound();
}
var fileInfo = new FileInfo(assetPath);
return Ok(new
{
assetId = assetId,
assetType = assetType,
size = fileInfo.Length,
checksum = CalculateMD5(assetPath),
version = "1.0.0"
});
}
private List<object> GetAssetList()
{
var assets = new List<object>();
if (Directory.Exists(_assetsPath))
{
foreach (var typeDir in Directory.GetDirectories(_assetsPath))
{
var assetType = Path.GetFileName(typeDir);
foreach (var file in Directory.GetFiles(typeDir, "*.pak"))
{
var assetId = Path.GetFileNameWithoutExtension(file);
var fileInfo = new FileInfo(file);
assets.Add(new
{
id = assetId,
type = assetType,
size = fileInfo.Length,
url = $"/synergy/content/download/{assetType}/{assetId}"
});
}
}
}
return assets;
}
private string CalculateMD5(string filePath)
{
using var md5 = System.Security.Cryptography.MD5.Create();
using var stream = System.IO.File.OpenRead(filePath);
var hash = md5.ComputeHash(stream);
return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant();
}
}
📁 Asset Directory Structure
Create this structure on your community server:
RR3CommunityServer/
└── Assets/
├── cars/
│ ├── nissan_silvia_s15.pak
│ ├── ford_focus_rs.pak
│ └── porsche_911_gt3.pak
├── tracks/
│ ├── silverstone_national.pak
│ ├── dubai_autodrome.pak
│ └── brands_hatch.pak
├── textures/
│ ├── ui_textures.pak
│ └── car_textures.pak
└── audio/
├── engine_sounds.pak
└── music.pak
🔧 How to Get Original Assets
Method 1: Extract from Existing Installation (Legal if you own the game)
# Connect Android device
adb shell
# Find RR3 data directory
cd /data/data/com.ea.games.r3_row/
# List downloaded assets
ls -la files/
# Pull assets to PC (for backup/preservation)
adb pull /data/data/com.ea.games.r3_row/files/ ./rr3-assets/
Method 2: Intercept Downloads (Monitor what gets downloaded)
# Monitor network traffic while game downloads assets
adb shell tcpdump -i any -w /sdcard/rr3_traffic.pcap
# Or use Charles Proxy / Fiddler to see requests
# Game will request: https://cdn.ea.com/rr3/assets/cars/xxx.pak
Method 3: APK Assets (Some are bundled)
# Decompile APK
apktool d realracing3.apk -o rr3-decompiled
# Check assets folder
cd rr3-decompiled/assets/
ls -la
# Look for .pak, .unity3d, or compressed files
🌐 Admin Panel for Asset Management
Add to Pages/Assets.cshtml:
@page
@model AssetsModel
@{
ViewData["Title"] = "Asset Management";
}
<div class="container-fluid">
<div class="row">
<div class="col-12">
<h2>🎨 Asset Management</h2>
<p>Manage game assets (cars, tracks, textures)</p>
</div>
</div>
<div class="row mb-3">
<div class="col-md-3">
<div class="card">
<div class="card-body">
<h5>Cars</h5>
<h3>@Model.CarAssetCount</h3>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card">
<div class="card-body">
<h5>Tracks</h5>
<h3>@Model.TrackAssetCount</h3>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card">
<div class="card-body">
<h5>Textures</h5>
<h3>@Model.TextureAssetCount</h3>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card">
<div class="card-body">
<h5>Total Size</h5>
<h3>@Model.TotalSizeMB MB</h3>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-12">
<div class="card">
<div class="card-header">
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#uploadModal">
📤 Upload Asset
</button>
</div>
<div class="card-body">
<table class="table">
<thead>
<tr>
<th>Type</th>
<th>Asset ID</th>
<th>Size</th>
<th>Checksum</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
@foreach (var asset in Model.Assets)
{
<tr>
<td><span class="badge bg-info">@asset.Type</span></td>
<td>@asset.Id</td>
<td>@asset.SizeMB MB</td>
<td><code>@asset.Checksum</code></td>
<td>
<a href="/synergy/content/download/@asset.Type/@asset.Id" class="btn btn-sm btn-success">Download</a>
<button class="btn btn-sm btn-danger" onclick="deleteAsset('@asset.Type', '@asset.Id')">Delete</button>
</td>
</tr>
}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<!-- Upload Modal -->
<div class="modal fade" id="uploadModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Upload Asset</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<form method="post" enctype="multipart/form-data">
<div class="modal-body">
<div class="mb-3">
<label class="form-label">Asset Type</label>
<select class="form-select" name="AssetType" required>
<option value="cars">Cars</option>
<option value="tracks">Tracks</option>
<option value="textures">Textures</option>
<option value="audio">Audio</option>
</select>
</div>
<div class="mb-3">
<label class="form-label">Asset ID</label>
<input type="text" class="form-control" name="AssetId" placeholder="e.g., nissan_silvia_s15" required />
</div>
<div class="mb-3">
<label class="form-label">File (.pak)</label>
<input type="file" class="form-control" name="AssetFile" accept=".pak" required />
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-primary">Upload</button>
</div>
</form>
</div>
</div>
</div>
🎮 Testing Asset Downloads
1. Start Server with Assets
cd RR3CommunityServer/RR3CommunityServer
dotnet run
2. Check Asset Manifest
curl http://localhost:5001/synergy/content/manifest
3. Download Test Asset
curl http://localhost:5001/synergy/content/download/cars/nissan_silvia_s15 -o test.pak
4. Verify in Game
- Connect APK to community server
- Start game
- Game should request assets from your server
- Monitor logs:
adb logcat | grep -E "(Download|Asset|Content)"
📝 Asset Metadata Format
Each asset should have a companion .json metadata file:
{
"id": "nissan_silvia_s15",
"type": "car",
"name": "Nissan Silvia Spec-R",
"version": "1.0.0",
"size": 15728640,
"checksum": "5d41402abc4b2a76b9719d911017c592",
"dependencies": [
"car_textures",
"engine_sounds"
],
"tags": ["class_c", "nissan", "drift"]
}
🔐 Security Considerations
For Game Preservation (Legal Use)
✅ Allowed:
- Backing up assets from games you own
- Running private servers for personal use
- Preserving games after shutdown
- Using with legally obtained APK
❌ Not Allowed:
- Distributing EA's copyrighted assets
- Pirating the game
- Selling assets
- Public redistribution without permission
Asset Checksums
Always verify integrity:
private bool VerifyAssetChecksum(string filePath, string expectedChecksum)
{
var actualChecksum = CalculateMD5(filePath);
return actualChecksum.Equals(expectedChecksum, StringComparison.OrdinalIgnoreCase);
}
🚀 Implementation Steps
Server Side (rr3-server)
- ✅ Add
ContentController.cs - ✅ Create
Assets/directory structure - ✅ Update
DirectorControllerto includesynergy.content - ✅ Add admin page for asset management
- ✅ Implement upload/download endpoints
APK Side (rr3-apk)
No changes needed! 🎉
The APK already uses the Director pattern. Once your community server returns the synergy.content URL, the game will automatically use it!
Asset Extraction
- Install original RR3 from Play Store
- Let it download all assets
- Use
adb pullto extract assets - Upload to your community server
- Share with community (if legally allowed)
📊 Asset Types in RR3
Cars (~10-20 MB each)
- 3D models
- Physics data
- Paint variants
- Upgrade visual changes
Tracks (~50-100 MB each)
- 3D environment
- AI pathfinding data
- Weather variants
- Time of day variants
Textures (~5-10 MB per pack)
- UI elements
- Car liveries
- Environmental textures
- Effects
Audio (~1-5 MB per pack)
- Engine sounds per car
- Music tracks
- UI sounds
- Ambient audio
💡 Future Enhancements
- Asset CDN: Distribute assets via CDN for faster downloads
- Compression: Serve compressed .pak files
- Versioning: Support multiple asset versions
- Differential Updates: Only download changed files
- P2P Distribution: BitTorrent for large assets
- Asset Workshop: Community-created content
- Mod Support: Custom cars/tracks
🎯 Expected Behavior
Without Community Assets
- Game connects to community server ✅
- Gameplay works (with existing assets) ✅
- New cars might not load properly ❌
- Missing tracks won't be available ❌
With Community Assets
- Game connects to community server ✅
- Downloads assets from community server ✅
- All content available ✅
- Full offline gameplay ✅
📚 Resources
- Unity .pak Format: Research Unity asset bundle format
- EA Nimble SDK: Understanding the download system
- Asset Extraction Tools: QuickBMS, Unity Asset Bundle Extractor
- Network Analysis: Charles Proxy, Wireshark
This enables TRUE game preservation! 🎮
Players can download all game content from community servers, making RR3 fully playable even if EA shuts down their servers!
Legal Note: Only use assets you legally own. This is for preservation, not piracy.