mirror of
https://github.com/luanslimadev/Wand-Enhancer.git
synced 2026-08-28 17:01:05 +00:00
fix build script
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using AsarSharp.Integrity;
|
||||
using AsarSharp.PickleTools;
|
||||
using AsarSharp.Utils;
|
||||
using Newtonsoft.Json;
|
||||
@@ -10,7 +12,8 @@ namespace AsarSharp.AsarFileSystem
|
||||
public static class Disk
|
||||
{
|
||||
private const int StreamBufferSize = 1024 * 1024;
|
||||
private static Dictionary<string, Filesystem> _filesystemCache = new Dictionary<string, Filesystem>();
|
||||
private static readonly ConcurrentDictionary<string, Filesystem> _filesystemCache =
|
||||
new ConcurrentDictionary<string, Filesystem>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
public class ArchiveHeader
|
||||
{
|
||||
@@ -30,36 +33,29 @@ namespace AsarSharp.AsarFileSystem
|
||||
public string Filename { get; set; }
|
||||
public bool Unpack { get; set; }
|
||||
}
|
||||
|
||||
|
||||
|
||||
#region Reading
|
||||
|
||||
|
||||
public static ArchiveHeader ReadArchiveHeaderSync(string archivePath)
|
||||
{
|
||||
using (var fs = new FileStream(archivePath, FileMode.Open, FileAccess.Read, FileShare.Read, StreamBufferSize, FileOptions.SequentialScan))
|
||||
{
|
||||
// read the size of the header (8 bytes)
|
||||
using (var fs = new FileStream(archivePath, FileMode.Open, FileAccess.Read, FileShare.Read,
|
||||
65536, FileOptions.SequentialScan))
|
||||
{
|
||||
byte[] sizeBuf = new byte[8];
|
||||
if (fs.Read(sizeBuf, 0, 8) != 8)
|
||||
{
|
||||
throw new Exception("Unable to read header size");
|
||||
}
|
||||
|
||||
|
||||
var sizePickle = Pickle.CreateFromBuffer(sizeBuf);
|
||||
var size = sizePickle.CreateIterator().ReadUInt32();
|
||||
|
||||
// Read the header of the specified size
|
||||
|
||||
var headerBuf = new byte[size];
|
||||
if(fs.Read(headerBuf, 0, (int)size) != size)
|
||||
{
|
||||
if (fs.Read(headerBuf, 0, (int)size) != size)
|
||||
throw new Exception("Unable to read header");
|
||||
}
|
||||
|
||||
|
||||
var headerPickle = Pickle.CreateFromBuffer(headerBuf);
|
||||
var header = headerPickle.CreateIterator().ReadString();
|
||||
|
||||
var headerObj = JsonConvert.DeserializeObject<FilesystemEntry>(header);
|
||||
|
||||
|
||||
return new ArchiveHeader
|
||||
{
|
||||
Header = headerObj,
|
||||
@@ -68,82 +64,62 @@ namespace AsarSharp.AsarFileSystem
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public static Filesystem ReadFilesystemSync(string archivePath)
|
||||
{
|
||||
if (!_filesystemCache.ContainsKey(archivePath) || _filesystemCache[archivePath] == null)
|
||||
return _filesystemCache.GetOrAdd(archivePath, key =>
|
||||
{
|
||||
ArchiveHeader header = ReadArchiveHeaderSync(archivePath);
|
||||
Filesystem filesystem = new Filesystem(archivePath);
|
||||
var header = ReadArchiveHeaderSync(key);
|
||||
var filesystem = new Filesystem(key);
|
||||
filesystem.SetHeader(header.Header, header.HeaderSize);
|
||||
_filesystemCache[archivePath] = filesystem;
|
||||
}
|
||||
|
||||
return _filesystemCache[archivePath];
|
||||
return filesystem;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public static byte[] ReadFileSync(Filesystem filesystem, string filename, FilesystemEntry info)
|
||||
{
|
||||
if (!info.IsFile || !info.Size.HasValue)
|
||||
{
|
||||
throw new ArgumentException("Entry is not a file", nameof(info));
|
||||
}
|
||||
|
||||
long size = info.Size.Value;
|
||||
byte[] buffer = new byte[size];
|
||||
|
||||
if (size <= 0)
|
||||
{
|
||||
return buffer;
|
||||
}
|
||||
|
||||
|
||||
if (size <= 0) return buffer;
|
||||
|
||||
if (info.Unpacked == true)
|
||||
{
|
||||
// It's an unpacked file, read it directly
|
||||
string filePath = Path.Combine($"{filesystem.GetRootPath()}.unpacked", filename);
|
||||
return File.ReadAllBytes(filePath);
|
||||
}
|
||||
|
||||
// Read from the ASAR archive
|
||||
using (var fs = new FileStream(filesystem.GetRootPath(), FileMode.Open, FileAccess.Read, FileShare.Read, StreamBufferSize, FileOptions.SequentialScan))
|
||||
using (var fs = new FileStream(filesystem.GetRootPath(), FileMode.Open, FileAccess.Read,
|
||||
FileShare.Read, 65536, FileOptions.RandomAccess))
|
||||
{
|
||||
// Important: the offset must take into account the size of the Pickle header (8 bytes)
|
||||
// and the size of the header itself
|
||||
long offset = 8 + filesystem.GetHeaderSize() + long.Parse(info.Offset);
|
||||
fs.Position = offset;
|
||||
|
||||
// Read the whole file at once
|
||||
int bytesRead = fs.Read(buffer, 0, (int)size);
|
||||
if (bytesRead != size)
|
||||
{
|
||||
throw new Exception($"Failed to read entire file, got {bytesRead} bytes instead of {size}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
public static bool UncacheFilesystem(string archivePath)
|
||||
{
|
||||
if (_filesystemCache.ContainsKey(archivePath))
|
||||
{
|
||||
_filesystemCache.Remove(archivePath);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
return _filesystemCache.TryRemove(archivePath, out _);
|
||||
}
|
||||
|
||||
public static void UncacheAll()
|
||||
{
|
||||
_filesystemCache.Clear();
|
||||
}
|
||||
|
||||
|
||||
public static void CopyFile(string dest, string rootPath, string filename)
|
||||
{
|
||||
if(dest == null || rootPath == null || filename == null)
|
||||
if (dest == null || rootPath == null || filename == null)
|
||||
throw new ArgumentNullException();
|
||||
|
||||
string normalizedDestRoot = Path.GetFullPath(dest)
|
||||
@@ -152,63 +128,97 @@ namespace AsarSharp.AsarFileSystem
|
||||
.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
|
||||
|
||||
if (string.Equals(normalizedDestRoot, normalizedRootPath, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
string sourcePath = Path.GetFullPath(Path.Combine(rootPath, filename));
|
||||
string destPath = Path.GetFullPath(Path.Combine(dest, filename));
|
||||
|
||||
if (string.Equals(sourcePath, destPath, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(destPath) ?? throw new InvalidOperationException());
|
||||
using (var sourceStream = new FileStream(sourcePath, FileMode.Open, FileAccess.Read, FileShare.Read, StreamBufferSize, FileOptions.SequentialScan))
|
||||
using (var destinationStream = new FileStream(destPath, FileMode.Create, FileAccess.Write, FileShare.None, StreamBufferSize, FileOptions.SequentialScan))
|
||||
using (var src = new FileStream(sourcePath, FileMode.Open, FileAccess.Read, FileShare.Read, StreamBufferSize, FileOptions.SequentialScan))
|
||||
using (var dst = new FileStream(destPath, FileMode.Create, FileAccess.Write, FileShare.None, StreamBufferSize, FileOptions.SequentialScan))
|
||||
{
|
||||
sourceStream.CopyTo(destinationStream, StreamBufferSize);
|
||||
src.CopyTo(dst, StreamBufferSize);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static void WriteFileSystem(string dest, Filesystem fileSystem,
|
||||
FilesystemFilesAndLinks lists,
|
||||
Dictionary<string, CrawledFileType> metadata)
|
||||
FilesystemFilesAndLinks lists, Dictionary<string, CrawledFileType> metadata)
|
||||
{
|
||||
var fsHeader = fileSystem.GetHeader();
|
||||
var headerPickle = Pickle.CreateEmpty();
|
||||
var serializerSettings = new JsonSerializerSettings()
|
||||
{ NullValueHandling = NullValueHandling.Ignore, DefaultValueHandling = DefaultValueHandling.Ignore } ;
|
||||
var serializerSettings = new JsonSerializerSettings
|
||||
{
|
||||
NullValueHandling = NullValueHandling.Ignore,
|
||||
DefaultValueHandling = DefaultValueHandling.Ignore
|
||||
};
|
||||
|
||||
var headerJson = JsonConvert.SerializeObject(fsHeader,serializerSettings);
|
||||
// --- Phase 1: write placeholder header ---
|
||||
string headerJson = JsonConvert.SerializeObject(fileSystem.GetHeader(), serializerSettings);
|
||||
var headerPickle = Pickle.CreateEmpty();
|
||||
headerPickle.WriteString(headerJson);
|
||||
var headerBuf = headerPickle.ToBuffer();
|
||||
|
||||
|
||||
var sizePickle = Pickle.CreateEmpty();
|
||||
sizePickle.WriteUInt32((uint)headerBuf.Length);
|
||||
var sizeBuf = sizePickle.ToBuffer();
|
||||
|
||||
sizePickle.WriteUInt32((uint)headerPickle.GetTotalSize());
|
||||
int sizePickleSize = sizePickle.GetTotalSize();
|
||||
|
||||
var buf = new byte[StreamBufferSize];
|
||||
var blockBuf = new byte[4 * 1024 * 1024]; // shared across all files — avoids 4MB alloc per file
|
||||
|
||||
using (var fs = new FileStream(dest, FileMode.Create, FileAccess.Write, FileShare.None, StreamBufferSize, FileOptions.SequentialScan))
|
||||
{
|
||||
fs.Write(sizeBuf, 0, sizeBuf.Length);
|
||||
fs.Write(headerBuf, 0, headerBuf.Length);
|
||||
|
||||
sizePickle.WriteTo(fs);
|
||||
headerPickle.WriteTo(fs);
|
||||
|
||||
// --- Phase 2: stream files, hash in one pass, patch nodes in-memory ---
|
||||
foreach (var file in lists.Files)
|
||||
{
|
||||
if (file.Unpack)
|
||||
{
|
||||
var filename = Extensions.GetRelativePath(fileSystem.GetRootPath(), file.Filename);
|
||||
CopyFile($"{dest}.unpacked", fileSystem.GetRootPath(), filename);
|
||||
var relName = Extensions.GetRelativePath(fileSystem.GetRootPath(), file.Filename);
|
||||
CopyFile($"{dest}.unpacked", fileSystem.GetRootPath(), relName);
|
||||
CopyAndHash(file.Filename, null, buf, blockBuf, fileSystem);
|
||||
continue;
|
||||
}
|
||||
using (var transformedFileStream = new FileStream(file.Filename, FileMode.Open, FileAccess.Read, FileShare.Read, StreamBufferSize, FileOptions.SequentialScan))
|
||||
{
|
||||
transformedFileStream.CopyTo(fs, StreamBufferSize);
|
||||
}
|
||||
|
||||
CopyAndHash(file.Filename, fs, buf, blockBuf, fileSystem);
|
||||
}
|
||||
|
||||
// --- Phase 3: re-serialize header with real hashes, seek back, overwrite ---
|
||||
string patchedJson = JsonConvert.SerializeObject(fileSystem.GetHeader(), serializerSettings);
|
||||
var patchedPickle = Pickle.CreateEmpty();
|
||||
patchedPickle.WriteString(patchedJson);
|
||||
|
||||
var patchedSizePickle = Pickle.CreateEmpty();
|
||||
patchedSizePickle.WriteUInt32((uint)patchedPickle.GetTotalSize());
|
||||
|
||||
fs.Position = 0;
|
||||
patchedSizePickle.WriteTo(fs);
|
||||
patchedPickle.WriteTo(fs);
|
||||
}
|
||||
}
|
||||
|
||||
private static void CopyAndHash(string srcPath, Stream dest, byte[] buf, byte[] blockBuf, Filesystem fs)
|
||||
{
|
||||
string relPath = Extensions.GetRelativePath(fs.GetRootPath(), srcPath);
|
||||
var node = fs.GetNode(relPath, followLinks: false);
|
||||
|
||||
long fileSize = node?.Size ?? 0;
|
||||
int estimatedBlocks = fileSize > 0 ? (int)((fileSize + 4 * 1024 * 1024 - 1) / (4 * 1024 * 1024)) : 0;
|
||||
|
||||
using (var hasher = new IntegrityHelper.StreamingHasher(estimatedBlocks, blockBuf))
|
||||
using (var src = new FileStream(srcPath, FileMode.Open, FileAccess.Read, FileShare.Read, StreamBufferSize, FileOptions.SequentialScan))
|
||||
{
|
||||
int read;
|
||||
while ((read = src.Read(buf, 0, buf.Length)) > 0)
|
||||
{
|
||||
hasher.Append(buf, 0, read);
|
||||
dest?.Write(buf, 0, read);
|
||||
}
|
||||
|
||||
if (node != null)
|
||||
node.Integrity = hasher.Finalise();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user