mirror of
https://github.com/electronicarts/CnC_Remastered_Collection.git
synced 2025-08-18 03:41:24 +02:00
C&C Remastered Map Editor
Initial commit of C&C Remastered Map Editor code
This commit is contained in:
196
CnCTDRAMapEditor/Utility/MegafileBuilder.cs
Normal file
196
CnCTDRAMapEditor/Utility/MegafileBuilder.cs
Normal file
@@ -0,0 +1,196 @@
|
||||
//
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user