diff --git a/OpenRA.Mods.Cnc/FileFormats/WsaReader.cs b/OpenRA.Mods.Cnc/FileFormats/WsaReader.cs new file mode 100644 index 0000000000..cb4e109268 --- /dev/null +++ b/OpenRA.Mods.Cnc/FileFormats/WsaReader.cs @@ -0,0 +1,136 @@ +#region Copyright & License Information +/* + * Copyright 2007-2021 The OpenRA Developers (see AUTHORS) + * This file is part of OpenRA, which is free software. It is made + * available to you under the terms of the GNU General Public License + * as published by the Free Software Foundation, 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 WsaReader : IVideo + { + readonly Stream stream; + + public ushort Frames { get { return frameCount; } } + public byte Framerate { get { return 1; } } + public ushort Width { get { return width; } } + public ushort Height { get { return height; } } + + readonly ushort frameCount; + readonly ushort width; + readonly ushort height; + readonly uint[] palette; + readonly uint[] frameOffsets; + + uint[,] coloredFrameData; + public uint[,] FrameData { get { return coloredFrameData; } } + + int currentFrame; + byte[] previousFrameData; + byte[] currentFrameData; + + public byte[] AudioData { get { return null; } } + public int CurrentFrame { get { return currentFrame; } } + public int SampleRate { get { return 0; } } + public int SampleBits { get { return 0; } } + public int AudioChannels { get { return 0; } } + public bool HasAudio { get { return false; } } + + public WsaReader(Stream stream) + { + this.stream = stream; + + frameCount = stream.ReadUInt16(); + + var x = stream.ReadUInt16(); + var y = stream.ReadUInt16(); + + width = stream.ReadUInt16(); + height = stream.ReadUInt16(); + + var delta = stream.ReadUInt16() + 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) + { + palette = new uint[256]; + for (var i = 0; i < palette.Length; i++) + { + var r = (byte)(stream.ReadByte() << 2); + var g = (byte)(stream.ReadByte() << 2); + var b = (byte)(stream.ReadByte() << 2); + + // Replicate high bits into the (currently zero) low bits. + r |= (byte)(r >> 6); + g |= (byte)(g >> 6); + b |= (byte)(b >> 6); + + palette[i] = (uint)((255 << 24) | (r << 16) | (g << 8) | b); + } + + for (var i = 0; i < frameOffsets.Length; i++) + frameOffsets[i] += 768; + } + + Reset(); + } + + public void Reset() + { + currentFrame = 0; + previousFrameData = null; + LoadFrame(); + } + + public void AdvanceFrame() + { + previousFrameData = currentFrameData; + currentFrame++; + LoadFrame(); + } + + void LoadFrame() + { + if (currentFrame >= frameCount) + return; + + stream.Seek(frameOffsets[currentFrame], SeekOrigin.Begin); + + var dataLength = frameOffsets[currentFrame + 1] - frameOffsets[currentFrame]; + + var rawData = StreamExts.ReadBytes(stream, (int)dataLength); + var intermediateData = new byte[width * height]; + + // Format80 decompression + LCWCompression.DecodeInto(rawData, intermediateData); + + // and Format40 decompression + currentFrameData = new byte[width * height]; + if (previousFrameData == null) + Array.Clear(currentFrameData, 0, currentFrameData.Length); + else + Array.Copy(previousFrameData, currentFrameData, currentFrameData.Length); + + XORDeltaCompression.DecodeInto(intermediateData, currentFrameData, 0); + + var c = 0; + var frameSize = Exts.NextPowerOf2(Math.Max(width, height)); + coloredFrameData = new uint[frameSize, frameSize]; + for (var y = 0; y < height; y++) + for (var x = 0; x < width; x++) + coloredFrameData[y, x] = palette[currentFrameData[c++]]; + } + } +} diff --git a/OpenRA.Mods.Cnc/VideoLoaders/WsaLoader.cs b/OpenRA.Mods.Cnc/VideoLoaders/WsaLoader.cs new file mode 100644 index 0000000000..1ffac8ed57 --- /dev/null +++ b/OpenRA.Mods.Cnc/VideoLoaders/WsaLoader.cs @@ -0,0 +1,66 @@ +#region Copyright & License Information +/* + * Copyright 2007-2021 The OpenRA Developers (see AUTHORS) + * This file is part of OpenRA, which is free software. It is made + * available to you under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. For more + * information, see COPYING. + */ +#endregion + +using System.IO; +using System.Linq; +using OpenRA.Mods.Cnc.FileFormats; +using OpenRA.Video; + +namespace OpenRA.Mods.Cnc.VideoLoaders +{ + public class WsaLoader : IVideoLoader + { + public bool TryParseVideo(Stream s, out IVideo video) + { + video = null; + + if (!IsWsa(s)) + return false; + + video = new WsaReader(s); + return true; + } + + bool IsWsa(Stream s) + { + var start = s.Position; + + var frames = s.ReadUInt16(); + + var x = s.ReadUInt16(); + var y = s.ReadUInt16(); + var width = s.ReadUInt16(); + var height = s.ReadUInt16(); + + if (width <= 0 || height <= 0) + return false; + + var delta = s.ReadUInt16() + 37; + + var flags = s.ReadUInt16(); + + var offsets = new uint[frames + 2]; + for (var i = 0; i < offsets.Length; i++) + offsets[i] = s.ReadUInt32(); + + if (flags == 1) + { + var palette = StreamExts.ReadBytes(s, 768); + for (var i = 0; i < offsets.Length; i++) + offsets[i] += 768; + } + + s.Position = start; + + return s.Length == offsets.Last(); + } + } +} diff --git a/OpenRA.Mods.Common/Widgets/VideoPlayerWidget.cs b/OpenRA.Mods.Common/Widgets/VideoPlayerWidget.cs index 2133ce2d9c..d966cdf394 100644 --- a/OpenRA.Mods.Common/Widgets/VideoPlayerWidget.cs +++ b/OpenRA.Mods.Common/Widgets/VideoPlayerWidget.cs @@ -195,7 +195,7 @@ namespace OpenRA.Mods.Common.Widgets return; onComplete = after; - if (stopped) + if (stopped && video.HasAudio) Game.Sound.PlayVideo(video.AudioData, video.AudioChannels, video.SampleBits, video.SampleRate); else Game.Sound.PlayVideo(); diff --git a/mods/cnc/mod.yaml b/mods/cnc/mod.yaml index dfdbda06c6..6973cda1b1 100644 --- a/mods/cnc/mod.yaml +++ b/mods/cnc/mod.yaml @@ -24,6 +24,7 @@ Packages: ~scores2.mix ~scores-covertops.mix ~transit.mix + ~general.mix cnc|bits/snow.mix cnc|bits cnc|bits/jungle @@ -217,7 +218,7 @@ SoundFormats: Aud, Wav SpriteFormats: ShpTD, TmpTD, ShpTS, TmpRA -VideoFormats: Vqa +VideoFormats: Vqa, Wsa TerrainFormat: DefaultTerrain @@ -232,7 +233,7 @@ SpriteSequenceFormat: ClassicTilesetSpecificSpriteSequence ModelSequenceFormat: PlaceholderModelSequence AssetBrowser: - SupportedExtensions: .shp, .tem, .des, .sno, .jun, .vqa + SupportedExtensions: .shp, .tem, .des, .sno, .jun, .vqa, .wsa GameSpeeds: slowest: diff --git a/mods/ra/installer/allies95.yaml b/mods/ra/installer/allies95.yaml index 8416d7ca5a..e782373520 100644 --- a/mods/ra/installer/allies95.yaml +++ b/mods/ra/installer/allies95.yaml @@ -21,6 +21,9 @@ allied: Red Alert 95 (Allied Disc, English) ^SupportDir|Content/ra/v2/conquer.mix: Offset: 236 Length: 2177047 + ^SupportDir|Content/ra/v2/general.mix: + Offset: 2239848 + Length: 14932344 ^SupportDir|Content/ra/v2/interior.mix: Offset: 17172192 Length: 247425 @@ -391,4 +394,4 @@ allied-linux: Red Alert 95 (Allied Disc, English) Length: 309406 ^SupportDir|Content/ra/v2/temperat.mix: Offset: 453566435 - Length: 1038859 \ No newline at end of file + Length: 1038859 diff --git a/mods/ra/installer/soviet95.yaml b/mods/ra/installer/soviet95.yaml index 1a9a4d4c9d..20f484fa9e 100644 --- a/mods/ra/installer/soviet95.yaml +++ b/mods/ra/installer/soviet95.yaml @@ -21,6 +21,9 @@ soviet: Red Alert 95 (Soviet Disc, English) ^SupportDir|Content/ra/v2/conquer.mix: Offset: 236 Length: 2177047 + ^SupportDir|Content/ra/v2/general.mix: + Offset: 2239848 + Length: 14932344 ^SupportDir|Content/ra/v2/interior.mix: Offset: 17172192 Length: 247425 @@ -414,4 +417,4 @@ soviet-linux: Red Alert 95 (Soviet Disc, English) Length: 1006778 ^SupportDir|Content/ra/v2/temperat.mix: Offset: 499538555 - Length: 1038859 \ No newline at end of file + Length: 1038859 diff --git a/mods/ra/mod.yaml b/mods/ra/mod.yaml index 17191ebde5..bc74f670d6 100644 --- a/mods/ra/mod.yaml +++ b/mods/ra/mod.yaml @@ -30,6 +30,7 @@ Packages: ~expand2.mix ~hires1.mix ~desert.mix + ~general.mix ra|bits ra|bits/desert ra|bits/scripts @@ -223,7 +224,7 @@ SoundFormats: Aud, Wav SpriteFormats: ShpD2, ShpTD, TmpRA, TmpTD, ShpTS -VideoFormats: Vqa +VideoFormats: Vqa, Wsa TerrainFormat: DefaultTerrain @@ -237,7 +238,7 @@ SpriteSequenceFormat: ClassicTilesetSpecificSpriteSequence ModelSequenceFormat: PlaceholderModelSequence AssetBrowser: - SupportedExtensions: .shp, .tmp, .tem, .des, .sno, .int, .vqa + SupportedExtensions: .shp, .tmp, .tem, .des, .sno, .int, .vqa, .wsa GameSpeeds: slowest: