540 lines
16 KiB
Markdown
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.
|