diff --git a/OpenRA.Game/CryptoUtil.cs b/OpenRA.Game/CryptoUtil.cs index bb5a2ef8b4..bdfc8c558a 100644 --- a/OpenRA.Game/CryptoUtil.cs +++ b/OpenRA.Game/CryptoUtil.cs @@ -165,9 +165,9 @@ namespace OpenRA if (length < 0x80) return length; - var data = new byte[4]; - s.ReadBytes(data, 0, Math.Min(length & 0x7F, 4)); - return BitConverter.ToInt32(data.ToArray(), 0); + Span data = stackalloc byte[4]; + s.ReadBytes(data[..Math.Min(length & 0x7F, 4)]); + return BitConverter.ToInt32(data); } static int TripletFullLength(int dataLength) diff --git a/OpenRA.Game/FileFormats/Png.cs b/OpenRA.Game/FileFormats/Png.cs index 6fb7788eb0..f6588207ff 100644 --- a/OpenRA.Game/FileFormats/Png.cs +++ b/OpenRA.Game/FileFormats/Png.cs @@ -48,7 +48,7 @@ namespace OpenRA.FileFormats while (true) { var length = IPAddress.NetworkToHostOrder(s.ReadInt32()); - var type = Encoding.UTF8.GetString(s.ReadBytes(4)); + var type = s.ReadASCII(4); var content = s.ReadBytes(length); /*var crc = */s.ReadInt32(); @@ -287,10 +287,10 @@ namespace OpenRA.FileFormats var typeBytes = Encoding.ASCII.GetBytes(type); output.Write(IPAddress.HostToNetworkOrder((int)input.Length)); - output.WriteArray(typeBytes); + output.Write(typeBytes); var data = input.ReadAllBytes(); - output.WriteArray(data); + output.Write(data); var crc32 = new Crc32(); crc32.Update(typeBytes); @@ -302,7 +302,7 @@ namespace OpenRA.FileFormats { using (var output = new MemoryStream()) { - output.WriteArray(Signature); + output.Write(Signature); using (var header = new MemoryStream()) { header.Write(IPAddress.HostToNetworkOrder(Width)); @@ -371,7 +371,7 @@ namespace OpenRA.FileFormats { using (var text = new MemoryStream()) { - text.WriteArray(Encoding.ASCII.GetBytes(kv.Key + (char)0 + kv.Value)); + text.Write(Encoding.ASCII.GetBytes(kv.Key + (char)0 + kv.Value)); WritePngChunk(output, "tEXt", text); } } diff --git a/OpenRA.Game/FileFormats/ReplayMetadata.cs b/OpenRA.Game/FileFormats/ReplayMetadata.cs index 3aa5fae87b..0d1fbb15af 100644 --- a/OpenRA.Game/FileFormats/ReplayMetadata.cs +++ b/OpenRA.Game/FileFormats/ReplayMetadata.cs @@ -47,7 +47,7 @@ namespace OpenRA.FileFormats throw new NotSupportedException($"Metadata version {version} is not supported"); // Read game info (max 100K limit as a safeguard against corrupted files) - var data = fs.ReadString(Encoding.UTF8, 1024 * 100); + var data = fs.ReadLengthPrefixedString(Encoding.UTF8, 1024 * 100); GameInfo = GameInformation.Deserialize(data); } @@ -62,7 +62,7 @@ namespace OpenRA.FileFormats { // Write lobby info data writer.Flush(); - dataLength += writer.BaseStream.WriteString(Encoding.UTF8, GameInfo.Serialize()); + dataLength += writer.BaseStream.WriteLengthPrefixedString(Encoding.UTF8, GameInfo.Serialize()); } // Write total length & end marker diff --git a/OpenRA.Game/FileSystem/ZipFile.cs b/OpenRA.Game/FileSystem/ZipFile.cs index bc43a634c3..b6c130bf9e 100644 --- a/OpenRA.Game/FileSystem/ZipFile.cs +++ b/OpenRA.Game/FileSystem/ZipFile.cs @@ -117,10 +117,7 @@ namespace OpenRA.FileSystem void Commit() { - var pos = pkgStream.Position; - pkgStream.Position = 0; - File.WriteAllBytes(Name, pkgStream.ReadBytes((int)pkgStream.Length)); - pkgStream.Position = pos; + File.WriteAllBytes(Name, pkgStream.ToArray()); } public void Update(string filename, byte[] contents) diff --git a/OpenRA.Game/Network/Connection.cs b/OpenRA.Game/Network/Connection.cs index fd0d59319a..9063ce80b3 100644 --- a/OpenRA.Game/Network/Connection.cs +++ b/OpenRA.Game/Network/Connection.cs @@ -261,14 +261,14 @@ namespace OpenRA.Network { var ms = new MemoryStream(); ms.Write(packet.Length); - ms.WriteArray(packet); + ms.Write(packet); foreach (var s in queuedSyncPackets) { var q = OrderIO.SerializeSync(s); ms.Write(q.Length); - ms.WriteArray(q); + ms.Write(q); sentSync.Enqueue(s); } diff --git a/OpenRA.Game/Network/GameSave.cs b/OpenRA.Game/Network/GameSave.cs index f549878070..378cc3eca3 100644 --- a/OpenRA.Game/Network/GameSave.cs +++ b/OpenRA.Game/Network/GameSave.cs @@ -122,10 +122,10 @@ namespace OpenRA.Network LastSyncFrame = rs.ReadInt32(); lastSyncPacket = rs.ReadBytes(Order.SyncHashOrderLength); - var globalSettings = MiniYaml.FromString(rs.ReadString(Encoding.UTF8, Connection.MaxOrderLength)); + var globalSettings = MiniYaml.FromString(rs.ReadLengthPrefixedString(Encoding.UTF8, Connection.MaxOrderLength)); GlobalSettings = Session.Global.Deserialize(globalSettings[0].Value); - var slots = MiniYaml.FromString(rs.ReadString(Encoding.UTF8, Connection.MaxOrderLength)); + var slots = MiniYaml.FromString(rs.ReadLengthPrefixedString(Encoding.UTF8, Connection.MaxOrderLength)); Slots = new Dictionary(); foreach (var s in slots) { @@ -133,7 +133,7 @@ namespace OpenRA.Network Slots.Add(slot.PlayerReference, slot); } - var slotClients = MiniYaml.FromString(rs.ReadString(Encoding.UTF8, Connection.MaxOrderLength)); + var slotClients = MiniYaml.FromString(rs.ReadLengthPrefixedString(Encoding.UTF8, Connection.MaxOrderLength)); SlotClients = new Dictionary(); foreach (var s in slotClients) { @@ -144,7 +144,7 @@ namespace OpenRA.Network if (rs.Position != traitDataOffset || rs.ReadInt32() != TraitDataMarker) throw new InvalidDataException("Invalid orasav file"); - var traitData = MiniYaml.FromString(rs.ReadString(Encoding.UTF8, Connection.MaxOrderLength)); + var traitData = MiniYaml.FromString(rs.ReadLengthPrefixedString(Encoding.UTF8, Connection.MaxOrderLength)); foreach (var td in traitData) TraitData.Add(Exts.ParseInt32Invariant(td.Key), td.Value); @@ -229,7 +229,7 @@ namespace OpenRA.Network ordersStream.Write(data.Length + 8); ordersStream.Write(frame); ordersStream.Write(clientSlot); - ordersStream.WriteArray(data); + ordersStream.Write(data); LastOrdersFrame = frame; } @@ -294,17 +294,17 @@ namespace OpenRA.Network file.Write(lastSyncPacket, 0, Order.SyncHashOrderLength); var globalSettingsNodes = new List() { GlobalSettings.Serialize() }; - file.WriteString(Encoding.UTF8, globalSettingsNodes.WriteToString()); + file.WriteLengthPrefixedString(Encoding.UTF8, globalSettingsNodes.WriteToString()); var slotNodes = Slots .Select(s => s.Value.Serialize()) .ToList(); - file.WriteString(Encoding.UTF8, slotNodes.WriteToString()); + file.WriteLengthPrefixedString(Encoding.UTF8, slotNodes.WriteToString()); var slotClientNodes = SlotClients .Select(s => s.Value.Serialize(s.Key)) .ToList(); - file.WriteString(Encoding.UTF8, slotClientNodes.WriteToString()); + file.WriteLengthPrefixedString(Encoding.UTF8, slotClientNodes.WriteToString()); var traitDataOffset = file.Length; file.Write(TraitDataMarker); @@ -312,7 +312,7 @@ namespace OpenRA.Network var traitDataNodes = TraitData .Select(kv => new MiniYamlNode(kv.Key.ToStringInvariant(), kv.Value)) .ToList(); - file.WriteString(Encoding.UTF8, traitDataNodes.WriteToString()); + file.WriteLengthPrefixedString(Encoding.UTF8, traitDataNodes.WriteToString()); file.Write((int)ordersStream.Length); file.Write((int)traitDataOffset); diff --git a/OpenRA.Game/Network/OrderIO.cs b/OpenRA.Game/Network/OrderIO.cs index ee8e3933fb..afb587ab79 100644 --- a/OpenRA.Game/Network/OrderIO.cs +++ b/OpenRA.Game/Network/OrderIO.cs @@ -27,7 +27,7 @@ namespace OpenRA.Network // the Order objects directly on the local client. data = new MemoryStream(); foreach (var o in orders) - data.WriteArray(o.Serialize()); + data.Write(o.Serialize()); } public OrderPacket(MemoryStream data) @@ -86,7 +86,7 @@ namespace OpenRA.Network ms.Write(data.Frame); ms.WriteByte((byte)OrderType.SyncHash); ms.Write(data.SyncHash); - ms.WriteArray(BitConverter.GetBytes(data.DefeatState)); + ms.Write(data.DefeatState); return ms.GetBuffer(); } @@ -95,7 +95,7 @@ namespace OpenRA.Network var ms = new MemoryStream(14); ms.Write(0); ms.WriteByte((byte)OrderType.Ping); - ms.WriteArray(BitConverter.GetBytes(timestamp)); + ms.Write(timestamp); ms.WriteByte(queueLength); return ms.GetBuffer(); } diff --git a/OpenRA.Game/Network/ReplayRecorder.cs b/OpenRA.Game/Network/ReplayRecorder.cs index 736d8cb09c..c326643efc 100644 --- a/OpenRA.Game/Network/ReplayRecorder.cs +++ b/OpenRA.Game/Network/ReplayRecorder.cs @@ -67,7 +67,7 @@ namespace OpenRA.Network } } - file.WriteArray(initialContent); + file.Write(initialContent); writer = new BinaryWriter(file); } @@ -93,7 +93,7 @@ namespace OpenRA.Network { var ms = new MemoryStream(4 + data.Length); ms.Write(frame); - ms.WriteArray(data); + ms.Write(data); Receive(clientID, ms.GetBuffer()); } diff --git a/OpenRA.Game/Server/Connection.cs b/OpenRA.Game/Server/Connection.cs index a34c040662..e4a1b7d7d3 100644 --- a/OpenRA.Game/Server/Connection.cs +++ b/OpenRA.Game/Server/Connection.cs @@ -64,7 +64,7 @@ namespace OpenRA.Server ms.Write(0); ms.Write(0); ms.WriteByte((byte)OrderType.Ping); - ms.WriteArray(BitConverter.GetBytes(Game.RunTime)); + ms.Write(Game.RunTime); return ms.GetBuffer(); } diff --git a/OpenRA.Game/Server/Server.cs b/OpenRA.Game/Server/Server.cs index 84872f98b2..c8543cb667 100644 --- a/OpenRA.Game/Server/Server.cs +++ b/OpenRA.Game/Server/Server.cs @@ -707,7 +707,7 @@ namespace OpenRA.Server ms.Write(data.Length + 4); ms.Write(client); ms.Write(frame); - ms.WriteArray(data); + ms.Write(data); return ms.GetBuffer(); } diff --git a/OpenRA.Game/StreamExts.cs b/OpenRA.Game/StreamExts.cs index 289e99265f..fe3612bf67 100644 --- a/OpenRA.Game/StreamExts.cs +++ b/OpenRA.Game/StreamExts.cs @@ -20,10 +20,23 @@ namespace OpenRA { public static class StreamExts { + public static void ReadBytes(this Stream s, Span dest) + { + while (dest.Length > 0) + { + var bytesRead = s.Read(dest); + if (bytesRead == 0) + throw new EndOfStreamException(); + + dest = dest[bytesRead..]; + } + } + public static byte[] ReadBytes(this Stream s, int count) { if (count < 0) throw new ArgumentOutOfRangeException(nameof(count), "Non-negative number required."); + var buffer = new byte[count]; s.ReadBytes(buffer, 0, count); return buffer; @@ -33,10 +46,11 @@ namespace OpenRA { if (count < 0) throw new ArgumentOutOfRangeException(nameof(count), "Non-negative number required."); + while (count > 0) { - int bytesRead; - if ((bytesRead = s.Read(buffer, offset, count)) == 0) + var bytesRead = s.Read(buffer, offset, count); + if (bytesRead == 0) throw new EndOfStreamException(); offset += bytesRead; count -= bytesRead; @@ -48,6 +62,7 @@ namespace OpenRA var b = s.ReadByte(); if (b == -1) return -1; + s.Seek(-1, SeekOrigin.Current); return (byte)b; } @@ -57,52 +72,85 @@ namespace OpenRA var b = s.ReadByte(); if (b == -1) throw new EndOfStreamException(); + return (byte)b; } public static ushort ReadUInt16(this Stream s) { - return (ushort)(s.ReadUInt8() | s.ReadUInt8() << 8); + Span buffer = stackalloc byte[2]; + s.ReadBytes(buffer); + return BitConverter.ToUInt16(buffer); } public static short ReadInt16(this Stream s) { - return (short)(s.ReadUInt8() | s.ReadUInt8() << 8); + Span buffer = stackalloc byte[2]; + s.ReadBytes(buffer); + return BitConverter.ToInt16(buffer); } public static uint ReadUInt32(this Stream s) { - return (uint)(s.ReadUInt8() | s.ReadUInt8() << 8 | s.ReadUInt8() << 16 | s.ReadUInt8() << 24); + Span buffer = stackalloc byte[4]; + s.ReadBytes(buffer); + return BitConverter.ToUInt32(buffer); } public static int ReadInt32(this Stream s) { - return s.ReadUInt8() | s.ReadUInt8() << 8 | s.ReadUInt8() << 16 | s.ReadUInt8() << 24; + Span buffer = stackalloc byte[4]; + s.ReadBytes(buffer); + return BitConverter.ToInt32(buffer); } public static void Write(this Stream s, int value) { - s.WriteArray(BitConverter.GetBytes(value)); + Span buffer = stackalloc byte[4]; + BitConverter.TryWriteBytes(buffer, value); + s.Write(buffer); + } + + public static void Write(this Stream s, long value) + { + Span buffer = stackalloc byte[8]; + BitConverter.TryWriteBytes(buffer, value); + s.Write(buffer); + } + + public static void Write(this Stream s, ulong value) + { + Span buffer = stackalloc byte[8]; + BitConverter.TryWriteBytes(buffer, value); + s.Write(buffer); } public static void Write(this Stream s, float value) { - s.WriteArray(BitConverter.GetBytes(value)); + Span buffer = stackalloc byte[4]; + BitConverter.TryWriteBytes(buffer, value); + s.Write(buffer); } - public static float ReadFloat(this Stream s) + public static float ReadSingle(this Stream s) { - return BitConverter.ToSingle(s.ReadBytes(4), 0); + Span buffer = stackalloc byte[4]; + s.ReadBytes(buffer); + return BitConverter.ToSingle(buffer); } public static double ReadDouble(this Stream s) { - return BitConverter.ToDouble(s.ReadBytes(8), 0); + Span buffer = stackalloc byte[8]; + s.ReadBytes(buffer); + return BitConverter.ToDouble(buffer); } public static string ReadASCII(this Stream s, int length) { - return new string(Encoding.ASCII.GetChars(s.ReadBytes(length))); + Span buffer = length < 128 ? stackalloc byte[length] : new byte[length]; + s.ReadBytes(buffer); + return Encoding.ASCII.GetString(buffer); } public static string ReadASCIIZ(this Stream s) @@ -111,7 +159,12 @@ namespace OpenRA byte b; while ((b = s.ReadUInt8()) != 0) bytes.Add(b); - return new string(Encoding.ASCII.GetChars(bytes.ToArray())); + +#if NET5_0_OR_GREATER + return Encoding.ASCII.GetString(System.Runtime.InteropServices.CollectionsMarshal.AsSpan(bytes)); +#else + return Encoding.ASCII.GetString(bytes.ToArray()); +#endif } public static string ReadAllText(this Stream s) @@ -133,17 +186,11 @@ namespace OpenRA int count; while ((count = s.Read(buffer, 0, buffer.Length)) > 0) bytes.AddRange(buffer.Take(count)); + return bytes.ToArray(); } } - // Note: renamed from Write() to avoid being aliased by - // System.IO.Stream.Write(System.ReadOnlySpan) (which is not implemented in Mono) - public static void WriteArray(this Stream s, byte[] data) - { - s.Write(data, 0, data.Length); - } - public static IEnumerable ReadAllLines(this Stream s) { string line; @@ -210,19 +257,24 @@ namespace OpenRA } } - // The string is assumed to be length-prefixed, as written by WriteString() - public static string ReadString(this Stream s, Encoding encoding, int maxLength) + /// + /// The string is assumed to be length-prefixed, as written by . + /// + public static string ReadLengthPrefixedString(this Stream s, Encoding encoding, int maxLength) { var length = s.ReadInt32(); if (length > maxLength) throw new InvalidOperationException($"The length of the string ({length}) is longer than the maximum allowed ({maxLength})."); - return encoding.GetString(s.ReadBytes(length)); + Span buffer = length < 128 ? stackalloc byte[length] : new byte[length]; + s.ReadBytes(buffer); + return encoding.GetString(buffer); } - // Writes a length-prefixed string using the specified encoding and returns - // the number of bytes written. - public static int WriteString(this Stream s, Encoding encoding, string text) + /// + /// Writes a length-prefixed string using the specified encoding and returns the number of bytes written. + /// + public static int WriteLengthPrefixedString(this Stream s, Encoding encoding, string text) { byte[] bytes; @@ -232,7 +284,7 @@ namespace OpenRA bytes = Array.Empty(); s.Write(bytes.Length); - s.WriteArray(bytes); + s.Write(bytes); return 4 + bytes.Length; } diff --git a/OpenRA.Mods.Cnc/FileFormats/AudReader.cs b/OpenRA.Mods.Cnc/FileFormats/AudReader.cs index 442a8a0169..66a97ddf84 100644 --- a/OpenRA.Mods.Cnc/FileFormats/AudReader.cs +++ b/OpenRA.Mods.Cnc/FileFormats/AudReader.cs @@ -150,6 +150,8 @@ namespace OpenRA.Mods.Cnc.FileFormats { readonly int outputSize; int dataSize; + byte[] inputBuffer; + byte[] outputBuffer; public WestwoodCompressedAudStream(Stream stream, int outputSize, int dataSize) : base(stream) @@ -167,8 +169,10 @@ namespace OpenRA.Mods.Cnc.FileFormats var chunk = AudChunk.Read(baseStream); - var input = baseStream.ReadBytes(chunk.CompressedSize); - var output = new byte[chunk.OutputSize]; + 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) @@ -178,6 +182,13 @@ namespace OpenRA.Mods.Cnc.FileFormats return dataSize <= 0; } + + static Span EnsureArraySize(ref byte[] array, int desiredSize) + { + if (array == null || array.Length < desiredSize) + array = new byte[desiredSize]; + return array.AsSpan(..desiredSize); + } } } } diff --git a/OpenRA.Mods.Cnc/FileFormats/HvaReader.cs b/OpenRA.Mods.Cnc/FileFormats/HvaReader.cs index 282f989ac8..5cfae8bca7 100644 --- a/OpenRA.Mods.Cnc/FileFormats/HvaReader.cs +++ b/OpenRA.Mods.Cnc/FileFormats/HvaReader.cs @@ -45,7 +45,7 @@ namespace OpenRA.Mods.Cnc.FileFormats Transforms[c + 15] = 1; for (var k = 0; k < 12; k++) - Transforms[c + ids[k]] = s.ReadFloat(); + Transforms[c + ids[k]] = s.ReadSingle(); Array.Copy(Transforms, 16 * (LimbCount * j + i), testMatrix, 0, 16); if (Util.MatrixInverse(testMatrix) == null) diff --git a/OpenRA.Mods.Cnc/FileFormats/VqaVideo.cs b/OpenRA.Mods.Cnc/FileFormats/VqaVideo.cs index 091227fb5e..6b62ed82ed 100644 --- a/OpenRA.Mods.Cnc/FileFormats/VqaVideo.cs +++ b/OpenRA.Mods.Cnc/FileFormats/VqaVideo.cs @@ -199,16 +199,16 @@ namespace OpenRA.Mods.Cnc.FileFormats else if (AudioChannels == 1) { var rawAudio = stream.ReadBytes((int)length); - audio1.WriteArray(rawAudio); + audio1.Write(rawAudio); } else { var rawAudio = stream.ReadBytes((int)length / 2); - audio1.WriteArray(rawAudio); + audio1.Write(rawAudio); rawAudio = stream.ReadBytes((int)length / 2); - audio2.WriteArray(rawAudio); + audio2.Write(rawAudio); if (length % 2 != 0) - stream.ReadBytes(2); + stream.Position += 2; } compressed = type == "SND2"; @@ -216,7 +216,7 @@ namespace OpenRA.Mods.Cnc.FileFormats default: if (length + stream.Position > stream.Length) throw new NotSupportedException($"Vqa uses unknown Subtype: {type}"); - stream.ReadBytes((int)length); + stream.Position += length; break; } @@ -308,7 +308,7 @@ namespace OpenRA.Mods.Cnc.FileFormats break; default: // Don't parse sound here. - stream.ReadBytes((int)length); + stream.Position += length; break; } @@ -382,8 +382,7 @@ namespace OpenRA.Mods.Cnc.FileFormats // frame-modifier chunk case "CBP0": case "CBPZ": - var bytes = s.ReadBytes(subchunkLength); - bytes.CopyTo(cbp, chunkBufferOffset); + s.ReadBytes(cbp, chunkBufferOffset, subchunkLength); chunkBufferOffset += subchunkLength; currentChunkBuffer++; cbpIsCompressed = type == "CBPZ"; diff --git a/OpenRA.Mods.Cnc/FileFormats/VxlReader.cs b/OpenRA.Mods.Cnc/FileFormats/VxlReader.cs index a89cb11f0f..f09608de7a 100644 --- a/OpenRA.Mods.Cnc/FileFormats/VxlReader.cs +++ b/OpenRA.Mods.Cnc/FileFormats/VxlReader.cs @@ -141,12 +141,12 @@ namespace OpenRA.Mods.Cnc.FileFormats { limbDataOffset[i] = s.ReadUInt32(); s.Seek(8, SeekOrigin.Current); - Limbs[i].Scale = s.ReadFloat(); + 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.ReadFloat(); + Limbs[i].Bounds[j] = s.ReadSingle(); Limbs[i].Size = s.ReadBytes(3); Limbs[i].Type = (NormalType)s.ReadUInt8(); } diff --git a/OpenRA.Mods.Common/AudioLoaders/OggLoader.cs b/OpenRA.Mods.Common/AudioLoaders/OggLoader.cs index ec60bfbaee..2ad8e492f4 100644 --- a/OpenRA.Mods.Common/AudioLoaders/OggLoader.cs +++ b/OpenRA.Mods.Common/AudioLoaders/OggLoader.cs @@ -76,9 +76,7 @@ namespace OpenRA.Mods.Common.AudioLoaders { readonly OggFormat format; - // This buffer can be static because it can only be used by 1 instance per thread. - [ThreadStatic] - static float[] conversionBuffer; + float[] conversionBuffer; public OggStream(OggFormat format) { @@ -105,13 +103,10 @@ namespace OpenRA.Mods.Common.AudioLoaders // Make sure we don't have an odd count. count -= count % format.reader.Channels; - // Get the buffer, creating a new one if none exists or the existing one is too small. - var floatBuffer = conversionBuffer ??= new float[count]; - if (floatBuffer.Length < count) - floatBuffer = conversionBuffer = new float[count]; + var floatBuffer = EnsureArraySize(ref conversionBuffer, count); // Let NVorbis do the actual reading. - var samples = format.reader.ReadSamples(floatBuffer, offset, count); + var samples = format.reader.ReadSamples(floatBuffer); // Move the data back to the request buffer and convert to 16-bit signed samples for OpenAL. for (var i = 0; i < samples; i++) @@ -137,6 +132,13 @@ namespace OpenRA.Mods.Common.AudioLoaders base.Dispose(disposing); } + + static Span EnsureArraySize(ref float[] array, int desiredSize) + { + if (array == null || array.Length < desiredSize) + array = new float[desiredSize]; + return array.AsSpan(..desiredSize); + } } } } diff --git a/OpenRA.Mods.Common/FileFormats/ImaAdpcmReader.cs b/OpenRA.Mods.Common/FileFormats/ImaAdpcmReader.cs index 795c77fd3c..7e66a0c4e2 100644 --- a/OpenRA.Mods.Common/FileFormats/ImaAdpcmReader.cs +++ b/OpenRA.Mods.Common/FileFormats/ImaAdpcmReader.cs @@ -9,7 +9,7 @@ */ #endregion -using System.IO; +using System; namespace OpenRA.Mods.Common.FileFormats { @@ -56,15 +56,14 @@ namespace OpenRA.Mods.Common.FileFormats return (short)current; } - public static byte[] LoadImaAdpcmSound(byte[] raw, ref int index) + public static byte[] LoadImaAdpcmSound(ReadOnlySpan raw, ref int index) { var currentSample = 0; return LoadImaAdpcmSound(raw, ref index, ref currentSample); } - public static byte[] LoadImaAdpcmSound(byte[] raw, ref int index, ref int currentSample) + public static byte[] LoadImaAdpcmSound(ReadOnlySpan raw, ref int index, ref int currentSample) { - var s = new MemoryStream(raw); var dataSize = raw.Length; var outputSize = raw.Length * 4; @@ -73,7 +72,7 @@ namespace OpenRA.Mods.Common.FileFormats while (dataSize-- > 0) { - var b = s.ReadUInt8(); + var b = raw[offset / 4]; var t = DecodeImaAdpcmSample(b, ref index, ref currentSample); output[offset++] = (byte)t; diff --git a/OpenRA.Mods.Common/FileFormats/WavReader.cs b/OpenRA.Mods.Common/FileFormats/WavReader.cs index c11e8207ce..b664631fd7 100644 --- a/OpenRA.Mods.Common/FileFormats/WavReader.cs +++ b/OpenRA.Mods.Common/FileFormats/WavReader.cs @@ -67,12 +67,12 @@ namespace OpenRA.Mods.Common.FileFormats blockAlign = s.ReadInt16(); sampleBits = s.ReadInt16(); lengthInSeconds = (float)(s.Length * 8) / (channels * sampleRate * sampleBits); - s.ReadBytes(fmtChunkSize - 16); + s.Position += fmtChunkSize - 16; break; case "fact": var chunkSize = s.ReadInt32(); uncompressedSize = s.ReadInt32(); - s.ReadBytes(chunkSize - 4); + s.Position += chunkSize - 4; break; case "data": dataSize = s.ReadInt32(); @@ -82,7 +82,7 @@ namespace OpenRA.Mods.Common.FileFormats case "LIST": case "cue ": var listCueChunkSize = s.ReadInt32(); - s.ReadBytes(listCueChunkSize); + s.Position += listCueChunkSize; break; default: s.Position = s.Length; // Skip to end of stream @@ -156,12 +156,13 @@ namespace OpenRA.Mods.Common.FileFormats // Decode and output remaining data in this block var blockOffset = 0; + Span chunk = stackalloc byte[4]; while (blockOffset < blockDataSize) { for (var c = 0; c < channels; c++) { // Decode 4 bytes (to 16 bytes of output) per channel - var chunk = baseStream.ReadBytes(4); + baseStream.ReadBytes(chunk); var decoded = ImaAdpcmReader.LoadImaAdpcmSound(chunk, ref index[c], ref predictor[c]); // Interleave output, one sample per channel diff --git a/OpenRA.Mods.Common/FileFormats/WestwoodCompressedReader.cs b/OpenRA.Mods.Common/FileFormats/WestwoodCompressedReader.cs index 97b509ced5..d2ebf3c292 100644 --- a/OpenRA.Mods.Common/FileFormats/WestwoodCompressedReader.cs +++ b/OpenRA.Mods.Common/FileFormats/WestwoodCompressedReader.cs @@ -20,11 +20,11 @@ namespace OpenRA.Mods.Common.FileFormats static readonly int[] AudWsStepTable2 = { -2, -1, 0, 1 }; static readonly int[] AudWsStepTable4 = { -9, -8, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 8 }; - public static void DecodeWestwoodCompressedSample(byte[] input, byte[] output) + public static void DecodeWestwoodCompressedSample(ReadOnlySpan input, Span output) { if (input.Length == output.Length) { - Array.Copy(input, output, output.Length); + input.CopyTo(output); return; } diff --git a/OpenRA.Mods.Common/FileSystem/InstallShieldPackage.cs b/OpenRA.Mods.Common/FileSystem/InstallShieldPackage.cs index 21935c87e5..9941e1c32b 100644 --- a/OpenRA.Mods.Common/FileSystem/InstallShieldPackage.cs +++ b/OpenRA.Mods.Common/FileSystem/InstallShieldPackage.cs @@ -73,7 +73,7 @@ namespace OpenRA.Mods.Common.FileSystem var dirName = s.ReadASCII(nameLength); // Skip to the end of the chunk - s.ReadBytes(chunkSize - nameLength - 6); + s.Position += chunkSize - nameLength - 6; directories.Add(dirName, fileCount); }