Merge pull request #10418 from pchote/format-names
Rename WW compression formats and move into Mods.Common.
This commit is contained in:
152
OpenRA.Mods.Common/FileFormats/IniFile.cs
Normal file
152
OpenRA.Mods.Common/FileFormats/IniFile.cs
Normal file
@@ -0,0 +1,152 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2015 The OpenRA Developers (see AUTHORS)
|
||||
* 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. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace OpenRA.Mods.Common.FileFormats
|
||||
{
|
||||
public class IniFile
|
||||
{
|
||||
Dictionary<string, IniSection> sections = new Dictionary<string, IniSection>();
|
||||
|
||||
public IniFile(Stream s)
|
||||
{
|
||||
using (s)
|
||||
Load(s);
|
||||
}
|
||||
|
||||
public IniFile(params Stream[] streams)
|
||||
{
|
||||
foreach (var s in streams)
|
||||
Load(s);
|
||||
}
|
||||
|
||||
public void Load(Stream s)
|
||||
{
|
||||
var reader = new StreamReader(s);
|
||||
IniSection currentSection = null;
|
||||
|
||||
while (!reader.EndOfStream)
|
||||
{
|
||||
var line = reader.ReadLine();
|
||||
|
||||
if (line.Length == 0) continue;
|
||||
|
||||
switch (line[0])
|
||||
{
|
||||
case ';': break;
|
||||
case '[': currentSection = ProcessSection(line); break;
|
||||
default: ProcessEntry(line, currentSection); break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Regex sectionPattern = new Regex(@"^\[([^]]*)\]");
|
||||
|
||||
IniSection ProcessSection(string line)
|
||||
{
|
||||
var m = sectionPattern.Match(line);
|
||||
if (!m.Success)
|
||||
return null;
|
||||
var sectionName = m.Groups[1].Value.ToLowerInvariant();
|
||||
|
||||
IniSection ret;
|
||||
if (!sections.TryGetValue(sectionName, out ret))
|
||||
sections.Add(sectionName, ret = new IniSection(sectionName));
|
||||
return ret;
|
||||
}
|
||||
|
||||
static bool ProcessEntry(string line, IniSection currentSection)
|
||||
{
|
||||
var comment = line.IndexOf(';');
|
||||
if (comment >= 0)
|
||||
line = line.Substring(0, comment);
|
||||
|
||||
line = line.Trim();
|
||||
if (line.Length == 0)
|
||||
return false;
|
||||
|
||||
var key = line;
|
||||
var value = "";
|
||||
var eq = line.IndexOf('=');
|
||||
if (eq >= 0)
|
||||
{
|
||||
key = line.Substring(0, eq).Trim();
|
||||
value = line.Substring(eq + 1, line.Length - eq - 1).Trim();
|
||||
}
|
||||
|
||||
if (currentSection == null)
|
||||
throw new InvalidOperationException("No current INI section");
|
||||
|
||||
if (!currentSection.Contains(key))
|
||||
currentSection.Add(key, value);
|
||||
return true;
|
||||
}
|
||||
|
||||
public IniSection GetSection(string s)
|
||||
{
|
||||
return GetSection(s, false);
|
||||
}
|
||||
|
||||
public IniSection GetSection(string s, bool allowFail)
|
||||
{
|
||||
IniSection section;
|
||||
if (sections.TryGetValue(s.ToLowerInvariant(), out section))
|
||||
return section;
|
||||
|
||||
if (allowFail)
|
||||
return new IniSection(s);
|
||||
throw new InvalidOperationException("Section does not exist in map or rules: " + s);
|
||||
}
|
||||
|
||||
public IEnumerable<IniSection> Sections { get { return sections.Values; } }
|
||||
}
|
||||
|
||||
public class IniSection : IEnumerable<KeyValuePair<string, string>>
|
||||
{
|
||||
public string Name { get; private set; }
|
||||
Dictionary<string, string> values = new Dictionary<string, string>();
|
||||
|
||||
public IniSection(string name)
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
|
||||
public void Add(string key, string value)
|
||||
{
|
||||
values[key] = value;
|
||||
}
|
||||
|
||||
public bool Contains(string key)
|
||||
{
|
||||
return values.ContainsKey(key);
|
||||
}
|
||||
|
||||
public string GetValue(string key, string defaultValue)
|
||||
{
|
||||
string s;
|
||||
return values.TryGetValue(key, out s) ? s : defaultValue;
|
||||
}
|
||||
|
||||
public IEnumerator<KeyValuePair<string, string>> GetEnumerator()
|
||||
{
|
||||
return values.GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
}
|
||||
}
|
||||
207
OpenRA.Mods.Common/FileFormats/LCWCompression.cs
Normal file
207
OpenRA.Mods.Common/FileFormats/LCWCompression.cs
Normal file
@@ -0,0 +1,207 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2015 The OpenRA Developers (see AUTHORS)
|
||||
* 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. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace OpenRA.Mods.Common.FileFormats
|
||||
{
|
||||
class FastByteReader
|
||||
{
|
||||
readonly byte[] src;
|
||||
int offset;
|
||||
|
||||
public FastByteReader(byte[] src, int offset = 0)
|
||||
{
|
||||
this.src = src;
|
||||
this.offset = offset;
|
||||
}
|
||||
|
||||
public bool Done() { return offset >= src.Length; }
|
||||
public byte ReadByte() { return src[offset++]; }
|
||||
public int ReadWord()
|
||||
{
|
||||
var x = ReadByte();
|
||||
return x | (ReadByte() << 8);
|
||||
}
|
||||
|
||||
public void CopyTo(byte[] dest, int offset, int count)
|
||||
{
|
||||
Array.Copy(src, this.offset, dest, offset, count);
|
||||
this.offset += count;
|
||||
}
|
||||
|
||||
public int Remaining() { return src.Length - offset; }
|
||||
}
|
||||
|
||||
// Lempel - Castle - Welch algorithm (aka Format80)
|
||||
public static class LCWCompression
|
||||
{
|
||||
static void ReplicatePrevious(byte[] dest, int destIndex, int srcIndex, int count)
|
||||
{
|
||||
if (srcIndex > destIndex)
|
||||
throw new NotImplementedException("srcIndex > destIndex {0} {1}".F(srcIndex, destIndex));
|
||||
|
||||
if (destIndex - srcIndex == 1)
|
||||
{
|
||||
for (var i = 0; i < count; i++)
|
||||
dest[destIndex + i] = dest[destIndex - 1];
|
||||
}
|
||||
else
|
||||
{
|
||||
for (var i = 0; i < count; i++)
|
||||
dest[destIndex + i] = dest[srcIndex + i];
|
||||
}
|
||||
}
|
||||
|
||||
public static int DecodeInto(byte[] src, byte[] dest, int srcOffset = 0, bool reverse = false)
|
||||
{
|
||||
var ctx = new FastByteReader(src, srcOffset);
|
||||
var destIndex = 0;
|
||||
while (true)
|
||||
{
|
||||
var i = ctx.ReadByte();
|
||||
if ((i & 0x80) == 0)
|
||||
{
|
||||
// case 2
|
||||
var secondByte = ctx.ReadByte();
|
||||
var count = ((i & 0x70) >> 4) + 3;
|
||||
var rpos = ((i & 0xf) << 8) + secondByte;
|
||||
|
||||
if (destIndex + count > dest.Length)
|
||||
return destIndex;
|
||||
|
||||
ReplicatePrevious(dest, destIndex, destIndex - rpos, count);
|
||||
destIndex += count;
|
||||
}
|
||||
else if ((i & 0x40) == 0)
|
||||
{
|
||||
// case 1
|
||||
var count = i & 0x3F;
|
||||
if (count == 0)
|
||||
return destIndex;
|
||||
|
||||
ctx.CopyTo(dest, destIndex, count);
|
||||
destIndex += count;
|
||||
}
|
||||
else
|
||||
{
|
||||
var count3 = i & 0x3F;
|
||||
if (count3 == 0x3E)
|
||||
{
|
||||
// case 4
|
||||
var count = ctx.ReadWord();
|
||||
var color = ctx.ReadByte();
|
||||
|
||||
for (var end = destIndex + count; destIndex < end; destIndex++)
|
||||
dest[destIndex] = color;
|
||||
}
|
||||
else if (count3 == 0x3F)
|
||||
{
|
||||
// case 5
|
||||
var count = ctx.ReadWord();
|
||||
var srcIndex = reverse ? destIndex - ctx.ReadWord() : ctx.ReadWord();
|
||||
if (srcIndex >= destIndex)
|
||||
throw new NotImplementedException("srcIndex >= destIndex {0} {1}".F(srcIndex, destIndex));
|
||||
|
||||
for (var end = destIndex + count; destIndex < end; destIndex++)
|
||||
dest[destIndex] = dest[srcIndex++];
|
||||
}
|
||||
else
|
||||
{
|
||||
// case 3
|
||||
var count = count3 + 3;
|
||||
var srcIndex = reverse ? destIndex - ctx.ReadWord() : ctx.ReadWord();
|
||||
if (srcIndex >= destIndex)
|
||||
throw new NotImplementedException("srcIndex >= destIndex {0} {1}".F(srcIndex, destIndex));
|
||||
|
||||
for (var end = destIndex + count; destIndex < end; destIndex++)
|
||||
dest[destIndex] = dest[srcIndex++];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int CountSame(byte[] src, int offset, int maxCount)
|
||||
{
|
||||
maxCount = Math.Min(src.Length - offset, maxCount);
|
||||
if (maxCount <= 0)
|
||||
return 0;
|
||||
|
||||
var first = src[offset++];
|
||||
var count = 1;
|
||||
|
||||
while (count < maxCount && src[offset++] == first)
|
||||
count++;
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static void WriteCopyBlocks(byte[] src, int offset, int count, MemoryStream output)
|
||||
{
|
||||
while (count > 0)
|
||||
{
|
||||
var writeNow = Math.Min(count, 0x3F);
|
||||
output.WriteByte((byte)(0x80 | writeNow));
|
||||
output.Write(src, offset, writeNow);
|
||||
|
||||
count -= writeNow;
|
||||
offset += writeNow;
|
||||
}
|
||||
}
|
||||
|
||||
// Quick and dirty LCW encoder version 2
|
||||
// Uses raw copy and RLE compression
|
||||
public static byte[] Encode(byte[] src)
|
||||
{
|
||||
using (var ms = new MemoryStream())
|
||||
{
|
||||
var offset = 0;
|
||||
var left = src.Length;
|
||||
var blockStart = 0;
|
||||
|
||||
while (offset < left)
|
||||
{
|
||||
var repeatCount = CountSame(src, offset, 0xFFFF);
|
||||
if (repeatCount >= 4)
|
||||
{
|
||||
// Write what we haven't written up to now
|
||||
WriteCopyBlocks(src, blockStart, offset - blockStart, ms);
|
||||
|
||||
// Command 4: Repeat byte n times
|
||||
ms.WriteByte(0xFE);
|
||||
|
||||
// Low byte
|
||||
ms.WriteByte((byte)(repeatCount & 0xFF));
|
||||
|
||||
// High byte
|
||||
ms.WriteByte((byte)(repeatCount >> 8));
|
||||
|
||||
// Value to repeat
|
||||
ms.WriteByte(src[offset]);
|
||||
|
||||
offset += repeatCount;
|
||||
blockStart = offset;
|
||||
}
|
||||
else
|
||||
offset++;
|
||||
}
|
||||
|
||||
// Write what we haven't written up to now
|
||||
WriteCopyBlocks(src, blockStart, offset - blockStart, ms);
|
||||
|
||||
// Write terminator
|
||||
ms.WriteByte(0x80);
|
||||
|
||||
return ms.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
34
OpenRA.Mods.Common/FileFormats/RLEZerosCompression.cs
Normal file
34
OpenRA.Mods.Common/FileFormats/RLEZerosCompression.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2015 The OpenRA Developers (see AUTHORS)
|
||||
* 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. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
namespace OpenRA.Mods.Common.FileFormats
|
||||
{
|
||||
// Run length encoded sequences of zeros (aka Format2)
|
||||
public static class RLEZerosCompression
|
||||
{
|
||||
public static void DecodeInto(byte[] src, byte[] dest, int destIndex)
|
||||
{
|
||||
var r = new FastByteReader(src);
|
||||
|
||||
while (!r.Done())
|
||||
{
|
||||
var cmd = r.ReadByte();
|
||||
if (cmd == 0)
|
||||
{
|
||||
var count = r.ReadByte();
|
||||
while (count-- > 0)
|
||||
dest[destIndex++] = 0;
|
||||
}
|
||||
else
|
||||
dest[destIndex++] = cmd;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
533
OpenRA.Mods.Common/FileFormats/VqaReader.cs
Normal file
533
OpenRA.Mods.Common/FileFormats/VqaReader.cs
Normal file
@@ -0,0 +1,533 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2015 The OpenRA Developers (see AUTHORS)
|
||||
* 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. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using OpenRA.FileFormats;
|
||||
|
||||
namespace OpenRA.Mods.Common.FileFormats
|
||||
{
|
||||
public class VqaReader
|
||||
{
|
||||
public readonly ushort Frames;
|
||||
public readonly byte Framerate;
|
||||
public readonly ushort Width;
|
||||
public readonly ushort Height;
|
||||
|
||||
Stream stream;
|
||||
int currentFrame;
|
||||
ushort numColors;
|
||||
ushort blockWidth;
|
||||
ushort blockHeight;
|
||||
byte chunkBufferParts;
|
||||
int2 blocks;
|
||||
uint[] offsets;
|
||||
uint[] palette;
|
||||
uint videoFlags; // if 0x10 is set the video is a 16 bit hq video (ts and later)
|
||||
int sampleRate;
|
||||
int sampleBits;
|
||||
int audioChannels;
|
||||
|
||||
// Stores a list of subpixels, referenced by the VPTZ chunk
|
||||
byte[] cbf;
|
||||
byte[] cbp;
|
||||
byte[] cbfBuffer;
|
||||
|
||||
// Buffer for loading file subchunks, the maximum chunk size of a file is not defined
|
||||
// and the header definition for the size of the biggest chunks (color data) isn't accurate.
|
||||
// But 256k is large enough for all TS videos(< 200k).
|
||||
byte[] fileBuffer = new byte[256000];
|
||||
int maxCbfzSize = 256000;
|
||||
int vtprSize = 0;
|
||||
int currentChunkBuffer = 0;
|
||||
int chunkBufferOffset = 0;
|
||||
|
||||
// Top half contains block info, bottom half contains references to cbf array
|
||||
byte[] origData;
|
||||
|
||||
// Final frame output
|
||||
uint[,] frameData;
|
||||
byte[] audioData; // audio for this frame: 22050Hz 16bit mono pcm, uncompressed.
|
||||
bool hasAudio;
|
||||
|
||||
public byte[] AudioData { get { return audioData; } }
|
||||
public int CurrentFrame { get { return currentFrame; } }
|
||||
public int SampleRate { get { return sampleRate; } }
|
||||
public int SampleBits { get { return sampleBits; } }
|
||||
public int AudioChannels { get { return audioChannels; } }
|
||||
public bool HasAudio { get { return hasAudio; } }
|
||||
|
||||
public VqaReader(Stream stream)
|
||||
{
|
||||
this.stream = stream;
|
||||
|
||||
// Decode FORM chunk
|
||||
if (stream.ReadASCII(4) != "FORM")
|
||||
throw new InvalidDataException("Invalid vqa (invalid FORM section)");
|
||||
/*var length = */stream.ReadUInt32();
|
||||
|
||||
if (stream.ReadASCII(8) != "WVQAVQHD")
|
||||
throw new InvalidDataException("Invalid vqa (not WVQAVQHD)");
|
||||
/*var length2 = */stream.ReadUInt32();
|
||||
|
||||
/*var version = */stream.ReadUInt16();
|
||||
videoFlags = stream.ReadUInt16();
|
||||
Frames = stream.ReadUInt16();
|
||||
Width = stream.ReadUInt16();
|
||||
Height = stream.ReadUInt16();
|
||||
|
||||
blockWidth = stream.ReadUInt8();
|
||||
blockHeight = stream.ReadUInt8();
|
||||
Framerate = stream.ReadUInt8();
|
||||
chunkBufferParts = stream.ReadUInt8();
|
||||
blocks = new int2(Width / blockWidth, Height / blockHeight);
|
||||
|
||||
numColors = stream.ReadUInt16();
|
||||
/*var maxBlocks = */stream.ReadUInt16();
|
||||
/*var unknown1 = */stream.ReadUInt16();
|
||||
/*var unknown2 = */stream.ReadUInt32();
|
||||
|
||||
// Audio
|
||||
sampleRate = stream.ReadUInt16();
|
||||
audioChannels = stream.ReadByte();
|
||||
sampleBits = stream.ReadByte();
|
||||
|
||||
/*var unknown3 =*/stream.ReadUInt32();
|
||||
/*var unknown4 =*/stream.ReadUInt16();
|
||||
/*maxCbfzSize =*/stream.ReadUInt32(); // Unreliable
|
||||
|
||||
/*var unknown5 =*/stream.ReadUInt32();
|
||||
|
||||
var frameSize = Exts.NextPowerOf2(Math.Max(Width, Height));
|
||||
|
||||
if (IsHqVqa)
|
||||
{
|
||||
cbfBuffer = new byte[maxCbfzSize];
|
||||
cbf = new byte[maxCbfzSize * 3];
|
||||
origData = new byte[maxCbfzSize];
|
||||
}
|
||||
else
|
||||
{
|
||||
cbfBuffer = new byte[Width * Height];
|
||||
cbf = new byte[Width * Height];
|
||||
cbp = new byte[Width * Height];
|
||||
origData = new byte[2 * blocks.X * blocks.Y];
|
||||
}
|
||||
|
||||
palette = new uint[numColors];
|
||||
frameData = new uint[frameSize, frameSize];
|
||||
var type = stream.ReadASCII(4);
|
||||
while (type != "FINF")
|
||||
{
|
||||
// Sub type is a file tag
|
||||
if (type[3] == 'F')
|
||||
{
|
||||
var jmp = int2.Swap(stream.ReadUInt32());
|
||||
stream.Seek(jmp, SeekOrigin.Current);
|
||||
type = stream.ReadASCII(4);
|
||||
}
|
||||
else
|
||||
throw new NotSupportedException("Vqa uses unknown Subtype: {0}".F(type));
|
||||
}
|
||||
|
||||
/*var length = */stream.ReadUInt16();
|
||||
/*var unknown4 = */stream.ReadUInt16();
|
||||
|
||||
// Frame offsets
|
||||
offsets = new uint[Frames];
|
||||
for (var i = 0; i < Frames; i++)
|
||||
{
|
||||
offsets[i] = stream.ReadUInt32();
|
||||
if (offsets[i] > 0x40000000)
|
||||
offsets[i] -= 0x40000000;
|
||||
offsets[i] <<= 1;
|
||||
}
|
||||
|
||||
CollectAudioData();
|
||||
|
||||
Reset();
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
currentFrame = chunkBufferOffset = currentChunkBuffer = 0;
|
||||
LoadFrame();
|
||||
}
|
||||
|
||||
void CollectAudioData()
|
||||
{
|
||||
var audio1 = new MemoryStream(); // left channel / mono
|
||||
var audio2 = new MemoryStream(); // right channel
|
||||
var adpcmIndex = 0;
|
||||
var compressed = false;
|
||||
for (var i = 0; i < Frames; i++)
|
||||
{
|
||||
stream.Seek(offsets[i], SeekOrigin.Begin);
|
||||
var end = (i < Frames - 1) ? offsets[i + 1] : stream.Length;
|
||||
|
||||
while (stream.Position < end)
|
||||
{
|
||||
var type = stream.ReadASCII(4);
|
||||
if (type == "SN2J")
|
||||
{
|
||||
var jmp = int2.Swap(stream.ReadUInt32());
|
||||
stream.Seek(jmp, SeekOrigin.Current);
|
||||
type = stream.ReadASCII(4);
|
||||
}
|
||||
|
||||
var length = int2.Swap(stream.ReadUInt32());
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case "SND0":
|
||||
case "SND2":
|
||||
if (audioChannels == 0)
|
||||
throw new NotSupportedException();
|
||||
else if (audioChannels == 1)
|
||||
{
|
||||
var rawAudio = stream.ReadBytes((int)length);
|
||||
audio1.Write(rawAudio);
|
||||
}
|
||||
else
|
||||
{
|
||||
var rawAudio = stream.ReadBytes((int)length / 2);
|
||||
audio1.Write(rawAudio);
|
||||
rawAudio = stream.ReadBytes((int)length / 2);
|
||||
audio2.Write(rawAudio);
|
||||
if (length % 2 != 0)
|
||||
stream.ReadBytes(2);
|
||||
}
|
||||
|
||||
compressed = type == "SND2";
|
||||
break;
|
||||
default:
|
||||
if (length + stream.Position > stream.Length)
|
||||
throw new NotSupportedException("Vqa uses unknown Subtype: {0}".F(type));
|
||||
stream.ReadBytes((int)length);
|
||||
break;
|
||||
}
|
||||
|
||||
// Chunks are aligned on even bytes; advance by a byte if the next one is null
|
||||
if (stream.Peek() == 0) stream.ReadByte();
|
||||
}
|
||||
}
|
||||
|
||||
if (audioChannels == 1)
|
||||
audioData = compressed ? AudLoader.LoadSound(audio1.ToArray(), ref adpcmIndex) : audio1.ToArray();
|
||||
else
|
||||
{
|
||||
byte[] leftData, rightData;
|
||||
if (!compressed)
|
||||
{
|
||||
leftData = audio1.ToArray();
|
||||
rightData = audio2.ToArray();
|
||||
}
|
||||
else
|
||||
{
|
||||
adpcmIndex = 0;
|
||||
leftData = AudLoader.LoadSound(audio1.ToArray(), ref adpcmIndex);
|
||||
adpcmIndex = 0;
|
||||
rightData = AudLoader.LoadSound(audio2.ToArray(), ref adpcmIndex);
|
||||
}
|
||||
|
||||
audioData = new byte[rightData.Length + leftData.Length];
|
||||
var rightIndex = 0;
|
||||
var leftIndex = 0;
|
||||
for (var i = 0; i < audioData.Length;)
|
||||
{
|
||||
audioData[i++] = leftData[leftIndex++];
|
||||
audioData[i++] = leftData[leftIndex++];
|
||||
audioData[i++] = rightData[rightIndex++];
|
||||
audioData[i++] = rightData[rightIndex++];
|
||||
}
|
||||
}
|
||||
|
||||
hasAudio = audioData.Length > 0;
|
||||
}
|
||||
|
||||
public void AdvanceFrame()
|
||||
{
|
||||
currentFrame++;
|
||||
LoadFrame();
|
||||
}
|
||||
|
||||
void LoadFrame()
|
||||
{
|
||||
if (currentFrame >= Frames)
|
||||
return;
|
||||
|
||||
// Seek to the start of the frame
|
||||
stream.Seek(offsets[currentFrame], SeekOrigin.Begin);
|
||||
var end = (currentFrame < Frames - 1) ? offsets[currentFrame + 1] : stream.Length;
|
||||
|
||||
while (stream.Position < end)
|
||||
{
|
||||
var type = stream.ReadASCII(4);
|
||||
var length = 0U;
|
||||
if (type == "SN2J")
|
||||
{
|
||||
var jmp = int2.Swap(stream.ReadUInt32());
|
||||
stream.Seek(jmp, SeekOrigin.Current);
|
||||
type = stream.ReadASCII(4);
|
||||
if (type == "SND2")
|
||||
{
|
||||
length = int2.Swap(stream.ReadUInt32());
|
||||
stream.Seek(length, SeekOrigin.Current);
|
||||
type = stream.ReadASCII(4);
|
||||
}
|
||||
else
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
length = int2.Swap(stream.ReadUInt32());
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case "VQFR":
|
||||
DecodeVQFR(stream);
|
||||
break;
|
||||
case "\0VQF":
|
||||
stream.ReadByte();
|
||||
DecodeVQFR(stream);
|
||||
break;
|
||||
case "VQFL":
|
||||
DecodeVQFR(stream, "VQFL");
|
||||
break;
|
||||
default:
|
||||
// Don't parse sound here.
|
||||
stream.ReadBytes((int)length);
|
||||
break;
|
||||
}
|
||||
|
||||
// Chunks are aligned on even bytes; advance by a byte if the next one is null
|
||||
if (stream.Peek() == 0) stream.ReadByte();
|
||||
}
|
||||
}
|
||||
|
||||
// VQA Frame
|
||||
public void DecodeVQFR(Stream s, string parentType = "VQFR")
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
// Chunks are aligned on even bytes; may be padded with a single null
|
||||
if (s.Peek() == 0) s.ReadByte();
|
||||
var type = s.ReadASCII(4);
|
||||
var subchunkLength = (int)int2.Swap(s.ReadUInt32());
|
||||
|
||||
switch (type)
|
||||
{
|
||||
// Full frame-modifier
|
||||
case "CBFZ":
|
||||
var decodeMode = s.Peek() == 0;
|
||||
s.ReadBytes(fileBuffer, 0, subchunkLength);
|
||||
Array.Clear(cbf, 0, cbf.Length);
|
||||
Array.Clear(cbfBuffer, 0, cbfBuffer.Length);
|
||||
var decodeCount = 0;
|
||||
decodeCount = LCWCompression.DecodeInto(fileBuffer, cbfBuffer, decodeMode ? 1 : 0, decodeMode);
|
||||
if ((videoFlags & 0x10) == 16)
|
||||
{
|
||||
var p = 0;
|
||||
for (var i = 0; i < decodeCount; i += 2)
|
||||
{
|
||||
var packed = cbfBuffer[i + 1] << 8 | cbfBuffer[i];
|
||||
/* 15 bit 0
|
||||
0rrrrrgg gggbbbbb
|
||||
HI byte LO byte*/
|
||||
cbf[p++] = (byte)((packed & 0x7C00) >> 7);
|
||||
cbf[p++] = (byte)((packed & 0x3E0) >> 2);
|
||||
cbf[p++] = (byte)((packed & 0x1f) << 3);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
cbf = cbfBuffer;
|
||||
}
|
||||
|
||||
if (parentType == "VQFL")
|
||||
return;
|
||||
break;
|
||||
case "CBF0":
|
||||
cbf = s.ReadBytes(subchunkLength);
|
||||
break;
|
||||
|
||||
// frame-modifier chunk
|
||||
case "CBP0":
|
||||
case "CBPZ":
|
||||
// Partial buffer is full; dump and recreate
|
||||
if (currentChunkBuffer == chunkBufferParts)
|
||||
{
|
||||
if (type == "CBP0")
|
||||
cbf = (byte[])cbp.Clone();
|
||||
else
|
||||
LCWCompression.DecodeInto(cbp, cbf);
|
||||
|
||||
chunkBufferOffset = currentChunkBuffer = 0;
|
||||
}
|
||||
|
||||
var bytes = s.ReadBytes(subchunkLength);
|
||||
bytes.CopyTo(cbp, chunkBufferOffset);
|
||||
chunkBufferOffset += subchunkLength;
|
||||
currentChunkBuffer++;
|
||||
break;
|
||||
|
||||
// Palette
|
||||
case "CPL0":
|
||||
for (var i = 0; i < numColors; i++)
|
||||
{
|
||||
var r = (byte)(s.ReadUInt8() << 2);
|
||||
var g = (byte)(s.ReadUInt8() << 2);
|
||||
var b = (byte)(s.ReadUInt8() << 2);
|
||||
palette[i] = (uint)((255 << 24) | (r << 16) | (g << 8) | b);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
// Frame data
|
||||
case "VPTZ":
|
||||
LCWCompression.DecodeInto(s.ReadBytes(subchunkLength), origData);
|
||||
|
||||
// This is the last subchunk
|
||||
return;
|
||||
case "VPRZ":
|
||||
Array.Clear(origData, 0, origData.Length);
|
||||
s.ReadBytes(fileBuffer, 0, subchunkLength);
|
||||
if (fileBuffer[0] != 0)
|
||||
vtprSize = LCWCompression.DecodeInto(fileBuffer, origData);
|
||||
else
|
||||
LCWCompression.DecodeInto(fileBuffer, origData, 1, true);
|
||||
return;
|
||||
case "VPTR":
|
||||
Array.Clear(origData, 0, origData.Length);
|
||||
s.ReadBytes(origData, 0, subchunkLength);
|
||||
vtprSize = subchunkLength;
|
||||
return;
|
||||
default:
|
||||
throw new InvalidDataException("Unknown sub-chunk {0}".F(type));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int cachedFrame = -1;
|
||||
|
||||
void DecodeFrameData()
|
||||
{
|
||||
cachedFrame = currentFrame;
|
||||
if (IsHqVqa)
|
||||
{
|
||||
/* The VP?? chunks of the video file contains an array of instructions for
|
||||
* how the blocks of the finished frame will be filled with color data blocks
|
||||
* contained in the CBF? chunks.
|
||||
*/
|
||||
var p = 0;
|
||||
for (var y = 0; y < blocks.Y;)
|
||||
{
|
||||
for (var x = 0; x < blocks.X;)
|
||||
{
|
||||
if (y >= blocks.Y)
|
||||
break;
|
||||
|
||||
// The first 3 bits of the short determine the type of instruction with the rest being one or two parameters.
|
||||
var val = (int)origData[p++];
|
||||
val |= origData[p++] << 8;
|
||||
var para_A = val & 0x1fff;
|
||||
var para_B1 = val & 0xFF;
|
||||
var para_B2 = (((val / 256) & 0x1f) + 1) * 2;
|
||||
switch (val >> 13)
|
||||
{
|
||||
case 0:
|
||||
x += para_A;
|
||||
break;
|
||||
case 1:
|
||||
WriteBlock(para_B1, para_B2, ref x, ref y);
|
||||
break;
|
||||
case 2:
|
||||
WriteBlock(para_B1, 1, ref x, ref y);
|
||||
for (var i = 0; i < para_B2; i++)
|
||||
WriteBlock(origData[p++], 1, ref x, ref y);
|
||||
break;
|
||||
case 3:
|
||||
WriteBlock(para_A, 1, ref x, ref y);
|
||||
break;
|
||||
case 5:
|
||||
WriteBlock(para_A, origData[p++], ref x, ref y);
|
||||
break;
|
||||
default:
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
|
||||
y++;
|
||||
}
|
||||
|
||||
if (p != vtprSize)
|
||||
throw new IndexOutOfRangeException();
|
||||
}
|
||||
else
|
||||
{
|
||||
for (var y = 0; y < blocks.Y; y++)
|
||||
{
|
||||
for (var x = 0; x < blocks.X; x++)
|
||||
{
|
||||
var px = origData[x + y * blocks.X];
|
||||
var mod = origData[x + (y + blocks.Y) * blocks.X];
|
||||
for (var j = 0; j < blockHeight; j++)
|
||||
{
|
||||
for (var i = 0; i < blockWidth; i++)
|
||||
{
|
||||
var cbfi = (mod * 256 + px) * 8 + j * blockWidth + i;
|
||||
var color = (mod == 0x0f) ? px : cbf[cbfi];
|
||||
frameData[y * blockHeight + j, x * blockWidth + i] = palette[color];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public uint[,] FrameData
|
||||
{
|
||||
get
|
||||
{
|
||||
if (cachedFrame != currentFrame)
|
||||
DecodeFrameData();
|
||||
|
||||
return frameData;
|
||||
}
|
||||
}
|
||||
|
||||
bool IsHqVqa { get { return (videoFlags & 0x10) == 16; } }
|
||||
|
||||
void WriteBlock(int blockNumber, int count, ref int x, ref int y)
|
||||
{
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var frameX = x * blockWidth;
|
||||
var frameY = y * blockHeight;
|
||||
var offset = blockNumber * blockHeight * blockWidth * 3;
|
||||
for (var by = 0; by < blockHeight; by++)
|
||||
for (var bx = 0; bx < blockWidth; bx++)
|
||||
{
|
||||
var p = (bx + by * blockWidth) * 3;
|
||||
|
||||
frameData[frameY + by, frameX + bx] = (uint)(0xFF << 24 | cbf[offset + p] << 16 | cbf[offset + p + 1] << 8 | cbf[offset + p + 2]);
|
||||
}
|
||||
|
||||
x++;
|
||||
if (x >= blocks.X)
|
||||
{
|
||||
x = 0;
|
||||
y++;
|
||||
if (y >= blocks.Y && i != count - 1)
|
||||
throw new IndexOutOfRangeException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
79
OpenRA.Mods.Common/FileFormats/XORDeltaCompression.cs
Normal file
79
OpenRA.Mods.Common/FileFormats/XORDeltaCompression.cs
Normal file
@@ -0,0 +1,79 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2015 The OpenRA Developers (see AUTHORS)
|
||||
* 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. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
namespace OpenRA.Mods.Common.FileFormats
|
||||
{
|
||||
// Data that is to be XORed against another set of data (aka Format40)
|
||||
public static class XORDeltaCompression
|
||||
{
|
||||
public static int DecodeInto(byte[] src, byte[] dest, int srcOffset)
|
||||
{
|
||||
var ctx = new FastByteReader(src, srcOffset);
|
||||
var destIndex = 0;
|
||||
|
||||
while (true)
|
||||
{
|
||||
var i = ctx.ReadByte();
|
||||
if ((i & 0x80) == 0)
|
||||
{
|
||||
var count = i & 0x7F;
|
||||
if (count == 0)
|
||||
{
|
||||
// case 6
|
||||
count = ctx.ReadByte();
|
||||
var value = ctx.ReadByte();
|
||||
for (var end = destIndex + count; destIndex < end; destIndex++)
|
||||
dest[destIndex] ^= value;
|
||||
}
|
||||
else
|
||||
{
|
||||
// case 5
|
||||
for (var end = destIndex + count; destIndex < end; destIndex++)
|
||||
dest[destIndex] ^= ctx.ReadByte();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var count = i & 0x7F;
|
||||
if (count == 0)
|
||||
{
|
||||
count = ctx.ReadWord();
|
||||
if (count == 0)
|
||||
return destIndex;
|
||||
|
||||
if ((count & 0x8000) == 0)
|
||||
{
|
||||
// case 2
|
||||
destIndex += count & 0x7FFF;
|
||||
}
|
||||
else if ((count & 0x4000) == 0)
|
||||
{
|
||||
// case 3
|
||||
for (var end = destIndex + (count & 0x3FFF); destIndex < end; destIndex++)
|
||||
dest[destIndex] ^= ctx.ReadByte();
|
||||
}
|
||||
else
|
||||
{
|
||||
// case 4
|
||||
var value = ctx.ReadByte();
|
||||
for (var end = destIndex + (count & 0x3FFF); destIndex < end; destIndex++)
|
||||
dest[destIndex] ^= value;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// case 1
|
||||
destIndex += count;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -719,6 +719,11 @@
|
||||
<Compile Include="Traits\World\EditorSelectionLayer.cs" />
|
||||
<Compile Include="Graphics\DetectionCircleRenderable.cs" />
|
||||
<Compile Include="Traits\Render\WithIdleAnimation.cs" />
|
||||
<Compile Include="FileFormats\VqaReader.cs" />
|
||||
<Compile Include="FileFormats\LCWCompression.cs" />
|
||||
<Compile Include="FileFormats\XORDeltaCompression.cs" />
|
||||
<Compile Include="FileFormats\RLEZerosCompression.cs" />
|
||||
<Compile Include="FileFormats\IniFile.cs" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<PropertyGroup>
|
||||
|
||||
@@ -13,10 +13,10 @@ using System.Drawing;
|
||||
using System.IO;
|
||||
using Eluant;
|
||||
using OpenRA.Effects;
|
||||
using OpenRA.FileFormats;
|
||||
using OpenRA.GameRules;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Mods.Common.Effects;
|
||||
using OpenRA.Mods.Common.FileFormats;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Scripting;
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using OpenRA.FileFormats;
|
||||
using OpenRA.Mods.Common.FileFormats;
|
||||
using OpenRA.Mods.Common.Widgets;
|
||||
using OpenRA.Widgets;
|
||||
|
||||
|
||||
@@ -11,8 +11,8 @@
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using OpenRA.FileFormats;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Mods.Common.FileFormats;
|
||||
|
||||
namespace OpenRA.Mods.Common.SpriteLoaders
|
||||
{
|
||||
@@ -21,7 +21,7 @@ namespace OpenRA.Mods.Common.SpriteLoaders
|
||||
[Flags] enum FormatFlags : int
|
||||
{
|
||||
PaletteTable = 1,
|
||||
SkipFormat80 = 2,
|
||||
NotLCWCompressed = 2,
|
||||
VariableLengthTable = 4
|
||||
}
|
||||
|
||||
@@ -70,14 +70,14 @@ namespace OpenRA.Mods.Common.SpriteLoaders
|
||||
|
||||
// Decode image data
|
||||
var compressed = s.ReadBytes(dataLeft);
|
||||
if ((flags & FormatFlags.SkipFormat80) == 0)
|
||||
if ((flags & FormatFlags.NotLCWCompressed) == 0)
|
||||
{
|
||||
var temp = new byte[dataSize];
|
||||
Format80.DecodeInto(compressed, temp);
|
||||
LCWCompression.DecodeInto(compressed, temp);
|
||||
compressed = temp;
|
||||
}
|
||||
|
||||
Format2.DecodeInto(compressed, Data, 0);
|
||||
RLEZerosCompression.DecodeInto(compressed, Data, 0);
|
||||
|
||||
// Lookup values in lookup table
|
||||
for (var j = 0; j < Data.Length; j++)
|
||||
|
||||
@@ -13,8 +13,8 @@ using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using OpenRA.FileFormats;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Mods.Common.FileFormats;
|
||||
|
||||
namespace OpenRA.Mods.Common.SpriteLoaders
|
||||
{
|
||||
@@ -71,7 +71,7 @@ namespace OpenRA.Mods.Common.SpriteLoaders
|
||||
|
||||
public class ShpTDSprite
|
||||
{
|
||||
enum Format { Format20 = 0x20, Format40 = 0x40, Format80 = 0x80 }
|
||||
enum Format { XORPrev = 0x20, XORLCW = 0x40, LCW = 0x80 }
|
||||
|
||||
class ImageHeader : ISpriteFrame
|
||||
{
|
||||
@@ -142,9 +142,9 @@ namespace OpenRA.Mods.Common.SpriteLoaders
|
||||
for (var i = 0; i < imageCount; i++)
|
||||
{
|
||||
var h = headers[i];
|
||||
if (h.Format == Format.Format20)
|
||||
if (h.Format == Format.XORPrev)
|
||||
h.RefImage = headers[i - 1];
|
||||
else if (h.Format == Format.Format40 && !offsets.TryGetValue(h.RefOffset, out h.RefImage))
|
||||
else if (h.Format == Format.XORLCW && !offsets.TryGetValue(h.RefOffset, out h.RefImage))
|
||||
throw new InvalidDataException("Reference doesn't point to image data {0}->{1}".F(h.FileOffset, h.RefOffset));
|
||||
}
|
||||
|
||||
@@ -166,8 +166,8 @@ namespace OpenRA.Mods.Common.SpriteLoaders
|
||||
|
||||
switch (h.Format)
|
||||
{
|
||||
case Format.Format20:
|
||||
case Format.Format40:
|
||||
case Format.XORPrev:
|
||||
case Format.XORLCW:
|
||||
{
|
||||
if (h.RefImage.Data == null)
|
||||
{
|
||||
@@ -177,14 +177,14 @@ namespace OpenRA.Mods.Common.SpriteLoaders
|
||||
}
|
||||
|
||||
h.Data = CopyImageData(h.RefImage.Data);
|
||||
Format40.DecodeInto(shpBytes, h.Data, (int)(h.FileOffset - shpBytesFileOffset));
|
||||
XORDeltaCompression.DecodeInto(shpBytes, h.Data, (int)(h.FileOffset - shpBytesFileOffset));
|
||||
break;
|
||||
}
|
||||
|
||||
case Format.Format80:
|
||||
case Format.LCW:
|
||||
{
|
||||
var imageBytes = new byte[Size.Width * Size.Height];
|
||||
Format80.DecodeInto(shpBytes, imageBytes, (int)(h.FileOffset - shpBytesFileOffset));
|
||||
LCWCompression.DecodeInto(shpBytes, imageBytes, (int)(h.FileOffset - shpBytesFileOffset));
|
||||
h.Data = imageBytes;
|
||||
break;
|
||||
}
|
||||
@@ -203,7 +203,7 @@ namespace OpenRA.Mods.Common.SpriteLoaders
|
||||
|
||||
public static void Write(Stream s, Size size, IEnumerable<byte[]> frames)
|
||||
{
|
||||
var compressedFrames = frames.Select(f => Format80.Encode(f)).ToList();
|
||||
var compressedFrames = frames.Select(f => LCWCompression.Encode(f)).ToList();
|
||||
|
||||
// note: end-of-file and all-zeroes headers
|
||||
var dataOffset = 14 + (compressedFrames.Count + 2) * 8;
|
||||
@@ -219,7 +219,7 @@ namespace OpenRA.Mods.Common.SpriteLoaders
|
||||
|
||||
foreach (var f in compressedFrames)
|
||||
{
|
||||
var ih = new ImageHeader { Format = Format.Format80, FileOffset = (uint)dataOffset };
|
||||
var ih = new ImageHeader { Format = Format.LCW, FileOffset = (uint)dataOffset };
|
||||
dataOffset += f.Length;
|
||||
|
||||
ih.WriteTo(bw);
|
||||
|
||||
@@ -10,8 +10,8 @@
|
||||
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using OpenRA.FileFormats;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Mods.Common.FileFormats;
|
||||
|
||||
namespace OpenRA.Mods.Common.SpriteLoaders
|
||||
{
|
||||
@@ -67,7 +67,7 @@ namespace OpenRA.Mods.Common.SpriteLoaders
|
||||
for (var j = 0; j < height; j++)
|
||||
{
|
||||
var length = s.ReadUInt16() - 2;
|
||||
Format2.DecodeInto(s.ReadBytes(length), Data, dataWidth * j);
|
||||
RLEZerosCompression.DecodeInto(s.ReadBytes(length), Data, dataWidth * j);
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
@@ -13,8 +13,8 @@ using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using OpenRA.FileFormats;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Mods.Common.FileFormats;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
@@ -242,7 +242,7 @@ namespace OpenRA.Mods.Common.UtilityCommands
|
||||
var src = reader.ReadBytes((int)length);
|
||||
|
||||
/*int actualLength =*/
|
||||
Format80.DecodeInto(src, dest);
|
||||
LCWCompression.DecodeInto(src, dest);
|
||||
|
||||
chunks.Add(dest);
|
||||
}
|
||||
|
||||
@@ -10,8 +10,8 @@
|
||||
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using OpenRA.FileFormats;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Mods.Common.FileFormats;
|
||||
using OpenRA.Widgets;
|
||||
|
||||
namespace OpenRA.Mods.Common.Widgets
|
||||
|
||||
Reference in New Issue
Block a user