mirror of
https://github.com/luanslimadev/Wand-Enhancer.git
synced 2026-08-28 17:01:05 +00:00
feat: ship 1.0.8.0 release automation and runtime overhaul
- reduce ASAR IO overhead with streamed archive reads, buffered copies, faster relative-path handling and placeholder integrity records - fix in-place app.asar.unpacked packing/extraction self-copy cases that caused locked-file failures - tighten JS patch discovery with candidate bundle filters and search hints - require prebuilt remote-panel dist artifacts and clean up embedded bridge/script packaging - add unified build entrypoints for PowerShell, cmd and bash and move native CMake output under .tmp - add release metadata validation, changelog section extraction, pre-commit hook and GitHub Actions validation/release pipelines - make CHANGELOG the source of truth for release notes and document the tag-driven release flow - add updater release notes UI with latest/full changelog loading and localize the new update strings - modularize bridge renderer scripts, add installed apps and game status sync, and support remote launch/stop commands - centralize bridge protocol, IPC and WebSocket constants and improve LAN IP selection for QR pairing - refactor remote panel controls/state enums, persist accent color, polish library/session UI and refresh assets
This commit is contained in:
+31
-24
@@ -1,14 +1,13 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using AsarSharp.AsarFileSystem;
|
||||
using AsarSharp.Integrity;
|
||||
using AsarSharp.Utils;
|
||||
|
||||
namespace AsarSharp
|
||||
{
|
||||
|
||||
public class CreateOptions
|
||||
{
|
||||
public Regex Unpack { get; set; }
|
||||
@@ -28,10 +27,10 @@ namespace AsarSharp
|
||||
_destPath = destPath ?? throw new ArgumentNullException(nameof(destPath));
|
||||
_options = options;
|
||||
}
|
||||
|
||||
|
||||
public void CreatePackageWithOptions()
|
||||
{
|
||||
var result = FileSystemCrawler.CrawlFileSystem(_folderPath);
|
||||
var result = FileSystemCrawler.CrawlFileSystem(_folderPath);
|
||||
_filenames = result.filenames;
|
||||
_metadata = result.metadata;
|
||||
CreatePackageFromFiles();
|
||||
@@ -41,28 +40,25 @@ namespace AsarSharp
|
||||
public void CreatePackageFromFiles()
|
||||
{
|
||||
var filesystem = new Filesystem(_folderPath);
|
||||
var files = new List<Disk.BasicFileInfo>();
|
||||
|
||||
var filenamesSorted = _filenames.ToList();
|
||||
|
||||
foreach (var filename in filenamesSorted)
|
||||
var files = new List<Disk.BasicFileInfo>(_filenames.Count);
|
||||
|
||||
foreach (var filename in _filenames)
|
||||
{
|
||||
HandleFile(filesystem, filename, files);
|
||||
}
|
||||
|
||||
InsertsDone(filesystem, files);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
private void HandleFile(Filesystem filesystem, string filename, List<Disk.BasicFileInfo> files)
|
||||
{
|
||||
if (!_metadata.ContainsKey(filename))
|
||||
if (!_metadata.TryGetValue(filename, out var file))
|
||||
{
|
||||
var fileType = FileSystemCrawler.DetermineFileType(filename);
|
||||
_metadata[filename] = fileType ?? throw new Exception("Unknown file type for file: " + filename);
|
||||
file = FileSystemCrawler.DetermineFileType(filename)
|
||||
?? throw new Exception("Unknown file type for file: " + filename);
|
||||
_metadata[filename] = file;
|
||||
}
|
||||
var file = _metadata[filename];
|
||||
|
||||
switch (file.Type)
|
||||
{
|
||||
@@ -70,9 +66,18 @@ namespace AsarSharp
|
||||
filesystem.InsertDirectory(filename, false);
|
||||
break;
|
||||
case FileType.File:
|
||||
var shouldUnpack = ShouldUnpackPath(Extensions.GetRelativePath(_folderPath, Path.GetDirectoryName(filename)));
|
||||
string parentDir = Path.GetDirectoryName(filename) ?? string.Empty;
|
||||
string relParent = Extensions.GetRelativePath(_folderPath, parentDir);
|
||||
bool shouldUnpack = ShouldUnpackPath(relParent);
|
||||
files.Add(new Disk.BasicFileInfo { Filename = filename, Unpack = shouldUnpack });
|
||||
filesystem.InsertFile(filename, shouldUnpack, file);
|
||||
|
||||
// Build a placeholder integrity record up front. Real
|
||||
// SHA-256 hashes are filled in by Disk.WriteFileSystem
|
||||
// during the streamed write — eliminates the second pass
|
||||
// over each file (open → hash → close → open → copy → close).
|
||||
long size = (file.Stat is FileInfo fi) ? fi.Length : 0;
|
||||
var placeholder = IntegrityHelper.CreatePlaceholder(size);
|
||||
filesystem.InsertFile(filename, shouldUnpack, file, placeholder);
|
||||
break;
|
||||
case FileType.Link:
|
||||
throw new NotImplementedException();
|
||||
@@ -81,14 +86,16 @@ namespace AsarSharp
|
||||
|
||||
private bool ShouldUnpackPath(string relativePath)
|
||||
{
|
||||
return _options.Unpack?.IsMatch(relativePath) == true;
|
||||
return _options?.Unpack?.IsMatch(relativePath) == true;
|
||||
}
|
||||
|
||||
private void InsertsDone(Filesystem filesystem, List<Disk.BasicFileInfo> files)
|
||||
{
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(_destPath) ?? throw new InvalidOperationException());
|
||||
Disk.WriteFileSystem(_destPath, filesystem, new Disk.FilesystemFilesAndLinks { Files = files, Links = null }, _metadata);
|
||||
Directory.CreateDirectory(
|
||||
Path.GetDirectoryName(_destPath)
|
||||
?? throw new InvalidOperationException());
|
||||
Disk.WriteFileSystem(_destPath, filesystem,
|
||||
new Disk.FilesystemFilesAndLinks { Files = files, Links = null }, _metadata);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+136
-102
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
@@ -10,126 +10,62 @@ namespace AsarSharp
|
||||
{
|
||||
public class AsarExtractor
|
||||
{
|
||||
private const int IO_BUFFER_SIZE = 1024 * 1024;
|
||||
private const int FS_INTERNAL_BUFFER = 4096;
|
||||
|
||||
public static void ExtractAll(string archivePath, string dest)
|
||||
{
|
||||
var filesystem = Disk.ReadFilesystemSync(archivePath);
|
||||
var filenames = filesystem.ListFiles();
|
||||
|
||||
// under windows just extract links as regular files
|
||||
// On Windows, links are extracted as plain files.
|
||||
bool followLinks = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
|
||||
|
||||
// create destination directory
|
||||
Directory.CreateDirectory(dest);
|
||||
|
||||
byte[] ioBuffer = new byte[IO_BUFFER_SIZE];
|
||||
var dirCache = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { Path.GetFullPath(dest) };
|
||||
var extractionErrors = new List<Exception>();
|
||||
foreach (var fullPath in filenames)
|
||||
string rootPath = filesystem.GetRootPath();
|
||||
long dataOffset = 8 + filesystem.GetHeaderSize();
|
||||
|
||||
// One archive handle for all reads — old code opened it per file.
|
||||
using (var archive = new FileStream(rootPath, FileMode.Open, FileAccess.Read, FileShare.Read,
|
||||
FS_INTERNAL_BUFFER, FileOptions.RandomAccess))
|
||||
{
|
||||
try
|
||||
foreach (var fullPath in filenames)
|
||||
{
|
||||
// Remove leading slash
|
||||
var filename = fullPath.Substring(1);
|
||||
var destFilename = Path.Combine(dest, filename);
|
||||
var file = filesystem.GetFile(filename, followLinks);
|
||||
|
||||
// Check that the file is not written outside the specified destination folder
|
||||
string relativePath = Extensions.GetRelativePath(dest, destFilename);
|
||||
if (relativePath.StartsWith(".."))
|
||||
try
|
||||
{
|
||||
throw new InvalidOperationException($"{fullPath}: file \"{destFilename}\" writes out of the package");
|
||||
}
|
||||
var filename = fullPath.Substring(1);
|
||||
var destFilename = Path.Combine(dest, filename);
|
||||
var file = filesystem.GetFile(filename, followLinks);
|
||||
|
||||
if (file.IsDirectory)
|
||||
{
|
||||
// it's a directory, create it and continue with the next entry
|
||||
Directory.CreateDirectory(destFilename);
|
||||
}
|
||||
// TODO (LINK NOT SUPPORTED)
|
||||
else if (file.IsLink)
|
||||
{
|
||||
// it's a symlink, create a symlink
|
||||
var linkSrcPath = Extensions.GetDirectoryName(Path.Combine(dest, file.Link));
|
||||
var linkDestPath = Extensions.GetDirectoryName(destFilename);
|
||||
var relativeLinkPath = Extensions.GetRelativePath(linkDestPath, linkSrcPath);
|
||||
|
||||
// try to delete output file, because we can't overwrite a link
|
||||
try
|
||||
{
|
||||
File.Delete(destFilename);
|
||||
}
|
||||
catch {
|
||||
// Ignore errors during file link deletion
|
||||
}
|
||||
|
||||
var linkTo = Path.Combine(relativeLinkPath, Path.GetFileName(file.Link));
|
||||
|
||||
if (Extensions.GetRelativePath(dest, linkSrcPath).StartsWith(".."))
|
||||
// Path-traversal guard.
|
||||
string relativePath = Extensions.GetRelativePath(dest, destFilename);
|
||||
if (relativePath.StartsWith(".."))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"{fullPath}: file \"{file.Link}\" links out of the package to \"{linkSrcPath}\"");
|
||||
$"{fullPath}: file \"{destFilename}\" writes out of the package");
|
||||
}
|
||||
|
||||
// On Windows, creating symlinks requires additional permissions or enabling Developer Mode,
|
||||
// so just copy the contents of the file
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
if (file.IsDirectory)
|
||||
{
|
||||
var targetPath = Path.Combine(linkSrcPath, Path.GetFileName(file.Link));
|
||||
if (Directory.Exists(targetPath))
|
||||
{
|
||||
Directory.CreateDirectory(destFilename);
|
||||
Extensions.CopyDirectory(targetPath, destFilename);
|
||||
}
|
||||
else if (File.Exists(targetPath))
|
||||
{
|
||||
Directory.CreateDirectory(Extensions.GetDirectoryName(destFilename));
|
||||
File.Copy(targetPath, destFilename, true);
|
||||
}
|
||||
EnsureDirectory(destFilename, dirCache);
|
||||
continue;
|
||||
}
|
||||
else
|
||||
|
||||
if (file.IsLink)
|
||||
{
|
||||
// On Unix systems we use symlinks
|
||||
Directory.CreateDirectory(Extensions.GetDirectoryName(destFilename));
|
||||
Extensions.CreateSymbolicLink(linkTo, destFilename);
|
||||
ExtractLink(dest, fullPath, destFilename, file, dirCache);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (file.IsFile)
|
||||
{
|
||||
// it's a file, try to extract it
|
||||
|
||||
if (!file.IsFile) continue;
|
||||
|
||||
try
|
||||
{
|
||||
// Unpacked entries already live on disk next to the archive in
|
||||
// "<archive>.unpacked". When the caller extracts INTO that same
|
||||
// directory (e.g. re-extracting in place to repack later) reading +
|
||||
// writing the file is a self-copy that needlessly fails when the
|
||||
// file is locked by another process (TrainerLib_x64.dll) or has been
|
||||
// removed from disk by an installer (auxiliary/GameLauncher.exe).
|
||||
if (file.Unpacked == true)
|
||||
{
|
||||
string unpackedSourcePath = Path.GetFullPath(
|
||||
Path.Combine($"{filesystem.GetRootPath()}.unpacked", filename));
|
||||
string unpackedDestPath = Path.GetFullPath(destFilename);
|
||||
|
||||
if (string.Equals(unpackedSourcePath, unpackedDestPath, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// Nothing to do – the file is already at the destination.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!File.Exists(unpackedSourcePath))
|
||||
{
|
||||
// The header references an unpacked file that no longer
|
||||
// exists on disk; skip it instead of aborting the whole
|
||||
// extraction so the rest of the asar can still be repacked.
|
||||
continue;
|
||||
}
|
||||
|
||||
Directory.CreateDirectory(Extensions.GetDirectoryName(destFilename));
|
||||
File.Copy(unpackedSourcePath, destFilename, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
byte[] content = Disk.ReadFileSync(filesystem, filename, file);
|
||||
File.WriteAllBytes(destFilename, content);
|
||||
}
|
||||
ExtractFile(archive, dataOffset, rootPath, filename, destFilename, file, ioBuffer, dirCache);
|
||||
|
||||
if (file.Executable == true && !RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
@@ -141,10 +77,10 @@ namespace AsarSharp
|
||||
extractionErrors.Add(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
extractionErrors.Add(ex);
|
||||
catch (Exception ex)
|
||||
{
|
||||
extractionErrors.Add(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,5 +92,103 @@ namespace AsarSharp
|
||||
extractionErrors);
|
||||
}
|
||||
}
|
||||
|
||||
private static void EnsureDirectory(string path, HashSet<string> cache)
|
||||
{
|
||||
string full = Path.GetFullPath(path);
|
||||
if (cache.Contains(full)) return;
|
||||
Directory.CreateDirectory(full);
|
||||
// Mark every ancestor too so siblings skip the syscall.
|
||||
string p = full;
|
||||
while (!string.IsNullOrEmpty(p) && cache.Add(p))
|
||||
{
|
||||
p = Path.GetDirectoryName(p);
|
||||
}
|
||||
}
|
||||
|
||||
private static void EnsureParentDir(string filePath, HashSet<string> cache)
|
||||
{
|
||||
string parent = Path.GetDirectoryName(filePath);
|
||||
if (string.IsNullOrEmpty(parent)) return;
|
||||
EnsureDirectory(parent, cache);
|
||||
}
|
||||
|
||||
private static void ExtractFile(FileStream archive, long dataOffset, string rootPath,
|
||||
string filename, string destFilename, FilesystemEntry file, byte[] buffer,
|
||||
HashSet<string> dirCache)
|
||||
{
|
||||
EnsureParentDir(destFilename, dirCache);
|
||||
|
||||
if (file.Unpacked == true)
|
||||
{
|
||||
string unpackedSourcePath = Path.GetFullPath(Path.Combine($"{rootPath}.unpacked", filename));
|
||||
string unpackedDestPath = Path.GetFullPath(destFilename);
|
||||
|
||||
if (string.Equals(unpackedSourcePath, unpackedDestPath, StringComparison.OrdinalIgnoreCase))
|
||||
return; // self-copy
|
||||
if (!File.Exists(unpackedSourcePath))
|
||||
return; // header references a missing unpacked file — skip rather than abort
|
||||
|
||||
File.Copy(unpackedSourcePath, destFilename, true);
|
||||
return;
|
||||
}
|
||||
|
||||
long size = file.Size ?? 0;
|
||||
using (var dst = new FileStream(destFilename, FileMode.Create, FileAccess.Write, FileShare.None,
|
||||
FS_INTERNAL_BUFFER, FileOptions.SequentialScan))
|
||||
{
|
||||
if (size <= 0) return;
|
||||
|
||||
archive.Position = dataOffset + long.Parse(file.Offset);
|
||||
long remaining = size;
|
||||
while (remaining > 0)
|
||||
{
|
||||
int toRead = remaining > buffer.Length ? buffer.Length : (int)remaining;
|
||||
int got = archive.Read(buffer, 0, toRead);
|
||||
if (got <= 0) throw new EndOfStreamException("Archive truncated");
|
||||
dst.Write(buffer, 0, got);
|
||||
remaining -= got;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void ExtractLink(string dest, string fullPath, string destFilename,
|
||||
FilesystemEntry file, HashSet<string> dirCache)
|
||||
{
|
||||
var linkSrcPath = Extensions.GetDirectoryName(Path.Combine(dest, file.Link));
|
||||
var linkDestPath = Extensions.GetDirectoryName(destFilename);
|
||||
var relativeLinkPath = Extensions.GetRelativePath(linkDestPath, linkSrcPath);
|
||||
|
||||
try { File.Delete(destFilename); }
|
||||
catch { /* ignore — failing to remove an existing link is non-fatal */ }
|
||||
|
||||
var linkTo = Path.Combine(relativeLinkPath, Path.GetFileName(file.Link));
|
||||
|
||||
if (Extensions.GetRelativePath(dest, linkSrcPath).StartsWith(".."))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"{fullPath}: file \"{file.Link}\" links out of the package to \"{linkSrcPath}\"");
|
||||
}
|
||||
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
var targetPath = Path.Combine(linkSrcPath, Path.GetFileName(file.Link));
|
||||
if (Directory.Exists(targetPath))
|
||||
{
|
||||
EnsureDirectory(destFilename, dirCache);
|
||||
Extensions.CopyDirectory(targetPath, destFilename);
|
||||
}
|
||||
else if (File.Exists(targetPath))
|
||||
{
|
||||
EnsureParentDir(destFilename, dirCache);
|
||||
File.Copy(targetPath, destFilename, true);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
EnsureParentDir(destFilename, dirCache);
|
||||
Extensions.CreateSymbolicLink(linkTo, destFilename);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using AsarSharp.PickleTools;
|
||||
@@ -9,6 +9,7 @@ namespace AsarSharp.AsarFileSystem
|
||||
{
|
||||
public static class Disk
|
||||
{
|
||||
private const int StreamBufferSize = 1024 * 1024;
|
||||
private static Dictionary<string, Filesystem> _filesystemCache = new Dictionary<string, Filesystem>();
|
||||
|
||||
public class ArchiveHeader
|
||||
@@ -35,7 +36,7 @@ namespace AsarSharp.AsarFileSystem
|
||||
|
||||
public static ArchiveHeader ReadArchiveHeaderSync(string archivePath)
|
||||
{
|
||||
using (FileStream fs = File.OpenRead(archivePath))
|
||||
using (var fs = new FileStream(archivePath, FileMode.Open, FileAccess.Read, FileShare.Read, StreamBufferSize, FileOptions.SequentialScan))
|
||||
{
|
||||
// read the size of the header (8 bytes)
|
||||
byte[] sizeBuf = new byte[8];
|
||||
@@ -103,7 +104,7 @@ namespace AsarSharp.AsarFileSystem
|
||||
}
|
||||
|
||||
// Read from the ASAR archive
|
||||
using (FileStream fs = File.OpenRead(filesystem.GetRootPath()))
|
||||
using (var fs = new FileStream(filesystem.GetRootPath(), FileMode.Open, FileAccess.Read, FileShare.Read, StreamBufferSize, FileOptions.SequentialScan))
|
||||
{
|
||||
// Important: the offset must take into account the size of the Pickle header (8 bytes)
|
||||
// and the size of the header itself
|
||||
@@ -145,19 +146,29 @@ namespace AsarSharp.AsarFileSystem
|
||||
if(dest == null || rootPath == null || filename == null)
|
||||
throw new ArgumentNullException();
|
||||
|
||||
if (dest == rootPath)
|
||||
string normalizedDestRoot = Path.GetFullPath(dest)
|
||||
.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
|
||||
string normalizedRootPath = Path.GetFullPath(rootPath)
|
||||
.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
|
||||
|
||||
if (string.Equals(normalizedDestRoot, normalizedRootPath, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
string sourcePath = Path.Combine(rootPath, filename);
|
||||
string destPath = Path.Combine(dest, filename);
|
||||
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))
|
||||
using (var destinationStream = new FileStream(destPath, FileMode.Create, FileAccess.Write))
|
||||
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))
|
||||
{
|
||||
sourceStream.CopyTo(destinationStream);
|
||||
sourceStream.CopyTo(destinationStream, StreamBufferSize);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,7 +190,7 @@ namespace AsarSharp.AsarFileSystem
|
||||
sizePickle.WriteUInt32((uint)headerBuf.Length);
|
||||
var sizeBuf = sizePickle.ToBuffer();
|
||||
|
||||
using (FileStream fs = File.Create(dest))
|
||||
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);
|
||||
@@ -192,9 +203,9 @@ namespace AsarSharp.AsarFileSystem
|
||||
CopyFile($"{dest}.unpacked", fileSystem.GetRootPath(), filename);
|
||||
continue;
|
||||
}
|
||||
using (var transformedFileStream = new FileStream(file.Filename, FileMode.Open, FileAccess.Read))
|
||||
using (var transformedFileStream = new FileStream(file.Filename, FileMode.Open, FileAccess.Read, FileShare.Read, StreamBufferSize, FileOptions.SequentialScan))
|
||||
{
|
||||
transformedFileStream.CopyTo(fs);
|
||||
transformedFileStream.CopyTo(fs, StreamBufferSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using AsarSharp.Integrity;
|
||||
@@ -150,7 +150,6 @@ namespace AsarSharp.AsarFileSystem
|
||||
public static string ReadLink(string path)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
return Path.GetFileName(path);
|
||||
// TODO , NOT IMPLEMENTED
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
@@ -28,32 +28,37 @@ namespace AsarSharp.AsarFileSystem
|
||||
|
||||
public static class FileSystemCrawler
|
||||
{
|
||||
|
||||
|
||||
public static CrawledFileType DetermineFileType(string filename)
|
||||
{
|
||||
var fileInfo = new FileInfo(filename);
|
||||
if (fileInfo.Exists)
|
||||
FileAttributes attributes;
|
||||
try
|
||||
{
|
||||
return new CrawledFileType { Type = FileType.File, Stat = fileInfo };
|
||||
attributes = File.GetAttributes(filename);
|
||||
}
|
||||
|
||||
var directoryInfo = new DirectoryInfo(filename);
|
||||
if (directoryInfo.Exists)
|
||||
catch (Exception ex) when (ex is IOException || ex is UnauthorizedAccessException)
|
||||
{
|
||||
return new CrawledFileType { Type = FileType.Directory, Stat = directoryInfo };
|
||||
return null;
|
||||
}
|
||||
|
||||
var linkInfo = new FileInfo(filename);
|
||||
if (linkInfo.Exists && (linkInfo.Attributes & FileAttributes.ReparsePoint) == FileAttributes.ReparsePoint)
|
||||
|
||||
bool isDirectory = (attributes & FileAttributes.Directory) == FileAttributes.Directory;
|
||||
bool isLink = (attributes & FileAttributes.ReparsePoint) == FileAttributes.ReparsePoint;
|
||||
FileSystemInfo info = isDirectory
|
||||
? (FileSystemInfo)new DirectoryInfo(filename)
|
||||
: new FileInfo(filename);
|
||||
|
||||
if (isLink)
|
||||
{
|
||||
return new CrawledFileType { Type = FileType.Link, Stat = linkInfo };
|
||||
return new CrawledFileType { Type = FileType.Link, Stat = info };
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
if (isDirectory)
|
||||
{
|
||||
return new CrawledFileType { Type = FileType.Directory, Stat = info };
|
||||
}
|
||||
|
||||
return new CrawledFileType { Type = FileType.File, Stat = info };
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static (List<string> filenames, Dictionary<string, CrawledFileType> metadata) CrawlFileSystem(string dir)
|
||||
{
|
||||
var metadata = new Dictionary<string, CrawledFileType>();
|
||||
@@ -73,7 +78,12 @@ namespace AsarSharp.AsarFileSystem
|
||||
filenames.Add(result.filename);
|
||||
}
|
||||
|
||||
var filteredFilenames = new List<string>();
|
||||
if (links.Count == 0)
|
||||
{
|
||||
return (filenames, metadata);
|
||||
}
|
||||
|
||||
var filteredFilenames = new List<string>(filenames.Count);
|
||||
|
||||
foreach (var filename in filenames)
|
||||
{
|
||||
@@ -107,7 +117,6 @@ namespace AsarSharp.AsarFileSystem
|
||||
return (filteredFilenames, metadata);
|
||||
}
|
||||
|
||||
|
||||
// (File order is not important!!!)
|
||||
public static List<string> CrawlIterative(string dir)
|
||||
{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
@@ -11,6 +11,7 @@ namespace AsarSharp.Integrity
|
||||
private const string ALGORITHM = "SHA256";
|
||||
// 4MB default block size
|
||||
private const int BLOCK_SIZE = 4 * 1024 * 1024;
|
||||
private static readonly char[] HexDigits = "0123456789abcdef".ToCharArray();
|
||||
|
||||
public class FileIntegrity
|
||||
{
|
||||
@@ -29,20 +30,21 @@ namespace AsarSharp.Integrity
|
||||
|
||||
public static FileIntegrity GetFileIntegrity(string path)
|
||||
{
|
||||
using (var fileStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))
|
||||
using (var fileStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, BLOCK_SIZE, FileOptions.SequentialScan))
|
||||
using(var fileHash = SHA256.Create())
|
||||
using (var blockHash = SHA256.Create())
|
||||
{
|
||||
|
||||
var blockHashes = new List<string>();
|
||||
int estimatedBlockCount = fileStream.Length > 0
|
||||
? (int)((fileStream.Length + BLOCK_SIZE - 1) / BLOCK_SIZE)
|
||||
: 0;
|
||||
var blockHashes = new List<string>(estimatedBlockCount);
|
||||
var buffer = new byte[BLOCK_SIZE];
|
||||
int bytesRead;
|
||||
|
||||
while ((bytesRead = fileStream.Read(buffer, 0, BLOCK_SIZE)) > 0)
|
||||
while ((bytesRead = fileStream.Read(buffer, 0, buffer.Length)) > 0)
|
||||
{
|
||||
var block = new byte[bytesRead];
|
||||
Array.Copy(buffer, block, bytesRead);
|
||||
blockHashes.Add(HashBlock(block));
|
||||
fileHash.TransformBlock(block, 0, block.Length, null, 0);
|
||||
blockHashes.Add(HashBlock(blockHash, buffer, bytesRead));
|
||||
fileHash.TransformBlock(buffer, 0, bytesRead, null, 0);
|
||||
}
|
||||
|
||||
fileHash.TransformFinalBlock(Array.Empty<byte>(), 0, 0);
|
||||
@@ -50,20 +52,34 @@ namespace AsarSharp.Integrity
|
||||
return new FileIntegrity
|
||||
{
|
||||
Algorithm = ALGORITHM,
|
||||
Hash = BitConverter.ToString(fileHash.Hash).Replace("-", "").ToLowerInvariant(),
|
||||
Hash = ToLowerHex(fileHash.Hash),
|
||||
BlockSize = BLOCK_SIZE,
|
||||
Blocks = blockHashes,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private static string HashBlock(byte[] block)
|
||||
private static string HashBlock(HashAlgorithm hashAlgorithm, byte[] buffer, int bytesRead)
|
||||
{
|
||||
using (var sha256 = SHA256.Create())
|
||||
return ToLowerHex(hashAlgorithm.ComputeHash(buffer, 0, bytesRead));
|
||||
}
|
||||
|
||||
private static string ToLowerHex(byte[] bytes)
|
||||
{
|
||||
if (bytes == null || bytes.Length == 0)
|
||||
{
|
||||
var hash = sha256.ComputeHash(block);
|
||||
return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant();
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var chars = new char[bytes.Length * 2];
|
||||
for (int index = 0; index < bytes.Length; index++)
|
||||
{
|
||||
byte value = bytes[index];
|
||||
chars[index * 2] = HexDigits[value >> 4];
|
||||
chars[index * 2 + 1] = HexDigits[value & 0x0F];
|
||||
}
|
||||
|
||||
return new string(chars);
|
||||
}
|
||||
}
|
||||
}
|
||||
+93
-219
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace AsarSharp.PickleTools
|
||||
@@ -12,10 +13,10 @@ namespace AsarSharp.PickleTools
|
||||
public const int SIZE_FLOAT = 4;
|
||||
public const int SIZE_DOUBLE = 8;
|
||||
|
||||
// Size of memory allocation unit for payload
|
||||
public const int PAYLOAD_UNIT = 64;
|
||||
// Initial payload allocation. Bumped from 64 — large headers used to
|
||||
// realloc many times when growing geometrically from 64.
|
||||
public const int PAYLOAD_UNIT = 4096;
|
||||
|
||||
// Maximum value for read-only
|
||||
public const long CAPACITY_READ_ONLY = 9007199254740992;
|
||||
|
||||
private byte[] _header;
|
||||
@@ -57,55 +58,40 @@ namespace AsarSharp.PickleTools
|
||||
SetPayloadSize(0);
|
||||
}
|
||||
}
|
||||
|
||||
public static Pickle CreateEmpty()
|
||||
{
|
||||
return new Pickle();
|
||||
}
|
||||
|
||||
public static Pickle CreateFromBuffer(byte[] buffer)
|
||||
{
|
||||
return new Pickle(buffer);
|
||||
}
|
||||
|
||||
public byte[] GetHeader()
|
||||
{
|
||||
return _header;
|
||||
}
|
||||
public static Pickle CreateEmpty() => new Pickle();
|
||||
public static Pickle CreateFromBuffer(byte[] buffer) => new Pickle(buffer);
|
||||
|
||||
public int GetHeaderSize()
|
||||
{
|
||||
return _headerSize;
|
||||
}
|
||||
|
||||
public PickleIterator CreateIterator()
|
||||
{
|
||||
return new PickleIterator(this);
|
||||
}
|
||||
public byte[] GetHeader() => _header;
|
||||
public int GetHeaderSize() => _headerSize;
|
||||
|
||||
/// <summary>
|
||||
/// Converts Pickle to a byte array
|
||||
/// </summary>
|
||||
public PickleIterator CreateIterator() => new PickleIterator(this);
|
||||
|
||||
/// <summary>Total byte length of the serialised pickle (header + payload).</summary>
|
||||
public int GetTotalSize() => _headerSize + GetPayloadSize();
|
||||
|
||||
/// <summary>Materialise the pickle into a fresh byte array (allocates).</summary>
|
||||
public byte[] ToBuffer()
|
||||
{
|
||||
int resultSize = _headerSize + GetPayloadSize();
|
||||
int resultSize = GetTotalSize();
|
||||
byte[] result = new byte[resultSize];
|
||||
Array.Copy(_header, 0, result, 0, resultSize);
|
||||
Buffer.BlockCopy(_header, 0, result, 0, resultSize);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
public bool WriteBool(bool value)
|
||||
/// <summary>Write the serialised pickle straight to <paramref name="stream"/> — no extra copy.</summary>
|
||||
public void WriteTo(Stream stream)
|
||||
{
|
||||
return WriteInt(value ? 1 : 0);
|
||||
stream.Write(_header, 0, GetTotalSize());
|
||||
}
|
||||
|
||||
|
||||
|
||||
public bool WriteBool(bool value) => WriteInt(value ? 1 : 0);
|
||||
|
||||
public bool WriteInt(int value)
|
||||
{
|
||||
EnsureCapacity(SIZE_INT32);
|
||||
|
||||
var dataLength = AlignInt(SIZE_INT32, SIZE_UINT32);
|
||||
var newSize = _writeOffset + dataLength;
|
||||
const int dataLength = SIZE_INT32; // already 4-byte aligned
|
||||
int newSize = _writeOffset + dataLength;
|
||||
|
||||
if (newSize > _capacityAfterHeader)
|
||||
{
|
||||
@@ -113,13 +99,6 @@ namespace AsarSharp.PickleTools
|
||||
}
|
||||
|
||||
WriteInt32LE(value, _headerSize + _writeOffset);
|
||||
|
||||
var endOffset = _headerSize + _writeOffset + SIZE_INT32;
|
||||
for (int i = endOffset; i < endOffset + dataLength - SIZE_INT32; i++)
|
||||
{
|
||||
_header[i] = 0;
|
||||
}
|
||||
|
||||
SetPayloadSize(newSize);
|
||||
_writeOffset = newSize;
|
||||
return true;
|
||||
@@ -128,10 +107,8 @@ namespace AsarSharp.PickleTools
|
||||
|
||||
public bool WriteUInt32(uint value)
|
||||
{
|
||||
EnsureCapacity(SIZE_UINT32);
|
||||
|
||||
var dataLength = AlignInt(SIZE_UINT32, SIZE_UINT32);
|
||||
var newSize = _writeOffset + dataLength;
|
||||
const int dataLength = SIZE_UINT32;
|
||||
int newSize = _writeOffset + dataLength;
|
||||
|
||||
if (newSize > _capacityAfterHeader)
|
||||
{
|
||||
@@ -139,24 +116,15 @@ namespace AsarSharp.PickleTools
|
||||
}
|
||||
|
||||
WriteUInt32LE(value, _headerSize + _writeOffset);
|
||||
|
||||
var endOffset = _headerSize + _writeOffset + SIZE_UINT32;
|
||||
for (int i = endOffset; i < endOffset + dataLength - SIZE_UINT32; i++)
|
||||
{
|
||||
_header[i] = 0;
|
||||
}
|
||||
|
||||
SetPayloadSize(newSize);
|
||||
_writeOffset = newSize;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public bool WriteInt64(long value)
|
||||
{
|
||||
EnsureCapacity(SIZE_INT64);
|
||||
|
||||
var dataLength = AlignInt(SIZE_INT64, SIZE_UINT32);
|
||||
var newSize = _writeOffset + dataLength;
|
||||
const int dataLength = SIZE_INT64;
|
||||
int newSize = _writeOffset + dataLength;
|
||||
|
||||
if (newSize > _capacityAfterHeader)
|
||||
{
|
||||
@@ -164,13 +132,6 @@ namespace AsarSharp.PickleTools
|
||||
}
|
||||
|
||||
WriteInt64LE(value, _headerSize + _writeOffset);
|
||||
|
||||
var endOffset = _headerSize + _writeOffset + SIZE_INT64;
|
||||
for (int i = endOffset; i < endOffset + dataLength - SIZE_INT64; i++)
|
||||
{
|
||||
_header[i] = 0;
|
||||
}
|
||||
|
||||
SetPayloadSize(newSize);
|
||||
_writeOffset = newSize;
|
||||
return true;
|
||||
@@ -179,10 +140,8 @@ namespace AsarSharp.PickleTools
|
||||
|
||||
public bool WriteUInt64(ulong value)
|
||||
{
|
||||
EnsureCapacity(SIZE_UINT64);
|
||||
|
||||
var dataLength = AlignInt(SIZE_UINT64, SIZE_UINT32);
|
||||
var newSize = _writeOffset + dataLength;
|
||||
const int dataLength = SIZE_UINT64;
|
||||
int newSize = _writeOffset + dataLength;
|
||||
|
||||
if (newSize > _capacityAfterHeader)
|
||||
{
|
||||
@@ -190,102 +149,69 @@ namespace AsarSharp.PickleTools
|
||||
}
|
||||
|
||||
WriteUInt64LE(value, _headerSize + _writeOffset);
|
||||
|
||||
var endOffset = _headerSize + _writeOffset + SIZE_UINT64;
|
||||
for (int i = endOffset; i < endOffset + dataLength - SIZE_UINT64; i++)
|
||||
{
|
||||
_header[i] = 0;
|
||||
}
|
||||
|
||||
SetPayloadSize(newSize);
|
||||
_writeOffset = newSize;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public bool WriteFloat(float value)
|
||||
{
|
||||
EnsureCapacity(SIZE_FLOAT);
|
||||
|
||||
var dataLength = AlignInt(SIZE_FLOAT, SIZE_UINT32);
|
||||
var newSize = _writeOffset + dataLength;
|
||||
const int dataLength = SIZE_FLOAT;
|
||||
int newSize = _writeOffset + dataLength;
|
||||
|
||||
if (newSize > _capacityAfterHeader)
|
||||
{
|
||||
Resize(Math.Max((int)_capacityAfterHeader * 2, newSize));
|
||||
}
|
||||
|
||||
byte[] bytes = BitConverter.GetBytes(value);
|
||||
if (!BitConverter.IsLittleEndian)
|
||||
{
|
||||
Array.Reverse(bytes);
|
||||
}
|
||||
|
||||
Array.Copy(bytes, 0, _header, _headerSize + _writeOffset, SIZE_FLOAT);
|
||||
|
||||
var endOffset = _headerSize + _writeOffset + SIZE_FLOAT;
|
||||
for (int i = endOffset; i < endOffset + dataLength - SIZE_FLOAT; i++)
|
||||
{
|
||||
_header[i] = 0;
|
||||
}
|
||||
int bits = BitConverter.ToInt32(BitConverter.GetBytes(value), 0);
|
||||
WriteInt32LE(bits, _headerSize + _writeOffset);
|
||||
|
||||
SetPayloadSize(newSize);
|
||||
_writeOffset = newSize;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public bool WriteDouble(double value)
|
||||
{
|
||||
EnsureCapacity(SIZE_DOUBLE);
|
||||
|
||||
var dataLength = AlignInt(SIZE_DOUBLE, SIZE_UINT32);
|
||||
var newSize = _writeOffset + dataLength;
|
||||
const int dataLength = SIZE_DOUBLE;
|
||||
int newSize = _writeOffset + dataLength;
|
||||
|
||||
if (newSize > _capacityAfterHeader)
|
||||
{
|
||||
Resize(Math.Max((int)_capacityAfterHeader * 2, newSize));
|
||||
}
|
||||
|
||||
byte[] bytes = BitConverter.GetBytes(value);
|
||||
if (!BitConverter.IsLittleEndian)
|
||||
{
|
||||
Array.Reverse(bytes);
|
||||
}
|
||||
|
||||
Array.Copy(bytes, 0, _header, _headerSize + _writeOffset, SIZE_DOUBLE);
|
||||
|
||||
var endOffset = _headerSize + _writeOffset + SIZE_DOUBLE;
|
||||
for (int i = endOffset; i < endOffset + dataLength - SIZE_DOUBLE; i++)
|
||||
{
|
||||
_header[i] = 0;
|
||||
}
|
||||
long bits = BitConverter.DoubleToInt64Bits(value);
|
||||
WriteInt64LE(bits, _headerSize + _writeOffset);
|
||||
|
||||
SetPayloadSize(newSize);
|
||||
_writeOffset = newSize;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public bool WriteString(string value)
|
||||
{
|
||||
byte[] strBytes = Encoding.UTF8.GetBytes(value);
|
||||
int length = strBytes.Length;
|
||||
int byteLen = Encoding.UTF8.GetByteCount(value);
|
||||
|
||||
if (!WriteInt(length))
|
||||
if (!WriteInt(byteLen))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var dataLength = AlignInt(length, SIZE_UINT32);
|
||||
var newSize = _writeOffset + dataLength;
|
||||
int aligned = AlignInt(byteLen, SIZE_UINT32);
|
||||
int newSize = _writeOffset + aligned;
|
||||
|
||||
if (newSize > _capacityAfterHeader)
|
||||
{
|
||||
Resize(Math.Max((int)_capacityAfterHeader * 2, newSize));
|
||||
}
|
||||
|
||||
Array.Copy(strBytes, 0, _header, _headerSize + _writeOffset, length);
|
||||
int writeStart = _headerSize + _writeOffset;
|
||||
Encoding.UTF8.GetBytes(value, 0, value.Length, _header, writeStart);
|
||||
|
||||
var endOffset = _headerSize + _writeOffset + length;
|
||||
for (int i = endOffset; i < endOffset + dataLength - length; i++)
|
||||
// zero alignment padding
|
||||
for (int i = writeStart + byteLen; i < writeStart + aligned; i++)
|
||||
{
|
||||
_header[i] = 0;
|
||||
}
|
||||
@@ -294,132 +220,80 @@ namespace AsarSharp.PickleTools
|
||||
_writeOffset = newSize;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public void SetPayloadSize(int payloadSize)
|
||||
{
|
||||
WriteUInt32LE((uint)payloadSize, 0);
|
||||
}
|
||||
|
||||
public int GetPayloadSize()
|
||||
{
|
||||
return (int)ReadUInt32LE(0);
|
||||
}
|
||||
|
||||
|
||||
public int GetPayloadSize() => (int)ReadUInt32LE(0);
|
||||
|
||||
private void Resize(int newCapacity)
|
||||
{
|
||||
newCapacity = AlignInt(newCapacity, PAYLOAD_UNIT);
|
||||
byte[] newHeader = new byte[_header.Length + newCapacity];
|
||||
Array.Copy(_header, 0, newHeader, 0, _header.Length);
|
||||
Buffer.BlockCopy(_header, 0, newHeader, 0, _header.Length);
|
||||
_header = newHeader;
|
||||
_capacityAfterHeader = newCapacity;
|
||||
}
|
||||
|
||||
|
||||
public static int AlignInt(int i, int alignment)
|
||||
{
|
||||
return i + ((alignment - (i % alignment)) % alignment);
|
||||
}
|
||||
|
||||
private void EnsureCapacity(int additionalSize)
|
||||
{
|
||||
var dataLength = AlignInt(additionalSize, SIZE_UINT32);
|
||||
var newSize = _writeOffset + dataLength;
|
||||
|
||||
if (newSize > _capacityAfterHeader)
|
||||
{
|
||||
Resize(Math.Max((int)_capacityAfterHeader * 2, newSize));
|
||||
}
|
||||
}
|
||||
|
||||
#region Auxiliary methods for reading/writing values in Little Endian
|
||||
|
||||
private uint ReadUInt32LE(int offset)
|
||||
{
|
||||
if (BitConverter.IsLittleEndian)
|
||||
{
|
||||
return BitConverter.ToUInt32(_header, offset);
|
||||
}
|
||||
else
|
||||
{
|
||||
return (uint)(_header[offset] |
|
||||
(_header[offset + 1] << 8) |
|
||||
(_header[offset + 2] << 16) |
|
||||
(_header[offset + 3] << 24));
|
||||
}
|
||||
// _header is allocated by us so always little-endian-friendly when on LE host.
|
||||
return (uint)(_header[offset] |
|
||||
(_header[offset + 1] << 8) |
|
||||
(_header[offset + 2] << 16) |
|
||||
(_header[offset + 3] << 24));
|
||||
}
|
||||
|
||||
private void WriteInt32LE(int value, int offset)
|
||||
{
|
||||
if (BitConverter.IsLittleEndian)
|
||||
{
|
||||
byte[] bytes = BitConverter.GetBytes(value);
|
||||
Array.Copy(bytes, 0, _header, offset, 4);
|
||||
}
|
||||
else
|
||||
{
|
||||
_header[offset] = (byte)value;
|
||||
_header[offset + 1] = (byte)(value >> 8);
|
||||
_header[offset + 2] = (byte)(value >> 16);
|
||||
_header[offset + 3] = (byte)(value >> 24);
|
||||
}
|
||||
_header[offset] = (byte)value;
|
||||
_header[offset + 1] = (byte)(value >> 8);
|
||||
_header[offset + 2] = (byte)(value >> 16);
|
||||
_header[offset + 3] = (byte)(value >> 24);
|
||||
}
|
||||
|
||||
private void WriteUInt32LE(uint value, int offset)
|
||||
{
|
||||
if (BitConverter.IsLittleEndian)
|
||||
{
|
||||
byte[] bytes = BitConverter.GetBytes(value);
|
||||
Array.Copy(bytes, 0, _header, offset, 4);
|
||||
}
|
||||
else
|
||||
{
|
||||
_header[offset] = (byte)value;
|
||||
_header[offset + 1] = (byte)(value >> 8);
|
||||
_header[offset + 2] = (byte)(value >> 16);
|
||||
_header[offset + 3] = (byte)(value >> 24);
|
||||
}
|
||||
_header[offset] = (byte)value;
|
||||
_header[offset + 1] = (byte)(value >> 8);
|
||||
_header[offset + 2] = (byte)(value >> 16);
|
||||
_header[offset + 3] = (byte)(value >> 24);
|
||||
}
|
||||
|
||||
private void WriteInt64LE(long value, int offset)
|
||||
{
|
||||
if (BitConverter.IsLittleEndian)
|
||||
{
|
||||
byte[] bytes = BitConverter.GetBytes(value);
|
||||
Array.Copy(bytes, 0, _header, offset, 8);
|
||||
}
|
||||
else
|
||||
{
|
||||
_header[offset] = (byte)value;
|
||||
_header[offset + 1] = (byte)(value >> 8);
|
||||
_header[offset + 2] = (byte)(value >> 16);
|
||||
_header[offset + 3] = (byte)(value >> 24);
|
||||
_header[offset + 4] = (byte)(value >> 32);
|
||||
_header[offset + 5] = (byte)(value >> 40);
|
||||
_header[offset + 6] = (byte)(value >> 48);
|
||||
_header[offset + 7] = (byte)(value >> 56);
|
||||
}
|
||||
}
|
||||
|
||||
private void WriteUInt64LE(ulong value, int offset)
|
||||
{
|
||||
if (BitConverter.IsLittleEndian)
|
||||
{
|
||||
byte[] bytes = BitConverter.GetBytes(value);
|
||||
Array.Copy(bytes, 0, _header, offset, 8);
|
||||
}
|
||||
else
|
||||
{
|
||||
_header[offset] = (byte)value;
|
||||
_header[offset + 1] = (byte)(value >> 8);
|
||||
_header[offset + 2] = (byte)(value >> 16);
|
||||
_header[offset + 3] = (byte)(value >> 24);
|
||||
_header[offset + 4] = (byte)(value >> 32);
|
||||
_header[offset + 5] = (byte)(value >> 40);
|
||||
_header[offset + 6] = (byte)(value >> 48);
|
||||
_header[offset + 7] = (byte)(value >> 56);
|
||||
}
|
||||
_header[offset] = (byte)value;
|
||||
_header[offset + 1] = (byte)(value >> 8);
|
||||
_header[offset + 2] = (byte)(value >> 16);
|
||||
_header[offset + 3] = (byte)(value >> 24);
|
||||
_header[offset + 4] = (byte)(value >> 32);
|
||||
_header[offset + 5] = (byte)(value >> 40);
|
||||
_header[offset + 6] = (byte)(value >> 48);
|
||||
_header[offset + 7] = (byte)(value >> 56);
|
||||
}
|
||||
|
||||
|
||||
private void WriteUInt64LE(ulong value, int offset)
|
||||
{
|
||||
_header[offset] = (byte)value;
|
||||
_header[offset + 1] = (byte)(value >> 8);
|
||||
_header[offset + 2] = (byte)(value >> 16);
|
||||
_header[offset + 3] = (byte)(value >> 24);
|
||||
_header[offset + 4] = (byte)(value >> 32);
|
||||
_header[offset + 5] = (byte)(value >> 40);
|
||||
_header[offset + 6] = (byte)(value >> 48);
|
||||
_header[offset + 7] = (byte)(value >> 56);
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,19 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace AsarSharp.Utils
|
||||
{
|
||||
internal static class Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Compute path relative to <paramref name="relativeTo"/>.
|
||||
/// Fast common-case (path is inside relativeTo): plain prefix-strip.
|
||||
/// Falls back to <see cref="Path.GetFullPath"/> + manual relativisation
|
||||
/// when paths must be normalised or '..' segments are required.
|
||||
/// Replaces previous URI-based implementation which was a large hot-path cost.
|
||||
/// </summary>
|
||||
public static string GetRelativePath(string relativeTo, string path)
|
||||
{
|
||||
if (string.IsNullOrEmpty(relativeTo))
|
||||
@@ -13,84 +21,134 @@ namespace AsarSharp.Utils
|
||||
if (string.IsNullOrEmpty(path))
|
||||
throw new ArgumentNullException(nameof(path));
|
||||
|
||||
var fullRelativeTo = Path.GetFullPath(relativeTo);
|
||||
var fullPath = Path.GetFullPath(path);
|
||||
// Fast path: literal prefix match (no normalisation). Covers ~all
|
||||
// intra-archive callers where both inputs already come from the
|
||||
// same crawl pass.
|
||||
string baseFast = TrimTrailingSeparators(relativeTo);
|
||||
string pathFast = TrimTrailingSeparators(path);
|
||||
|
||||
if (string.Equals(fullRelativeTo, fullPath, StringComparison.OrdinalIgnoreCase))
|
||||
return "";
|
||||
if (string.Equals(baseFast, pathFast, StringComparison.OrdinalIgnoreCase))
|
||||
return string.Empty;
|
||||
|
||||
var relativeToUri = new Uri(fullRelativeTo.EndsWith(Path.DirectorySeparatorChar.ToString())
|
||||
? fullRelativeTo
|
||||
: fullRelativeTo + Path.DirectorySeparatorChar);
|
||||
var pathUri = new Uri(fullPath.EndsWith(Path.DirectorySeparatorChar.ToString()) && !File.Exists(fullPath)
|
||||
? fullPath
|
||||
: fullPath + (Directory.Exists(fullPath) ? Path.DirectorySeparatorChar.ToString() : ""));
|
||||
if (pathFast.Length > baseFast.Length &&
|
||||
pathFast.StartsWith(baseFast, StringComparison.OrdinalIgnoreCase) &&
|
||||
IsSeparator(pathFast[baseFast.Length]))
|
||||
{
|
||||
return pathFast.Substring(baseFast.Length + 1);
|
||||
}
|
||||
|
||||
var relativeUri = relativeToUri.MakeRelativeUri(pathUri);
|
||||
var relativePath = Uri.UnescapeDataString(relativeUri.ToString())
|
||||
.Replace('/', Path.DirectorySeparatorChar);
|
||||
|
||||
return relativePath.TrimEnd(Path.DirectorySeparatorChar);
|
||||
// Slow path: normalise both sides and compute relative — used for
|
||||
// security checks (out-of-tree symlink/destination guards) and the
|
||||
// rare "go up" case.
|
||||
return GetRelativePathNormalised(relativeTo, path);
|
||||
}
|
||||
|
||||
|
||||
private static string GetRelativePathNormalised(string relativeTo, string path)
|
||||
{
|
||||
string fullBase = Path.GetFullPath(relativeTo);
|
||||
string fullPath = Path.GetFullPath(path);
|
||||
|
||||
fullBase = TrimTrailingSeparators(fullBase);
|
||||
fullPath = TrimTrailingSeparators(fullPath);
|
||||
|
||||
if (string.Equals(fullBase, fullPath, StringComparison.OrdinalIgnoreCase))
|
||||
return string.Empty;
|
||||
|
||||
if (fullPath.Length > fullBase.Length &&
|
||||
fullPath.StartsWith(fullBase, StringComparison.OrdinalIgnoreCase) &&
|
||||
IsSeparator(fullPath[fullBase.Length]))
|
||||
{
|
||||
return fullPath.Substring(fullBase.Length + 1);
|
||||
}
|
||||
|
||||
// Need to walk up the common ancestor.
|
||||
char sep = Path.DirectorySeparatorChar;
|
||||
string[] baseParts = fullBase.Split(new[] { '/', '\\' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
string[] pathParts = fullPath.Split(new[] { '/', '\\' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
int common = 0;
|
||||
int max = Math.Min(baseParts.Length, pathParts.Length);
|
||||
while (common < max &&
|
||||
string.Equals(baseParts[common], pathParts[common], StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
common++;
|
||||
}
|
||||
|
||||
var sb = new StringBuilder();
|
||||
for (int i = common; i < baseParts.Length; i++)
|
||||
{
|
||||
if (sb.Length > 0) sb.Append(sep);
|
||||
sb.Append("..");
|
||||
}
|
||||
for (int i = common; i < pathParts.Length; i++)
|
||||
{
|
||||
if (sb.Length > 0) sb.Append(sep);
|
||||
sb.Append(pathParts[i]);
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private static string TrimTrailingSeparators(string s)
|
||||
{
|
||||
int end = s.Length;
|
||||
while (end > 0 && IsSeparator(s[end - 1])) end--;
|
||||
return end == s.Length ? s : s.Substring(0, end);
|
||||
}
|
||||
|
||||
private static bool IsSeparator(char c) => c == '/' || c == '\\';
|
||||
|
||||
public static string GetDirectoryName(string path)
|
||||
{
|
||||
if (string.IsNullOrEmpty(path))
|
||||
return ".";
|
||||
|
||||
string result = Path.GetDirectoryName(path);
|
||||
|
||||
// If the result is an empty string, return “.” as in Node.js
|
||||
|
||||
if (string.IsNullOrEmpty(result))
|
||||
return ".";
|
||||
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
public static void CopyDirectory(string sourceDir, string destinationDir)
|
||||
{
|
||||
// Create the destination directory
|
||||
Directory.CreateDirectory(destinationDir);
|
||||
|
||||
// Get all files in the source directory
|
||||
foreach (var file in Directory.GetFiles(sourceDir))
|
||||
{
|
||||
var destFile = Path.Combine(destinationDir, Path.GetFileName(file));
|
||||
File.Copy(file, destFile, true);
|
||||
}
|
||||
|
||||
// Recursively copy all subdirectories
|
||||
foreach (var dir in Directory.GetDirectories(sourceDir))
|
||||
{
|
||||
var destDir = Path.Combine(destinationDir, Path.GetFileName(dir));
|
||||
CopyDirectory(dir, destDir);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static string GetBasePath(string dir)
|
||||
{
|
||||
// Look for the last path delimiter before any pattern
|
||||
int wildcardIndex = dir.IndexOfAny(new[] { '*', '?' });
|
||||
if (wildcardIndex == -1)
|
||||
{
|
||||
return dir;
|
||||
}
|
||||
|
||||
|
||||
int lastSeparatorIndex = dir.LastIndexOf(Path.DirectorySeparatorChar, wildcardIndex);
|
||||
if (lastSeparatorIndex == -1)
|
||||
{
|
||||
return ".";
|
||||
}
|
||||
|
||||
|
||||
return dir.Substring(0, lastSeparatorIndex);
|
||||
}
|
||||
|
||||
|
||||
public static void SetUnixFilePermission(string filePath, string permission)
|
||||
{
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
return;
|
||||
|
||||
// Use chmod
|
||||
var process = new System.Diagnostics.Process
|
||||
{
|
||||
StartInfo = new System.Diagnostics.ProcessStartInfo
|
||||
@@ -105,14 +163,12 @@ namespace AsarSharp.Utils
|
||||
process.Start();
|
||||
process.WaitForExit();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public static void CreateSymbolicLink(string linkTarget, string linkPath)
|
||||
{
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
// On Windows, creating symlinks requires special privileges,
|
||||
// so on many systems it simply won't work without administrator privileges
|
||||
NativeMethods.CreateSymbolicLink(linkPath, linkTarget,
|
||||
Directory.Exists(linkTarget)
|
||||
? NativeMethods.SymLinkFlag.Directory
|
||||
@@ -120,7 +176,6 @@ namespace AsarSharp.Utils
|
||||
return;
|
||||
}
|
||||
|
||||
// In Unix systems we use the corresponding system call
|
||||
var process = new System.Diagnostics.Process
|
||||
{
|
||||
StartInfo = new System.Diagnostics.ProcessStartInfo
|
||||
@@ -135,11 +190,11 @@ namespace AsarSharp.Utils
|
||||
process.Start();
|
||||
process.WaitForExit();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public static bool IsWindowsPlatform()
|
||||
{
|
||||
return Environment.OSVersion.Platform == PlatformID.Win32NT;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user