Files
rr3-apk/ASSET_DOWNLOAD_SYSTEM.md

540 lines
16 KiB
Markdown

# 🎨 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:
```json
{
"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:
```java
// 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:
```csharp
[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:
```csharp
[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)
```bash
# 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)
```bash
# 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)
```bash
# 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`:
```html
@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
```bash
cd RR3CommunityServer/RR3CommunityServer
dotnet run
```
### 2. Check Asset Manifest
```bash
curl http://localhost:5001/synergy/content/manifest
```
### 3. Download Test Asset
```bash
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:
```bash
adb logcat | grep -E "(Download|Asset|Content)"
```
## 📝 Asset Metadata Format
Each asset should have a companion `.json` metadata file:
```json
{
"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:
```csharp
private bool VerifyAssetChecksum(string filePath, string expectedChecksum)
{
var actualChecksum = CalculateMD5(filePath);
return actualChecksum.Equals(expectedChecksum, StringComparison.OrdinalIgnoreCase);
}
```
## 🚀 Implementation Steps
### Server Side (rr3-server)
1. ✅ Add `ContentController.cs`
2. ✅ Create `Assets/` directory structure
3. ✅ Update `DirectorController` to include `synergy.content`
4. ✅ Add admin page for asset management
5. ✅ 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
1. Install original RR3 from Play Store
2. Let it download all assets
3. Use `adb pull` to extract assets
4. Upload to your community server
5. 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.