Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of BundleSlimmer v1.1.0
patchers/BundleSlimmer.dll
Decompiled a week agousing System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using System.Threading; using System.Threading.Tasks; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using Microsoft.CodeAnalysis; using Mono.Cecil; using SevenZip.Compression.LZ; using SevenZip.Compression.LZMA; using SevenZip.Compression.RangeCoder; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("BundleSlimmer")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.1.0.0")] [assembly: AssemblyInformationalVersion("1.1.0")] [assembly: AssemblyProduct("BundleSlimmer")] [assembly: AssemblyTitle("BundleSlimmer")] [assembly: AssemblyVersion("1.1.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace SevenZip { internal class CRC { public static readonly uint[] Table; private uint _value = uint.MaxValue; static CRC() { Table = new uint[256]; for (uint num = 0u; num < 256; num++) { uint num2 = num; for (int i = 0; i < 8; i++) { num2 = (((num2 & 1) == 0) ? (num2 >> 1) : ((num2 >> 1) ^ 0xEDB88320u)); } Table[num] = num2; } } public void Init() { _value = uint.MaxValue; } public void UpdateByte(byte b) { _value = Table[(byte)_value ^ b] ^ (_value >> 8); } public void Update(byte[] data, uint offset, uint size) { for (uint num = 0u; num < size; num++) { _value = Table[(byte)_value ^ data[offset + num]] ^ (_value >> 8); } } public uint GetDigest() { return _value ^ 0xFFFFFFFFu; } private static uint CalculateDigest(byte[] data, uint offset, uint size) { CRC cRC = new CRC(); cRC.Update(data, offset, size); return cRC.GetDigest(); } private static bool VerifyDigest(uint digest, byte[] data, uint offset, uint size) { return CalculateDigest(data, offset, size) == digest; } } internal class DataErrorException : ApplicationException { public DataErrorException() : base("Data Error") { } } public interface ICodeProgress { void SetProgress(long inSize, long outSize); } public interface ICoder { void Code(Stream inStream, Stream outStream, long inSize, long outSize, ICodeProgress progress); } internal class InvalidParamException : ApplicationException { public InvalidParamException() : base("Invalid Parameter") { } } public interface ISetDecoderProperties { void SetDecoderProperties(byte[] properties); } } namespace SevenZip.Compression.LZ { public class OutWindow { private byte[] _buffer; private uint _pos; private uint _windowSize; private uint _streamPos; private Stream _stream; public uint TrainSize; public void Create(uint windowSize) { if (_windowSize != windowSize) { _buffer = new byte[windowSize]; } _windowSize = windowSize; _pos = 0u; _streamPos = 0u; } public void Init(Stream stream, bool solid) { ReleaseStream(); _stream = stream; if (!solid) { _streamPos = 0u; _pos = 0u; TrainSize = 0u; } } public bool Train(Stream stream) { long length = stream.Length; uint num = (TrainSize = (uint)((length < _windowSize) ? length : _windowSize)); stream.Position = length - num; _streamPos = (_pos = 0u); while (num != 0) { uint num2 = _windowSize - _pos; if (num < num2) { num2 = num; } int num3 = stream.Read(_buffer, (int)_pos, (int)num2); if (num3 == 0) { return false; } num -= (uint)num3; _pos += (uint)num3; _streamPos += (uint)num3; if (_pos == _windowSize) { _streamPos = (_pos = 0u); } } return true; } public void ReleaseStream() { Flush(); _stream = null; } public void Flush() { uint num = _pos - _streamPos; if (num != 0) { _stream.Write(_buffer, (int)_streamPos, (int)num); if (_pos >= _windowSize) { _pos = 0u; } _streamPos = _pos; } } public void CopyBlock(uint distance, uint len) { uint num = _pos - distance - 1; if (num >= _windowSize) { num += _windowSize; } while (len != 0) { if (num >= _windowSize) { num = 0u; } _buffer[_pos++] = _buffer[num++]; if (_pos >= _windowSize) { Flush(); } len--; } } public void PutByte(byte b) { _buffer[_pos++] = b; if (_pos >= _windowSize) { Flush(); } } public byte GetByte(uint distance) { uint num = _pos - distance - 1; if (num >= _windowSize) { num += _windowSize; } return _buffer[num]; } } } namespace SevenZip.Compression.RangeCoder { internal struct BitDecoder { public const int kNumBitModelTotalBits = 11; public const uint kBitModelTotal = 2048u; private const int kNumMoveBits = 5; private uint Prob; public void UpdateModel(int numMoveBits, uint symbol) { if (symbol == 0) { Prob += 2048 - Prob >> numMoveBits; } else { Prob -= Prob >> numMoveBits; } } public void Init() { Prob = 1024u; } public uint Decode(Decoder rangeDecoder) { uint num = (rangeDecoder.Range >> 11) * Prob; if (rangeDecoder.Code < num) { rangeDecoder.Range = num; Prob += 2048 - Prob >> 5; if (rangeDecoder.Range < 16777216) { rangeDecoder.Code = (rangeDecoder.Code << 8) | rangeDecoder.NextByte(); rangeDecoder.Range <<= 8; } return 0u; } rangeDecoder.Range -= num; rangeDecoder.Code -= num; Prob -= Prob >> 5; if (rangeDecoder.Range < 16777216) { rangeDecoder.Code = (rangeDecoder.Code << 8) | rangeDecoder.NextByte(); rangeDecoder.Range <<= 8; } return 1u; } } internal struct BitTreeDecoder { private BitDecoder[] Models; private int NumBitLevels; public BitTreeDecoder(int numBitLevels) { NumBitLevels = numBitLevels; Models = new BitDecoder[1 << numBitLevels]; } public void Init() { for (uint num = 1u; num < 1 << NumBitLevels; num++) { Models[num].Init(); } } public uint Decode(Decoder rangeDecoder) { uint num = 1u; for (int num2 = NumBitLevels; num2 > 0; num2--) { num = (num << 1) + Models[num].Decode(rangeDecoder); } return num - (uint)(1 << NumBitLevels); } public uint ReverseDecode(Decoder rangeDecoder) { uint num = 1u; uint num2 = 0u; for (int i = 0; i < NumBitLevels; i++) { uint num3 = Models[num].Decode(rangeDecoder); num <<= 1; num += num3; num2 |= num3 << i; } return num2; } public static uint ReverseDecode(BitDecoder[] Models, uint startIndex, Decoder rangeDecoder, int NumBitLevels) { uint num = 1u; uint num2 = 0u; for (int i = 0; i < NumBitLevels; i++) { uint num3 = Models[startIndex + num].Decode(rangeDecoder); num <<= 1; num += num3; num2 |= num3 << i; } return num2; } } internal class Decoder { public const uint kTopValue = 16777216u; public uint Range; public uint Code; public Stream Stream; private readonly byte[] _in = new byte[65536]; private int _inPos; private int _inLen; [MethodImpl(MethodImplOptions.AggressiveInlining)] public byte NextByte() { if (_inPos == _inLen) { _inLen = Stream.Read(_in, 0, _in.Length); _inPos = 0; if (_inLen <= 0) { _inLen = 0; return byte.MaxValue; } } return _in[_inPos++]; } public void Init(Stream stream) { Stream = stream; _inPos = (_inLen = 0); Code = 0u; Range = uint.MaxValue; for (int i = 0; i < 5; i++) { Code = (Code << 8) | NextByte(); } } public void ReleaseStream() { Stream = null; } public void CloseStream() { Stream.Close(); } public void Normalize() { while (Range < 16777216) { Code = (Code << 8) | NextByte(); Range <<= 8; } } public void Normalize2() { if (Range < 16777216) { Code = (Code << 8) | NextByte(); Range <<= 8; } } public uint GetThreshold(uint total) { return Code / (Range /= total); } public void Decode(uint start, uint size, uint total) { Code -= start * Range; Range *= size; Normalize(); } public uint DecodeDirectBits(int numTotalBits) { uint num = Range; uint num2 = Code; uint num3 = 0u; for (int num4 = numTotalBits; num4 > 0; num4--) { num >>= 1; uint num5 = num2 - num >> 31; num2 -= num & (num5 - 1); num3 = (num3 << 1) | (1 - num5); if (num < 16777216) { num2 = (num2 << 8) | NextByte(); num <<= 8; } } Range = num; Code = num2; return num3; } public uint DecodeBit(uint size0, int numTotalBits) { uint num = (Range >> numTotalBits) * size0; uint result; if (Code < num) { result = 0u; Range = num; } else { result = 1u; Code -= num; Range -= num; } Normalize(); return result; } } } namespace SevenZip.Compression.LZMA { internal abstract class Base { public struct State { public uint Index; public void Init() { Index = 0u; } public void UpdateChar() { if (Index < 4) { Index = 0u; } else if (Index < 10) { Index -= 3u; } else { Index -= 6u; } } public void UpdateMatch() { Index = ((Index < 7) ? 7u : 10u); } public void UpdateRep() { Index = ((Index < 7) ? 8u : 11u); } public void UpdateShortRep() { Index = ((Index < 7) ? 9u : 11u); } public bool IsCharState() { return Index < 7; } } public const uint kNumRepDistances = 4u; public const uint kNumStates = 12u; public const int kNumPosSlotBits = 6; public const int kDicLogSizeMin = 0; public const int kNumLenToPosStatesBits = 2; public const uint kNumLenToPosStates = 4u; public const uint kMatchMinLen = 2u; public const int kNumAlignBits = 4; public const uint kAlignTableSize = 16u; public const uint kAlignMask = 15u; public const uint kStartPosModelIndex = 4u; public const uint kEndPosModelIndex = 14u; public const uint kNumPosModels = 10u; public const uint kNumFullDistances = 128u; public const uint kNumLitPosStatesBitsEncodingMax = 4u; public const uint kNumLitContextBitsMax = 8u; public const int kNumPosStatesBitsMax = 4; public const uint kNumPosStatesMax = 16u; public const int kNumPosStatesBitsEncodingMax = 4; public const uint kNumPosStatesEncodingMax = 16u; public const int kNumLowLenBits = 3; public const int kNumMidLenBits = 3; public const int kNumHighLenBits = 8; public const uint kNumLowLenSymbols = 8u; public const uint kNumMidLenSymbols = 8u; public const uint kNumLenSymbols = 272u; public const uint kMatchMaxLen = 273u; public static uint GetLenToPosState(uint len) { len -= 2; if (len < 4) { return len; } return 3u; } } public class Decoder : ICoder, ISetDecoderProperties { private class LenDecoder { private BitDecoder m_Choice; private BitDecoder m_Choice2; private BitTreeDecoder[] m_LowCoder = new BitTreeDecoder[16]; private BitTreeDecoder[] m_MidCoder = new BitTreeDecoder[16]; private BitTreeDecoder m_HighCoder = new BitTreeDecoder(8); private uint m_NumPosStates; public void Create(uint numPosStates) { for (uint num = m_NumPosStates; num < numPosStates; num++) { m_LowCoder[num] = new BitTreeDecoder(3); m_MidCoder[num] = new BitTreeDecoder(3); } m_NumPosStates = numPosStates; } public void Init() { m_Choice.Init(); for (uint num = 0u; num < m_NumPosStates; num++) { m_LowCoder[num].Init(); m_MidCoder[num].Init(); } m_Choice2.Init(); m_HighCoder.Init(); } public uint Decode(SevenZip.Compression.RangeCoder.Decoder rangeDecoder, uint posState) { if (m_Choice.Decode(rangeDecoder) == 0) { return m_LowCoder[posState].Decode(rangeDecoder); } uint num = 8u; if (m_Choice2.Decode(rangeDecoder) == 0) { return num + m_MidCoder[posState].Decode(rangeDecoder); } num += 8; return num + m_HighCoder.Decode(rangeDecoder); } } private class LiteralDecoder { private struct Decoder2 { private BitDecoder[] m_Decoders; public void Create() { m_Decoders = new BitDecoder[768]; } public void Init() { for (int i = 0; i < 768; i++) { m_Decoders[i].Init(); } } public byte DecodeNormal(SevenZip.Compression.RangeCoder.Decoder rangeDecoder) { uint num = 1u; do { num = (num << 1) | m_Decoders[num].Decode(rangeDecoder); } while (num < 256); return (byte)num; } public byte DecodeWithMatchByte(SevenZip.Compression.RangeCoder.Decoder rangeDecoder, byte matchByte) { uint num = 1u; do { uint num2 = (uint)((matchByte >> 7) & 1); matchByte <<= 1; uint num3 = m_Decoders[(1 + num2 << 8) + num].Decode(rangeDecoder); num = (num << 1) | num3; if (num2 != num3) { while (num < 256) { num = (num << 1) | m_Decoders[num].Decode(rangeDecoder); } break; } } while (num < 256); return (byte)num; } } private Decoder2[] m_Coders; private int m_NumPrevBits; private int m_NumPosBits; private uint m_PosMask; public void Create(int numPosBits, int numPrevBits) { if (m_Coders == null || m_NumPrevBits != numPrevBits || m_NumPosBits != numPosBits) { m_NumPosBits = numPosBits; m_PosMask = (uint)((1 << numPosBits) - 1); m_NumPrevBits = numPrevBits; uint num = (uint)(1 << m_NumPrevBits + m_NumPosBits); m_Coders = new Decoder2[num]; for (uint num2 = 0u; num2 < num; num2++) { m_Coders[num2].Create(); } } } public void Init() { uint num = (uint)(1 << m_NumPrevBits + m_NumPosBits); for (uint num2 = 0u; num2 < num; num2++) { m_Coders[num2].Init(); } } private uint GetState(uint pos, byte prevByte) { return ((pos & m_PosMask) << m_NumPrevBits) + (uint)(prevByte >> 8 - m_NumPrevBits); } public byte DecodeNormal(SevenZip.Compression.RangeCoder.Decoder rangeDecoder, uint pos, byte prevByte) { return m_Coders[GetState(pos, prevByte)].DecodeNormal(rangeDecoder); } public byte DecodeWithMatchByte(SevenZip.Compression.RangeCoder.Decoder rangeDecoder, uint pos, byte prevByte, byte matchByte) { return m_Coders[GetState(pos, prevByte)].DecodeWithMatchByte(rangeDecoder, matchByte); } } private OutWindow m_OutWindow = new OutWindow(); private SevenZip.Compression.RangeCoder.Decoder m_RangeDecoder = new SevenZip.Compression.RangeCoder.Decoder(); private BitDecoder[] m_IsMatchDecoders = new BitDecoder[192]; private BitDecoder[] m_IsRepDecoders = new BitDecoder[12]; private BitDecoder[] m_IsRepG0Decoders = new BitDecoder[12]; private BitDecoder[] m_IsRepG1Decoders = new BitDecoder[12]; private BitDecoder[] m_IsRepG2Decoders = new BitDecoder[12]; private BitDecoder[] m_IsRep0LongDecoders = new BitDecoder[192]; private BitTreeDecoder[] m_PosSlotDecoder = new BitTreeDecoder[4]; private BitDecoder[] m_PosDecoders = new BitDecoder[114]; private BitTreeDecoder m_PosAlignDecoder = new BitTreeDecoder(4); private LenDecoder m_LenDecoder = new LenDecoder(); private LenDecoder m_RepLenDecoder = new LenDecoder(); private LiteralDecoder m_LiteralDecoder = new LiteralDecoder(); private uint m_DictionarySize; private uint m_DictionarySizeCheck; private uint m_PosStateMask; private bool _solid; public Decoder() { m_DictionarySize = uint.MaxValue; for (int i = 0; (long)i < 4L; i++) { m_PosSlotDecoder[i] = new BitTreeDecoder(6); } } private void SetDictionarySize(uint dictionarySize) { if (m_DictionarySize != dictionarySize) { m_DictionarySize = dictionarySize; m_DictionarySizeCheck = Math.Max(m_DictionarySize, 1u); uint windowSize = Math.Max(m_DictionarySizeCheck, 4096u); m_OutWindow.Create(windowSize); } } private void SetLiteralProperties(int lp, int lc) { if (lp > 8) { throw new InvalidParamException(); } if (lc > 8) { throw new InvalidParamException(); } m_LiteralDecoder.Create(lp, lc); } private void SetPosBitsProperties(int pb) { if (pb > 4) { throw new InvalidParamException(); } uint num = (uint)(1 << pb); m_LenDecoder.Create(num); m_RepLenDecoder.Create(num); m_PosStateMask = num - 1; } private void Init(Stream inStream, Stream outStream) { m_RangeDecoder.Init(inStream); m_OutWindow.Init(outStream, _solid); for (uint num = 0u; num < 12; num++) { for (uint num2 = 0u; num2 <= m_PosStateMask; num2++) { uint num3 = (num << 4) + num2; m_IsMatchDecoders[num3].Init(); m_IsRep0LongDecoders[num3].Init(); } m_IsRepDecoders[num].Init(); m_IsRepG0Decoders[num].Init(); m_IsRepG1Decoders[num].Init(); m_IsRepG2Decoders[num].Init(); } m_LiteralDecoder.Init(); for (uint num4 = 0u; num4 < 4; num4++) { m_PosSlotDecoder[num4].Init(); } for (uint num5 = 0u; num5 < 114; num5++) { m_PosDecoders[num5].Init(); } m_LenDecoder.Init(); m_RepLenDecoder.Init(); m_PosAlignDecoder.Init(); } public void Code(Stream inStream, Stream outStream, long inSize, long outSize, ICodeProgress progress) { Init(inStream, outStream); Base.State state = default(Base.State); state.Init(); uint num = 0u; uint num2 = 0u; uint num3 = 0u; uint num4 = 0u; ulong num5 = 0uL; if (num5 < (ulong)outSize) { if (m_IsMatchDecoders[state.Index << 4].Decode(m_RangeDecoder) != 0) { throw new DataErrorException(); } state.UpdateChar(); byte b = m_LiteralDecoder.DecodeNormal(m_RangeDecoder, 0u, 0); m_OutWindow.PutByte(b); num5++; } while (num5 < (ulong)outSize) { uint num6 = (uint)(int)num5 & m_PosStateMask; if (m_IsMatchDecoders[(state.Index << 4) + num6].Decode(m_RangeDecoder) == 0) { byte prevByte = m_OutWindow.GetByte(0u); byte b2 = (state.IsCharState() ? m_LiteralDecoder.DecodeNormal(m_RangeDecoder, (uint)num5, prevByte) : m_LiteralDecoder.DecodeWithMatchByte(m_RangeDecoder, (uint)num5, prevByte, m_OutWindow.GetByte(num))); m_OutWindow.PutByte(b2); state.UpdateChar(); num5++; continue; } uint num8; if (m_IsRepDecoders[state.Index].Decode(m_RangeDecoder) == 1) { if (m_IsRepG0Decoders[state.Index].Decode(m_RangeDecoder) == 0) { if (m_IsRep0LongDecoders[(state.Index << 4) + num6].Decode(m_RangeDecoder) == 0) { state.UpdateShortRep(); m_OutWindow.PutByte(m_OutWindow.GetByte(num)); num5++; continue; } } else { uint num7; if (m_IsRepG1Decoders[state.Index].Decode(m_RangeDecoder) == 0) { num7 = num2; } else { if (m_IsRepG2Decoders[state.Index].Decode(m_RangeDecoder) == 0) { num7 = num3; } else { num7 = num4; num4 = num3; } num3 = num2; } num2 = num; num = num7; } num8 = m_RepLenDecoder.Decode(m_RangeDecoder, num6) + 2; state.UpdateRep(); } else { num4 = num3; num3 = num2; num2 = num; num8 = 2 + m_LenDecoder.Decode(m_RangeDecoder, num6); state.UpdateMatch(); uint num9 = m_PosSlotDecoder[Base.GetLenToPosState(num8)].Decode(m_RangeDecoder); if (num9 >= 4) { int num10 = (int)((num9 >> 1) - 1); num = (2 | (num9 & 1)) << num10; if (num9 < 14) { num += BitTreeDecoder.ReverseDecode(m_PosDecoders, num - num9 - 1, m_RangeDecoder, num10); } else { num += m_RangeDecoder.DecodeDirectBits(num10 - 4) << 4; num += m_PosAlignDecoder.ReverseDecode(m_RangeDecoder); } } else { num = num9; } } if (num >= m_OutWindow.TrainSize + num5 || num >= m_DictionarySizeCheck) { if (num == uint.MaxValue) { break; } throw new DataErrorException(); } m_OutWindow.CopyBlock(num, num8); num5 += num8; } m_OutWindow.Flush(); m_OutWindow.ReleaseStream(); m_RangeDecoder.ReleaseStream(); } public void SetDecoderProperties(byte[] properties) { if (properties.Length < 5) { throw new InvalidParamException(); } int lc = properties[0] % 9; int num = properties[0] / 9; int lp = num % 5; int num2 = num / 5; if (num2 > 4) { throw new InvalidParamException(); } uint num3 = 0u; for (int i = 0; i < 4; i++) { num3 += (uint)(properties[1 + i] << i * 8); } SetDictionarySize(num3); SetLiteralProperties(lp, lc); SetPosBitsProperties(num2); } public bool Train(Stream stream) { _solid = true; return m_OutWindow.Train(stream); } } } namespace BundleSlimmer { internal static class AudioPatch { private class Reader { private readonly FileStream f; private readonly long origin; private readonly byte[] b = new byte[8]; internal bool BigEndian; internal Reader(FileStream f, long origin) { this.f = f; this.origin = origin; f.Position = origin; } private byte[] Take(int n) { if (f.Read(b, 0, n) != n) { throw new EndOfStreamException(); } if (BigEndian != !BitConverter.IsLittleEndian) { Array.Reverse(b, 0, n); } return b; } internal byte U8() { return Take(1)[0]; } internal short I16() { return BitConverter.ToInt16(Take(2), 0); } internal int I32() { return BitConverter.ToInt32(Take(4), 0); } internal uint U32() { return BitConverter.ToUInt32(Take(4), 0); } internal long I64() { return BitConverter.ToInt64(Take(8), 0); } internal void Skip(long n) { f.Position += n; } internal void Align4() { f.Position = origin + ((f.Position - origin + 3) & -4); } internal string CString() { StringBuilder stringBuilder = new StringBuilder(); int num; while ((num = f.ReadByte()) > 0) { stringBuilder.Append((char)num); } return stringBuilder.ToString(); } } private const int AudioClipClassId = 83; private const int DecompressOnLoad = 0; private const int CompressedInMemory = 1; private const int FormatPcm = 0; internal static int Patch(FileStream file, long start, long size) { Reader reader = new Reader(file, start); reader.BigEndian = true; reader.U32(); reader.U32(); uint num = reader.U32(); long num2 = reader.U32(); if (num < 17 || num > 22) { return 0; } bool flag = reader.U8() != 0; reader.Skip(3L); if (num >= 22) { reader.U32(); reader.I64(); num2 = reader.I64(); reader.I64(); } reader.BigEndian = flag; reader.CString(); reader.I32(); bool flag2 = reader.U8() != 0; int num3 = reader.I32(); int[] array = new int[num3]; for (int i = 0; i < num3; i++) { int num4 = (array[i] = reader.I32()); reader.U8(); reader.I16(); if (num4 == 114) { reader.Skip(16L); } reader.Skip(16L); if (flag2) { int num5 = reader.I32(); int num6 = reader.I32(); reader.Skip((long)num5 * (long)((num >= 19) ? 32 : 24) + num6); if (num >= 21) { reader.Skip(4L * (long)reader.I32()); } } } int num7 = reader.I32(); int num8 = 0; byte[] buf = new byte[8]; for (int j = 0; j < num7; j++) { reader.Align4(); reader.I64(); long num9 = ((num >= 22) ? reader.I64() : reader.U32()); long num10 = reader.U32(); int num11 = reader.I32(); if (num11 < 0 || num11 >= num3 || array[num11] != 83) { continue; } long num12 = start + num2 + num9; if (num9 + num10 > size - num2 || num10 < 16) { continue; } long position = file.Position; int num13 = ReadInt(file, num12, buf, flag); long num14 = num12 + 4 + ((num13 + 3) & -4); if (num13 < 0 || num14 + 4 > num12 + num10) { file.Position = position; continue; } int num15 = ReadInt(file, num14, buf, flag); int num16 = ReadInt(file, num12 + num10 - 4, buf, flag); if (num15 == 0 && num16 != 0) { WriteInt(file, num14, 1, flag); num8++; } file.Position = position; } return num8; } private static int ReadInt(FileStream f, long pos, byte[] buf, bool big) { f.Position = pos; if (f.Read(buf, 0, 4) != 4) { throw new EndOfStreamException(); } if (!big) { return BitConverter.ToInt32(buf, 0); } return (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3]; } private static void WriteInt(FileStream f, long pos, int v, bool big) { byte[] bytes = BitConverter.GetBytes(v); if (big) { Array.Reverse(bytes); } f.Position = pos; f.Write(bytes, 0, 4); } } internal static class Converter { internal class Bundle { internal uint Version; internal string UnityVersion; internal string Revision; internal uint Flags; internal long DataStart; internal uint[] BlockUncompressed; internal uint[] BlockCompressed; internal ushort[] BlockFlags; internal byte[] Nodes; internal bool HasLzma { get { ushort[] blockFlags = BlockFlags; for (int i = 0; i < blockFlags.Length; i++) { if ((blockFlags[i] & 0x3F) == 1) { return true; } } return false; } } internal long UncompressedSize { get { long num = 0L; uint[] blockUncompressed = BlockUncompressed; foreach (uint num2 in blockUncompressed) { num += num2; } return num; } } } internal class Result { internal bool Rewritten; internal int ClipsFlipped; internal uint Checksum; internal long RawSize; } private struct Block { internal uint Uncompressed; internal uint Compressed; internal ushort Flags; } private class ChunkWriter : Stream { private readonly Stream output; private readonly byte[] buffer = new byte[131072]; private readonly byte[] packed = new byte[Lz4.MaxOutput(131072)]; private readonly int[] table = new int[65536]; private int filled; internal readonly List<Block> Blocks = new List<Block>(); internal long Total; internal uint Checksum = uint.MaxValue; public override bool CanRead => false; public override bool CanSeek => false; public override bool CanWrite => true; public override long Length => Total; public override long Position { get { return Total; } set { throw new NotSupportedException(); } } internal ChunkWriter(Stream output) { this.output = output; } public override void Write(byte[] data, int offset, int count) { Checksum = Crc.Update(Checksum, data, offset, count); Total += count; while (count > 0) { int num = Math.Min(count, 131072 - filled); Buffer.BlockCopy(data, offset, buffer, filled, num); filled += num; offset += num; count -= num; if (filled == 131072) { Flush(); } } } public override void WriteByte(byte value) { Write(new byte[1] { value }, 0, 1); } public override void Flush() { if (filled != 0) { int num = Lz4.Compress(buffer, filled, packed, table); if (num < filled) { output.Write(packed, 0, num); Blocks.Add(new Block { Uncompressed = (uint)filled, Compressed = (uint)num, Flags = 2 }); } else { output.Write(buffer, 0, filled); Blocks.Add(new Block { Uncompressed = (uint)filled, Compressed = (uint)filled, Flags = 0 }); } filled = 0; } } internal void Finish() { Flush(); } public override int Read(byte[] b, int o, int c) { throw new NotSupportedException(); } public override long Seek(long o, SeekOrigin so) { throw new NotSupportedException(); } public override void SetLength(long v) { throw new NotSupportedException(); } } private static class Crc { internal const uint Init = uint.MaxValue; private static readonly uint[] Table = MakeTable(); private static uint[] MakeTable() { uint[] array = new uint[256]; for (uint num = 0u; num < 256; num++) { uint num2 = num; for (int i = 0; i < 8; i++) { num2 = (((num2 & 1) != 0) ? (0xEDB88320u ^ (num2 >> 1)) : (num2 >> 1)); } array[num] = num2; } return array; } internal static uint Update(uint crc, byte[] data, int offset, int count) { for (int i = offset; i < offset + count; i++) { crc = Table[(crc ^ data[i]) & 0xFF] ^ (crc >> 8); } return crc; } } private const int Chunk = 131072; private const uint FlagCompressionMask = 63u; private const uint FlagBlocksInfoAtEnd = 128u; private const uint FlagPaddingAtStart = 512u; private const uint OutFlags = 194u; private const ushort BlockLz4 = 2; internal static bool LooksLikeBundle(string path) { byte[] array = new byte[8]; using FileStream fileStream = File.OpenRead(path); return fileStream.Read(array, 0, 8) == 8 && Encoding.ASCII.GetString(array) == "UnityFS\0"; } internal static Bundle Read(Stream s) { Bundle bundle = new Bundle(); if (ReadString(s) != "UnityFS") { throw new InvalidDataException("not a UnityFS bundle"); } bundle.Version = ReadU32(s); bundle.UnityVersion = ReadString(s); bundle.Revision = ReadString(s); ReadI64(s); uint num = ReadU32(s); uint rawSize = ReadU32(s); bundle.Flags = ReadU32(s); if (bundle.Version >= 7) { Align16(s); } long position = s.Position; if ((bundle.Flags & 0x80) != 0) { s.Position = s.Length - num; } byte[] array = Decode(ReadExact(s, (int)num), bundle.Flags & 0x3F, (int)rawSize); if ((bundle.Flags & 0x80) != 0) { bundle.DataStart = position; } else { bundle.DataStart = position + num; } if ((bundle.Flags & 0x200) != 0) { bundle.DataStart = (bundle.DataStart + 15) & -16; } MemoryStream memoryStream = new MemoryStream(array) { Position = 16L }; int num2 = (int)ReadU32(memoryStream); bundle.BlockUncompressed = new uint[num2]; bundle.BlockCompressed = new uint[num2]; bundle.BlockFlags = new ushort[num2]; for (int i = 0; i < num2; i++) { bundle.BlockUncompressed[i] = ReadU32(memoryStream); bundle.BlockCompressed[i] = ReadU32(memoryStream); bundle.BlockFlags[i] = (ushort)((memoryStream.ReadByte() << 8) | memoryStream.ReadByte()); } bundle.Nodes = new byte[array.Length - memoryStream.Position]; Array.Copy(array, memoryStream.Position, bundle.Nodes, 0L, bundle.Nodes.Length); return bundle; } internal static Result Convert(string src, string dst, bool fixAudio) { Result result = new Result(); string path = dst + ".raw"; try { Bundle bundle; using (FileStream fileStream = new FileStream(src, FileMode.Open, FileAccess.Read, FileShare.Read, 1048576)) { using FileStream fileStream2 = new FileStream(path, FileMode.Create, FileAccess.ReadWrite, FileShare.None, 1048576); bundle = Read(fileStream); result.RawSize = bundle.UncompressedSize; Unpack(bundle, fileStream, fileStream2); if (fileStream2.Length != bundle.UncompressedSize) { throw new InvalidDataException($"data size {fileStream2.Length} != {bundle.UncompressedSize}"); } if (fixAudio) { foreach (KeyValuePair<long, long> item in SerializedNodes(bundle)) { result.ClipsFlipped += AudioPatch.Patch(fileStream2, item.Key, item.Value); } } } if (!bundle.HasLzma && result.ClipsFlipped == 0) { return result; } using (FileStream fileStream3 = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, 1048576)) { using FileStream fileStream4 = new FileStream(dst, FileMode.Create, FileAccess.ReadWrite, FileShare.None, 1048576); WriteHeader(fileStream4, bundle, 0L, 0u, 0u); ChunkWriter chunkWriter = new ChunkWriter(fileStream4); byte[] array = new byte[1048576]; int count; while ((count = fileStream3.Read(array, 0, array.Length)) > 0) { chunkWriter.Write(array, 0, count); } chunkWriter.Finish(); MemoryStream memoryStream = new MemoryStream(); memoryStream.Write(new byte[16], 0, 16); WriteU32(memoryStream, (uint)chunkWriter.Blocks.Count); foreach (Block block in chunkWriter.Blocks) { WriteU32(memoryStream, block.Uncompressed); WriteU32(memoryStream, block.Compressed); memoryStream.WriteByte((byte)(block.Flags >> 8)); memoryStream.WriteByte((byte)block.Flags); } memoryStream.Write(bundle.Nodes, 0, bundle.Nodes.Length); byte[] array2 = memoryStream.ToArray(); byte[] array3 = new byte[Lz4.MaxOutput(array2.Length)]; int num = Lz4.Compress(array2, array2.Length, array3, new int[65536]); fileStream4.Write(array3, 0, num); long position = fileStream4.Position; fileStream4.Position = 0L; WriteHeader(fileStream4, bundle, position, (uint)num, (uint)array2.Length); result.Checksum = chunkWriter.Checksum; } result.Rewritten = true; return result; } finally { try { File.Delete(path); } catch { } } } private static void Unpack(Bundle b, Stream input, Stream raw) { long num = b.DataStart; for (int i = 0; i < b.BlockFlags.Length; i++) { input.Position = num; num += b.BlockCompressed[i]; uint num2 = (uint)(b.BlockFlags[i] & 0x3F); if (num2 == 1) { byte[] decoderProperties = ReadExact(input, 5); SevenZip.Compression.LZMA.Decoder decoder = new SevenZip.Compression.LZMA.Decoder(); decoder.SetDecoderProperties(decoderProperties); decoder.Code(input, raw, b.BlockCompressed[i] - 5, b.BlockUncompressed[i], null); } else { byte[] array = Decode(ReadExact(input, (int)b.BlockCompressed[i]), num2, (int)b.BlockUncompressed[i]); raw.Write(array, 0, array.Length); } } } private static IEnumerable<KeyValuePair<long, long>> SerializedNodes(Bundle b) { MemoryStream r = new MemoryStream(b.Nodes); int count = (int)ReadU32(r); for (int i = 0; i < count; i++) { long key = ReadI64(r); long value = ReadI64(r); uint num = ReadU32(r); ReadString(r); if ((num & 4) != 0) { yield return new KeyValuePair<long, long>(key, value); } } } internal static void Verify(string path, uint expectedChecksum, long expectedSize) { using FileStream fileStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, 1048576); Bundle bundle = Read(fileStream); if (bundle.HasLzma) { throw new InvalidDataException("still LZMA"); } uint num = uint.MaxValue; long num2 = 0L; fileStream.Position = bundle.DataStart; for (int i = 0; i < bundle.BlockFlags.Length; i++) { byte[] array = Decode(ReadExact(fileStream, (int)bundle.BlockCompressed[i]), (uint)(bundle.BlockFlags[i] & 0x3F), (int)bundle.BlockUncompressed[i]); num = Crc.Update(num, array, 0, array.Length); num2 += array.Length; } if (num2 != expectedSize || num != expectedChecksum) { throw new InvalidDataException("converted data does not match the original"); } } private static void WriteHeader(Stream s, Bundle b, long size, uint packedInfo, uint rawInfo) { WriteString(s, "UnityFS"); WriteU32(s, b.Version); WriteString(s, b.UnityVersion); WriteString(s, b.Revision); WriteU32(s, (uint)(size >> 32)); WriteU32(s, (uint)size); WriteU32(s, packedInfo); WriteU32(s, rawInfo); WriteU32(s, 194u); if (b.Version >= 7) { while (s.Position % 16 != 0L) { s.WriteByte(0); } } } private static byte[] Decode(byte[] packed, uint kind, int rawSize) { switch (kind) { case 0u: return packed; case 1u: { SevenZip.Compression.LZMA.Decoder decoder = new SevenZip.Compression.LZMA.Decoder(); byte[] array2 = new byte[5]; Array.Copy(packed, array2, 5); decoder.SetDecoderProperties(array2); MemoryStream memoryStream = new MemoryStream(rawSize); decoder.Code(new MemoryStream(packed, 5, packed.Length - 5), memoryStream, packed.Length - 5, rawSize, null); return memoryStream.ToArray(); } case 2u: case 3u: { byte[] array = new byte[rawSize]; Lz4.Decompress(packed, 0, packed.Length, array, rawSize); return array; } default: throw new NotSupportedException("unknown bundle compression " + kind); } } private static byte[] ReadExact(Stream s, int n) { byte[] array = new byte[n]; int num; for (int i = 0; i < n; i += num) { num = s.Read(array, i, n - i); if (num <= 0) { throw new EndOfStreamException(); } } return array; } private static string ReadString(Stream s) { StringBuilder stringBuilder = new StringBuilder(); int num; while ((num = s.ReadByte()) > 0) { stringBuilder.Append((char)num); } return stringBuilder.ToString(); } private static uint ReadU32(Stream s) { byte[] array = ReadExact(s, 4); return (uint)((array[0] << 24) | (array[1] << 16) | (array[2] << 8) | array[3]); } private static long ReadI64(Stream s) { return (long)(((ulong)ReadU32(s) << 32) | ReadU32(s)); } private static void Align16(Stream s) { s.Position = (s.Position + 15) & -16; } private static void WriteString(Stream s, string v) { byte[] bytes = Encoding.ASCII.GetBytes(v); s.Write(bytes, 0, bytes.Length); s.WriteByte(0); } private static void WriteU32(Stream s, uint v) { s.WriteByte((byte)(v >> 24)); s.WriteByte((byte)(v >> 16)); s.WriteByte((byte)(v >> 8)); s.WriteByte((byte)v); } } internal static class Lz4 { private const int MinMatch = 4; private const int HashBits = 16; private const int LastLiterals = 5; private const int MatchSafety = 12; internal static int MaxOutput(int length) { return length + length / 255 + 16; } internal static int Compress(byte[] src, int srcLen, byte[] dst, int[] table) { Array.Clear(table, 0, table.Length); int num = 0; int num2 = 0; int op = 0; int num3 = srcLen - 12; while (num < num3) { uint num4 = BitConverter.ToUInt32(src, num); int num5 = (int)num4 * -1640531535 >>> 16; int num6 = table[num5] - 1; table[num5] = num + 1; if (num6 < 0 || num - num6 > 65535 || BitConverter.ToUInt32(src, num6) != num4) { num++; continue; } int num7 = num + 4; int num8 = num6 + 4; int num9 = srcLen - 5; while (num7 < num9 && src[num7] == src[num8]) { num7++; num8++; } op = WriteSequence(src, num2, num - num2, dst, op, num - num6, num7 - num - 4); num = num7; num2 = num; } return WriteLastLiterals(src, num2, srcLen - num2, dst, op); } private static int WriteSequence(byte[] src, int litStart, int litLen, byte[] dst, int op, int offset, int matchLen) { dst[op++] = (byte)((Math.Min(litLen, 15) << 4) | Math.Min(matchLen, 15)); op = WriteLength(dst, op, litLen); Buffer.BlockCopy(src, litStart, dst, op, litLen); op += litLen; dst[op++] = (byte)offset; dst[op++] = (byte)(offset >> 8); return WriteLength(dst, op, matchLen); } private static int WriteLastLiterals(byte[] src, int litStart, int litLen, byte[] dst, int op) { dst[op++] = (byte)(Math.Min(litLen, 15) << 4); op = WriteLength(dst, op, litLen); Buffer.BlockCopy(src, litStart, dst, op, litLen); return op + litLen; } private static int WriteLength(byte[] dst, int op, int len) { if (len < 15) { return op; } for (len -= 15; len >= 255; len -= 255) { dst[op++] = byte.MaxValue; } dst[op++] = (byte)len; return op; } internal static void Decompress(byte[] src, int srcOff, int srcLen, byte[] dst, int dstLen) { int num = srcOff; int num2 = srcOff + srcLen; int num3 = 0; while (num < num2) { int num4 = src[num++]; int num5 = num4 >> 4; if (num5 == 15) { int num6; do { num6 = src[num++]; num5 += num6; } while (num6 == 255); } Buffer.BlockCopy(src, num, dst, num3, num5); num += num5; num3 += num5; if (num >= num2) { break; } int num7 = src[num] | (src[num + 1] << 8); num += 2; int num8 = num4 & 0xF; if (num8 == 15) { int num9; do { num9 = src[num++]; num8 += num9; } while (num9 == 255); } num8 += 4; int num10 = num3 - num7; if (num7 <= 0 || num10 < 0) { throw new InvalidOperationException("bad LZ4 offset"); } for (int i = 0; i < num8; i++) { dst[num3++] = dst[num10 + i]; } } if (num3 != dstLen) { throw new InvalidOperationException($"LZ4 size mismatch: {num3} != {dstLen}"); } } } public static class Patcher { private const long MinFileBytes = 262144L; private const long GB = 1073741824L; private const string ManifestVersion = "2"; private static ManualLogSource log; public static IEnumerable<string> TargetDLLs { get; } = new string[0]; public static void Patch(AssemblyDefinition assembly) { } public static void Initialize() { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) log = Logger.CreateLogSource("BundleSlimmer"); ConfigFile val = new ConfigFile(Path.Combine(Paths.ConfigPath, "lcmemfix.BundleSlimmer.cfg"), true); ConfigEntry<bool> val2 = val.Bind<bool>("General", "Enabled", true, "Convert LZMA asset bundles in BepInEx/plugins to chunked LZ4 before the game starts. Unity unpacks a whole LZMA bundle into RAM; LZ4 bundles are read from disk as needed."); ConfigEntry<bool> val3 = val.Bind<bool>("General", "FixAudio", true, "Also switch bundle audio clips from Decompress On Load (raw PCM held in RAM) to Compressed In Memory."); ConfigEntry<int> val4 = val.Bind<int>("General", "Threads", 0, "Bundles converted at the same time. 0 = half the CPU cores."); ConfigEntry<float> val5 = val.Bind<float>("General", "KeepFreeGB", 3f, "Skip a bundle if converting it would leave less than this much free space on the drive."); if (!val2.Value) { return; } try { Run((val4.Value > 0) ? val4.Value : Math.Max(1, Environment.ProcessorCount / 2), (long)(val5.Value * 1.0737418E+09f), val3.Value); } catch (Exception ex) { log.LogError((object)("Stopped: " + ex)); } } private static void Run(int threads, long keepFree, bool fixAudio) { Stopwatch stopwatch = Stopwatch.StartNew(); string path = Path.Combine(Paths.CachePath, "lcmemfix.BundleSlimmer.txt"); string version = "2" + (fixAudio ? "a" : ""); Dictionary<string, string> dictionary = LoadManifest(path, version); ConcurrentDictionary<string, string> finished = new ConcurrentDictionary<string, string>(dictionary); List<string> list = new List<string>(); foreach (string item in Directory.EnumerateFiles(Paths.PluginPath, "*", SearchOption.AllDirectories)) { if (item.EndsWith(".slimming", StringComparison.OrdinalIgnoreCase) || item.EndsWith(".slimming.raw", StringComparison.OrdinalIgnoreCase)) { TryDelete(item); continue; } try { FileInfo fileInfo = new FileInfo(item); if (fileInfo.Length >= 262144) { string key = Name(item); if ((!dictionary.TryGetValue(key, out var value) || !(value == Stamp(fileInfo))) && Converter.LooksLikeBundle(item)) { list.Add(item); } } } catch (Exception ex) { log.LogDebug((object)("Skipped " + Name(item) + ": " + ex.Message)); } } if (list.Count == 0) { log.LogInfo((object)$"All bundles already slim ({stopwatch.ElapsedMilliseconds} ms)"); return; } log.LogMessage((object)($"Checking {list.Count} new or changed asset bundles. Converting them makes them read from disk " + "instead of sitting unpacked in RAM. This happens once per mod version and can take a few minutes.")); int converted = 0; int failed = 0; int clips = 0; long before = 0L; long after = 0L; Parallel.ForEach(list.OrderByDescending((string p) => new FileInfo(p).Length).ToList(), new ParallelOptions { MaxDegreeOfParallelism = threads }, delegate(string text) { long length = new FileInfo(text).Length; Converter.Result result = ConvertOne(text, keepFree, fixAudio); if (result == null) { Interlocked.Increment(ref failed); } else { finished[Name(text)] = Stamp(new FileInfo(text)); if (result.Rewritten) { long length2 = new FileInfo(text).Length; Interlocked.Add(ref before, length); Interlocked.Add(ref after, length2); Interlocked.Add(ref clips, result.ClipsFlipped); int num = Interlocked.Increment(ref converted); log.LogInfo((object)($"[{num}] {Name(text)}: {length / 1048576} MB -> {length2 / 1048576} MB" + ((result.ClipsFlipped > 0) ? $", {result.ClipsFlipped} audio clips fixed" : ""))); } } }); SaveManifest(path, version, finished); log.LogMessage((object)($"Rewrote {converted} of {list.Count} bundles in {stopwatch.Elapsed.TotalSeconds:F0} s " + $"({before / 1048576} MB -> {after / 1048576} MB on disk, {clips} audio clips fixed)" + ((failed > 0) ? $", {failed} left as they were" : ""))); } private static Converter.Result ConvertOne(string path, long keepFree, bool fixAudio) { string text = path + ".slimming"; try { long uncompressedSize; using (FileStream s = File.OpenRead(path)) { uncompressedSize = Converter.Read(s).UncompressedSize; } long availableFreeSpace = new DriveInfo(Path.GetPathRoot(Path.GetFullPath(path))).AvailableFreeSpace; if (availableFreeSpace - 2 * uncompressedSize < keepFree) { log.LogWarning((object)$"Skipped {Name(path)}: not enough free disk space ({availableFreeSpace / 1073741824} GB free)"); return null; } Converter.Result result = Converter.Convert(path, text, fixAudio); if (!result.Rewritten) { return result; } Converter.Verify(text, result.Checksum, result.RawSize); File.Delete(path); File.Move(text, path); return result; } catch (Exception ex) { log.LogWarning((object)("Could not convert " + Name(path) + ", left as it was: " + ex.Message)); TryDelete(text); return null; } } private static string Stamp(FileInfo fi) { return fi.Length + ":" + fi.LastWriteTimeUtc.Ticks; } private static Dictionary<string, string> LoadManifest(string path, string version) { Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); try { if (!File.Exists(path)) { return dictionary; } string[] array = File.ReadAllLines(path); if (array.Length == 0 || array[0] != "v" + version) { return dictionary; } foreach (string item in array.Skip(1)) { int num = item.LastIndexOf('|'); if (num > 0) { dictionary[item.Substring(0, num)] = item.Substring(num + 1); } } } catch (Exception ex) { log.LogWarning((object)("Could not read the list of finished bundles, checking all of them: " + ex.Message)); } return dictionary; } private static void SaveManifest(string path, string version, IDictionary<string, string> map) { try { Directory.CreateDirectory(Path.GetDirectoryName(path)); List<string> list = new List<string> { "v" + version }; list.AddRange(from kv in map orderby kv.Key select kv.Key + "|" + kv.Value); File.WriteAllLines(path, list); } catch (Exception ex) { log.LogWarning((object)("Could not save the list of finished bundles: " + ex.Message)); } } private static string Name(string path) { return path.Substring(Paths.PluginPath.Length).TrimStart('\\', '/'); } private static void TryDelete(string path) { try { File.Delete(path); } catch { } } } }