CnC_Remastered_Collection

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

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 }