Files
OpenRA/OpenRA.Mods.Cnc/SpriteLoaders/ShpRemasteredLoader.cs
RoosterDragon 87aa7c11c5 Provide buffer size in ShpRemasteredSprite.
As the expected size is quite small here, providing an explicit buffer size helps as otherwise default buffers for 1024 characters are allocated by the StreamReader which must be cleaned up by the GC afterwards. These smaller buffers still need cleanup but waste less memory.
2024-09-18 12:32:44 +03:00

125 lines
3.4 KiB
C#

#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version. For more
* information, see COPYING.
*/
#endregion
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using ICSharpCode.SharpZipLib.Zip;
using OpenRA.Graphics;
using OpenRA.Mods.Common.SpriteLoaders;
using OpenRA.Primitives;
namespace OpenRA.Mods.Cnc.SpriteLoaders
{
public class ShpRemasteredLoader : ISpriteLoader
{
public static bool IsShpRemastered(Stream s)
{
var start = s.Position;
var isZipFile = s.ReadUInt32() == 0x04034B50;
s.Position = start;
return isZipFile;
}
public bool TryParseSprite(Stream s, string filename, out ISpriteFrame[] frames, out TypeDictionary metadata)
{
metadata = null;
if (!IsShpRemastered(s))
{
frames = null;
return false;
}
frames = new ShpRemasteredSprite(s).Frames.ToArray();
return true;
}
}
public class ShpRemasteredSprite
{
static readonly Regex FilenameRegex = new(@"^(?<prefix>.+?[\-_])(?<frame>\d{4})\.tga$");
static readonly Regex MetaRegex = new(@"^\{""size"":\[(?<width>\d+),(?<height>\d+)\],""crop"":\[(?<left>\d+),(?<top>\d+),(?<right>\d+),(?<bottom>\d+)\]\}$");
static int ParseGroup(Match match, string group)
{
return Exts.ParseInt32Invariant(match.Groups[group].Value);
}
public IReadOnlyList<ISpriteFrame> Frames { get; }
public ShpRemasteredSprite(Stream stream)
{
var container = new ZipFile(stream);
string framePrefix = null;
var frameCount = 0;
foreach (ZipEntry entry in container)
{
var match = FilenameRegex.Match(entry.Name);
if (!match.Success)
continue;
var prefix = match.Groups["prefix"].Value;
framePrefix ??= prefix;
if (prefix != framePrefix)
throw new InvalidDataException($"Frame prefix mismatch: `{prefix}` != `{framePrefix}`");
frameCount = Math.Max(frameCount, Exts.ParseInt32Invariant(match.Groups["frame"].Value) + 1);
}
var frames = new ISpriteFrame[frameCount];
for (var i = 0; i < frames.Length; i++)
{
var tgaEntry = container.GetEntry($"{framePrefix}{i:D4}.tga");
// Blank frame
if (tgaEntry == null)
{
frames[i] = new TgaSprite.TgaFrame();
continue;
}
var metaEntry = container.GetEntry($"{framePrefix}{i:D4}.meta");
using (var tgaStream = container.GetInputStream(tgaEntry))
{
var metaStream = metaEntry != null ? container.GetInputStream(metaEntry) : null;
if (metaStream != null)
{
string metaText;
#if NET5_0_OR_GREATER
using (metaStream)
using (var metaReader = new StreamReader(metaStream, bufferSize: 64))
metaText = metaReader.ReadToEnd();
#else
metaText = metaStream.ReadAllText();
#endif
var meta = MetaRegex.Match(metaText);
var crop = Rectangle.FromLTRB(
ParseGroup(meta, "left"), ParseGroup(meta, "top"),
ParseGroup(meta, "right"), ParseGroup(meta, "bottom"));
var frameSize = new Size(ParseGroup(meta, "width"), ParseGroup(meta, "height"));
frames[i] = new TgaSprite.TgaFrame(tgaStream, frameSize, crop);
}
else
frames[i] = new TgaSprite.TgaFrame(tgaStream);
}
}
Frames = frames;
}
}
}