Megafile.cs (5132B)
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; 17 using System.Collections.Generic; 18 using System.IO; 19 using System.IO.MemoryMappedFiles; 20 using System.Runtime.InteropServices; 21 22 namespace MobiusEditor.Utility 23 { 24 [StructLayout(LayoutKind.Sequential, Pack = 2)] 25 struct SubFileData 26 { 27 public ushort Flags; 28 public uint CRCValue; 29 public int SubfileIndex; 30 public uint SubfileSize; 31 public uint SubfileImageDataOffset; 32 public ushort SubfileNameIndex; 33 34 public static readonly uint Size = (uint)Marshal.SizeOf(typeof(SubFileData)); 35 } 36 37 public class Megafile : IEnumerable<string>, IEnumerable, IDisposable 38 { 39 private readonly MemoryMappedFile megafileMap; 40 41 private readonly string[] stringTable; 42 43 private readonly Dictionary<string, SubFileData> fileTable = new Dictionary<string, SubFileData>(); 44 45 public Megafile(string megafilePath) 46 { 47 megafileMap = MemoryMappedFile.CreateFromFile( 48 new FileStream(megafilePath, FileMode.Open, FileAccess.Read, FileShare.Read) , null, 0, MemoryMappedFileAccess.Read, HandleInheritability.None, false 49 ); 50 51 var numFiles = 0U; 52 var numStrings = 0U; 53 var stringTableSize = 0U; 54 var fileTableSize = 0U; 55 56 var readOffset = 0U; 57 using (var magicNumberReader = new BinaryReader(megafileMap.CreateViewStream(readOffset, 4, MemoryMappedFileAccess.Read))) 58 { 59 var magicNumber = magicNumberReader.ReadUInt32(); 60 if ((magicNumber == 0xFFFFFFFF) || (magicNumber == 0x8FFFFFFF)) 61 { 62 // Skip header size and version 63 readOffset += 8; 64 } 65 } 66 67 readOffset += 4U; 68 using (var headerReader = new BinaryReader(megafileMap.CreateViewStream(readOffset, 12, MemoryMappedFileAccess.Read))) 69 { 70 numFiles = headerReader.ReadUInt32(); 71 numStrings = headerReader.ReadUInt32(); 72 stringTableSize = headerReader.ReadUInt32(); 73 fileTableSize = numFiles * SubFileData.Size; 74 } 75 76 readOffset += 12U; 77 using (var stringReader = new BinaryReader(megafileMap.CreateViewStream(readOffset, stringTableSize, MemoryMappedFileAccess.Read))) 78 { 79 stringTable = new string[numStrings]; 80 81 for (var i = 0U; i < numStrings; ++i) 82 { 83 var stringSize = stringReader.ReadUInt16(); 84 stringTable[i] = new string(stringReader.ReadChars(stringSize)); 85 } 86 } 87 88 readOffset += stringTableSize; 89 using (var subFileAccessor = megafileMap.CreateViewAccessor(readOffset, fileTableSize, MemoryMappedFileAccess.Read)) 90 { 91 for (var i = 0U; i < numFiles; ++i) 92 { 93 subFileAccessor.Read(i * SubFileData.Size, out SubFileData subFile); 94 var fullName = stringTable[subFile.SubfileNameIndex]; 95 fileTable[fullName] = subFile; 96 } 97 } 98 } 99 100 public Stream Open(string path) 101 { 102 if (!fileTable.TryGetValue(path, out SubFileData subFile)) 103 { 104 return null; 105 } 106 107 return megafileMap.CreateViewStream(subFile.SubfileImageDataOffset, subFile.SubfileSize, MemoryMappedFileAccess.Read); 108 } 109 110 public IEnumerator<string> GetEnumerator() 111 { 112 foreach (var file in stringTable) 113 { 114 yield return file; 115 } 116 } 117 118 IEnumerator IEnumerable.GetEnumerator() 119 { 120 return GetEnumerator(); 121 } 122 123 #region IDisposable Support 124 private bool disposedValue = false; 125 126 protected virtual void Dispose(bool disposing) 127 { 128 if (!disposedValue) 129 { 130 if (disposing) 131 { 132 megafileMap.Dispose(); 133 } 134 disposedValue = true; 135 } 136 } 137 138 public void Dispose() 139 { 140 Dispose(true); 141 } 142 #endregion 143 } 144 }