Initial commit: RR3 APK and documentation
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
539
ASSET_DOWNLOAD_SYSTEM.md
Normal file
539
ASSET_DOWNLOAD_SYSTEM.md
Normal file
@@ -0,0 +1,539 @@
|
||||
# 🎨 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.
|
||||
Reference in New Issue
Block a user