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>
190 lines
6.2 KiB
C#
190 lines
6.2 KiB
C#
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);
|