MegafileBuilder.cs 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. //
  2. // Copyright 2020 Electronic Arts Inc.
  3. //
  4. // The Command & Conquer Map Editor and corresponding source code is free
  5. // software: you can redistribute it and/or modify it under the terms of
  6. // the GNU General Public License as published by the Free Software Foundation,
  7. // either version 3 of the License, or (at your option) any later version.
  8. // The Command & Conquer Map Editor and corresponding source code is distributed
  9. // in the hope that it will be useful, but with permitted additional restrictions
  10. // under Section 7 of the GPL. See the GNU General Public License in LICENSE.TXT
  11. // distributed with this program. You should have received a copy of the
  12. // GNU General Public License along with permitted additional restrictions
  13. // with this program. If not, see https://github.com/electronicarts/CnC_Remastered_Collection
  14. using System;
  15. using System.Collections.Generic;
  16. using System.IO;
  17. using System.Linq;
  18. using System.Runtime.InteropServices;
  19. using System.Text;
  20. namespace MobiusEditor.Utility
  21. {
  22. public class MegafileBuilder : IDisposable
  23. {
  24. #region IDisposable Support
  25. private bool disposedValue = false;
  26. protected virtual void Dispose(bool disposing)
  27. {
  28. if (!disposedValue)
  29. {
  30. if (disposing)
  31. {
  32. Out.Dispose();
  33. }
  34. disposedValue = true;
  35. }
  36. }
  37. public void Dispose()
  38. {
  39. Dispose(true);
  40. }
  41. #endregion
  42. private const float Version = 0.99f;
  43. public string RootPath { get; private set; }
  44. private Stream Out { get; set; }
  45. private List<(string, object)> Files = new List<(string, object)>();
  46. public MegafileBuilder(string rootPath, string outFile)
  47. {
  48. RootPath = rootPath.ToUpper();
  49. Out = new FileStream(outFile, FileMode.Create);
  50. }
  51. public void AddFile(string path)
  52. {
  53. if (File.Exists(path))
  54. {
  55. Files.Add((Path.GetFileName(path), path));
  56. }
  57. }
  58. public void AddFile(string path, Stream stream)
  59. {
  60. Files.Add((Path.GetFileName(path), stream));
  61. }
  62. public void AddDirectory(string path)
  63. {
  64. AddDirectory(path, "*.*");
  65. }
  66. public void AddDirectory(string path, string searchPattern)
  67. {
  68. var uriPath = new Uri(path);
  69. foreach (var file in Directory.GetFiles(path, searchPattern, SearchOption.AllDirectories))
  70. {
  71. var relativePath = Uri.UnescapeDataString(uriPath.MakeRelativeUri(new Uri(file)).ToString()).Replace('/', Path.DirectorySeparatorChar);
  72. Files.Add((relativePath, file));
  73. }
  74. }
  75. public void Write()
  76. {
  77. var headerSize = sizeof(uint) * 6U;
  78. headerSize += SubFileData.Size * (uint)Files.Count;
  79. var strings = new List<string>();
  80. Func<string, ushort> stringIndex = (string value) =>
  81. {
  82. var index = strings.IndexOf(value);
  83. if (index < 0)
  84. {
  85. index = strings.Count;
  86. if (index > ushort.MaxValue)
  87. {
  88. throw new IndexOutOfRangeException();
  89. }
  90. strings.Add(value);
  91. }
  92. return (ushort)index;
  93. };
  94. var files = new List<(ushort index, uint crc, Stream stream, bool dispose)>();
  95. foreach (var (filename, source) in Files)
  96. {
  97. var name = Encoding.ASCII.GetBytes(filename);
  98. var crc = CRC.Calculate(name);
  99. if (source is string)
  100. {
  101. var file = source as string;
  102. if (File.Exists(file))
  103. {
  104. files.Add((stringIndex(Path.Combine(RootPath, filename).ToUpper()), crc, new FileStream(file, FileMode.Open, FileAccess.Read), true));
  105. }
  106. }
  107. else if (source is Stream)
  108. {
  109. files.Add((stringIndex(Path.Combine(RootPath, filename).ToUpper()), crc, source as Stream, false));
  110. }
  111. }
  112. files = files.OrderBy(x => x.crc).ToList();
  113. var stringsSize = sizeof(ushort) * (uint)strings.Count;
  114. stringsSize += (uint)strings.Sum(x => x.Length);
  115. headerSize += stringsSize;
  116. var subfileImageOffset = headerSize;
  117. using (var writer = new BinaryWriter(Out))
  118. {
  119. writer.Write(0xFFFFFFFF);
  120. writer.Write(Version);
  121. writer.Write(headerSize);
  122. writer.Write((uint)Files.Count);
  123. writer.Write((uint)strings.Count);
  124. writer.Write(stringsSize);
  125. foreach (var item in strings)
  126. {
  127. writer.Write((ushort)item.Length);
  128. writer.Write(item.ToCharArray());
  129. }
  130. using (var fileStream = new MemoryStream())
  131. {
  132. for (var i = 0; i < files.Count; ++i)
  133. {
  134. var (index, crc, stream, dispose) = files[i];
  135. var fileSize = (uint)(stream.Length - stream.Position);
  136. var fileBytes = new byte[fileSize];
  137. stream.Read(fileBytes, 0, fileBytes.Length);
  138. fileStream.Write(fileBytes, 0, fileBytes.Length);
  139. if (dispose)
  140. {
  141. stream.Dispose();
  142. }
  143. SubFileData data = new SubFileData
  144. {
  145. Flags = 0,
  146. CRCValue = crc,
  147. SubfileIndex = i,
  148. SubfileSize = fileSize,
  149. SubfileImageDataOffset = subfileImageOffset,
  150. SubfileNameIndex = index
  151. };
  152. var ptr = Marshal.AllocHGlobal((int)SubFileData.Size);
  153. Marshal.StructureToPtr(data, ptr, false);
  154. var bytes = new byte[SubFileData.Size];
  155. Marshal.Copy(ptr, bytes, 0, bytes.Length);
  156. Marshal.FreeHGlobal(ptr);
  157. writer.Write(bytes);
  158. subfileImageOffset += data.SubfileSize;
  159. }
  160. fileStream.Seek(0, SeekOrigin.Begin);
  161. fileStream.CopyTo(writer.BaseStream);
  162. }
  163. }
  164. }
  165. }
  166. }