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