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