From 9ce60b396fd3f211b26c4d533b41138d6de6348b Mon Sep 17 00:00:00 2001 From: icedream Date: Wed, 22 Oct 2014 15:47:38 +0200 Subject: [PATCH] Initial code commit. --- GarrysMod.AddonCreator.sln | 22 ++ GarrysMod.AddonCreator/Addon.cs | 255 +++++++++++++++ GarrysMod.AddonCreator/AddonFileInfo.cs | 16 + GarrysMod.AddonCreator/BinaryCodecExt.cs | 37 +++ .../GarrysMod.AddonCreator.csproj | 71 ++++ GarrysMod.AddonCreator/JsonAddonFileInfo.cs | 20 ++ GarrysMod.AddonCreator/OptimizedCRC.cs | 121 +++++++ GarrysMod.AddonCreator/ParallelCRC.cs | 305 ++++++++++++++++++ .../PhysicalAddonFileInfo.cs | 24 ++ GarrysMod.AddonCreator/Program.cs | 116 +++++++ .../Properties/AssemblyInfo.cs | 36 +++ .../SegmentedAddonFileInfo.cs | 40 +++ GarrysMod.AddonCreator/TraditionalCRC.cs | 76 +++++ GarrysMod.AddonCreator/packages.config | 4 + 14 files changed, 1143 insertions(+) create mode 100644 GarrysMod.AddonCreator.sln create mode 100644 GarrysMod.AddonCreator/Addon.cs create mode 100644 GarrysMod.AddonCreator/AddonFileInfo.cs create mode 100644 GarrysMod.AddonCreator/BinaryCodecExt.cs create mode 100644 GarrysMod.AddonCreator/GarrysMod.AddonCreator.csproj create mode 100644 GarrysMod.AddonCreator/JsonAddonFileInfo.cs create mode 100644 GarrysMod.AddonCreator/OptimizedCRC.cs create mode 100644 GarrysMod.AddonCreator/ParallelCRC.cs create mode 100644 GarrysMod.AddonCreator/PhysicalAddonFileInfo.cs create mode 100644 GarrysMod.AddonCreator/Program.cs create mode 100644 GarrysMod.AddonCreator/Properties/AssemblyInfo.cs create mode 100644 GarrysMod.AddonCreator/SegmentedAddonFileInfo.cs create mode 100644 GarrysMod.AddonCreator/TraditionalCRC.cs create mode 100644 GarrysMod.AddonCreator/packages.config diff --git a/GarrysMod.AddonCreator.sln b/GarrysMod.AddonCreator.sln new file mode 100644 index 0000000..b9742fe --- /dev/null +++ b/GarrysMod.AddonCreator.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.30723.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GarrysMod.AddonCreator", "GarrysMod.AddonCreator\GarrysMod.AddonCreator.csproj", "{E33D031D-7866-40F9-9362-2776CB4A7B5B}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {E33D031D-7866-40F9-9362-2776CB4A7B5B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E33D031D-7866-40F9-9362-2776CB4A7B5B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E33D031D-7866-40F9-9362-2776CB4A7B5B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E33D031D-7866-40F9-9362-2776CB4A7B5B}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/GarrysMod.AddonCreator/Addon.cs b/GarrysMod.AddonCreator/Addon.cs new file mode 100644 index 0000000..64b708e --- /dev/null +++ b/GarrysMod.AddonCreator/Addon.cs @@ -0,0 +1,255 @@ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using CRC32; + +namespace GarrysMod.AddonCreator +{ + public class Addon + { + private static readonly byte[] FormatIdent = Encoding.ASCII.GetBytes("GMAD"); + private const byte FormatVersion = 3; + private const uint AppID = 4000; + private const uint CompressionSignature = 0xbeefcace; + + public static void CreateFromFolder() + { + + } + + /// + /// Imports a gmod addon into this instance. + /// + /// Path to a gmod addon file. + /// Import all metadata (title, description, creator, timestamp, etc.) as well? + public void Import(string path, bool withMetadata = true) + { + using (var stream = File.OpenRead(path)) + { + var sr = new BinaryReader(stream, Encoding.GetEncoding("windows-1252")); + + // Check format header + if (!sr.ReadBytes(4).SequenceEqual(FormatIdent) + || sr.ReadByte() != FormatVersion) + throw new FormatException(); + + // Check addon's CRC32 hash + { + var baseAddon = new byte[stream.Length - sizeof (int)]; + var oldpos = stream.Position; + stream.Position = 0; + stream.Read(baseAddon, 0, baseAddon.Length); + var baseAddonHash = sr.ReadInt32(); + if (ParallelCRC.Compute(baseAddon) != baseAddonHash) + { + throw new IOException("Data corrupted (calculated hash mismatching hash in addon file)"); + } + } + + // Import metadata + var newSteamID = sr.ReadUInt64(); + var newBuildTimestamp = sr.ReadUInt64(); + var newRequiredContentLen = sr.ReadByte(); + for (var b = 0; b < newRequiredContentLen; b++) + { + var value = sr.ReadString(true); + if (withMetadata && !RequiredContent.Contains(value)) + RequiredContent.Add(value); + } + if (withMetadata) + { + SteamID = newSteamID; + BuildTimestamp = newBuildTimestamp; + } + + // file list + var newFilesList = new Dictionary>(); + do + { + var fileId = sr.ReadUInt32(); + if (fileId == 0) + break; // end of list + + // key, size, hash + var filePath = sr.ReadString(true); + var fileSize = sr.ReadInt64(); + var fileHash = sr.ReadInt32(); + + // avoid duplicates + if (newFilesList.ContainsKey(filePath)) + { + throw new IOException("Found duplicate file path in addon file. Contact the addon creator and tell him to build a new proper addon file."); + } + + newFilesList.Add(filePath, new Tuple(fileSize, fileHash)); + } while (true); + + foreach (var file in newFilesList) + { + var filePath = file.Key; + var fileSize = file.Value.Item1; + var fileHash = file.Value.Item2; + var fileContent = new byte[fileSize]; + + // long-compatible file reading + for (long i = 0; i < fileSize; i += int.MaxValue) + { + var tempContent = sr.ReadBytes((int)Math.Min(int.MaxValue, fileSize)); + tempContent.CopyTo(fileContent, i); + } + + // CRC check for this file + var fileCalcHash = ParallelCRC.Compute(fileContent); + if (fileCalcHash != fileHash) + { + throw new IOException("File " + filePath + " in addon file is corrupted (hash mismatch)"); + } + + Files.Add(filePath, new SegmentedAddonFileInfo(stream, sr.BaseStream.Position, fileSize, fileHash)); + } + } + } + + /// + /// Returns the timestamp of when the addon was built. This data is retrieved from full imports and for new (unsaved) addons this is 0. + /// + public ulong BuildTimestamp { get; private set; } + + /// + /// Exports this addon into a GMA file. + /// + /// The output file path, should be pointing to a writable location ending with ".gma". + public void Export(string path) + { + // TODO: Enforce .gma file extension + + // Checking for existing addon.json + if (!Files.ContainsKey("addon.json")) + { + throw new FileNotFoundException("Addon building requires a valid addon.json file."); + } + + // TODO: Check addon.json for errors + // TODO: Ignore or remove files marked to be ignored in addon.json + // TODO: Sort in alphabetic order + // TODO: Filter files by general whitelist + + using (var stream = new MemoryStream()) + { + // TODO: Standardized encoding - Garry should use standardized encoding, currently he uses Encoding.Default which is applocale-dependent... + var sw = new BinaryWriter(stream, Encoding.GetEncoding("windows-1252")); + + // Format header + sw.Write(FormatIdent); + sw.Write(FormatVersion); + + // Creator steam ID + sw.Write(SteamID); + + // Build timestamp + sw.Write(BuildTimestamp = (ulong)(DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0)).TotalSeconds); + + // Required content + if (RequiredContent.Count > byte.MaxValue) + { + throw new IndexOutOfRangeException("Required content count must not exceed " + byte.MaxValue + " entries."); + } + sw.Write((byte)RequiredContent.Count); + foreach (string content in RequiredContent) + { + sw.Write(content, true); + } + + // Metadata + sw.Write(Title, true); + sw.Write(Description, true); + sw.Write(Author, true); + sw.Write(Version); + + // File list + if (Files.Count > uint.MaxValue) + { + throw new IndexOutOfRangeException("Number of addon files must not exceed " + uint.MaxValue + " elements."); + } + uint fileNum = 0; + foreach (var file in Files) + { + fileNum++; + sw.Write(fileNum); + sw.Write(file.Key.ToLower(), true); // Path + sw.Write(file.Value.Size); + sw.Write(file.Value.Crc32Hash); + } + sw.Write((uint)0); // End of file list + + // File contents + foreach (var file in Files) + { + if (file.Value.Size == 0) + continue; + + sw.Write(file.Value.GetContents()); + } + + // Addon CRC + var addonHash = ParallelCRC.Compute(stream.ToArray()); + sw.Write(addonHash); + + using (var outfile = File.Create(path)) + { + stream.CopyTo(outfile); + } + } + } + + /// + /// The name of this addon. + /// + public string Title { get; set; } + + /// + /// The author of this addon. + /// + public string Author { get; set; } + + /// + /// A description of this addon. + /// + public string Description { get; set; } + + /// + /// This addon's version. + /// + public int Version { get; set; } + + /// + /// The files to include in the addon. + /// + public Dictionary Files { get; private set; } + + /// + /// Currently unused. + /// + public ulong SteamID { get; set; } + + /// + /// Content that needs to exist in order to run this addon. + /// + public List RequiredContent { get; private set; } + + /// + /// Initializes a new instance of + /// + public Addon() + { + Files = new Dictionary(); + RequiredContent = new List(); + Version = 1; + } + } + + // TODO: Newtonsoft.Json reference +} diff --git a/GarrysMod.AddonCreator/AddonFileInfo.cs b/GarrysMod.AddonCreator/AddonFileInfo.cs new file mode 100644 index 0000000..df2ceb8 --- /dev/null +++ b/GarrysMod.AddonCreator/AddonFileInfo.cs @@ -0,0 +1,16 @@ +using CRC32; + +namespace GarrysMod.AddonCreator +{ + public abstract class AddonFileInfo + { + private long? _size; + private int? _hash; + + public virtual long Size { get { return _size.HasValue ? _size.Value : (_size = GetContents().Length).Value; } } + + public virtual int Crc32Hash { get { return _hash.HasValue ? _hash.Value : (_hash = ParallelCRC.Compute(GetContents())).Value; } } + + public abstract byte[] GetContents(); + } +} \ No newline at end of file diff --git a/GarrysMod.AddonCreator/BinaryCodecExt.cs b/GarrysMod.AddonCreator/BinaryCodecExt.cs new file mode 100644 index 0000000..f1ddcc4 --- /dev/null +++ b/GarrysMod.AddonCreator/BinaryCodecExt.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; + +namespace GarrysMod.AddonCreator +{ + static class BinaryCodecExt + { + public static string ReadString(this BinaryReader br, bool nullTerminated) + { + if (!nullTerminated) + return br.ReadString(); + + var sb = new StringBuilder(); + do + { + var c = br.ReadChar(); + if (c == 0) + break; + sb.Append(c); + } while (true); + return sb.ToString(); + } + + public static void Write(this BinaryWriter bw, string value, bool nullTerminated) + { + if (nullTerminated) + { + value += "\0"; + } + + bw.Write(value); + } + } +} diff --git a/GarrysMod.AddonCreator/GarrysMod.AddonCreator.csproj b/GarrysMod.AddonCreator/GarrysMod.AddonCreator.csproj new file mode 100644 index 0000000..2f90bf3 --- /dev/null +++ b/GarrysMod.AddonCreator/GarrysMod.AddonCreator.csproj @@ -0,0 +1,71 @@ + + + + + Debug + AnyCPU + {E33D031D-7866-40F9-9362-2776CB4A7B5B} + Exe + Properties + GarrysMod.AddonCreator + GarrysMod.AddonCreator + v4.0 + 512 + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + False + ..\packages\Newtonsoft.Json.6.0.5\lib\net40\Newtonsoft.Json.dll + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/GarrysMod.AddonCreator/JsonAddonFileInfo.cs b/GarrysMod.AddonCreator/JsonAddonFileInfo.cs new file mode 100644 index 0000000..b6c5f25 --- /dev/null +++ b/GarrysMod.AddonCreator/JsonAddonFileInfo.cs @@ -0,0 +1,20 @@ +using System.Text; +using Newtonsoft.Json; + +namespace GarrysMod.AddonCreator +{ + public class JsonAddonFileInfo : AddonFileInfo + { + private readonly byte[] _serializedJson; + + public JsonAddonFileInfo(object obj) + { + _serializedJson = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(obj)); + } + + public override byte[] GetContents() + { + return _serializedJson; + } + } +} \ No newline at end of file diff --git a/GarrysMod.AddonCreator/OptimizedCRC.cs b/GarrysMod.AddonCreator/OptimizedCRC.cs new file mode 100644 index 0000000..fa3eff4 --- /dev/null +++ b/GarrysMod.AddonCreator/OptimizedCRC.cs @@ -0,0 +1,121 @@ +using System; +using System.IO; + +namespace CRC32 +{ + public class OptimizedCRC + { + private const uint kCrcPoly = 0xEDB88320; + private const uint kInitial = 0xFFFFFFFF; + private static readonly uint[] Table; + private const uint CRC_NUM_TABLES = 8; + + static OptimizedCRC() + { + unchecked + { + Table = new uint[256 * CRC_NUM_TABLES]; + uint i; + for (i = 0; i < 256; i++) + { + uint r = i; + for (int j = 0; j < 8; j++) + r = (r >> 1) ^ (kCrcPoly & ~((r & 1) - 1)); + Table[i] = r; + } + for (; i < 256 * CRC_NUM_TABLES; i++) + { + uint r = Table[i - 256]; + Table[i] = Table[r & 0xFF] ^ (r >> 8); + } + } + } + + private uint value; + + public OptimizedCRC() + { + Init(); + } + + /// + /// Reset CRC + /// + public void Init() + { + value = kInitial; + } + + public int Value + { + get { return (int)~value; } + } + + public void UpdateByte(byte b) + { + value = (value >> 8) ^ Table[(byte)value ^ b]; + } + + public void Update(byte[] data, int offset, int count) + { + new ArraySegment(data, offset, count); // check arguments + if (count == 0) return; + + var table = OptimizedCRC.Table; // important for performance! + + uint crc = value; + + for (; (offset & 7) != 0 && count != 0; count--) + crc = (crc >> 8) ^ table[(byte)crc ^ data[offset++]]; + + if (count >= 8) + { + /* + * Idea from 7-zip project sources (http://7-zip.org/sdk.html) + */ + + int to = (count - 8) & ~7; + count -= to; + to += offset; + + while (offset != to) + { + crc ^= (uint)(data[offset] + (data[offset + 1] << 8) + (data[offset + 2] << 16) + (data[offset + 3] << 24)); + uint high = (uint)(data[offset + 4] + (data[offset + 5] << 8) + (data[offset + 6] << 16) + (data[offset + 7] << 24)); + offset += 8; + + crc = table[(byte)crc + 0x700] + ^ table[(byte)(crc >>= 8) + 0x600] + ^ table[(byte)(crc >>= 8) + 0x500] + ^ table[/*(byte)*/(crc >> 8) + 0x400] + ^ table[(byte)(high) + 0x300] + ^ table[(byte)(high >>= 8) + 0x200] + ^ table[(byte)(high >>= 8) + 0x100] + ^ table[/*(byte)*/(high >> 8) + 0x000]; + } + } + + while (count-- != 0) + crc = (crc >> 8) ^ table[(byte)crc ^ data[offset++]]; + + value = crc; + } + + static public int Compute(byte[] data, int offset, int size) + { + var crc = new OptimizedCRC(); + crc.Update(data, offset, size); + return crc.Value; + } + + static public int Compute(byte[] data) + { + return Compute(data, 0, data.Length); + } + + static public int Compute(ArraySegment block) + { + return Compute(block.Array, block.Offset, block.Count); + } + } +} \ No newline at end of file diff --git a/GarrysMod.AddonCreator/ParallelCRC.cs b/GarrysMod.AddonCreator/ParallelCRC.cs new file mode 100644 index 0000000..0a4e134 --- /dev/null +++ b/GarrysMod.AddonCreator/ParallelCRC.cs @@ -0,0 +1,305 @@ +using System; +using System.IO; +using System.Threading; +using System.Collections.Generic; + +namespace CRC32 +{ + public class ParallelCRC + { + private const uint kCrcPoly = 0xEDB88320; + private const uint kInitial = 0xFFFFFFFF; + private static readonly uint[] Table; + private const int CRC_NUM_TABLES = 8; + + private const int ThreadCost = 256 << 10; + private static int ThreadCount = Environment.ProcessorCount; + + + static ParallelCRC() + { + unchecked + { + Table = new uint[256 * CRC_NUM_TABLES]; + int i; + for (i = 0; i < 256; i++) + { + uint r = (uint)i; + for (int j = 0; j < 8; j++) + r = (r >> 1) ^ (kCrcPoly & ~((r & 1) - 1)); + Table[i] = r; + } + for (; i < 256 * CRC_NUM_TABLES; i++) + { + uint r = Table[i - 256]; + Table[i] = Table[r & 0xFF] ^ (r >> 8); + } + } + } + + private uint value; + + public ParallelCRC() + { + Init(); + } + + /// + /// Reset CRC + /// + public void Init() + { + value = kInitial; + } + + public int Value + { + get { return (int)~value; } + } + + public void UpdateByte(byte b) + { + value = (value >> 8) ^ Table[(byte)value ^ b]; + } + + public void Update(byte[] data, int offset, int count) + { + new ArraySegment(data, offset, count); // check arguments + + if (count <= ThreadCost || ThreadCount <= 1) + { + value = ProcessBlock(value, data, offset, count); + return; + } + + // choose optimal number of threads to use + + int threadCount = ThreadCount; + L0: + int bytesPerThread = (count + threadCount - 1) / threadCount; + if (bytesPerThread < ThreadCost >> 1) + { + threadCount--; + goto L0; + } + + // threadCount >= 2 + + Job lastJob = null; + while (count > bytesPerThread) + { + var job = new Job(new ArraySegment(data, offset, bytesPerThread), this, lastJob); + ThreadPool.QueueUserWorkItem(job.Do); + offset += bytesPerThread; + count -= bytesPerThread; + lastJob = job; + } + + // lastJob != null + var lastBlockCRC = ProcessBlock(kInitial, data, offset, count); + lastJob.WaitAndDispose(); + value = Combine(value, lastBlockCRC, count); + } + + private static uint ProcessBlock(uint crc, byte[] data, int offset, int count) + { + /* + * A copy of Optimized implementation. + */ + + if (count < 0) throw new ArgumentOutOfRangeException("count"); + if (count == 0) return crc; + + var table = ParallelCRC.Table; + + for (; (offset & 7) != 0 && count != 0; count--) + crc = (crc >> 8) ^ table[(byte)crc ^ data[offset++]]; + + if (count >= 8) + { + int to = (count - 8) & ~7; + count -= to; + to += offset; + + while (offset != to) + { + crc ^= (uint)(data[offset] + (data[offset + 1] << 8) + (data[offset + 2] << 16) + (data[offset + 3] << 24)); + uint high = (uint)(data[offset + 4] + (data[offset + 5] << 8) + (data[offset + 6] << 16) + (data[offset + 7] << 24)); + offset += 8; + + crc = table[(byte)crc + 0x700] + ^ table[(byte)(crc >>= 8) + 0x600] + ^ table[(byte)(crc >>= 8) + 0x500] + ^ table[/*(byte)*/(crc >> 8) + 0x400] + ^ table[(byte)(high) + 0x300] + ^ table[(byte)(high >>= 8) + 0x200] + ^ table[(byte)(high >>= 8) + 0x100] + ^ table[/*(byte)*/(high >> 8) + 0x000]; + } + } + + while (count-- != 0) + crc = (crc >> 8) ^ table[(byte)crc ^ data[offset++]]; + + return crc; + } + + static public int Compute(byte[] data, int offset, int count) + { + var crc = new ParallelCRC(); + crc.Update(data, offset, count); + return crc.Value; + } + + static public int Compute(byte[] data) + { + return Compute(data, 0, data.Length); + } + + static public int Compute(ArraySegment block) + { + return Compute(block.Array, block.Offset, block.Count); + } + + #region Combining + + /* + * CRC values combining algorithm. + * Taken from DotNetZip project sources (http://dotnetzip.codeplex.com/) + */ + + /// + /// This function is thread-safe! + /// + private static uint Combine(uint crc1, uint crc2, int length2) + { + if (length2 <= 0) return crc1; + if (crc1 == kInitial) return crc2; + + if (even_cache == null) + { + Prepare_even_odd_Cache(); + } + + uint[] even = CopyArray(even_cache); + uint[] odd = CopyArray(odd_cache); + + crc1 = ~crc1; + crc2 = ~crc2; + + uint len2 = (uint)length2; + + // apply len2 zeros to crc1 (first square will put the operator for one + // zero byte, eight zero bits, in even) + do + { + // apply zeros operator for this bit of len2 + gf2_matrix_square(even, odd); + + if ((len2 & 1) != 0) crc1 = gf2_matrix_times(even, crc1); + len2 >>= 1; + + if (len2 == 0) break; + + // another iteration of the loop with odd and even swapped + gf2_matrix_square(odd, even); + if ((len2 & 1) != 0) crc1 = gf2_matrix_times(odd, crc1); + len2 >>= 1; + } while (len2 != 0); + + crc1 ^= crc2; + return ~crc1; + } + + private static uint[] even_cache = null; + private static uint[] odd_cache; + + private static void Prepare_even_odd_Cache() + { + var even = new uint[32]; // even-power-of-two zeros operator + var odd = new uint[32]; // odd-power-of-two zeros operator + + // put operator for one zero bit in odd + odd[0] = kCrcPoly; // the CRC-32 polynomial + for (int i = 1; i < 32; i++) odd[i] = 1U << (i - 1); + + // put operator for two zero bits in even + gf2_matrix_square(even, odd); + + // put operator for four zero bits in odd + gf2_matrix_square(odd, even); + + odd_cache = odd; + even_cache = even; + } + + /// will not be modified + private static uint gf2_matrix_times(uint[] matrix, uint vec) + { + uint sum = 0; + int i = 0; + while (vec != 0) + { + if ((vec & 1) != 0) sum ^= matrix[i]; + vec >>= 1; + i++; + } + return sum; + } + + /// this array will be modified! + /// will not be modified + private static void gf2_matrix_square(uint[] square, uint[] mat) + { + for (int i = 0; i < 32; i++) + square[i] = gf2_matrix_times(mat, mat[i]); + } + + private static uint[] CopyArray(uint[] a) + { + var b = new uint[a.Length]; + Buffer.BlockCopy(a, 0, b, 0, a.Length * sizeof(uint)); + return b; + } + + #endregion Combining + + class Job + { + private ArraySegment data; + private Job waitForJob; + private ParallelCRC accumulator; + + private ManualResetEventSlim finished; + + public Job(ArraySegment data, ParallelCRC accumulator, Job waitForJob) + { + this.data = data; + this.accumulator = accumulator; + this.waitForJob = waitForJob; + this.finished = new ManualResetEventSlim(false); + } + + public void Do(object arg) + { + var crc = ProcessBlock(kInitial, data.Array, data.Offset, data.Count); + if (waitForJob != null) waitForJob.WaitAndDispose(); + accumulator.value = Combine(accumulator.value, crc, data.Count); + finished.Set(); + } + + public void WaitAndDispose() + { + finished.Wait(); + Dispose(); + } + + public void Dispose() + { + if (finished != null) finished.Dispose(); + finished = null; + } + } + } + +} \ No newline at end of file diff --git a/GarrysMod.AddonCreator/PhysicalAddonFileInfo.cs b/GarrysMod.AddonCreator/PhysicalAddonFileInfo.cs new file mode 100644 index 0000000..ce69040 --- /dev/null +++ b/GarrysMod.AddonCreator/PhysicalAddonFileInfo.cs @@ -0,0 +1,24 @@ +using System.IO; + +namespace GarrysMod.AddonCreator +{ + public class PhysicalAddonFileInfo : AddonFileInfo + { + public PhysicalAddonFileInfo(string path) + { + _fi = new FileInfo(path); + } + + private FileInfo _fi; + + public override long Size + { + get { return _fi.Length; } + } + + public override byte[] GetContents() + { + return File.ReadAllBytes(_fi.FullName); + } + } +} \ No newline at end of file diff --git a/GarrysMod.AddonCreator/Program.cs b/GarrysMod.AddonCreator/Program.cs new file mode 100644 index 0000000..59b8280 --- /dev/null +++ b/GarrysMod.AddonCreator/Program.cs @@ -0,0 +1,116 @@ +using System; +using System.IO; +using System.Linq; + +namespace GarrysMod.AddonCreator +{ + internal static class Program + { + private static void Main(string[] args) + { + switch (args[0]) + { + case "create": + { + var folder = new DirectoryInfo(args[1]); + var output = args[2]; + var addon = new Addon(); + + // recursively add files + foreach (var file in folder.EnumerateFiles("*", SearchOption.AllDirectories)) + { + var relpath = MakeRelativePath(folder.FullName, file.FullName).Replace(Path.DirectorySeparatorChar, '/'); + Console.WriteLine("Adding: {0}", relpath); + + addon.Files.Add(relpath, new PhysicalAddonFileInfo(file.FullName)); + } + + // create addon + Console.WriteLine("Exporting addon..."); + addon.Export(output); + + Console.WriteLine("Done."); + break; + } + case "extract": + { + var gma = args[1]; + var folder = new DirectoryInfo(args[2]); + var addon = new Addon(); + addon.Import(gma); + + Console.WriteLine("Loaded addon {0} by {1}, Version {2}", addon.Title, addon.Author, addon.Version); + + // extract files + foreach (var file in addon.Files) + { + var relpath = file.Key; + var targetFile = new FileInfo(Path.Combine(folder.FullName, relpath.Replace('/', Path.DirectorySeparatorChar))); + + Console.WriteLine("Extracting: {0}", relpath); + + // create directory + var dir = targetFile.Directory; + if (dir == null) + continue; // I still need to think about the weird logic here + dir.Create(); + + // create file + using (var fs = targetFile.Create()) + { + var buffer = file.Value.GetContents(); + + // long-compatible copy algorithm + for (long i = 0; i < buffer.LongLength; i += int.MaxValue) + { + var toWrite = (int)Math.Min(int.MaxValue, buffer.LongLength - i); + var toWriteBuf = buffer.AsEnumerable(); + for (long j = 0; j < i; j += int.MaxValue) + { + toWriteBuf = toWriteBuf.Skip(int.MaxValue); + } + fs.Write(toWriteBuf.ToArray(), 0, toWrite); + } + } + } + + Console.WriteLine("Done."); + break; + } + } + } + + /// + /// Creates a relative path from one file or folder to another. + /// + /// Contains the directory that defines the start of the relative path. + /// Contains the path that defines the endpoint of the relative path. + /// The relative path from the start directory to the end path. + /// + /// + /// + private static String MakeRelativePath(String fromPath, String toPath) + { + if (String.IsNullOrEmpty(fromPath)) throw new ArgumentNullException("fromPath"); + if (String.IsNullOrEmpty(toPath)) throw new ArgumentNullException("toPath"); + + var fromUri = new Uri(fromPath); + var toUri = new Uri(toPath); + + if (fromUri.Scheme != toUri.Scheme) + { + return toPath; + } // path can't be made relative. + + Uri relativeUri = fromUri.MakeRelativeUri(toUri); + String relativePath = Uri.UnescapeDataString(relativeUri.ToString()); + + if (toUri.Scheme.ToUpperInvariant() == "FILE") + { + relativePath = relativePath.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar); + } + + return relativePath; + } + } +} \ No newline at end of file diff --git a/GarrysMod.AddonCreator/Properties/AssemblyInfo.cs b/GarrysMod.AddonCreator/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..4b21687 --- /dev/null +++ b/GarrysMod.AddonCreator/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// Allgemeine Informationen über eine Assembly werden über die folgenden +// Attribute gesteuert. Ändern Sie diese Attributwerte, um die Informationen zu ändern, +// die mit einer Assembly verknüpft sind. +[assembly: AssemblyTitle("GarrysMod.AddonCreator")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Hewlett-Packard")] +[assembly: AssemblyProduct("GarrysMod.AddonCreator")] +[assembly: AssemblyCopyright("Copyright © Hewlett-Packard 2014")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Durch Festlegen von ComVisible auf "false" werden die Typen in dieser Assembly unsichtbar +// für COM-Komponenten. Wenn Sie auf einen Typ in dieser Assembly von +// COM zugreifen müssen, legen Sie das ComVisible-Attribut für diesen Typ auf "true" fest. +[assembly: ComVisible(false)] + +// Die folgende GUID bestimmt die ID der Typbibliothek, wenn dieses Projekt für COM verfügbar gemacht wird +[assembly: Guid("bb585862-950d-415b-b518-eb9c7e3d50f3")] + +// Versionsinformationen für eine Assembly bestehen aus den folgenden vier Werten: +// +// Hauptversion +// Nebenversion +// Buildnummer +// Revision +// +// Sie können alle Werte angeben oder die standardmäßigen Build- und Revisionsnummern +// übernehmen, indem Sie "*" eingeben: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/GarrysMod.AddonCreator/SegmentedAddonFileInfo.cs b/GarrysMod.AddonCreator/SegmentedAddonFileInfo.cs new file mode 100644 index 0000000..0b30b29 --- /dev/null +++ b/GarrysMod.AddonCreator/SegmentedAddonFileInfo.cs @@ -0,0 +1,40 @@ +using System; +using System.IO; + +namespace GarrysMod.AddonCreator +{ + public class SegmentedAddonFileInfo : AddonFileInfo + { + private Stream _stream; + private long _pos; + private long _len; + private int _hash; + + public SegmentedAddonFileInfo(Stream stream, long pos, long len, int fileHash) + { + _stream = stream; + _pos = pos; + _len = len; + _hash = fileHash; + } + + public override byte[] GetContents() + { + lock (_stream) + { + var output = new byte[_len]; + var oldpos = _stream.Position; + _stream.Position = _pos; + for (long i = 0; i < _len; i += int.MaxValue) // for loop for supporting long file sizes + { + var toRead = (int) Math.Min(int.MaxValue, _len); + var buffer = new byte[toRead]; + var readReal = _stream.Read(buffer, 0, toRead); + i -= (toRead - readReal); // make absolutely sure everything gets read + buffer.CopyTo(output, i); + } + return output; + } + } + } +} \ No newline at end of file diff --git a/GarrysMod.AddonCreator/TraditionalCRC.cs b/GarrysMod.AddonCreator/TraditionalCRC.cs new file mode 100644 index 0000000..0fda792 --- /dev/null +++ b/GarrysMod.AddonCreator/TraditionalCRC.cs @@ -0,0 +1,76 @@ +using System; +using System.IO; + +namespace CRC32 +{ + public class TraditionalCRC + { + private const uint kCrcPoly = 0xEDB88320; + private const uint kInitial = 0xFFFFFFFF; + private static readonly uint[] Table; + + static TraditionalCRC() + { + unchecked + { + Table = new uint[256]; + for (uint i = 0; i < 256; i++) + { + uint r = i; + for (int j = 0; j < 8; j++) + r = (r >> 1) ^ (kCrcPoly & ~((r & 1) - 1)); + Table[i] = r; + } + } + } + + private uint value; + + public TraditionalCRC() + { + Init(); + } + + /// + /// Reset CRC + /// + public void Init() + { + value = kInitial; + } + + public int Value + { + get { return (int)~value; } + } + + public void UpdateByte(byte b) + { + value = (value >> 8) ^ Table[(byte)value ^ b]; + } + + public void Update(byte[] data, int offset, int count) + { + if (count < 0) throw new ArgumentOutOfRangeException("count"); + while (count-- != 0) + value = (value >> 8) ^ Table[(byte)value ^ data[offset++]]; + } + + static public int Compute(byte[] data, int offset, int count) + { + var crc = new TraditionalCRC(); + crc.Update(data, offset, count); + return crc.Value; + } + + static public int Compute(byte[] data) + { + return Compute(data, 0, data.Length); + } + + static public int Compute(ArraySegment block) + { + return Compute(block.Array, block.Offset, block.Count); + } + } +} \ No newline at end of file diff --git a/GarrysMod.AddonCreator/packages.config b/GarrysMod.AddonCreator/packages.config new file mode 100644 index 0000000..3d97f00 --- /dev/null +++ b/GarrysMod.AddonCreator/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file