Files
AnotherReplayReader/MinimapReader.cs
2026-06-21 14:42:04 +02:00

165 lines
5.6 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using AnotherReplayReader.ReplayFile;
using Pfim;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.ComTypes;
using System.Threading.Tasks;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using TechnologyAssembler.Core.IO;
namespace AnotherReplayReader
{
internal sealed class MinimapReader
{
public static readonly IReadOnlyDictionary<ImageFormat, PixelFormat> FormatMapper = new Dictionary<ImageFormat, PixelFormat>
{
{ ImageFormat.Rgb24, PixelFormats.Bgr24 },
{ ImageFormat.Rgba32, PixelFormats.Bgr32 },
{ ImageFormat.Rgb8, PixelFormats.Gray8 },
{ ImageFormat.R5g5b5a1, PixelFormats.Bgr555 },
{ ImageFormat.R5g5b5, PixelFormats.Bgr555 },
{ ImageFormat.R5g6b5, PixelFormats.Bgr565 },
};
private readonly BigMinimapCache _cache;
private readonly string _mapFolderPath;
private readonly string _modFolderPath;
public MinimapReader(BigMinimapCache cache, string mapFolderPath, string modFolderPath)
{
_cache = cache;
_mapFolderPath = mapFolderPath;
_modFolderPath = modFolderPath;
}
public Task<BitmapSource?> TryReadTargaAsync(Replay replay, double dpiX = 96.0, double dpiY = 96.0)
{
var mapPath = replay.MapPath.TrimEnd('/');
var mapName = mapPath.Substring(mapPath.LastIndexOf('/') + 1);
var minimapPath = $"{mapPath}/{mapName}_art.tga";
return Task.Run(() => TryReadTarga(minimapPath, replay.Mod, dpiX, dpiY));
}
public BitmapSource? TryReadTarga(string path, Mod mod, double dpiX = 96.0, double dpiY = 96.0)
{
using var memoryStream = new MemoryStream();
{
using var stream = TryGetStream(path, mod);
if (stream is null)
{
return null;
}
stream.CopyTo(memoryStream);
}
bool isJpgPng = IsJpgPng(memoryStream);
memoryStream.Position = 0;
if (isJpgPng)
{
try
{
var decoder = BitmapDecoder.Create(memoryStream, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.OnLoad);
return decoder.Frames[0];
}
catch { }
}
memoryStream.Position = 0;
using var targa = Targa.Create(memoryStream, new PfimConfig());
if (targa == null)
{
return null;
}
try
{
var bitmap = BitmapSource.Create(targa.Width, targa.Height, dpiX, dpiY, FormatMapper[targa.Format], null, targa.Data, targa.Stride);
bitmap.Freeze();
return bitmap;
}
catch (Exception exception)
{
Debug.Instance.DebugMessage += $"Exception creating BitmapSource from minimap:\r\n {exception}\r\n";
return null;
}
}
private Stream? TryGetStream(string path, Mod mod)
{
const string customMapPrefix = "data/maps/internal/";
if (Directory.Exists(_mapFolderPath) && path.StartsWith(customMapPrefix))
{
var minimapPath = Path.Combine(_mapFolderPath, path.Substring(customMapPrefix.Length));
if (File.Exists(minimapPath))
{
return File.OpenRead(minimapPath);
}
}
if (!mod.IsRA3)
{
var modSkudefPaths = Enumerable.Empty<string>();
foreach (var subFolder in Directory.EnumerateDirectories(_modFolderPath))
{
modSkudefPaths = modSkudefPaths.Concat(Directory.EnumerateFiles(subFolder, $"{mod.ModName}_{mod.ModVersion}.SkuDef"));
}
DronePlatform.BuildTechnologyAssembler();
foreach (var modSkudefPath in modSkudefPaths)
{
try
{
using var fs = new SkuDefFileSystemProvider("modConfig", modSkudefPath);
if (!fs.FileExists(path))
{
continue;
}
return fs.OpenStream(path, VirtualFileModeType.Open);
}
catch (Exception exception)
{
Debug.Instance.DebugMessage += $"Exception when reading minimap from mod bigs:\r\n {exception}\r\n";
}
}
}
if (_cache != null && _cache.TryGetEntry(path, out var big))
{
return big;
}
return null;
}
private static bool IsJpgPng(Stream stream)
{
var header = new byte[4];
var pos = stream.Position;
stream.Read(header, 0, header.Length);
stream.Position = pos;
// PNG
if (header[0] == 0x89 &&
header[1] == 0x50 &&
header[2] == 0x4E &&
header[3] == 0x47)
{
return true;
}
// JPEG
if (header[0] == 0xFF &&
header[1] == 0xD8)
{
return true;
}
// TGA很粗略判断通常无统一magic只能 fallback
return false;
}
}
}