Initial commit: OpenRA game engine
Fork from OpenRA/OpenRA with one-click launch script (start-ra.cmd) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
205
OpenRA.Mods.Cnc/FileFormats/AudReader.cs
Normal file
205
OpenRA.Mods.Cnc/FileFormats/AudReader.cs
Normal file
@@ -0,0 +1,205 @@
|
||||
#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 OpenRA.Mods.Common.FileFormats;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.Mods.Cnc.FileFormats
|
||||
{
|
||||
[Flags]
|
||||
enum SoundFlags : byte
|
||||
{
|
||||
Stereo = 0x1,
|
||||
_16Bit = 0x2,
|
||||
}
|
||||
|
||||
enum SoundFormat : byte
|
||||
{
|
||||
WestwoodCompressed = 1,
|
||||
ImaAdpcm = 99,
|
||||
}
|
||||
|
||||
struct AudChunk
|
||||
{
|
||||
public int CompressedSize;
|
||||
public int OutputSize;
|
||||
|
||||
public static AudChunk Read(Stream s)
|
||||
{
|
||||
AudChunk c;
|
||||
c.CompressedSize = s.ReadUInt16();
|
||||
c.OutputSize = s.ReadUInt16();
|
||||
|
||||
if (s.ReadUInt32() != 0xdeaf)
|
||||
throw new InvalidDataException("Chunk header is bogus");
|
||||
|
||||
return c;
|
||||
}
|
||||
}
|
||||
|
||||
public static class AudReader
|
||||
{
|
||||
public static bool LoadSound(Stream s, out Func<Stream> result, out int sampleRate, out int sampleBits, out int channels, out float lengthInSeconds)
|
||||
{
|
||||
result = null;
|
||||
var startPosition = s.Position;
|
||||
try
|
||||
{
|
||||
sampleRate = s.ReadUInt16();
|
||||
var dataSize = s.ReadInt32();
|
||||
var outputSize = s.ReadInt32();
|
||||
var audioFlags = (SoundFlags)s.ReadUInt8();
|
||||
sampleBits = (audioFlags & SoundFlags._16Bit) == 0 ? 8 : 16;
|
||||
channels = (audioFlags & SoundFlags.Stereo) == 0 ? 1 : 2;
|
||||
lengthInSeconds = (float)(outputSize * 8) / (channels * sampleBits * sampleRate);
|
||||
|
||||
var readFormat = s.ReadUInt8();
|
||||
if (!Enum.IsDefined(typeof(SoundFormat), readFormat))
|
||||
return false;
|
||||
|
||||
var offsetPosition = s.Position;
|
||||
var streamLength = s.Length;
|
||||
var segmentLength = (int)(streamLength - offsetPosition);
|
||||
|
||||
result = () =>
|
||||
{
|
||||
var audioStream = SegmentStream.CreateWithoutOwningStream(s, offsetPosition, segmentLength);
|
||||
|
||||
switch (readFormat)
|
||||
{
|
||||
case (int)SoundFormat.ImaAdpcm:
|
||||
return new ImaAdpcmAudStream(audioStream, outputSize, dataSize);
|
||||
|
||||
case (int)SoundFormat.WestwoodCompressed:
|
||||
return new WestwoodCompressedAudStream(audioStream, outputSize, dataSize);
|
||||
|
||||
default:
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
};
|
||||
}
|
||||
finally
|
||||
{
|
||||
s.Position = startPosition;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
sealed class ImaAdpcmAudStream : ReadOnlyAdapterStream
|
||||
{
|
||||
readonly int outputSize;
|
||||
int dataSize;
|
||||
byte[] inputBuffer;
|
||||
|
||||
int currentSample;
|
||||
int baseOffset;
|
||||
int index;
|
||||
|
||||
public ImaAdpcmAudStream(Stream stream, int outputSize, int dataSize)
|
||||
: base(stream)
|
||||
{
|
||||
this.outputSize = outputSize;
|
||||
this.dataSize = dataSize;
|
||||
}
|
||||
|
||||
public override long Length => outputSize;
|
||||
|
||||
protected override bool BufferData(Stream baseStream, Queue<byte> data)
|
||||
{
|
||||
if (dataSize <= 0)
|
||||
return true;
|
||||
|
||||
var chunk = AudChunk.Read(baseStream);
|
||||
var input = EnsureArraySize(ref inputBuffer, chunk.CompressedSize);
|
||||
baseStream.ReadBytes(input);
|
||||
|
||||
for (var n = 0; n < chunk.CompressedSize; n++)
|
||||
{
|
||||
var b = input[n];
|
||||
|
||||
var t = ImaAdpcmReader.DecodeImaAdpcmSample(b, ref index, ref currentSample);
|
||||
data.Enqueue((byte)t);
|
||||
data.Enqueue((byte)(t >> 8));
|
||||
baseOffset += 2;
|
||||
|
||||
if (baseOffset < outputSize)
|
||||
{
|
||||
/* possible that only half of the final byte is used! */
|
||||
t = ImaAdpcmReader.DecodeImaAdpcmSample((byte)(b >> 4), ref index, ref currentSample);
|
||||
data.Enqueue((byte)t);
|
||||
data.Enqueue((byte)(t >> 8));
|
||||
baseOffset += 2;
|
||||
}
|
||||
}
|
||||
|
||||
dataSize -= 8 + chunk.CompressedSize;
|
||||
|
||||
return dataSize <= 0;
|
||||
}
|
||||
|
||||
static Span<byte> EnsureArraySize(ref byte[] array, int desiredSize)
|
||||
{
|
||||
if (array == null || array.Length < desiredSize)
|
||||
array = new byte[desiredSize];
|
||||
return array.AsSpan(..desiredSize);
|
||||
}
|
||||
}
|
||||
|
||||
sealed class WestwoodCompressedAudStream : ReadOnlyAdapterStream
|
||||
{
|
||||
readonly int outputSize;
|
||||
int dataSize;
|
||||
byte[] inputBuffer;
|
||||
byte[] outputBuffer;
|
||||
|
||||
public WestwoodCompressedAudStream(Stream stream, int outputSize, int dataSize)
|
||||
: base(stream)
|
||||
{
|
||||
this.outputSize = outputSize;
|
||||
this.dataSize = dataSize;
|
||||
}
|
||||
|
||||
public override long Length => outputSize;
|
||||
|
||||
protected override bool BufferData(Stream baseStream, Queue<byte> data)
|
||||
{
|
||||
if (dataSize <= 0)
|
||||
return true;
|
||||
|
||||
var chunk = AudChunk.Read(baseStream);
|
||||
|
||||
var input = EnsureArraySize(ref inputBuffer, chunk.CompressedSize);
|
||||
var output = EnsureArraySize(ref outputBuffer, chunk.OutputSize);
|
||||
|
||||
baseStream.ReadBytes(input);
|
||||
WestwoodCompressedReader.DecodeWestwoodCompressedSample(input, output);
|
||||
|
||||
foreach (var b in output)
|
||||
data.Enqueue(b);
|
||||
|
||||
dataSize -= 8 + chunk.CompressedSize;
|
||||
|
||||
return dataSize <= 0;
|
||||
}
|
||||
|
||||
static Span<byte> EnsureArraySize(ref byte[] array, int desiredSize)
|
||||
{
|
||||
if (array == null || array.Length < desiredSize)
|
||||
array = new byte[desiredSize];
|
||||
return array.AsSpan(..desiredSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
410
OpenRA.Mods.Cnc/FileFormats/Blowfish.cs
Normal file
410
OpenRA.Mods.Cnc/FileFormats/Blowfish.cs
Normal file
@@ -0,0 +1,410 @@
|
||||
#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
|
||||
|
||||
namespace OpenRA.Mods.Cnc.FileFormats
|
||||
{
|
||||
sealed class Blowfish
|
||||
{
|
||||
public Blowfish(byte[] key)
|
||||
{
|
||||
for (int i = 0, j = 0; i < 18; ++i)
|
||||
{
|
||||
uint a = key[j++ % key.Length];
|
||||
uint b = key[j++ % key.Length];
|
||||
uint c = key[j++ % key.Length];
|
||||
uint d = key[j++ % key.Length];
|
||||
|
||||
lookupMfromP[i] ^= a << 24 | b << 16 | c << 8 | d;
|
||||
}
|
||||
|
||||
uint l = 0, r = 0;
|
||||
|
||||
for (var i = 0; i < 18;)
|
||||
{
|
||||
Encrypt(ref l, ref r);
|
||||
lookupMfromP[i++] = l;
|
||||
lookupMfromP[i++] = r;
|
||||
}
|
||||
|
||||
for (var i = 0; i < 4; ++i)
|
||||
for (var j = 0; j < 256;)
|
||||
{
|
||||
Encrypt(ref l, ref r);
|
||||
lookupMfromS[i, j++] = l;
|
||||
lookupMfromS[i, j++] = r;
|
||||
}
|
||||
}
|
||||
|
||||
public uint[] Encrypt(uint[] data) { return RunCipher(data, Encrypt); }
|
||||
public uint[] Decrypt(uint[] data) { return RunCipher(data, Decrypt); }
|
||||
|
||||
delegate void CipherFunc(ref uint a, ref uint b);
|
||||
|
||||
static uint[] RunCipher(uint[] data, CipherFunc f)
|
||||
{
|
||||
var result = new uint[data.Length];
|
||||
|
||||
var size = data.Length / 2;
|
||||
var i = 0;
|
||||
while (size-- > 0)
|
||||
{
|
||||
var a = SwapBytes(data[i]);
|
||||
var b = SwapBytes(data[i + 1]);
|
||||
|
||||
f(ref a, ref b);
|
||||
|
||||
result[i++] = SwapBytes(a);
|
||||
result[i++] = SwapBytes(b);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void Encrypt(ref uint a, ref uint b)
|
||||
{
|
||||
uint tempA = a, tempB = b;
|
||||
tempA ^= lookupMfromP[0];
|
||||
|
||||
var x = false;
|
||||
for (var i = 1; i <= 16; i++, x ^= true)
|
||||
{
|
||||
if (x)
|
||||
Round(ref tempA, tempB, i);
|
||||
else
|
||||
Round(ref tempB, tempA, i);
|
||||
}
|
||||
|
||||
tempB ^= lookupMfromP[17];
|
||||
|
||||
a = tempB;
|
||||
b = tempA;
|
||||
}
|
||||
|
||||
void Decrypt(ref uint a, ref uint b)
|
||||
{
|
||||
uint tempA = a, tempB = b;
|
||||
tempA ^= lookupMfromP[17];
|
||||
|
||||
var x = false;
|
||||
for (var i = 16; i >= 1; i--, x ^= true)
|
||||
{
|
||||
if (x)
|
||||
Round(ref tempA, tempB, i);
|
||||
else
|
||||
Round(ref tempB, tempA, i);
|
||||
}
|
||||
|
||||
tempB ^= lookupMfromP[0];
|
||||
|
||||
a = tempB;
|
||||
b = tempA;
|
||||
}
|
||||
|
||||
uint S(uint x, int i)
|
||||
{
|
||||
return lookupMfromS[i, (x >> ((3 - i) << 3)) & 0xff];
|
||||
}
|
||||
|
||||
uint ConvertBFtoF(uint x)
|
||||
{
|
||||
return ((S(x, 0) + S(x, 1)) ^ S(x, 2)) + S(x, 3);
|
||||
}
|
||||
|
||||
void Round(ref uint a, uint b, int n)
|
||||
{
|
||||
a ^= ConvertBFtoF(b) ^ lookupMfromP[n];
|
||||
}
|
||||
|
||||
static uint SwapBytes(uint i)
|
||||
{
|
||||
i = (i << 16) | (i >> 16);
|
||||
i = ((i << 8) & 0xff00ff00) | ((i >> 8) & 0x00ff00ff);
|
||||
return i;
|
||||
}
|
||||
|
||||
readonly uint[] lookupMfromP =
|
||||
[
|
||||
0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344,
|
||||
0xa4093822, 0x299f31d0, 0x082efa98, 0xec4e6c89,
|
||||
0x452821e6, 0x38d01377, 0xbe5466cf, 0x34e90c6c,
|
||||
0xc0ac29b7, 0xc97c50dd, 0x3f84d5b5, 0xb5470917,
|
||||
0x9216d5d9, 0x8979fb1b
|
||||
];
|
||||
|
||||
readonly uint[,] lookupMfromS =
|
||||
{
|
||||
{
|
||||
0xd1310ba6, 0x98dfb5ac, 0x2ffd72db, 0xd01adfb7,
|
||||
0xb8e1afed, 0x6a267e96, 0xba7c9045, 0xf12c7f99,
|
||||
0x24a19947, 0xb3916cf7, 0x0801f2e2, 0x858efc16,
|
||||
0x636920d8, 0x71574e69, 0xa458fea3, 0xf4933d7e,
|
||||
0x0d95748f, 0x728eb658, 0x718bcd58, 0x82154aee,
|
||||
0x7b54a41d, 0xc25a59b5, 0x9c30d539, 0x2af26013,
|
||||
0xc5d1b023, 0x286085f0, 0xca417918, 0xb8db38ef,
|
||||
0x8e79dcb0, 0x603a180e, 0x6c9e0e8b, 0xb01e8a3e,
|
||||
0xd71577c1, 0xbd314b27, 0x78af2fda, 0x55605c60,
|
||||
0xe65525f3, 0xaa55ab94, 0x57489862, 0x63e81440,
|
||||
0x55ca396a, 0x2aab10b6, 0xb4cc5c34, 0x1141e8ce,
|
||||
0xa15486af, 0x7c72e993, 0xb3ee1411, 0x636fbc2a,
|
||||
0x2ba9c55d, 0x741831f6, 0xce5c3e16, 0x9b87931e,
|
||||
0xafd6ba33, 0x6c24cf5c, 0x7a325381, 0x28958677,
|
||||
0x3b8f4898, 0x6b4bb9af, 0xc4bfe81b, 0x66282193,
|
||||
0x61d809cc, 0xfb21a991, 0x487cac60, 0x5dec8032,
|
||||
0xef845d5d, 0xe98575b1, 0xdc262302, 0xeb651b88,
|
||||
0x23893e81, 0xd396acc5, 0x0f6d6ff3, 0x83f44239,
|
||||
0x2e0b4482, 0xa4842004, 0x69c8f04a, 0x9e1f9b5e,
|
||||
0x21c66842, 0xf6e96c9a, 0x670c9c61, 0xabd388f0,
|
||||
0x6a51a0d2, 0xd8542f68, 0x960fa728, 0xab5133a3,
|
||||
0x6eef0b6c, 0x137a3be4, 0xba3bf050, 0x7efb2a98,
|
||||
0xa1f1651d, 0x39af0176, 0x66ca593e, 0x82430e88,
|
||||
0x8cee8619, 0x456f9fb4, 0x7d84a5c3, 0x3b8b5ebe,
|
||||
0xe06f75d8, 0x85c12073, 0x401a449f, 0x56c16aa6,
|
||||
0x4ed3aa62, 0x363f7706, 0x1bfedf72, 0x429b023d,
|
||||
0x37d0d724, 0xd00a1248, 0xdb0fead3, 0x49f1c09b,
|
||||
0x075372c9, 0x80991b7b, 0x25d479d8, 0xf6e8def7,
|
||||
0xe3fe501a, 0xb6794c3b, 0x976ce0bd, 0x04c006ba,
|
||||
0xc1a94fb6, 0x409f60c4, 0x5e5c9ec2, 0x196a2463,
|
||||
0x68fb6faf, 0x3e6c53b5, 0x1339b2eb, 0x3b52ec6f,
|
||||
0x6dfc511f, 0x9b30952c, 0xcc814544, 0xaf5ebd09,
|
||||
0xbee3d004, 0xde334afd, 0x660f2807, 0x192e4bb3,
|
||||
0xc0cba857, 0x45c8740f, 0xd20b5f39, 0xb9d3fbdb,
|
||||
0x5579c0bd, 0x1a60320a, 0xd6a100c6, 0x402c7279,
|
||||
0x679f25fe, 0xfb1fa3cc, 0x8ea5e9f8, 0xdb3222f8,
|
||||
0x3c7516df, 0xfd616b15, 0x2f501ec8, 0xad0552ab,
|
||||
0x323db5fa, 0xfd238760, 0x53317b48, 0x3e00df82,
|
||||
0x9e5c57bb, 0xca6f8ca0, 0x1a87562e, 0xdf1769db,
|
||||
0xd542a8f6, 0x287effc3, 0xac6732c6, 0x8c4f5573,
|
||||
0x695b27b0, 0xbbca58c8, 0xe1ffa35d, 0xb8f011a0,
|
||||
0x10fa3d98, 0xfd2183b8, 0x4afcb56c, 0x2dd1d35b,
|
||||
0x9a53e479, 0xb6f84565, 0xd28e49bc, 0x4bfb9790,
|
||||
0xe1ddf2da, 0xa4cb7e33, 0x62fb1341, 0xcee4c6e8,
|
||||
0xef20cada, 0x36774c01, 0xd07e9efe, 0x2bf11fb4,
|
||||
0x95dbda4d, 0xae909198, 0xeaad8e71, 0x6b93d5a0,
|
||||
0xd08ed1d0, 0xafc725e0, 0x8e3c5b2f, 0x8e7594b7,
|
||||
0x8ff6e2fb, 0xf2122b64, 0x8888b812, 0x900df01c,
|
||||
0x4fad5ea0, 0x688fc31c, 0xd1cff191, 0xb3a8c1ad,
|
||||
0x2f2f2218, 0xbe0e1777, 0xea752dfe, 0x8b021fa1,
|
||||
0xe5a0cc0f, 0xb56f74e8, 0x18acf3d6, 0xce89e299,
|
||||
0xb4a84fe0, 0xfd13e0b7, 0x7cc43b81, 0xd2ada8d9,
|
||||
0x165fa266, 0x80957705, 0x93cc7314, 0x211a1477,
|
||||
0xe6ad2065, 0x77b5fa86, 0xc75442f5, 0xfb9d35cf,
|
||||
0xebcdaf0c, 0x7b3e89a0, 0xd6411bd3, 0xae1e7e49,
|
||||
0x00250e2d, 0x2071b35e, 0x226800bb, 0x57b8e0af,
|
||||
0x2464369b, 0xf009b91e, 0x5563911d, 0x59dfa6aa,
|
||||
0x78c14389, 0xd95a537f, 0x207d5ba2, 0x02e5b9c5,
|
||||
0x83260376, 0x6295cfa9, 0x11c81968, 0x4e734a41,
|
||||
0xb3472dca, 0x7b14a94a, 0x1b510052, 0x9a532915,
|
||||
0xd60f573f, 0xbc9bc6e4, 0x2b60a476, 0x81e67400,
|
||||
0x08ba6fb5, 0x571be91f, 0xf296ec6b, 0x2a0dd915,
|
||||
0xb6636521, 0xe7b9f9b6, 0xff34052e, 0xc5855664,
|
||||
0x53b02d5d, 0xa99f8fa1, 0x08ba4799, 0x6e85076a
|
||||
},
|
||||
{
|
||||
0x4b7a70e9, 0xb5b32944, 0xdb75092e, 0xc4192623,
|
||||
0xad6ea6b0, 0x49a7df7d, 0x9cee60b8, 0x8fedb266,
|
||||
0xecaa8c71, 0x699a17ff, 0x5664526c, 0xc2b19ee1,
|
||||
0x193602a5, 0x75094c29, 0xa0591340, 0xe4183a3e,
|
||||
0x3f54989a, 0x5b429d65, 0x6b8fe4d6, 0x99f73fd6,
|
||||
0xa1d29c07, 0xefe830f5, 0x4d2d38e6, 0xf0255dc1,
|
||||
0x4cdd2086, 0x8470eb26, 0x6382e9c6, 0x021ecc5e,
|
||||
0x09686b3f, 0x3ebaefc9, 0x3c971814, 0x6b6a70a1,
|
||||
0x687f3584, 0x52a0e286, 0xb79c5305, 0xaa500737,
|
||||
0x3e07841c, 0x7fdeae5c, 0x8e7d44ec, 0x5716f2b8,
|
||||
0xb03ada37, 0xf0500c0d, 0xf01c1f04, 0x0200b3ff,
|
||||
0xae0cf51a, 0x3cb574b2, 0x25837a58, 0xdc0921bd,
|
||||
0xd19113f9, 0x7ca92ff6, 0x94324773, 0x22f54701,
|
||||
0x3ae5e581, 0x37c2dadc, 0xc8b57634, 0x9af3dda7,
|
||||
0xa9446146, 0x0fd0030e, 0xecc8c73e, 0xa4751e41,
|
||||
0xe238cd99, 0x3bea0e2f, 0x3280bba1, 0x183eb331,
|
||||
0x4e548b38, 0x4f6db908, 0x6f420d03, 0xf60a04bf,
|
||||
0x2cb81290, 0x24977c79, 0x5679b072, 0xbcaf89af,
|
||||
0xde9a771f, 0xd9930810, 0xb38bae12, 0xdccf3f2e,
|
||||
0x5512721f, 0x2e6b7124, 0x501adde6, 0x9f84cd87,
|
||||
0x7a584718, 0x7408da17, 0xbc9f9abc, 0xe94b7d8c,
|
||||
0xec7aec3a, 0xdb851dfa, 0x63094366, 0xc464c3d2,
|
||||
0xef1c1847, 0x3215d908, 0xdd433b37, 0x24c2ba16,
|
||||
0x12a14d43, 0x2a65c451, 0x50940002, 0x133ae4dd,
|
||||
0x71dff89e, 0x10314e55, 0x81ac77d6, 0x5f11199b,
|
||||
0x043556f1, 0xd7a3c76b, 0x3c11183b, 0x5924a509,
|
||||
0xf28fe6ed, 0x97f1fbfa, 0x9ebabf2c, 0x1e153c6e,
|
||||
0x86e34570, 0xeae96fb1, 0x860e5e0a, 0x5a3e2ab3,
|
||||
0x771fe71c, 0x4e3d06fa, 0x2965dcb9, 0x99e71d0f,
|
||||
0x803e89d6, 0x5266c825, 0x2e4cc978, 0x9c10b36a,
|
||||
0xc6150eba, 0x94e2ea78, 0xa5fc3c53, 0x1e0a2df4,
|
||||
0xf2f74ea7, 0x361d2b3d, 0x1939260f, 0x19c27960,
|
||||
0x5223a708, 0xf71312b6, 0xebadfe6e, 0xeac31f66,
|
||||
0xe3bc4595, 0xa67bc883, 0xb17f37d1, 0x018cff28,
|
||||
0xc332ddef, 0xbe6c5aa5, 0x65582185, 0x68ab9802,
|
||||
0xeecea50f, 0xdb2f953b, 0x2aef7dad, 0x5b6e2f84,
|
||||
0x1521b628, 0x29076170, 0xecdd4775, 0x619f1510,
|
||||
0x13cca830, 0xeb61bd96, 0x0334fe1e, 0xaa0363cf,
|
||||
0xb5735c90, 0x4c70a239, 0xd59e9e0b, 0xcbaade14,
|
||||
0xeecc86bc, 0x60622ca7, 0x9cab5cab, 0xb2f3846e,
|
||||
0x648b1eaf, 0x19bdf0ca, 0xa02369b9, 0x655abb50,
|
||||
0x40685a32, 0x3c2ab4b3, 0x319ee9d5, 0xc021b8f7,
|
||||
0x9b540b19, 0x875fa099, 0x95f7997e, 0x623d7da8,
|
||||
0xf837889a, 0x97e32d77, 0x11ed935f, 0x16681281,
|
||||
0x0e358829, 0xc7e61fd6, 0x96dedfa1, 0x7858ba99,
|
||||
0x57f584a5, 0x1b227263, 0x9b83c3ff, 0x1ac24696,
|
||||
0xcdb30aeb, 0x532e3054, 0x8fd948e4, 0x6dbc3128,
|
||||
0x58ebf2ef, 0x34c6ffea, 0xfe28ed61, 0xee7c3c73,
|
||||
0x5d4a14d9, 0xe864b7e3, 0x42105d14, 0x203e13e0,
|
||||
0x45eee2b6, 0xa3aaabea, 0xdb6c4f15, 0xfacb4fd0,
|
||||
0xc742f442, 0xef6abbb5, 0x654f3b1d, 0x41cd2105,
|
||||
0xd81e799e, 0x86854dc7, 0xe44b476a, 0x3d816250,
|
||||
0xcf62a1f2, 0x5b8d2646, 0xfc8883a0, 0xc1c7b6a3,
|
||||
0x7f1524c3, 0x69cb7492, 0x47848a0b, 0x5692b285,
|
||||
0x095bbf00, 0xad19489d, 0x1462b174, 0x23820e00,
|
||||
0x58428d2a, 0x0c55f5ea, 0x1dadf43e, 0x233f7061,
|
||||
0x3372f092, 0x8d937e41, 0xd65fecf1, 0x6c223bdb,
|
||||
0x7cde3759, 0xcbee7460, 0x4085f2a7, 0xce77326e,
|
||||
0xa6078084, 0x19f8509e, 0xe8efd855, 0x61d99735,
|
||||
0xa969a7aa, 0xc50c06c2, 0x5a04abfc, 0x800bcadc,
|
||||
0x9e447a2e, 0xc3453484, 0xfdd56705, 0x0e1e9ec9,
|
||||
0xdb73dbd3, 0x105588cd, 0x675fda79, 0xe3674340,
|
||||
0xc5c43465, 0x713e38d8, 0x3d28f89e, 0xf16dff20,
|
||||
0x153e21e7, 0x8fb03d4a, 0xe6e39f2b, 0xdb83adf7
|
||||
},
|
||||
{
|
||||
0xe93d5a68, 0x948140f7, 0xf64c261c, 0x94692934,
|
||||
0x411520f7, 0x7602d4f7, 0xbcf46b2e, 0xd4a20068,
|
||||
0xd4082471, 0x3320f46a, 0x43b7d4b7, 0x500061af,
|
||||
0x1e39f62e, 0x97244546, 0x14214f74, 0xbf8b8840,
|
||||
0x4d95fc1d, 0x96b591af, 0x70f4ddd3, 0x66a02f45,
|
||||
0xbfbc09ec, 0x03bd9785, 0x7fac6dd0, 0x31cb8504,
|
||||
0x96eb27b3, 0x55fd3941, 0xda2547e6, 0xabca0a9a,
|
||||
0x28507825, 0x530429f4, 0x0a2c86da, 0xe9b66dfb,
|
||||
0x68dc1462, 0xd7486900, 0x680ec0a4, 0x27a18dee,
|
||||
0x4f3ffea2, 0xe887ad8c, 0xb58ce006, 0x7af4d6b6,
|
||||
0xaace1e7c, 0xd3375fec, 0xce78a399, 0x406b2a42,
|
||||
0x20fe9e35, 0xd9f385b9, 0xee39d7ab, 0x3b124e8b,
|
||||
0x1dc9faf7, 0x4b6d1856, 0x26a36631, 0xeae397b2,
|
||||
0x3a6efa74, 0xdd5b4332, 0x6841e7f7, 0xca7820fb,
|
||||
0xfb0af54e, 0xd8feb397, 0x454056ac, 0xba489527,
|
||||
0x55533a3a, 0x20838d87, 0xfe6ba9b7, 0xd096954b,
|
||||
0x55a867bc, 0xa1159a58, 0xcca92963, 0x99e1db33,
|
||||
0xa62a4a56, 0x3f3125f9, 0x5ef47e1c, 0x9029317c,
|
||||
0xfdf8e802, 0x04272f70, 0x80bb155c, 0x05282ce3,
|
||||
0x95c11548, 0xe4c66d22, 0x48c1133f, 0xc70f86dc,
|
||||
0x07f9c9ee, 0x41041f0f, 0x404779a4, 0x5d886e17,
|
||||
0x325f51eb, 0xd59bc0d1, 0xf2bcc18f, 0x41113564,
|
||||
0x257b7834, 0x602a9c60, 0xdff8e8a3, 0x1f636c1b,
|
||||
0x0e12b4c2, 0x02e1329e, 0xaf664fd1, 0xcad18115,
|
||||
0x6b2395e0, 0x333e92e1, 0x3b240b62, 0xeebeb922,
|
||||
0x85b2a20e, 0xe6ba0d99, 0xde720c8c, 0x2da2f728,
|
||||
0xd0127845, 0x95b794fd, 0x647d0862, 0xe7ccf5f0,
|
||||
0x5449a36f, 0x877d48fa, 0xc39dfd27, 0xf33e8d1e,
|
||||
0x0a476341, 0x992eff74, 0x3a6f6eab, 0xf4f8fd37,
|
||||
0xa812dc60, 0xa1ebddf8, 0x991be14c, 0xdb6e6b0d,
|
||||
0xc67b5510, 0x6d672c37, 0x2765d43b, 0xdcd0e804,
|
||||
0xf1290dc7, 0xcc00ffa3, 0xb5390f92, 0x690fed0b,
|
||||
0x667b9ffb, 0xcedb7d9c, 0xa091cf0b, 0xd9155ea3,
|
||||
0xbb132f88, 0x515bad24, 0x7b9479bf, 0x763bd6eb,
|
||||
0x37392eb3, 0xcc115979, 0x8026e297, 0xf42e312d,
|
||||
0x6842ada7, 0xc66a2b3b, 0x12754ccc, 0x782ef11c,
|
||||
0x6a124237, 0xb79251e7, 0x06a1bbe6, 0x4bfb6350,
|
||||
0x1a6b1018, 0x11caedfa, 0x3d25bdd8, 0xe2e1c3c9,
|
||||
0x44421659, 0x0a121386, 0xd90cec6e, 0xd5abea2a,
|
||||
0x64af674e, 0xda86a85f, 0xbebfe988, 0x64e4c3fe,
|
||||
0x9dbc8057, 0xf0f7c086, 0x60787bf8, 0x6003604d,
|
||||
0xd1fd8346, 0xf6381fb0, 0x7745ae04, 0xd736fccc,
|
||||
0x83426b33, 0xf01eab71, 0xb0804187, 0x3c005e5f,
|
||||
0x77a057be, 0xbde8ae24, 0x55464299, 0xbf582e61,
|
||||
0x4e58f48f, 0xf2ddfda2, 0xf474ef38, 0x8789bdc2,
|
||||
0x5366f9c3, 0xc8b38e74, 0xb475f255, 0x46fcd9b9,
|
||||
0x7aeb2661, 0x8b1ddf84, 0x846a0e79, 0x915f95e2,
|
||||
0x466e598e, 0x20b45770, 0x8cd55591, 0xc902de4c,
|
||||
0xb90bace1, 0xbb8205d0, 0x11a86248, 0x7574a99e,
|
||||
0xb77f19b6, 0xe0a9dc09, 0x662d09a1, 0xc4324633,
|
||||
0xe85a1f02, 0x09f0be8c, 0x4a99a025, 0x1d6efe10,
|
||||
0x1ab93d1d, 0x0ba5a4df, 0xa186f20f, 0x2868f169,
|
||||
0xdcb7da83, 0x573906fe, 0xa1e2ce9b, 0x4fcd7f52,
|
||||
0x50115e01, 0xa70683fa, 0xa002b5c4, 0x0de6d027,
|
||||
0x9af88c27, 0x773f8641, 0xc3604c06, 0x61a806b5,
|
||||
0xf0177a28, 0xc0f586e0, 0x006058aa, 0x30dc7d62,
|
||||
0x11e69ed7, 0x2338ea63, 0x53c2dd94, 0xc2c21634,
|
||||
0xbbcbee56, 0x90bcb6de, 0xebfc7da1, 0xce591d76,
|
||||
0x6f05e409, 0x4b7c0188, 0x39720a3d, 0x7c927c24,
|
||||
0x86e3725f, 0x724d9db9, 0x1ac15bb4, 0xd39eb8fc,
|
||||
0xed545578, 0x08fca5b5, 0xd83d7cd3, 0x4dad0fc4,
|
||||
0x1e50ef5e, 0xb161e6f8, 0xa28514d9, 0x6c51133c,
|
||||
0x6fd5c7e7, 0x56e14ec4, 0x362abfce, 0xddc6c837,
|
||||
0xd79a3234, 0x92638212, 0x670efa8e, 0x406000e0
|
||||
},
|
||||
{
|
||||
0x3a39ce37, 0xd3faf5cf, 0xabc27737, 0x5ac52d1b,
|
||||
0x5cb0679e, 0x4fa33742, 0xd3822740, 0x99bc9bbe,
|
||||
0xd5118e9d, 0xbf0f7315, 0xd62d1c7e, 0xc700c47b,
|
||||
0xb78c1b6b, 0x21a19045, 0xb26eb1be, 0x6a366eb4,
|
||||
0x5748ab2f, 0xbc946e79, 0xc6a376d2, 0x6549c2c8,
|
||||
0x530ff8ee, 0x468dde7d, 0xd5730a1d, 0x4cd04dc6,
|
||||
0x2939bbdb, 0xa9ba4650, 0xac9526e8, 0xbe5ee304,
|
||||
0xa1fad5f0, 0x6a2d519a, 0x63ef8ce2, 0x9a86ee22,
|
||||
0xc089c2b8, 0x43242ef6, 0xa51e03aa, 0x9cf2d0a4,
|
||||
0x83c061ba, 0x9be96a4d, 0x8fe51550, 0xba645bd6,
|
||||
0x2826a2f9, 0xa73a3ae1, 0x4ba99586, 0xef5562e9,
|
||||
0xc72fefd3, 0xf752f7da, 0x3f046f69, 0x77fa0a59,
|
||||
0x80e4a915, 0x87b08601, 0x9b09e6ad, 0x3b3ee593,
|
||||
0xe990fd5a, 0x9e34d797, 0x2cf0b7d9, 0x022b8b51,
|
||||
0x96d5ac3a, 0x017da67d, 0xd1cf3ed6, 0x7c7d2d28,
|
||||
0x1f9f25cf, 0xadf2b89b, 0x5ad6b472, 0x5a88f54c,
|
||||
0xe029ac71, 0xe019a5e6, 0x47b0acfd, 0xed93fa9b,
|
||||
0xe8d3c48d, 0x283b57cc, 0xf8d56629, 0x79132e28,
|
||||
0x785f0191, 0xed756055, 0xf7960e44, 0xe3d35e8c,
|
||||
0x15056dd4, 0x88f46dba, 0x03a16125, 0x0564f0bd,
|
||||
0xc3eb9e15, 0x3c9057a2, 0x97271aec, 0xa93a072a,
|
||||
0x1b3f6d9b, 0x1e6321f5, 0xf59c66fb, 0x26dcf319,
|
||||
0x7533d928, 0xb155fdf5, 0x03563482, 0x8aba3cbb,
|
||||
0x28517711, 0xc20ad9f8, 0xabcc5167, 0xccad925f,
|
||||
0x4de81751, 0x3830dc8e, 0x379d5862, 0x9320f991,
|
||||
0xea7a90c2, 0xfb3e7bce, 0x5121ce64, 0x774fbe32,
|
||||
0xa8b6e37e, 0xc3293d46, 0x48de5369, 0x6413e680,
|
||||
0xa2ae0810, 0xdd6db224, 0x69852dfd, 0x09072166,
|
||||
0xb39a460a, 0x6445c0dd, 0x586cdecf, 0x1c20c8ae,
|
||||
0x5bbef7dd, 0x1b588d40, 0xccd2017f, 0x6bb4e3bb,
|
||||
0xdda26a7e, 0x3a59ff45, 0x3e350a44, 0xbcb4cdd5,
|
||||
0x72eacea8, 0xfa6484bb, 0x8d6612ae, 0xbf3c6f47,
|
||||
0xd29be463, 0x542f5d9e, 0xaec2771b, 0xf64e6370,
|
||||
0x740e0d8d, 0xe75b1357, 0xf8721671, 0xaf537d5d,
|
||||
0x4040cb08, 0x4eb4e2cc, 0x34d2466a, 0x0115af84,
|
||||
0xe1b00428, 0x95983a1d, 0x06b89fb4, 0xce6ea048,
|
||||
0x6f3f3b82, 0x3520ab82, 0x011a1d4b, 0x277227f8,
|
||||
0x611560b1, 0xe7933fdc, 0xbb3a792b, 0x344525bd,
|
||||
0xa08839e1, 0x51ce794b, 0x2f32c9b7, 0xa01fbac9,
|
||||
0xe01cc87e, 0xbcc7d1f6, 0xcf0111c3, 0xa1e8aac7,
|
||||
0x1a908749, 0xd44fbd9a, 0xd0dadecb, 0xd50ada38,
|
||||
0x0339c32a, 0xc6913667, 0x8df9317c, 0xe0b12b4f,
|
||||
0xf79e59b7, 0x43f5bb3a, 0xf2d519ff, 0x27d9459c,
|
||||
0xbf97222c, 0x15e6fc2a, 0x0f91fc71, 0x9b941525,
|
||||
0xfae59361, 0xceb69ceb, 0xc2a86459, 0x12baa8d1,
|
||||
0xb6c1075e, 0xe3056a0c, 0x10d25065, 0xcb03a442,
|
||||
0xe0ec6e0e, 0x1698db3b, 0x4c98a0be, 0x3278e964,
|
||||
0x9f1f9532, 0xe0d392df, 0xd3a0342b, 0x8971f21e,
|
||||
0x1b0a7441, 0x4ba3348c, 0xc5be7120, 0xc37632d8,
|
||||
0xdf359f8d, 0x9b992f2e, 0xe60b6f47, 0x0fe3f11d,
|
||||
0xe54cda54, 0x1edad891, 0xce6279cf, 0xcd3e7e6f,
|
||||
0x1618b166, 0xfd2c1d05, 0x848fd2c5, 0xf6fb2299,
|
||||
0xf523f357, 0xa6327623, 0x93a83531, 0x56cccd02,
|
||||
0xacf08162, 0x5a75ebb5, 0x6e163697, 0x88d273cc,
|
||||
0xde966292, 0x81b949d0, 0x4c50901b, 0x71c65614,
|
||||
0xe6c6c7bd, 0x327a140a, 0x45e1d006, 0xc3f27b9a,
|
||||
0xc9aa53fd, 0x62a80f00, 0xbb25bfe2, 0x35bdd2f6,
|
||||
0x71126905, 0xb2040222, 0xb6cbcf7c, 0xcd769c2b,
|
||||
0x53113ec0, 0x1640e3d3, 0x38abbd60, 0x2547adf0,
|
||||
0xba38209c, 0xf746ce76, 0x77afa1c5, 0x20756060,
|
||||
0x85cbfe4e, 0x8ae88dd8, 0x7aaaf9b0, 0x4cf9aa7e,
|
||||
0x1948c25c, 0x02fb8a8c, 0x01c36ae4, 0xd6ebe1f9,
|
||||
0x90d4f869, 0xa65cdea0, 0x3f09252d, 0xc208e69f,
|
||||
0xb74e6132, 0xce77e25b, 0x578fdfe3, 0x3ac372e6
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
491
OpenRA.Mods.Cnc/FileFormats/BlowfishKeyProvider.cs
Normal file
491
OpenRA.Mods.Cnc/FileFormats/BlowfishKeyProvider.cs
Normal file
@@ -0,0 +1,491 @@
|
||||
#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.Linq;
|
||||
|
||||
namespace OpenRA.Mods.Cnc.FileFormats
|
||||
{
|
||||
/* TODO: Convert this direct C port into readable code. */
|
||||
|
||||
sealed class BlowfishKeyProvider
|
||||
{
|
||||
const string PublicKeyString = "AihRvNoIbTn85FZRYNZRcT+i6KpU+maCsEqr3Q5q+LDB5tH7Tz2qQ38V";
|
||||
|
||||
sealed class PublicKey
|
||||
{
|
||||
public readonly uint[] KeyOne = new uint[64];
|
||||
public readonly uint[] KeyTwo = new uint[64];
|
||||
public uint Len;
|
||||
}
|
||||
|
||||
readonly PublicKey pubkey = new();
|
||||
|
||||
readonly uint[] globOne = new uint[64];
|
||||
uint globOneBitLen, globOneLenXTwo;
|
||||
readonly uint[] globTwo = new uint[130];
|
||||
readonly uint[] globOneHigh = new uint[4];
|
||||
readonly uint[] globOneHighInv = new uint[4];
|
||||
uint globOneHighBitLen;
|
||||
uint globOneHighInvLow, globOneHighInvHigh;
|
||||
|
||||
static void InitBigNum(uint[] n, uint val, uint len)
|
||||
{
|
||||
for (var i = 0; i < len; i++) n[i] = 0;
|
||||
n[0] = val;
|
||||
}
|
||||
|
||||
static void MoveKeyToBig(uint[] n, byte[] key, uint klen, uint blen)
|
||||
{
|
||||
byte sign;
|
||||
|
||||
if ((key[0] & 0x80) != 0) sign = 0xff;
|
||||
else sign = 0;
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (uint* tempPn = &n[0])
|
||||
{
|
||||
var pn = (byte*)tempPn;
|
||||
var i = blen * 4;
|
||||
for (; i > klen; i--) pn[i - 1] = sign;
|
||||
for (; i > 0; i--) pn[i - 1] = key[klen - i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void KeyToBigNum(uint[] n, byte[] key, uint len)
|
||||
{
|
||||
uint keylen;
|
||||
int i;
|
||||
|
||||
var j = 0;
|
||||
|
||||
if (key[j] != 2) return;
|
||||
j++;
|
||||
|
||||
if ((key[j] & 0x80) != 0)
|
||||
{
|
||||
keylen = 0;
|
||||
for (i = 0; i < (key[j] & 0x7f); i++) keylen = (keylen << 8) | key[j + i + 1];
|
||||
j += (key[j] & 0x7f) + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
keylen = key[j];
|
||||
j++;
|
||||
}
|
||||
|
||||
if (keylen <= len * 4)
|
||||
MoveKeyToBig(n, key.Skip(j).ToArray(), keylen, len);
|
||||
}
|
||||
|
||||
static uint LenBigNum(uint[] n, uint len)
|
||||
{
|
||||
if (len == 0)
|
||||
return 0;
|
||||
|
||||
var i = len;
|
||||
while (n[--i] == 0)
|
||||
if (i == 0)
|
||||
return 0; // all zero
|
||||
|
||||
return i + 1;
|
||||
}
|
||||
|
||||
static uint BitLenBigNum(uint[] n, uint len)
|
||||
{
|
||||
uint ddlen, bitlen, mask;
|
||||
ddlen = LenBigNum(n, len);
|
||||
if (ddlen == 0) return 0;
|
||||
bitlen = ddlen * 32;
|
||||
mask = 0x80000000;
|
||||
while ((mask & n[ddlen - 1]) == 0)
|
||||
{
|
||||
mask >>= 1;
|
||||
bitlen--;
|
||||
}
|
||||
|
||||
return bitlen;
|
||||
}
|
||||
|
||||
void InitPublicKey()
|
||||
{
|
||||
InitBigNum(pubkey.KeyTwo, 0x10001, 64);
|
||||
|
||||
KeyToBigNum(pubkey.KeyOne, Convert.FromBase64String(PublicKeyString), 64);
|
||||
pubkey.Len = BitLenBigNum(pubkey.KeyOne, 64) - 1;
|
||||
}
|
||||
|
||||
static int CompareBigNum(uint[] n1, uint[] n2, uint len)
|
||||
{
|
||||
while (len > 0)
|
||||
{
|
||||
--len;
|
||||
if (n1[len] < n2[len]) return -1;
|
||||
if (n1[len] > n2[len]) return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void MoveBigNum(uint[] dest, uint[] src, uint len)
|
||||
{
|
||||
Array.Copy(src, dest, len);
|
||||
}
|
||||
|
||||
static void ShrBigNum(uint[] n, int bits, int len)
|
||||
{
|
||||
int i; var i2 = bits / 32;
|
||||
|
||||
if (i2 > 0)
|
||||
{
|
||||
for (i = 0; i < len - i2; i++) n[i] = n[i + i2];
|
||||
for (; i < len; i++) n[i] = 0;
|
||||
bits %= 32;
|
||||
}
|
||||
|
||||
if (bits == 0) return;
|
||||
for (i = 0; i < len - 1; i++) n[i] = (n[i] >> bits) | (n[i + 1] << (32 - bits));
|
||||
n[i] >>= bits;
|
||||
}
|
||||
|
||||
static void ShlBigNum(uint[] n, int bits, int len)
|
||||
{
|
||||
int i, i2;
|
||||
|
||||
i2 = bits / 32;
|
||||
if (i2 > 0)
|
||||
{
|
||||
for (i = len - 1; i > i2; i--) n[i] = n[i - i2];
|
||||
for (; i > 0; i--) n[i] = 0;
|
||||
bits %= 32;
|
||||
}
|
||||
|
||||
if (bits == 0) return;
|
||||
for (i = len - 1; i > 0; i--) n[i] = (n[i] << bits) | (n[i - 1] >> (32 - bits));
|
||||
n[0] <<= bits;
|
||||
}
|
||||
|
||||
static uint SubBigNum(uint[] dest, uint[] src1, uint[] src2, uint carry, int len)
|
||||
{
|
||||
uint i1, i2;
|
||||
|
||||
len += len;
|
||||
unsafe
|
||||
{
|
||||
fixed (uint* tempPs1 = &src1[0])
|
||||
fixed (uint* tempPs2 = &src2[0])
|
||||
fixed (uint* tempPd = &dest[0])
|
||||
{
|
||||
var ps1 = (ushort*)tempPs1;
|
||||
var ps2 = (ushort*)tempPs2;
|
||||
var pd = (ushort*)tempPd;
|
||||
|
||||
while (--len != -1)
|
||||
{
|
||||
i1 = *ps1++;
|
||||
i2 = *ps2++;
|
||||
*pd++ = (ushort)(i1 - i2 - carry);
|
||||
if (((i1 - i2 - carry) & 0x10000) != 0) carry = 1; else carry = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return carry;
|
||||
}
|
||||
|
||||
static unsafe uint SubBigNum(uint* dest, uint* src1, uint* src2, uint carry, int len)
|
||||
{
|
||||
uint i1, i2;
|
||||
|
||||
len += len;
|
||||
|
||||
var ps1 = (ushort*)src1;
|
||||
var ps2 = (ushort*)src2;
|
||||
var pd = (ushort*)dest;
|
||||
|
||||
while (--len != -1)
|
||||
{
|
||||
i1 = *ps1++;
|
||||
i2 = *ps2++;
|
||||
*pd++ = (ushort)(i1 - i2 - carry);
|
||||
if (((i1 - i2 - carry) & 0x10000) != 0) carry = 1; else carry = 0;
|
||||
}
|
||||
|
||||
return carry;
|
||||
}
|
||||
|
||||
static void InvertBigNum(uint[] n1, uint[] n2, uint len)
|
||||
{
|
||||
var nTmp = new uint[64];
|
||||
uint nTwoByteLen, bit;
|
||||
int nTwoBitLen;
|
||||
|
||||
InitBigNum(nTmp, 0, len);
|
||||
InitBigNum(n1, 0, len);
|
||||
nTwoBitLen = (int)BitLenBigNum(n2, len);
|
||||
bit = 1U << (nTwoBitLen % 32);
|
||||
var j = (nTwoBitLen + 32) / 32 - 1;
|
||||
nTwoByteLen = (uint)((nTwoBitLen - 1) / 32) * 4;
|
||||
nTmp[nTwoByteLen / 4] |= 1U << ((nTwoBitLen - 1) & 0x1f);
|
||||
|
||||
while (nTwoBitLen > 0)
|
||||
{
|
||||
nTwoBitLen--;
|
||||
ShlBigNum(nTmp, 1, (int)len);
|
||||
if (CompareBigNum(nTmp, n2, len) != -1)
|
||||
{
|
||||
SubBigNum(nTmp, nTmp, n2, 0, (int)len);
|
||||
n1[j] |= bit;
|
||||
}
|
||||
|
||||
bit >>= 1;
|
||||
if (bit == 0)
|
||||
{
|
||||
j--;
|
||||
bit = 0x80000000;
|
||||
}
|
||||
}
|
||||
|
||||
InitBigNum(nTmp, 0, len);
|
||||
}
|
||||
|
||||
static void IncrementBigNum(uint[] n, uint len)
|
||||
{
|
||||
var i = 0;
|
||||
while ((++n[i] == 0) && (--len > 0)) i++;
|
||||
}
|
||||
|
||||
void InitTwoDw(uint[] n, uint len)
|
||||
{
|
||||
MoveBigNum(globOne, n, len);
|
||||
globOneBitLen = BitLenBigNum(globOne, len);
|
||||
globOneLenXTwo = (globOneBitLen + 15) / 16;
|
||||
MoveBigNum(globOneHigh, globOne.Skip((int)LenBigNum(globOne, len) - 2).ToArray(), 2);
|
||||
globOneHighBitLen = BitLenBigNum(globOneHigh, 2) - 32;
|
||||
ShrBigNum(globOneHigh, (int)globOneHighBitLen, 2);
|
||||
InvertBigNum(globOneHighInv, globOneHigh, 2);
|
||||
ShrBigNum(globOneHighInv, 1, 2);
|
||||
globOneHighBitLen = (globOneHighBitLen + 15) % 16 + 1;
|
||||
IncrementBigNum(globOneHighInv, 2);
|
||||
if (BitLenBigNum(globOneHighInv, 2) > 32)
|
||||
{
|
||||
ShrBigNum(globOneHighInv, 1, 2);
|
||||
globOneHighBitLen--;
|
||||
}
|
||||
|
||||
globOneHighInvLow = (ushort)globOneHighInv[0];
|
||||
globOneHighInvHigh = (ushort)(globOneHighInv[0] >> 16);
|
||||
}
|
||||
|
||||
static unsafe void MulBignumWord(ushort* pn1, uint[] n2, uint mul, uint len)
|
||||
{
|
||||
uint i, tmp;
|
||||
fixed (uint* tempPn2 = &n2[0])
|
||||
{
|
||||
var pn2 = (ushort*)tempPn2;
|
||||
|
||||
tmp = 0;
|
||||
for (i = 0; i < len; i++)
|
||||
{
|
||||
tmp = mul * *pn2 + *pn1 + tmp;
|
||||
*pn1 = (ushort)tmp;
|
||||
pn1++;
|
||||
pn2++;
|
||||
tmp >>= 16;
|
||||
}
|
||||
|
||||
*pn1 += (ushort)tmp;
|
||||
}
|
||||
}
|
||||
|
||||
static void MulBigNum(uint[] dest, uint[] src1, uint[] src2, uint len)
|
||||
{
|
||||
uint i;
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (uint* tempSrc2 = &src2[0])
|
||||
fixed (uint* tempPdest = &dest[0])
|
||||
{
|
||||
var psrc2 = (ushort*)tempSrc2;
|
||||
var pdest = (ushort*)tempPdest;
|
||||
|
||||
InitBigNum(dest, 0, len * 2);
|
||||
for (i = 0; i < len * 2; i++)
|
||||
MulBignumWord(pdest++, src1, *psrc2++, len * 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void NotBigNum(uint[] n, uint len)
|
||||
{
|
||||
uint i;
|
||||
for (i = 0; i < len; i++) n[i] = ~n[i];
|
||||
}
|
||||
|
||||
static void NegBigNum(uint[] n, uint len)
|
||||
{
|
||||
NotBigNum(n, len);
|
||||
IncrementBigNum(n, len);
|
||||
}
|
||||
|
||||
unsafe uint GetMulWord(uint* n)
|
||||
{
|
||||
var wn = (ushort*)n;
|
||||
var i = (uint)((((((((((*(wn - 1) ^ 0xffff) & 0xffff) * globOneHighInvLow + 0x10000) >> 1)
|
||||
+ (((*(wn - 2) ^ 0xffff) * globOneHighInvHigh + globOneHighInvHigh) >> 1) + 1)
|
||||
>> 16) + ((((*(wn - 1) ^ 0xffff) & 0xffff) * globOneHighInvHigh) >> 1) +
|
||||
(((*wn ^ 0xffff) * globOneHighInvLow) >> 1) + 1) >> 14) + globOneHighInvHigh
|
||||
* (*wn ^ 0xffff) * 2) >> (int)globOneHighBitLen);
|
||||
if (i > 0xffff) i = 0xffff;
|
||||
return i & 0xffff;
|
||||
}
|
||||
|
||||
static void DecBigNum(uint[] n, uint len)
|
||||
{
|
||||
var i = 0;
|
||||
while ((--n[i] == 0xffffffff) && (--len > 0))
|
||||
i++;
|
||||
}
|
||||
|
||||
void CalcBigNum(uint[] n1, uint[] n2, uint[] n3, uint len)
|
||||
{
|
||||
uint globTwoXtwo, lenDiff;
|
||||
unsafe
|
||||
{
|
||||
fixed (uint* g1 = &globOne[0])
|
||||
fixed (uint* g2 = &globTwo[0])
|
||||
{
|
||||
MulBigNum(globTwo, n2, n3, len);
|
||||
globTwo[len * 2] = 0;
|
||||
globTwoXtwo = LenBigNum(globTwo, len * 2 + 1) * 2;
|
||||
if (globTwoXtwo >= globOneLenXTwo)
|
||||
{
|
||||
IncrementBigNum(globTwo, len * 2 + 1);
|
||||
NegBigNum(globTwo, len * 2 + 1);
|
||||
lenDiff = globTwoXtwo + 1 - globOneLenXTwo;
|
||||
var esi = (ushort*)g2 + (1 + globTwoXtwo - globOneLenXTwo);
|
||||
var edi = (ushort*)g2 + (globTwoXtwo + 1);
|
||||
for (; lenDiff != 0; lenDiff--)
|
||||
{
|
||||
edi--;
|
||||
var tmp = GetMulWord((uint*)edi);
|
||||
esi--;
|
||||
if (tmp > 0)
|
||||
{
|
||||
MulBignumWord(esi, globOne, tmp, 2 * len);
|
||||
if ((*edi & 0x8000) == 0 && SubBigNum((uint*)esi, (uint*)esi, g1, 0, (int)len) != 0)
|
||||
(*edi)--;
|
||||
}
|
||||
}
|
||||
|
||||
NegBigNum(globTwo, len);
|
||||
DecBigNum(globTwo, len);
|
||||
}
|
||||
|
||||
MoveBigNum(n1, globTwo, len);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ClearTempVars(uint len)
|
||||
{
|
||||
InitBigNum(globOne, 0, len);
|
||||
InitBigNum(globTwo, 0, len);
|
||||
InitBigNum(globOneHighInv, 0, 4);
|
||||
InitBigNum(globOneHigh, 0, 4);
|
||||
globOneBitLen = 0;
|
||||
globOneHighBitLen = 0;
|
||||
globOneLenXTwo = 0;
|
||||
globOneHighInvLow = 0;
|
||||
globOneHighInvHigh = 0;
|
||||
}
|
||||
|
||||
void CalcKey(uint[] n1, uint[] n2, uint[] n3, uint[] n4, uint len)
|
||||
{
|
||||
var n_tmp = new uint[64];
|
||||
uint n3_len, n4_len;
|
||||
int n3_bitlen;
|
||||
uint bit_mask;
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (uint* tempPn3 = &n3[0])
|
||||
{
|
||||
var pn3 = tempPn3;
|
||||
|
||||
InitBigNum(n1, 1, len);
|
||||
n4_len = LenBigNum(n4, len);
|
||||
InitTwoDw(n4, n4_len);
|
||||
n3_bitlen = (int)BitLenBigNum(n3, n4_len);
|
||||
n3_len = (uint)((n3_bitlen + 31) / 32);
|
||||
bit_mask = (1U << ((n3_bitlen - 1) % 32)) >> 1;
|
||||
pn3 += n3_len - 1;
|
||||
n3_bitlen--;
|
||||
MoveBigNum(n1, n2, n4_len);
|
||||
while (--n3_bitlen != -1)
|
||||
{
|
||||
if (bit_mask == 0)
|
||||
{
|
||||
bit_mask = 0x80000000;
|
||||
pn3--;
|
||||
}
|
||||
|
||||
CalcBigNum(n_tmp, n1, n1, n4_len);
|
||||
if ((*pn3 & bit_mask) != 0)
|
||||
CalcBigNum(n1, n_tmp, n2, n4_len);
|
||||
else
|
||||
MoveBigNum(n1, n_tmp, n4_len);
|
||||
bit_mask >>= 1;
|
||||
}
|
||||
|
||||
InitBigNum(n_tmp, 0, n4_len);
|
||||
ClearTempVars(len);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
byte[] ProcessPredata(byte[] src)
|
||||
{
|
||||
var dest = new byte[256];
|
||||
var n2 = new uint[64];
|
||||
var n3 = new uint[64];
|
||||
|
||||
var a = (int)((pubkey.Len - 1) / 8);
|
||||
var pre_len = (55 / a + 1) * (a + 1);
|
||||
var srcOffset = 0;
|
||||
var destOffset = 0;
|
||||
|
||||
while (a + 1 <= pre_len)
|
||||
{
|
||||
InitBigNum(n2, 0, 64);
|
||||
|
||||
Buffer.BlockCopy(src, srcOffset, n2, 0, a + 1);
|
||||
CalcKey(n3, n2, pubkey.KeyTwo, pubkey.KeyOne, 64);
|
||||
Buffer.BlockCopy(n3, 0, dest, destOffset, a);
|
||||
|
||||
pre_len -= a + 1;
|
||||
srcOffset += a + 1;
|
||||
destOffset += a;
|
||||
}
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
public byte[] DecryptKey(byte[] src)
|
||||
{
|
||||
InitPublicKey();
|
||||
return ProcessPredata(src).Take(56).ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
122
OpenRA.Mods.Cnc/FileFormats/CRC32.cs
Normal file
122
OpenRA.Mods.Cnc/FileFormats/CRC32.cs
Normal file
@@ -0,0 +1,122 @@
|
||||
#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;
|
||||
|
||||
namespace OpenRA.Mods.Cnc.FileFormats
|
||||
{
|
||||
/// <summary>
|
||||
/// Static class that uses a lookup table to calculates CRC32
|
||||
/// checksums of input strings.
|
||||
/// </summary>
|
||||
public static class CRC32
|
||||
{
|
||||
/// <summary>
|
||||
/// The CRC32 lookup table.
|
||||
/// </summary>
|
||||
static readonly uint[] LookUp =
|
||||
[
|
||||
0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA,
|
||||
0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3,
|
||||
0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988,
|
||||
0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91,
|
||||
0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE,
|
||||
0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7,
|
||||
0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC,
|
||||
0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5,
|
||||
0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172,
|
||||
0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B,
|
||||
0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940,
|
||||
0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59,
|
||||
0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116,
|
||||
0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F,
|
||||
0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924,
|
||||
0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D,
|
||||
0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A,
|
||||
0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433,
|
||||
0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818,
|
||||
0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01,
|
||||
0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E,
|
||||
0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457,
|
||||
0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C,
|
||||
0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65,
|
||||
0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2,
|
||||
0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB,
|
||||
0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0,
|
||||
0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9,
|
||||
0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086,
|
||||
0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F,
|
||||
0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4,
|
||||
0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD,
|
||||
0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A,
|
||||
0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683,
|
||||
0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8,
|
||||
0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1,
|
||||
0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE,
|
||||
0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7,
|
||||
0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC,
|
||||
0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5,
|
||||
0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252,
|
||||
0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B,
|
||||
0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60,
|
||||
0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79,
|
||||
0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236,
|
||||
0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F,
|
||||
0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04,
|
||||
0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D,
|
||||
0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A,
|
||||
0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713,
|
||||
0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38,
|
||||
0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21,
|
||||
0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E,
|
||||
0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777,
|
||||
0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C,
|
||||
0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45,
|
||||
0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2,
|
||||
0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB,
|
||||
0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0,
|
||||
0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9,
|
||||
0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6,
|
||||
0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF,
|
||||
0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94,
|
||||
0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// A CRC32 implementation that can be used on spans of bytes.
|
||||
/// </summary>
|
||||
/// <param name="data">The data from which to calculate the checksum.</param>
|
||||
/// <param name="polynomial">The polynomial to XOR with.</param>
|
||||
/// <returns>
|
||||
/// The calculated checksum.
|
||||
/// </returns>
|
||||
public static uint Calculate(ReadOnlySpan<byte> data, uint polynomial)
|
||||
{
|
||||
var crc = polynomial;
|
||||
for (var i = 0; i < data.Length; i++)
|
||||
crc = (crc >> 8) ^ LookUp[(crc & 0xFF) ^ data[i]];
|
||||
crc ^= polynomial;
|
||||
return crc;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A CRC32 implementation that can be used on spans of bytes, with default polynomial.
|
||||
/// </summary>
|
||||
/// <param name="data">The data from which to calculate the checksum.</param>
|
||||
/// <returns>
|
||||
/// The calculated checksum.
|
||||
/// </returns>
|
||||
public static uint Calculate(ReadOnlySpan<byte> data)
|
||||
{
|
||||
return Calculate(data, 0xFFFFFFFF);
|
||||
}
|
||||
}
|
||||
}
|
||||
63
OpenRA.Mods.Cnc/FileFormats/HvaReader.cs
Normal file
63
OpenRA.Mods.Cnc/FileFormats/HvaReader.cs
Normal file
@@ -0,0 +1,63 @@
|
||||
#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.IO;
|
||||
|
||||
namespace OpenRA.Mods.Cnc.FileFormats
|
||||
{
|
||||
public class HvaReader
|
||||
{
|
||||
public readonly uint FrameCount;
|
||||
public readonly uint LimbCount;
|
||||
public readonly float[] Transforms;
|
||||
|
||||
public HvaReader(Stream s, string fileName)
|
||||
{
|
||||
// Index swaps for transposing a matrix
|
||||
var ids = new byte[] { 0, 4, 8, 12, 1, 5, 9, 13, 2, 6, 10, 14 };
|
||||
|
||||
s.Seek(16, SeekOrigin.Begin);
|
||||
FrameCount = s.ReadUInt32();
|
||||
LimbCount = s.ReadUInt32();
|
||||
|
||||
// Skip limb names
|
||||
s.Seek(16 * LimbCount, SeekOrigin.Current);
|
||||
Transforms = new float[16 * FrameCount * LimbCount];
|
||||
|
||||
var testMatrix = new float[16];
|
||||
for (var j = 0; j < FrameCount; j++)
|
||||
for (var i = 0; i < LimbCount; i++)
|
||||
{
|
||||
// Convert to column-major matrices and add the final matrix row
|
||||
var c = 16 * (LimbCount * j + i);
|
||||
Transforms[c + 3] = 0;
|
||||
Transforms[c + 7] = 0;
|
||||
Transforms[c + 11] = 0;
|
||||
Transforms[c + 15] = 1;
|
||||
|
||||
for (var k = 0; k < 12; k++)
|
||||
Transforms[c + ids[k]] = s.ReadSingle();
|
||||
|
||||
Array.Copy(Transforms, 16 * (LimbCount * j + i), testMatrix, 0, 16);
|
||||
if (Util.MatrixInverse(testMatrix) == null)
|
||||
throw new InvalidDataException(
|
||||
$"The transformation matrix for HVA file `{fileName}` section {i} frame {j} is invalid because it is not invertible!");
|
||||
}
|
||||
}
|
||||
|
||||
public static HvaReader Load(string filename)
|
||||
{
|
||||
using (var s = File.OpenRead(filename))
|
||||
return new HvaReader(s, Path.GetFileName(filename));
|
||||
}
|
||||
}
|
||||
}
|
||||
45
OpenRA.Mods.Cnc/FileFormats/IdxEntry.cs
Normal file
45
OpenRA.Mods.Cnc/FileFormats/IdxEntry.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
#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.IO;
|
||||
|
||||
namespace OpenRA.Mods.Cnc.FileFormats
|
||||
{
|
||||
public class IdxEntry
|
||||
{
|
||||
public readonly string Filename;
|
||||
public readonly uint Offset;
|
||||
public readonly uint Length;
|
||||
public readonly uint SampleRate;
|
||||
public readonly uint Flags;
|
||||
public readonly uint ChunkSize;
|
||||
|
||||
public IdxEntry(Stream s)
|
||||
{
|
||||
var name = s.ReadASCII(16);
|
||||
var pos = name.IndexOf('\0');
|
||||
if (pos != 0)
|
||||
name = name[..pos];
|
||||
|
||||
Filename = $"{name}.wav";
|
||||
Offset = s.ReadUInt32();
|
||||
Length = s.ReadUInt32();
|
||||
SampleRate = s.ReadUInt32();
|
||||
Flags = s.ReadUInt32();
|
||||
ChunkSize = s.ReadUInt32();
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{Filename} - offset 0x{Offset:x8} - length 0x{Length:x8}";
|
||||
}
|
||||
}
|
||||
}
|
||||
44
OpenRA.Mods.Cnc/FileFormats/IdxReader.cs
Normal file
44
OpenRA.Mods.Cnc/FileFormats/IdxReader.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
#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.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace OpenRA.Mods.Cnc.FileFormats
|
||||
{
|
||||
public class IdxReader
|
||||
{
|
||||
public readonly int SoundCount;
|
||||
public List<IdxEntry> Entries;
|
||||
|
||||
public IdxReader(Stream s)
|
||||
{
|
||||
s.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
var id = s.ReadASCII(4);
|
||||
|
||||
if (id != "GABA")
|
||||
throw new InvalidDataException($"Unable to load Idx file, did not find magic id, found {id} instead");
|
||||
|
||||
var two = s.ReadInt32();
|
||||
|
||||
if (two != 2)
|
||||
throw new InvalidDataException($"Unable to load Idx file, did not find magic number 2, found {two} instead");
|
||||
|
||||
SoundCount = s.ReadInt32();
|
||||
|
||||
Entries = [];
|
||||
|
||||
for (var i = 0; i < SoundCount; i++)
|
||||
Entries.Add(new IdxEntry(s));
|
||||
}
|
||||
}
|
||||
}
|
||||
167
OpenRA.Mods.Cnc/FileFormats/LCWCompression.cs
Normal file
167
OpenRA.Mods.Cnc/FileFormats/LCWCompression.cs
Normal file
@@ -0,0 +1,167 @@
|
||||
#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.IO;
|
||||
using OpenRA.Mods.Common.FileFormats;
|
||||
|
||||
namespace OpenRA.Mods.Cnc.FileFormats
|
||||
{
|
||||
// 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 {srcIndex} {destIndex}");
|
||||
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
if (destIndex - srcIndex == 1)
|
||||
dest[destIndex + i] = dest[destIndex - 1];
|
||||
else
|
||||
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 it's case 5, else case 3
|
||||
var count = count3 == 0x3F ? ctx.ReadWord() : count3 + 3;
|
||||
var srcIndex = reverse ? destIndex - ctx.ReadWord() : ctx.ReadWord();
|
||||
if (srcIndex >= destIndex)
|
||||
throw new NotImplementedException($"srcIndex >= destIndex {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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
291
OpenRA.Mods.Cnc/FileFormats/LZOCompression.cs
Normal file
291
OpenRA.Mods.Cnc/FileFormats/LZOCompression.cs
Normal file
@@ -0,0 +1,291 @@
|
||||
#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
|
||||
#region Additional Copyright & License Information
|
||||
/*
|
||||
* C# port of the crude minilzo source version 2.06 by Frank Razenberg
|
||||
* The full LZO package can be found at https://www.oberhumer.com/opensource/lzo/
|
||||
*
|
||||
* Beware, you should never want to see C# code like this. You were warned.
|
||||
* I simply ran the MSVC preprocessor on the original source, changed the datatypes
|
||||
* to their C# counterpart and fixed changed some control flow stuff to amend for
|
||||
* the different goto semantics between C and C#.
|
||||
*
|
||||
* Original copyright notice is included below.
|
||||
*/
|
||||
|
||||
/*
|
||||
* minilzo.c -- mini subset of the LZO real-time data compression library
|
||||
*
|
||||
* This file is part of the LZO real-time data compression library.
|
||||
*
|
||||
* Copyright (C) 2011 Markus Franz Xaver Johannes Oberhumer
|
||||
* Copyright (C) 2010 Markus Franz Xaver Johannes Oberhumer
|
||||
* Copyright (C) 2009 Markus Franz Xaver Johannes Oberhumer
|
||||
* Copyright (C) 2008 Markus Franz Xaver Johannes Oberhumer
|
||||
* Copyright (C) 2007 Markus Franz Xaver Johannes Oberhumer
|
||||
* Copyright (C) 2006 Markus Franz Xaver Johannes Oberhumer
|
||||
* Copyright (C) 2005 Markus Franz Xaver Johannes Oberhumer
|
||||
* Copyright (C) 2004 Markus Franz Xaver Johannes Oberhumer
|
||||
* Copyright (C) 2003 Markus Franz Xaver Johannes Oberhumer
|
||||
* Copyright (C) 2002 Markus Franz Xaver Johannes Oberhumer
|
||||
* Copyright (C) 2001 Markus Franz Xaver Johannes Oberhumer
|
||||
* Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer
|
||||
* Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer
|
||||
* Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer
|
||||
* Copyright (C) 1997 Markus Franz Xaver Johannes Oberhumer
|
||||
* Copyright (C) 1996 Markus Franz Xaver Johannes Oberhumer
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* The LZO library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* The LZO library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with the LZO library; see the file COPYING.
|
||||
* If not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* Markus F.X.J. Oberhumer
|
||||
* <markus@oberhumer.com>
|
||||
* https://www.oberhumer.com/opensource/lzo/
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
|
||||
namespace OpenRA.Mods.Cnc.FileFormats
|
||||
{
|
||||
public static class LZOCompression
|
||||
{
|
||||
static unsafe int LZO1xDecompress(byte* @in, uint inLen, byte* @out, ref uint outLen, void* wrkmem)
|
||||
{
|
||||
byte* op;
|
||||
byte* ip;
|
||||
uint t;
|
||||
byte* mPos;
|
||||
var ipEnd = @in + inLen;
|
||||
outLen = 0;
|
||||
op = @out;
|
||||
ip = @in;
|
||||
var gtFirstLiteralRun = false;
|
||||
var gtMatchDone = false;
|
||||
if (*ip > 17)
|
||||
{
|
||||
t = (uint)(*ip++ - 17);
|
||||
if (t < 4)
|
||||
MatchNext(ref op, ref ip, ref t);
|
||||
else
|
||||
{
|
||||
do { *op++ = *ip++; } while (--t > 0);
|
||||
gtFirstLiteralRun = true;
|
||||
}
|
||||
}
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (gtFirstLiteralRun)
|
||||
{
|
||||
gtFirstLiteralRun = false;
|
||||
goto first_literal_run;
|
||||
}
|
||||
|
||||
t = *ip++;
|
||||
if (t >= 16)
|
||||
goto match;
|
||||
|
||||
if (t == 0)
|
||||
{
|
||||
while (*ip == 0)
|
||||
{
|
||||
t += 255;
|
||||
ip++;
|
||||
}
|
||||
|
||||
t += (uint)(15 + *ip++);
|
||||
}
|
||||
|
||||
*(uint*)op = *(uint*)ip;
|
||||
op += 4; ip += 4;
|
||||
if (--t > 0)
|
||||
{
|
||||
if (t >= 4)
|
||||
{
|
||||
do
|
||||
{
|
||||
*(uint*)op = *(uint*)ip;
|
||||
op += 4; ip += 4; t -= 4;
|
||||
}
|
||||
while (t >= 4);
|
||||
|
||||
if (t > 0)
|
||||
do { *op++ = *ip++; } while (--t > 0);
|
||||
}
|
||||
else
|
||||
do { *op++ = *ip++; } while (--t > 0);
|
||||
}
|
||||
|
||||
first_literal_run:
|
||||
t = *ip++;
|
||||
if (t >= 16)
|
||||
goto match;
|
||||
|
||||
mPos = op - (1 + 0x0800);
|
||||
mPos -= t >> 2;
|
||||
mPos -= *ip++ << 2;
|
||||
|
||||
*op++ = *mPos++; *op++ = *mPos++; *op++ = *mPos;
|
||||
gtMatchDone = true;
|
||||
|
||||
match:
|
||||
do
|
||||
{
|
||||
if (gtMatchDone)
|
||||
{
|
||||
gtMatchDone = false;
|
||||
goto match_done;
|
||||
}
|
||||
|
||||
if (t >= 64)
|
||||
{
|
||||
mPos = op - 1;
|
||||
mPos -= (t >> 2) & 7;
|
||||
mPos -= *ip++ << 3;
|
||||
t = (t >> 5) - 1;
|
||||
|
||||
CopyMatch(ref op, ref mPos, ref t);
|
||||
goto match_done;
|
||||
}
|
||||
else if (t >= 32)
|
||||
{
|
||||
t &= 31;
|
||||
if (t == 0)
|
||||
{
|
||||
while (*ip == 0)
|
||||
{
|
||||
t += 255;
|
||||
ip++;
|
||||
}
|
||||
|
||||
t += (uint)(31 + *ip++);
|
||||
}
|
||||
|
||||
mPos = op - 1;
|
||||
mPos -= (*(ushort*)ip) >> 2;
|
||||
ip += 2;
|
||||
}
|
||||
else if (t >= 16)
|
||||
{
|
||||
mPos = op;
|
||||
mPos -= (t & 8) << 11;
|
||||
t &= 7;
|
||||
if (t == 0)
|
||||
{
|
||||
while (*ip == 0)
|
||||
{
|
||||
t += 255;
|
||||
ip++;
|
||||
}
|
||||
|
||||
t += (uint)(7 + *ip++);
|
||||
}
|
||||
|
||||
mPos -= (*(ushort*)ip) >> 2;
|
||||
ip += 2;
|
||||
if (mPos == op)
|
||||
goto eof_found;
|
||||
mPos -= 0x4000;
|
||||
}
|
||||
else
|
||||
{
|
||||
mPos = op - 1;
|
||||
mPos -= t >> 2;
|
||||
mPos -= *ip++ << 2;
|
||||
*op++ = *mPos++; *op++ = *mPos;
|
||||
goto match_done;
|
||||
}
|
||||
|
||||
if (t >= 2 * 4 - (3 - 1) && op - mPos >= 4)
|
||||
{
|
||||
*(uint*)op = *(uint*)mPos;
|
||||
op += 4; mPos += 4; t -= 4 - (3 - 1);
|
||||
do
|
||||
{
|
||||
*(uint*)op = *(uint*)mPos;
|
||||
op += 4; mPos += 4; t -= 4;
|
||||
}
|
||||
while (t >= 4);
|
||||
|
||||
if (t > 0)
|
||||
do { *op++ = *mPos++; } while (--t > 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
// copy_match:
|
||||
*op++ = *mPos++; *op++ = *mPos++;
|
||||
do { *op++ = *mPos++; } while (--t > 0);
|
||||
}
|
||||
|
||||
match_done:
|
||||
t = (uint)(ip[-2] & 3);
|
||||
if (t == 0)
|
||||
break;
|
||||
|
||||
// match_next:
|
||||
*op++ = *ip++;
|
||||
if (t > 1)
|
||||
{
|
||||
*op++ = *ip++;
|
||||
if (t > 2)
|
||||
{
|
||||
*op++ = *ip++;
|
||||
}
|
||||
}
|
||||
|
||||
t = *ip++;
|
||||
}
|
||||
while (true);
|
||||
}
|
||||
|
||||
eof_found:
|
||||
outLen = (uint)(op - @out);
|
||||
return ip == ipEnd ? 0 : (ip < ipEnd ? (-8) : (-4));
|
||||
}
|
||||
|
||||
static unsafe void MatchNext(ref byte* op, ref byte* ip, ref uint t)
|
||||
{
|
||||
do { *op++ = *ip++; } while (--t > 0);
|
||||
t = *ip++;
|
||||
}
|
||||
|
||||
static unsafe void CopyMatch(ref byte* op, ref byte* mPos, ref uint t)
|
||||
{
|
||||
*op++ = *mPos++; *op++ = *mPos++;
|
||||
do { *op++ = *mPos++; } while (--t > 0);
|
||||
}
|
||||
|
||||
public static void DecodeInto(byte[] src, uint srcOffset, uint srcLength, byte[] dest, uint destOffset, ref uint destLength)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* r = src, w = dest, wrkmem = new byte[IntPtr.Size * 16384])
|
||||
{
|
||||
LZO1xDecompress(r + srcOffset, srcLength, w + destOffset, ref destLength, wrkmem);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
546
OpenRA.Mods.Cnc/FileFormats/VqaVideo.cs
Normal file
546
OpenRA.Mods.Cnc/FileFormats/VqaVideo.cs
Normal file
@@ -0,0 +1,546 @@
|
||||
#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.IO;
|
||||
using OpenRA.Mods.Common.FileFormats;
|
||||
using OpenRA.Video;
|
||||
|
||||
namespace OpenRA.Mods.Cnc.FileFormats
|
||||
{
|
||||
public class VqaVideo : IVideo
|
||||
{
|
||||
public ushort FrameCount { get; }
|
||||
public byte Framerate { get; }
|
||||
public ushort Width { get; }
|
||||
public ushort Height { get; }
|
||||
|
||||
public byte[] CurrentFrameData { get; }
|
||||
public int CurrentFrameIndex { get; private set; }
|
||||
|
||||
public bool HasAudio { get; set; }
|
||||
public byte[] AudioData { get; private set; } // audio for this frame: 22050Hz 16bit mono pcm, uncompressed.
|
||||
public int AudioChannels { get; }
|
||||
public int SampleBits { get; }
|
||||
public int SampleRate { get; }
|
||||
|
||||
readonly Stream stream;
|
||||
readonly ushort numColors;
|
||||
readonly ushort blockWidth;
|
||||
readonly ushort blockHeight;
|
||||
readonly byte chunkBufferParts;
|
||||
readonly int2 blocks;
|
||||
readonly uint[] offsets;
|
||||
readonly byte[] paletteBytes;
|
||||
readonly uint videoFlags; // if 0x10 is set the video is a 16 bit hq video (ts and later)
|
||||
readonly ushort totalFrameWidth;
|
||||
|
||||
// Stores a list of subpixels, referenced by the VPTZ chunk
|
||||
byte[] cbf;
|
||||
readonly byte[] cbp;
|
||||
readonly byte[] cbfBuffer;
|
||||
bool cbpIsCompressed;
|
||||
|
||||
// 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).
|
||||
readonly byte[] fileBuffer = new byte[256000];
|
||||
readonly int maxCbfzSize = 256000;
|
||||
int vtprSize = 0;
|
||||
int currentChunkBuffer = 0;
|
||||
int chunkBufferOffset = 0;
|
||||
|
||||
// Top half contains block info, bottom half contains references to cbf array
|
||||
readonly byte[] origData;
|
||||
|
||||
public VqaVideo(Stream stream, bool useFramePadding)
|
||||
{
|
||||
this.stream = stream;
|
||||
|
||||
// Decode FORM chunk
|
||||
if (stream.ReadASCII(4) != "FORM")
|
||||
throw new InvalidDataException("Invalid vqa (invalid FORM section)");
|
||||
stream.ReadUInt32(); // length
|
||||
|
||||
if (stream.ReadASCII(8) != "WVQAVQHD")
|
||||
throw new InvalidDataException("Invalid vqa (not WVQAVQHD)");
|
||||
stream.ReadUInt32(); // length2
|
||||
|
||||
stream.ReadUInt16(); // version
|
||||
videoFlags = stream.ReadUInt16();
|
||||
FrameCount = 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();
|
||||
stream.ReadUInt16(); // maxBlocks
|
||||
stream.ReadUInt16(); // unknown1
|
||||
stream.ReadUInt32(); // unknown2
|
||||
|
||||
// Audio
|
||||
SampleRate = stream.ReadUInt16();
|
||||
AudioChannels = stream.ReadUInt8();
|
||||
SampleBits = stream.ReadUInt8();
|
||||
|
||||
stream.ReadUInt32(); // unknown3
|
||||
stream.ReadUInt16(); // unknown4
|
||||
stream.ReadUInt32(); // maxCbfzSize, unreliable
|
||||
|
||||
stream.ReadUInt32(); // unknown5
|
||||
|
||||
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];
|
||||
}
|
||||
|
||||
paletteBytes = new byte[numColors * 4];
|
||||
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: {type}");
|
||||
}
|
||||
|
||||
stream.ReadUInt16(); // length
|
||||
stream.ReadUInt16(); // unknown4
|
||||
|
||||
// Frame offsets
|
||||
offsets = new uint[FrameCount];
|
||||
for (var i = 0; i < FrameCount; i++)
|
||||
{
|
||||
offsets[i] = stream.ReadUInt32();
|
||||
if (offsets[i] > 0x40000000)
|
||||
offsets[i] -= 0x40000000;
|
||||
offsets[i] <<= 1;
|
||||
}
|
||||
|
||||
CollectAudioData();
|
||||
|
||||
if (useFramePadding)
|
||||
{
|
||||
var frameSize = Exts.NextPowerOf2(Math.Max(Width, Height));
|
||||
CurrentFrameData = new byte[frameSize * frameSize * 4];
|
||||
totalFrameWidth = (ushort)frameSize;
|
||||
}
|
||||
else
|
||||
{
|
||||
CurrentFrameData = new byte[Width * Height * 4];
|
||||
totalFrameWidth = Width;
|
||||
}
|
||||
|
||||
Reset();
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
CurrentFrameIndex = 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 < FrameCount; i++)
|
||||
{
|
||||
stream.Seek(offsets[i], SeekOrigin.Begin);
|
||||
var end = (i < FrameCount - 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.Position += 2;
|
||||
}
|
||||
|
||||
compressed = type == "SND2";
|
||||
break;
|
||||
default:
|
||||
if (length + stream.Position > stream.Length)
|
||||
throw new NotSupportedException($"Vqa uses unknown Subtype: {type}");
|
||||
stream.Position += length;
|
||||
break;
|
||||
}
|
||||
|
||||
// Chunks are aligned on even bytes; advance by a byte if the next one is null
|
||||
if (stream.Peek() == 0) stream.ReadUInt8();
|
||||
}
|
||||
}
|
||||
|
||||
static byte[] GetAudioData(bool compressed, MemoryStream audio, ref int adpcmIndex)
|
||||
{
|
||||
var audioArray = audio.ToArray();
|
||||
if (!compressed)
|
||||
return audioArray;
|
||||
|
||||
var result = new byte[audio.Length * 4];
|
||||
ImaAdpcmReader.LoadImaAdpcmSound(audioArray, ref adpcmIndex, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
if (AudioChannels == 1)
|
||||
AudioData = GetAudioData(compressed, audio1, ref adpcmIndex);
|
||||
else
|
||||
{
|
||||
adpcmIndex = 0;
|
||||
var leftData = GetAudioData(compressed, audio1, ref adpcmIndex);
|
||||
adpcmIndex = 0;
|
||||
var rightData = GetAudioData(compressed, audio2, 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()
|
||||
{
|
||||
CurrentFrameIndex++;
|
||||
LoadFrame();
|
||||
}
|
||||
|
||||
void LoadFrame()
|
||||
{
|
||||
if (CurrentFrameIndex >= FrameCount)
|
||||
return;
|
||||
|
||||
// Seek to the start of the frame
|
||||
stream.Seek(offsets[CurrentFrameIndex], SeekOrigin.Begin);
|
||||
var end = (CurrentFrameIndex < FrameCount - 1) ? offsets[CurrentFrameIndex + 1] : stream.Length;
|
||||
|
||||
while (stream.Position < end)
|
||||
{
|
||||
var type = stream.ReadASCII(4);
|
||||
uint length;
|
||||
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.ReadUInt8();
|
||||
DecodeVQFR(stream);
|
||||
break;
|
||||
case "VQFL":
|
||||
DecodeVQFR(stream, "VQFL");
|
||||
break;
|
||||
default:
|
||||
// Don't parse sound here.
|
||||
stream.Position += length;
|
||||
break;
|
||||
}
|
||||
|
||||
// Chunks are aligned on even bytes; advance by a byte if the next one is null
|
||||
if (stream.Peek() == 0) stream.ReadUInt8();
|
||||
}
|
||||
|
||||
// Now that the frame data has been loaded (in the relevant private fields), decode it into CurrentFrameData.
|
||||
DecodeFrameData();
|
||||
}
|
||||
|
||||
// VQA Frame
|
||||
public void DecodeVQFR(Stream s, string parentType = "VQFR")
|
||||
{
|
||||
// The CBP chunks each contain 1/8th of the full lookup table
|
||||
// Annoyingly, the complete table is not applied until the frame
|
||||
// *after* the one that contains the 8th chunk.
|
||||
// Do we have a set of partial lookup tables ready to apply?
|
||||
if (currentChunkBuffer == chunkBufferParts && chunkBufferParts != 0)
|
||||
{
|
||||
if (!cbpIsCompressed)
|
||||
cbf = (byte[])cbp.Clone();
|
||||
else
|
||||
LCWCompression.DecodeInto(cbp, cbf);
|
||||
|
||||
chunkBufferOffset = currentChunkBuffer = 0;
|
||||
}
|
||||
|
||||
while (true)
|
||||
{
|
||||
// Chunks are aligned on even bytes; may be padded with a single null
|
||||
if (s.Peek() == 0) s.ReadUInt8();
|
||||
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 = 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":
|
||||
s.ReadBytes(cbp, chunkBufferOffset, subchunkLength);
|
||||
chunkBufferOffset += subchunkLength;
|
||||
currentChunkBuffer++;
|
||||
cbpIsCompressed = type == "CBPZ";
|
||||
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);
|
||||
paletteBytes[i * 4] = b;
|
||||
paletteBytes[i * 4 + 1] = g;
|
||||
paletteBytes[i * 4 + 2] = r;
|
||||
paletteBytes[i * 4 + 3] = 255;
|
||||
}
|
||||
|
||||
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 {type}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DecodeFrameData()
|
||||
{
|
||||
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 colorIndex = (mod == 0x0f) ? px : cbf[cbfi];
|
||||
|
||||
var pixelX = x * blockWidth + i;
|
||||
var pixelY = y * blockHeight + j;
|
||||
var pos = pixelY * totalFrameWidth + pixelX;
|
||||
CurrentFrameData[pos * 4] = paletteBytes[colorIndex * 4];
|
||||
CurrentFrameData[pos * 4 + 1] = paletteBytes[colorIndex * 4 + 1];
|
||||
CurrentFrameData[pos * 4 + 2] = paletteBytes[colorIndex * 4 + 2];
|
||||
CurrentFrameData[pos * 4 + 3] = paletteBytes[colorIndex * 4 + 3];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool IsHqVqa => (videoFlags & 0x10) == 16;
|
||||
|
||||
void WriteBlock(int blockNumber, int count, ref int x, ref int y)
|
||||
{
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
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;
|
||||
|
||||
var pixelX = x * blockWidth + bx;
|
||||
var pixelY = y * blockHeight + by;
|
||||
var pos = pixelY * totalFrameWidth + pixelX;
|
||||
CurrentFrameData[pos * 4] = cbf[offset + p + 2];
|
||||
CurrentFrameData[pos * 4 + 1] = cbf[offset + p + 1];
|
||||
CurrentFrameData[pos * 4 + 2] = cbf[offset + p];
|
||||
CurrentFrameData[pos * 4 + 3] = 255;
|
||||
}
|
||||
|
||||
x++;
|
||||
if (x >= blocks.X)
|
||||
{
|
||||
x = 0;
|
||||
y++;
|
||||
if (y >= blocks.Y && i != count - 1)
|
||||
throw new IndexOutOfRangeException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
158
OpenRA.Mods.Cnc/FileFormats/VxlReader.cs
Normal file
158
OpenRA.Mods.Cnc/FileFormats/VxlReader.cs
Normal file
@@ -0,0 +1,158 @@
|
||||
#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;
|
||||
|
||||
namespace OpenRA.Mods.Cnc.FileFormats
|
||||
{
|
||||
public enum NormalType : byte { TiberianSun = 2, RedAlert2 = 4 }
|
||||
public readonly record struct VxlElement(byte Color, byte Normal);
|
||||
|
||||
public class VxlLimb
|
||||
{
|
||||
public string Name;
|
||||
public float Scale;
|
||||
public float[] Bounds;
|
||||
public byte[] Size;
|
||||
public NormalType Type;
|
||||
|
||||
public uint VoxelCount;
|
||||
public Dictionary<byte, VxlElement>[,] VoxelMap;
|
||||
}
|
||||
|
||||
public class VxlReader
|
||||
{
|
||||
public readonly uint LimbCount;
|
||||
public VxlLimb[] Limbs;
|
||||
|
||||
readonly uint bodySize;
|
||||
|
||||
static void ReadVoxelData(Stream s, VxlLimb l)
|
||||
{
|
||||
var baseSize = l.Size[0] * l.Size[1];
|
||||
var colStart = new int[baseSize];
|
||||
for (var i = 0; i < baseSize; i++)
|
||||
colStart[i] = s.ReadInt32();
|
||||
s.Seek(4 * baseSize, SeekOrigin.Current);
|
||||
var dataStart = s.Position;
|
||||
|
||||
// Count the voxels in this limb
|
||||
l.VoxelCount = 0;
|
||||
for (var i = 0; i < baseSize; i++)
|
||||
{
|
||||
// Empty column
|
||||
if (colStart[i] == -1)
|
||||
continue;
|
||||
|
||||
s.Seek(dataStart + colStart[i], SeekOrigin.Begin);
|
||||
var z = 0;
|
||||
do
|
||||
{
|
||||
z += s.ReadUInt8();
|
||||
var count = s.ReadUInt8();
|
||||
z += count;
|
||||
l.VoxelCount += count;
|
||||
s.Seek(2 * count + 1, SeekOrigin.Current);
|
||||
}
|
||||
while (z < l.Size[2]);
|
||||
}
|
||||
|
||||
// Read the data
|
||||
l.VoxelMap = new Dictionary<byte, VxlElement>[l.Size[0], l.Size[1]];
|
||||
for (var i = 0; i < baseSize; i++)
|
||||
{
|
||||
// Empty column
|
||||
if (colStart[i] == -1)
|
||||
continue;
|
||||
|
||||
s.Seek(dataStart + colStart[i], SeekOrigin.Begin);
|
||||
|
||||
var x = (byte)(i % l.Size[0]);
|
||||
var y = (byte)(i / l.Size[0]);
|
||||
byte z = 0;
|
||||
var voxelMap = new Dictionary<byte, VxlElement>();
|
||||
l.VoxelMap[x, y] = voxelMap;
|
||||
do
|
||||
{
|
||||
z += s.ReadUInt8();
|
||||
var count = s.ReadUInt8();
|
||||
voxelMap.EnsureCapacity(voxelMap.Count + count);
|
||||
for (var j = 0; j < count; j++)
|
||||
{
|
||||
var v = new VxlElement(s.ReadUInt8(), s.ReadUInt8());
|
||||
voxelMap.Add(z, v);
|
||||
z++;
|
||||
}
|
||||
|
||||
// Skip duplicate count
|
||||
s.ReadUInt8();
|
||||
}
|
||||
while (z < l.Size[2]);
|
||||
}
|
||||
}
|
||||
|
||||
public VxlReader(Stream s)
|
||||
{
|
||||
if (!s.ReadASCII(16).StartsWith("Voxel Animation", StringComparison.Ordinal))
|
||||
throw new InvalidDataException("Invalid vxl header");
|
||||
|
||||
s.ReadUInt32();
|
||||
LimbCount = s.ReadUInt32();
|
||||
s.ReadUInt32();
|
||||
bodySize = s.ReadUInt32();
|
||||
s.Seek(770, SeekOrigin.Current);
|
||||
|
||||
// Read Limb headers
|
||||
Limbs = new VxlLimb[LimbCount];
|
||||
for (var i = 0; i < LimbCount; i++)
|
||||
{
|
||||
Limbs[i] = new VxlLimb
|
||||
{
|
||||
Name = s.ReadASCII(16)
|
||||
};
|
||||
|
||||
s.Seek(12, SeekOrigin.Current);
|
||||
}
|
||||
|
||||
// Skip to the Limb footers
|
||||
s.Seek(802 + 28 * LimbCount + bodySize, SeekOrigin.Begin);
|
||||
|
||||
var limbDataOffset = new uint[LimbCount];
|
||||
for (var i = 0; i < LimbCount; i++)
|
||||
{
|
||||
limbDataOffset[i] = s.ReadUInt32();
|
||||
s.Seek(8, SeekOrigin.Current);
|
||||
Limbs[i].Scale = s.ReadSingle();
|
||||
s.Seek(48, SeekOrigin.Current);
|
||||
|
||||
Limbs[i].Bounds = new float[6];
|
||||
for (var j = 0; j < 6; j++)
|
||||
Limbs[i].Bounds[j] = s.ReadSingle();
|
||||
Limbs[i].Size = s.ReadBytes(3);
|
||||
Limbs[i].Type = (NormalType)s.ReadUInt8();
|
||||
}
|
||||
|
||||
for (var i = 0; i < LimbCount; i++)
|
||||
{
|
||||
s.Seek(802 + 28 * LimbCount + limbDataOffset[i], SeekOrigin.Begin);
|
||||
ReadVoxelData(s, Limbs[i]);
|
||||
}
|
||||
}
|
||||
|
||||
public static VxlReader Load(string filename)
|
||||
{
|
||||
using (var s = File.OpenRead(filename))
|
||||
return new VxlReader(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
156
OpenRA.Mods.Cnc/FileFormats/WsaVideo.cs
Normal file
156
OpenRA.Mods.Cnc/FileFormats/WsaVideo.cs
Normal file
@@ -0,0 +1,156 @@
|
||||
#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.IO;
|
||||
using OpenRA.Video;
|
||||
|
||||
namespace OpenRA.Mods.Cnc.FileFormats
|
||||
{
|
||||
public class WsaVideo : IVideo
|
||||
{
|
||||
public ushort FrameCount { get; }
|
||||
public byte Framerate => 15;
|
||||
public ushort Width { get; }
|
||||
public ushort Height { get; }
|
||||
|
||||
public byte[] CurrentFrameData { get; }
|
||||
public int CurrentFrameIndex { get; private set; }
|
||||
|
||||
public bool HasAudio => false;
|
||||
public byte[] AudioData => null;
|
||||
public int AudioChannels => 0;
|
||||
public int SampleBits => 0;
|
||||
public int SampleRate => 0;
|
||||
|
||||
readonly Stream stream;
|
||||
readonly byte[] paletteBytes;
|
||||
readonly uint[] frameOffsets;
|
||||
readonly ushort totalFrameWidth;
|
||||
|
||||
byte[] previousFramePaletteIndexData;
|
||||
byte[] currentFramePaletteIndexData;
|
||||
|
||||
public WsaVideo(Stream stream, bool useFramePadding)
|
||||
{
|
||||
this.stream = stream;
|
||||
|
||||
FrameCount = stream.ReadUInt16();
|
||||
|
||||
stream.ReadUInt16(); // x
|
||||
stream.ReadUInt16(); // y
|
||||
|
||||
Width = stream.ReadUInt16();
|
||||
Height = stream.ReadUInt16();
|
||||
|
||||
stream.ReadUInt16(); // delta (+37)
|
||||
var flags = stream.ReadUInt16();
|
||||
|
||||
frameOffsets = new uint[FrameCount + 2];
|
||||
for (var i = 0; i < frameOffsets.Length; i++)
|
||||
frameOffsets[i] = stream.ReadUInt32();
|
||||
|
||||
if (flags == 1)
|
||||
{
|
||||
paletteBytes = new byte[1024];
|
||||
for (var i = 0; i < paletteBytes.Length;)
|
||||
{
|
||||
var r = (byte)(stream.ReadUInt8() << 2);
|
||||
var g = (byte)(stream.ReadUInt8() << 2);
|
||||
var b = (byte)(stream.ReadUInt8() << 2);
|
||||
|
||||
// Replicate high bits into the (currently zero) low bits.
|
||||
r |= (byte)(r >> 6);
|
||||
g |= (byte)(g >> 6);
|
||||
b |= (byte)(b >> 6);
|
||||
|
||||
paletteBytes[i++] = b;
|
||||
paletteBytes[i++] = g;
|
||||
paletteBytes[i++] = r;
|
||||
paletteBytes[i++] = 255;
|
||||
}
|
||||
|
||||
for (var i = 0; i < frameOffsets.Length; i++)
|
||||
frameOffsets[i] += 768;
|
||||
}
|
||||
|
||||
if (useFramePadding)
|
||||
{
|
||||
var frameSize = Exts.NextPowerOf2(Math.Max(Width, Height));
|
||||
CurrentFrameData = new byte[frameSize * frameSize * 4];
|
||||
totalFrameWidth = (ushort)frameSize;
|
||||
}
|
||||
else
|
||||
{
|
||||
CurrentFrameData = new byte[Width * Height * 4];
|
||||
totalFrameWidth = Width;
|
||||
}
|
||||
|
||||
Reset();
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
CurrentFrameIndex = 0;
|
||||
previousFramePaletteIndexData = null;
|
||||
LoadFrame();
|
||||
}
|
||||
|
||||
public void AdvanceFrame()
|
||||
{
|
||||
previousFramePaletteIndexData = currentFramePaletteIndexData;
|
||||
CurrentFrameIndex++;
|
||||
LoadFrame();
|
||||
}
|
||||
|
||||
void LoadFrame()
|
||||
{
|
||||
if (CurrentFrameIndex >= FrameCount)
|
||||
return;
|
||||
|
||||
stream.Seek(frameOffsets[CurrentFrameIndex], SeekOrigin.Begin);
|
||||
|
||||
var dataLength = frameOffsets[CurrentFrameIndex + 1] - frameOffsets[CurrentFrameIndex];
|
||||
|
||||
var rawData = stream.ReadBytes((int)dataLength);
|
||||
var intermediateData = new byte[Width * Height];
|
||||
|
||||
// Format80 decompression
|
||||
LCWCompression.DecodeInto(rawData, intermediateData);
|
||||
|
||||
// and Format40 decompression
|
||||
currentFramePaletteIndexData = new byte[Width * Height];
|
||||
if (previousFramePaletteIndexData == null)
|
||||
Array.Clear(currentFramePaletteIndexData, 0, currentFramePaletteIndexData.Length);
|
||||
else
|
||||
Array.Copy(previousFramePaletteIndexData, currentFramePaletteIndexData, currentFramePaletteIndexData.Length);
|
||||
|
||||
XORDeltaCompression.DecodeInto(intermediateData, currentFramePaletteIndexData, 0);
|
||||
|
||||
var c = 0;
|
||||
var position = 0;
|
||||
for (var y = 0; y < Height; y++)
|
||||
{
|
||||
for (var x = 0; x < Width; x++)
|
||||
{
|
||||
var colorIndex = currentFramePaletteIndexData[c++];
|
||||
CurrentFrameData[position++] = paletteBytes[colorIndex * 4];
|
||||
CurrentFrameData[position++] = paletteBytes[colorIndex * 4 + 1];
|
||||
CurrentFrameData[position++] = paletteBytes[colorIndex * 4 + 2];
|
||||
CurrentFrameData[position++] = paletteBytes[colorIndex * 4 + 3];
|
||||
}
|
||||
|
||||
// Recalculate the position in the byte array to the start of the next pixel row just in case there is padding in the frame.
|
||||
position = (y + 1) * totalFrameWidth * 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
82
OpenRA.Mods.Cnc/FileFormats/XORDeltaCompression.cs
Normal file
82
OpenRA.Mods.Cnc/FileFormats/XORDeltaCompression.cs
Normal file
@@ -0,0 +1,82 @@
|
||||
#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 OpenRA.Mods.Common.FileFormats;
|
||||
|
||||
namespace OpenRA.Mods.Cnc.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
60
OpenRA.Mods.Cnc/FileFormats/XccGlobalDatabase.cs
Normal file
60
OpenRA.Mods.Cnc/FileFormats/XccGlobalDatabase.cs
Normal file
@@ -0,0 +1,60 @@
|
||||
#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;
|
||||
|
||||
namespace OpenRA.Mods.Cnc.FileFormats
|
||||
{
|
||||
public sealed class XccGlobalDatabase : IDisposable
|
||||
{
|
||||
public readonly string[] Entries;
|
||||
readonly Stream s;
|
||||
|
||||
public XccGlobalDatabase(Stream stream)
|
||||
{
|
||||
s = stream;
|
||||
|
||||
var entries = new List<string>();
|
||||
var chars = new char[32];
|
||||
while (s.Peek() > -1)
|
||||
{
|
||||
var count = s.ReadInt32();
|
||||
entries.Capacity += count;
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
// Read filename
|
||||
byte c;
|
||||
var charsIndex = 0;
|
||||
while ((c = s.ReadUInt8()) != 0)
|
||||
{
|
||||
if (charsIndex >= chars.Length)
|
||||
Array.Resize(ref chars, chars.Length * 2);
|
||||
chars[charsIndex++] = (char)c;
|
||||
}
|
||||
|
||||
entries.Add(new string(chars, 0, charsIndex));
|
||||
|
||||
// Skip comment
|
||||
while (s.ReadUInt8() != 0) { }
|
||||
}
|
||||
}
|
||||
|
||||
Entries = entries.ToArray();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
s.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
69
OpenRA.Mods.Cnc/FileFormats/XccLocalDatabase.cs
Normal file
69
OpenRA.Mods.Cnc/FileFormats/XccLocalDatabase.cs
Normal file
@@ -0,0 +1,69 @@
|
||||
#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.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace OpenRA.Mods.Cnc.FileFormats
|
||||
{
|
||||
public class XccLocalDatabase
|
||||
{
|
||||
public readonly string[] Entries;
|
||||
public XccLocalDatabase(Stream s)
|
||||
{
|
||||
// Skip unnecessary header data
|
||||
s.Seek(48, SeekOrigin.Begin);
|
||||
var reader = new BinaryReader(s);
|
||||
var count = reader.ReadInt32();
|
||||
Entries = new string[count];
|
||||
var chars = new List<char>();
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
char c;
|
||||
while ((c = reader.ReadChar()) != 0)
|
||||
chars.Add(c);
|
||||
|
||||
Entries[i] = new string(chars.ToArray());
|
||||
chars.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
public XccLocalDatabase(IEnumerable<string> filenames)
|
||||
{
|
||||
Entries = filenames.ToArray();
|
||||
}
|
||||
|
||||
public byte[] Data()
|
||||
{
|
||||
var data = new MemoryStream();
|
||||
using (var writer = new BinaryWriter(data))
|
||||
{
|
||||
writer.Write(Encoding.ASCII.GetBytes("XCC by Olaf van der Spek"));
|
||||
writer.Write([0x1A, 0x04, 0x17, 0x27, 0x10, 0x19, 0x80, 0x00]);
|
||||
|
||||
writer.Write(Entries.Sum(e => e.Length) + Entries.Length + 52); // Size
|
||||
writer.Write(0); // Type
|
||||
writer.Write(0); // Version
|
||||
writer.Write(0); // Game/Format (0 == TD)
|
||||
writer.Write(Entries.Length); // Entries
|
||||
foreach (var e in Entries)
|
||||
{
|
||||
writer.Write(Encoding.ASCII.GetBytes(e));
|
||||
writer.Write((byte)0);
|
||||
}
|
||||
}
|
||||
|
||||
return data.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user