mirror of
https://github.com/simtactics/mysimulation.git
synced 2025-03-15 23:01:21 +00:00
183 lines
6.6 KiB
C#
Executable file
183 lines
6.6 KiB
C#
Executable file
using FSO.Files.Utils;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using deltaq;
|
|
|
|
namespace TSOVersionPatcher
|
|
{
|
|
public class TSOp
|
|
{
|
|
public List<FileEntry> Patches;
|
|
public List<FileEntry> Additions;
|
|
public List<string> Deletions;
|
|
|
|
private Stream Str;
|
|
|
|
public TSOp(Stream str)
|
|
{
|
|
Str = str;
|
|
using (var io = IoBuffer.FromStream(str, ByteOrder.LITTLE_ENDIAN))
|
|
{
|
|
var magic = io.ReadCString(4);
|
|
if (magic != "TSOp")
|
|
throw new Exception("Not a TSO patch file!");
|
|
var version = io.ReadInt32();
|
|
|
|
var ips = io.ReadCString(4);
|
|
if (ips != "IPS_")
|
|
throw new Exception("Invalid Patch Chunk!");
|
|
|
|
var patchCount = io.ReadInt32();
|
|
Patches = new List<FileEntry>();
|
|
for (int i = 0; i < patchCount; i++)
|
|
{
|
|
Patches.Add(new FileEntry()
|
|
{
|
|
FileTarget = io.ReadVariableLengthPascalString().Replace('\\', '/'),
|
|
Length = io.ReadInt32(),
|
|
Offset = str.Position
|
|
});
|
|
str.Seek(Patches.Last().Length, SeekOrigin.Current);
|
|
}
|
|
|
|
|
|
var add = io.ReadCString(4);
|
|
if (add != "ADD_")
|
|
throw new Exception("Invalid Addition Chunk!");
|
|
|
|
var addCount = io.ReadInt32();
|
|
Additions = new List<FileEntry>();
|
|
for (int i = 0; i < addCount; i++)
|
|
{
|
|
Additions.Add(new FileEntry()
|
|
{
|
|
FileTarget = io.ReadVariableLengthPascalString().Replace('\\', '/'),
|
|
Length = io.ReadInt32(),
|
|
Offset = str.Position
|
|
});
|
|
str.Seek(Additions.Last().Length, SeekOrigin.Current);
|
|
}
|
|
|
|
var del = io.ReadCString(4);
|
|
if (del != "DEL_")
|
|
throw new Exception("Invalid Deletion Chunk!");
|
|
|
|
var delCount = io.ReadInt32();
|
|
Deletions = new List<string>();
|
|
for (int i = 0; i < delCount; i++)
|
|
{
|
|
Deletions.Add(io.ReadVariableLengthPascalString());
|
|
}
|
|
}
|
|
}
|
|
|
|
private void RecursiveDirectoryScan(string folder, HashSet<string> fileNames, string basePath)
|
|
{
|
|
var files = Directory.GetFiles(folder);
|
|
foreach (var file in files)
|
|
{
|
|
fileNames.Add(GetRelativePath(basePath, file));
|
|
}
|
|
|
|
var dirs = Directory.GetDirectories(folder);
|
|
foreach (var dir in dirs)
|
|
{
|
|
RecursiveDirectoryScan(dir, fileNames, basePath);
|
|
}
|
|
}
|
|
|
|
private string GetRelativePath(string relativeTo, string path)
|
|
{
|
|
if (relativeTo.EndsWith("/") || relativeTo.EndsWith("\\")) relativeTo += "/";
|
|
var uri = new Uri(relativeTo);
|
|
var rel = Uri.UnescapeDataString(uri.MakeRelativeUri(new Uri(path)).ToString()).Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
|
|
if (rel.Contains(Path.DirectorySeparatorChar.ToString()) == false)
|
|
{
|
|
rel = $".{ Path.DirectorySeparatorChar }{ rel }";
|
|
}
|
|
return rel;
|
|
}
|
|
|
|
public void Apply(string source, string dest, Action<string, float> progress)
|
|
{
|
|
try
|
|
{
|
|
if (source != dest)
|
|
{
|
|
progress("Copying Unchanged Files...", 0);
|
|
//if our destination folder is different,
|
|
//copy unchanged files first.
|
|
var sourceFiles = new HashSet<string>();
|
|
RecursiveDirectoryScan(source, sourceFiles, source);
|
|
|
|
sourceFiles.ExceptWith(new HashSet<string>(Patches.Select(x => x.FileTarget)));
|
|
sourceFiles.ExceptWith(new HashSet<string>(Deletions));
|
|
|
|
foreach (var file in sourceFiles)
|
|
{
|
|
var destP = Path.Combine(dest, file);
|
|
Directory.CreateDirectory(Path.GetDirectoryName(destP));
|
|
|
|
File.Copy(Path.Combine(source, file), destP);
|
|
}
|
|
}
|
|
|
|
var reader = new BinaryReader(Str);
|
|
int total = Patches.Count + Additions.Count + Deletions.Count;
|
|
int fileNum = 0;
|
|
foreach (var patch in Patches)
|
|
{
|
|
progress($"Patching {patch.FileTarget}...", fileNum / (float)total);
|
|
var path = Path.Combine(source, patch.FileTarget);
|
|
var dpath = Path.Combine(dest, patch.FileTarget);
|
|
var data = File.ReadAllBytes(path);
|
|
Directory.CreateDirectory(Path.GetDirectoryName(dpath));
|
|
|
|
Str.Seek(patch.Offset, SeekOrigin.Begin);
|
|
var patchd = reader.ReadBytes(patch.Length);
|
|
BsPatch.Apply(data, patchd, File.Open(dpath, FileMode.Create, FileAccess.Write, FileShare.None));
|
|
fileNum++;
|
|
}
|
|
|
|
foreach (var add in Additions)
|
|
{
|
|
progress($"Adding {add.FileTarget}...", fileNum / (float)total);
|
|
var dpath = Path.Combine(dest, add.FileTarget);
|
|
Directory.CreateDirectory(Path.GetDirectoryName(dpath));
|
|
|
|
Str.Seek(add.Offset, SeekOrigin.Begin);
|
|
var addData = reader.ReadBytes(add.Length);
|
|
File.WriteAllBytes(dpath, addData);
|
|
fileNum++;
|
|
}
|
|
|
|
foreach (var del in Deletions)
|
|
{
|
|
try
|
|
{
|
|
progress($"Deleting {del}...", fileNum / (float)total);
|
|
File.Delete(Path.Combine(dest, del));
|
|
fileNum++;
|
|
}
|
|
catch
|
|
{
|
|
//file not found. not important - we wanted it deleted anyways.
|
|
}
|
|
}
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
progress(e.ToString(), -1f);
|
|
}
|
|
}
|
|
}
|
|
|
|
public class FileEntry
|
|
{
|
|
public string FileTarget;
|
|
public long Offset;
|
|
public int Length;
|
|
}
|
|
}
|