123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196 |
- //
- // Copyright 2020 Electronic Arts Inc.
- //
- // The Command & Conquer Map Editor and corresponding source code is free
- // software: you can redistribute it and/or modify it under the terms of
- // the GNU General Public License as published by the Free Software Foundation,
- // either version 3 of the License, or (at your option) any later version.
- // The Command & Conquer Map Editor and corresponding source code is distributed
- // in the hope that it will be useful, but with permitted additional restrictions
- // under Section 7 of the GPL. See the GNU General Public License in LICENSE.TXT
- // distributed with this program. You should have received a copy of the
- // GNU General Public License along with permitted additional restrictions
- // with this program. If not, see https://github.com/electronicarts/CnC_Remastered_Collection
- using System;
- using System.Collections.Generic;
- using System.IO;
- using System.Linq;
- using System.Runtime.InteropServices;
- using System.Text;
- namespace MobiusEditor.Utility
- {
- public class MegafileBuilder : IDisposable
- {
- #region IDisposable Support
- private bool disposedValue = false;
- protected virtual void Dispose(bool disposing)
- {
- if (!disposedValue)
- {
- if (disposing)
- {
- Out.Dispose();
- }
- disposedValue = true;
- }
- }
- public void Dispose()
- {
- Dispose(true);
- }
- #endregion
- private const float Version = 0.99f;
- public string RootPath { get; private set; }
- private Stream Out { get; set; }
- private List<(string, object)> Files = new List<(string, object)>();
- public MegafileBuilder(string rootPath, string outFile)
- {
- RootPath = rootPath.ToUpper();
- Out = new FileStream(outFile, FileMode.Create);
- }
- public void AddFile(string path)
- {
- if (File.Exists(path))
- {
- Files.Add((Path.GetFileName(path), path));
- }
- }
- public void AddFile(string path, Stream stream)
- {
- Files.Add((Path.GetFileName(path), stream));
- }
- public void AddDirectory(string path)
- {
- AddDirectory(path, "*.*");
- }
- public void AddDirectory(string path, string searchPattern)
- {
- var uriPath = new Uri(path);
- foreach (var file in Directory.GetFiles(path, searchPattern, SearchOption.AllDirectories))
- {
- var relativePath = Uri.UnescapeDataString(uriPath.MakeRelativeUri(new Uri(file)).ToString()).Replace('/', Path.DirectorySeparatorChar);
- Files.Add((relativePath, file));
- }
- }
- public void Write()
- {
- var headerSize = sizeof(uint) * 6U;
- headerSize += SubFileData.Size * (uint)Files.Count;
- var strings = new List<string>();
- Func<string, ushort> stringIndex = (string value) =>
- {
- var index = strings.IndexOf(value);
- if (index < 0)
- {
- index = strings.Count;
- if (index > ushort.MaxValue)
- {
- throw new IndexOutOfRangeException();
- }
- strings.Add(value);
- }
- return (ushort)index;
- };
- var files = new List<(ushort index, uint crc, Stream stream, bool dispose)>();
- foreach (var (filename, source) in Files)
- {
- var name = Encoding.ASCII.GetBytes(filename);
- var crc = CRC.Calculate(name);
- if (source is string)
- {
- var file = source as string;
- if (File.Exists(file))
- {
- files.Add((stringIndex(Path.Combine(RootPath, filename).ToUpper()), crc, new FileStream(file, FileMode.Open, FileAccess.Read), true));
- }
- }
- else if (source is Stream)
- {
- files.Add((stringIndex(Path.Combine(RootPath, filename).ToUpper()), crc, source as Stream, false));
- }
- }
- files = files.OrderBy(x => x.crc).ToList();
- var stringsSize = sizeof(ushort) * (uint)strings.Count;
- stringsSize += (uint)strings.Sum(x => x.Length);
- headerSize += stringsSize;
- var subfileImageOffset = headerSize;
- using (var writer = new BinaryWriter(Out))
- {
- writer.Write(0xFFFFFFFF);
- writer.Write(Version);
- writer.Write(headerSize);
- writer.Write((uint)Files.Count);
- writer.Write((uint)strings.Count);
- writer.Write(stringsSize);
- foreach (var item in strings)
- {
- writer.Write((ushort)item.Length);
- writer.Write(item.ToCharArray());
- }
- using (var fileStream = new MemoryStream())
- {
- for (var i = 0; i < files.Count; ++i)
- {
- var (index, crc, stream, dispose) = files[i];
- var fileSize = (uint)(stream.Length - stream.Position);
- var fileBytes = new byte[fileSize];
- stream.Read(fileBytes, 0, fileBytes.Length);
- fileStream.Write(fileBytes, 0, fileBytes.Length);
- if (dispose)
- {
- stream.Dispose();
- }
- SubFileData data = new SubFileData
- {
- Flags = 0,
- CRCValue = crc,
- SubfileIndex = i,
- SubfileSize = fileSize,
- SubfileImageDataOffset = subfileImageOffset,
- SubfileNameIndex = index
- };
- var ptr = Marshal.AllocHGlobal((int)SubFileData.Size);
- Marshal.StructureToPtr(data, ptr, false);
- var bytes = new byte[SubFileData.Size];
- Marshal.Copy(ptr, bytes, 0, bytes.Length);
- Marshal.FreeHGlobal(ptr);
- writer.Write(bytes);
- subfileImageOffset += data.SubfileSize;
- }
- fileStream.Seek(0, SeekOrigin.Begin);
- fileStream.CopyTo(writer.BaseStream);
- }
- }
- }
- }
- }
|