CnC_Remastered_Collection

Command and Conquer: Red Alert
Log | Files | Refs | README | LICENSE

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 }