Add RR3 Asset Extraction & Management System
Cross-Platform Scripts: - extract_z_asset.sh: Linux/Unix single file extraction - batch_extract_z_assets.sh: Linux/Unix batch extraction - pack_z_asset.sh: Linux/Unix asset packing - extract_z_asset.ps1: Windows PowerShell extraction Server Integration: - AssetExtractionService.cs: C# service for ZLIB extraction/packing - AssetManagementController.cs: API endpoints for asset management - POST /api/AssetManagement/extract - POST /api/AssetManagement/pack - POST /api/AssetManagement/batch-extract - GET /api/AssetManagement/list - Registered AssetExtractionService in Program.cs Features: - Extracts .z files (ZLIB compressed textures/data) - Packs files to .z format with ZLIB compression - Batch processing support - Cross-platform (Windows/Linux/macOS) - Server-side API for remote asset management - Path traversal protection Documentation: - ASSET_EXTRACTION_GUIDE.md: Complete integration guide - Tools/README.md: CLI tool documentation Based on: Tankonline/Real-Racing-3-Texture-Extraction-Tool Converted to cross-platform bash/PowerShell scripts + C# service Ready for .pak asset extraction when files arrive from community Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
189
RR3CommunityServer/Controllers/AssetManagementController.cs
Normal file
189
RR3CommunityServer/Controllers/AssetManagementController.cs
Normal file
@@ -0,0 +1,189 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using RR3CommunityServer.Services;
|
||||
|
||||
namespace RR3CommunityServer.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
public class AssetManagementController : ControllerBase
|
||||
{
|
||||
private readonly AssetExtractionService _assetExtraction;
|
||||
private readonly ILogger<AssetManagementController> _logger;
|
||||
private readonly string _assetBasePath;
|
||||
|
||||
public AssetManagementController(
|
||||
AssetExtractionService assetExtraction,
|
||||
ILogger<AssetManagementController> logger,
|
||||
IConfiguration configuration)
|
||||
{
|
||||
_assetExtraction = assetExtraction;
|
||||
_logger = logger;
|
||||
_assetBasePath = configuration["AssetBasePath"] ?? Path.Combine(Directory.GetCurrentDirectory(), "Assets");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extract a single .z asset file
|
||||
/// </summary>
|
||||
[HttpPost("extract")]
|
||||
public async Task<IActionResult> ExtractAsset([FromBody] ExtractRequest request)
|
||||
{
|
||||
try
|
||||
{
|
||||
var filePath = Path.Combine(_assetBasePath, request.FileName);
|
||||
var outputPath = await _assetExtraction.ExtractZFileAsync(filePath, request.OutputPath);
|
||||
|
||||
return Ok(new
|
||||
{
|
||||
resultCode = 0,
|
||||
message = "Success",
|
||||
data = new
|
||||
{
|
||||
inputFile = filePath,
|
||||
outputFile = outputPath,
|
||||
size = new FileInfo(outputPath).Length
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (FileNotFoundException ex)
|
||||
{
|
||||
return NotFound(new { resultCode = 404, message = ex.Message });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error extracting asset");
|
||||
return StatusCode(500, new { resultCode = 500, message = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pack a file to .z format
|
||||
/// </summary>
|
||||
[HttpPost("pack")]
|
||||
public async Task<IActionResult> PackAsset([FromBody] PackRequest request)
|
||||
{
|
||||
try
|
||||
{
|
||||
var filePath = Path.Combine(_assetBasePath, request.FileName);
|
||||
var outputPath = await _assetExtraction.PackZFileAsync(filePath, request.OutputPath);
|
||||
|
||||
return Ok(new
|
||||
{
|
||||
resultCode = 0,
|
||||
message = "Success",
|
||||
data = new
|
||||
{
|
||||
inputFile = filePath,
|
||||
outputFile = outputPath,
|
||||
size = new FileInfo(outputPath).Length
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (FileNotFoundException ex)
|
||||
{
|
||||
return NotFound(new { resultCode = 404, message = ex.Message });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error packing asset");
|
||||
return StatusCode(500, new { resultCode = 500, message = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Batch extract all .z files in a directory
|
||||
/// </summary>
|
||||
[HttpPost("batch-extract")]
|
||||
public async Task<IActionResult> BatchExtract([FromBody] BatchExtractRequest request)
|
||||
{
|
||||
try
|
||||
{
|
||||
var inputDir = Path.Combine(_assetBasePath, request.InputDirectory);
|
||||
var outputDir = string.IsNullOrEmpty(request.OutputDirectory)
|
||||
? null
|
||||
: Path.Combine(_assetBasePath, request.OutputDirectory);
|
||||
|
||||
var results = await _assetExtraction.BatchExtractAsync(inputDir, outputDir);
|
||||
|
||||
var successful = results.Count(r => !r.Value.StartsWith("ERROR:"));
|
||||
var failed = results.Count - successful;
|
||||
|
||||
return Ok(new
|
||||
{
|
||||
resultCode = 0,
|
||||
message = "Success",
|
||||
data = new
|
||||
{
|
||||
totalFiles = results.Count,
|
||||
successful,
|
||||
failed,
|
||||
results = results.Select(r => new
|
||||
{
|
||||
inputFile = Path.GetFileName(r.Key),
|
||||
outputFile = Path.GetFileName(r.Value),
|
||||
status = r.Value.StartsWith("ERROR:") ? "failed" : "success",
|
||||
error = r.Value.StartsWith("ERROR:") ? r.Value : null
|
||||
})
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (DirectoryNotFoundException ex)
|
||||
{
|
||||
return NotFound(new { resultCode = 404, message = ex.Message });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error in batch extraction");
|
||||
return StatusCode(500, new { resultCode = 500, message = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// List all available .z assets
|
||||
/// </summary>
|
||||
[HttpGet("list")]
|
||||
public IActionResult ListAssets([FromQuery] string? directory = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var searchDir = string.IsNullOrEmpty(directory)
|
||||
? _assetBasePath
|
||||
: Path.Combine(_assetBasePath, directory);
|
||||
|
||||
if (!Directory.Exists(searchDir))
|
||||
{
|
||||
return NotFound(new { resultCode = 404, message = "Directory not found" });
|
||||
}
|
||||
|
||||
var zFiles = Directory.GetFiles(searchDir, "*.z", SearchOption.AllDirectories)
|
||||
.Select(f => new
|
||||
{
|
||||
fileName = Path.GetFileName(f),
|
||||
relativePath = Path.GetRelativePath(_assetBasePath, f),
|
||||
size = new FileInfo(f).Length,
|
||||
modified = new FileInfo(f).LastWriteTimeUtc
|
||||
})
|
||||
.ToList();
|
||||
|
||||
return Ok(new
|
||||
{
|
||||
resultCode = 0,
|
||||
message = "Success",
|
||||
data = new
|
||||
{
|
||||
directory = searchDir,
|
||||
fileCount = zFiles.Count,
|
||||
files = zFiles
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error listing assets");
|
||||
return StatusCode(500, new { resultCode = 500, message = ex.Message });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public record ExtractRequest(string FileName, string? OutputPath = null);
|
||||
public record PackRequest(string FileName, string? OutputPath = null);
|
||||
public record BatchExtractRequest(string InputDirectory, string? OutputDirectory = null);
|
||||
Reference in New Issue
Block a user