electron removed

This commit is contained in:
kitbyte
2025-03-21 23:35:16 +02:00
parent 8e33e58939
commit 4049ade4b1
54 changed files with 3635 additions and 828 deletions
+94
View File
@@ -0,0 +1,94 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using AsarSharp.AsarFileSystem;
using AsarSharp.Utils;
namespace AsarSharp
{
public class CreateOptions
{
public Regex Unpack { get; set; }
}
public class AsarCreator
{
private readonly string _folderPath;
private readonly string _destPath;
private readonly CreateOptions _options;
private List<string> _filenames = new List<string>();
private Dictionary<string, CrawledFileType> _metadata = new Dictionary<string, CrawledFileType>();
public AsarCreator(string folderPath, string destPath, CreateOptions options)
{
_folderPath = folderPath ?? throw new ArgumentNullException(nameof(folderPath));
_destPath = destPath ?? throw new ArgumentNullException(nameof(destPath));
_options = options;
}
public void CreatePackageWithOptions()
{
var result = FileSystemCrawler.CrawlFileSystem(_folderPath);
_filenames = result.filenames;
_metadata = result.metadata;
CreatePackageFromFiles();
}
public void CreatePackageFromFiles()
{
var filesystem = new Filesystem(_folderPath);
var files = new List<Disk.BasicFileInfo>();
var filenamesSorted = _filenames.ToList();
foreach (var filename in filenamesSorted)
{
HandleFile(filesystem, filename, files);
}
InsertsDone(filesystem, files);
}
private void HandleFile(Filesystem filesystem, string filename, List<Disk.BasicFileInfo> files)
{
if (!_metadata.ContainsKey(filename))
{
var fileType = FileSystemCrawler.DetermineFileType(filename);
_metadata[filename] = fileType ?? throw new Exception("Unknown file type for file: " + filename);
}
var file = _metadata[filename];
switch (file.Type)
{
case FileType.Directory:
filesystem.InsertDirectory(filename, false);
break;
case FileType.File:
var shouldUnpack = ShouldUnpackPath(Extensions.GetRelativePath(_folderPath, Path.GetDirectoryName(filename)));
files.Add(new Disk.BasicFileInfo { Filename = filename, Unpack = shouldUnpack });
filesystem.InsertFile(filename, shouldUnpack, file);
break;
case FileType.Link:
throw new NotImplementedException();
}
}
private bool ShouldUnpackPath(string relativePath)
{
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);
}
}
}
+131
View File
@@ -0,0 +1,131 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using AsarSharp.AsarFileSystem;
using AsarSharp.Utils;
namespace AsarSharp
{
public class AsarExtractor
{
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
bool followLinks = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
// create destination directory
Directory.CreateDirectory(dest);
var extractionErrors = new List<Exception>();
foreach (var fullPath in filenames)
{
try
{
// 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(".."))
{
throw new InvalidOperationException($"{fullPath}: file \"{destFilename}\" writes out of the package");
}
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(".."))
{
throw new InvalidOperationException(
$"{fullPath}: file \"{file.Link}\" links out of the package to \"{linkSrcPath}\"");
}
// On Windows, creating symlinks requires additional permissions or enabling Developer Mode,
// so just copy the contents of the file
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
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);
}
}
else
{
// On Unix systems we use symlinks
Directory.CreateDirectory(Extensions.GetDirectoryName(destFilename));
Extensions.CreateSymbolicLink(linkTo, destFilename);
}
}
else if (file.IsFile)
{
// it's a file, try to extract it
try
{
byte[] content;
content = Disk.ReadFileSync(filesystem, filename, file);
File.WriteAllBytes(destFilename, content);
if (file.Executable == true && !RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
Extensions.SetUnixFilePermission(destFilename, "755");
}
}
catch (Exception e)
{
extractionErrors.Add(e);
}
}
}
catch (Exception ex)
{
extractionErrors.Add(ex);
}
}
if (extractionErrors.Count > 0)
{
throw new AggregateException(
"Unable to extract some files:\n\n" +
string.Join("\n\n", extractionErrors.Select(e => e.ToString())),
extractionErrors);
}
}
}
}
+203
View File
@@ -0,0 +1,203 @@
using System;
using System.Collections.Generic;
using System.IO;
using AsarSharp.PickleTools;
using AsarSharp.Utils;
using Newtonsoft.Json;
namespace AsarSharp.AsarFileSystem
{
public static class Disk
{
private static Dictionary<string, Filesystem> _filesystemCache = new Dictionary<string, Filesystem>();
public class ArchiveHeader
{
public FilesystemEntry Header { get; set; }
public string HeaderString { get; set; }
public int HeaderSize { get; set; }
}
public class FilesystemFilesAndLinks
{
public List<BasicFileInfo> Files { get; set; } = new List<BasicFileInfo>();
public List<BasicFileInfo> Links { get; set; } = new List<BasicFileInfo>();
}
public class BasicFileInfo
{
public string Filename { get; set; }
public bool Unpack { get; set; }
}
#region Reading
public static ArchiveHeader ReadArchiveHeaderSync(string archivePath)
{
using (FileStream fs = File.OpenRead(archivePath))
{
// read the size of the header (8 bytes)
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)
{
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,
HeaderString = header,
HeaderSize = (int)size
};
}
}
public static Filesystem ReadFilesystemSync(string archivePath)
{
if (!_filesystemCache.ContainsKey(archivePath) || _filesystemCache[archivePath] == null)
{
ArchiveHeader header = ReadArchiveHeaderSync(archivePath);
Filesystem filesystem = new Filesystem(archivePath);
filesystem.SetHeader(header.Header, header.HeaderSize);
_filesystemCache[archivePath] = filesystem;
}
return _filesystemCache[archivePath];
}
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 (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 (FileStream fs = File.OpenRead(filesystem.GetRootPath()))
{
// 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;
}
public static void UncacheAll()
{
_filesystemCache.Clear();
}
public static void CopyFile(string dest, string rootPath, string filename)
{
if(dest == null || rootPath == null || filename == null)
throw new ArgumentNullException();
if (dest == rootPath)
{
return;
}
string sourcePath = Path.Combine(rootPath, filename);
string destPath = Path.Combine(dest, filename);
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))
{
sourceStream.CopyTo(destinationStream);
}
}
public static void WriteFileSystem(string dest, Filesystem fileSystem,
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 headerJson = JsonConvert.SerializeObject(fsHeader,serializerSettings);
headerPickle.WriteString(headerJson);
var headerBuf = headerPickle.ToBuffer();
var sizePickle = Pickle.CreateEmpty();
sizePickle.WriteUInt32((uint)headerBuf.Length);
var sizeBuf = sizePickle.ToBuffer();
using (FileStream fs = File.Create(dest))
{
fs.Write(sizeBuf, 0, sizeBuf.Length);
fs.Write(headerBuf, 0, headerBuf.Length);
foreach (var file in lists.Files)
{
if (file.Unpack)
{
var filename = Extensions.GetRelativePath(fileSystem.GetRootPath(), file.Filename);
CopyFile($"{dest}.unpacked", fileSystem.GetRootPath(), filename);
continue;
}
using (var transformedFileStream = new FileStream(file.Filename, FileMode.Open, FileAccess.Read))
{
transformedFileStream.CopyTo(fs);
}
}
}
}
}
}
+235
View File
@@ -0,0 +1,235 @@
using System;
using System.Collections.Generic;
using System.IO;
using AsarSharp.Integrity;
using AsarSharp.Utils;
namespace AsarSharp.AsarFileSystem
{
public class Filesystem
{
private readonly string _src;
private FilesystemEntry _header;
private int _headerSize;
private long _offset;
private const uint UINT32_MAX = 0xFFFFFFFF; // 2^32 - 1
public Filesystem(string src)
{
_src = Path.GetFullPath(src);
_header = new FilesystemEntry { Files = new Dictionary<string, FilesystemEntry>(StringComparer.Ordinal) };
_headerSize = 0;
_offset = 0;
}
public string GetRootPath()
{
return _src;
}
public FilesystemEntry GetHeader()
{
return _header;
}
public int GetHeaderSize()
{
return _headerSize;
}
public void SetHeader(FilesystemEntry header, int headerSize)
{
_header = header;
_headerSize = headerSize;
}
public FilesystemEntry SearchNodeFromDirectory(string p)
{
FilesystemEntry json = _header;
// Normalize path delimiters to system delimiters
p = p.Replace('/', Path.DirectorySeparatorChar).Replace('\\', Path.DirectorySeparatorChar);
string[] dirs = p.Split(Path.DirectorySeparatorChar);
foreach (string dir in dirs)
{
if (dir == "." || string.IsNullOrEmpty(dir)) continue;
if (json.IsDirectory)
{
if (!json.Files.ContainsKey(dir))
{
json.Files[dir] = new FilesystemEntry { Files = new Dictionary<string, FilesystemEntry>(StringComparer.Ordinal) };
}
json = json.Files[dir];
}
else
{
throw new Exception($"Unexpected directory state while traversing: {p}");
}
}
return json;
}
public List<string> ListFiles(bool isPack = false)
{
var files = new List<string>();
FillFilesFromMetadata("/", _header);
return files;
void FillFilesFromMetadata(string basePath, FilesystemEntry metadata)
{
if (!metadata.IsDirectory)
{
return;
}
foreach (var entry in metadata.Files)
{
string childPath = entry.Key;
FilesystemEntry childMetadata = entry.Value;
string fullPath = Path.Combine(basePath, childPath).Replace('\\', '/');
string packState =
childMetadata.Unpacked == true ? "unpack" : "pack ";
files.Add(isPack ? $"{packState} : {fullPath}" : fullPath);
FillFilesFromMetadata(fullPath, childMetadata);
}
}
}
public FilesystemEntry GetNode(string p, bool followLinks = true)
{
// Normalize path delimiters
p = p.Replace('/', Path.DirectorySeparatorChar).Replace('\\', Path.DirectorySeparatorChar);
FilesystemEntry node = SearchNodeFromDirectory(Extensions.GetDirectoryName(p));
string name = Path.GetFileName(p);
// Process symbolic links
if (node.IsLink && followLinks)
{
return GetNode(Path.Combine(node.Link, name));
}
if (!string.IsNullOrEmpty(name))
{
if (node.IsDirectory && node.Files.TryGetValue(name, out var entry))
{
return entry;
}
return null;
}
return node;
}
public FilesystemEntry GetFile(string p, bool followLinks = true)
{
FilesystemEntry info = GetNode(p, followLinks);
if (info == null)
{
throw new Exception($"\"{p}\" was not found in this archive");
}
// If followLinks=false, do not allow symbolic links (TODO)
if (info.IsLink && followLinks)
{
return GetFile(info.Link, followLinks);
}
return info;
}
public static string ReadLink(string path)
{
throw new NotImplementedException();
return Path.GetFileName(path);
// TODO , NOT IMPLEMENTED
}
#region Writing
public FilesystemEntry SearchNodeFromPath(string p)
{
p = Extensions.GetRelativePath(_src, p);
if (string.IsNullOrEmpty(p))
{
return _header;
}
var name = Path.GetFileName(p);
var node = SearchNodeFromDirectory(Extensions.GetDirectoryName(p));
if (node.Files == null)
{
node.Files = new Dictionary<string, FilesystemEntry>();
}
if (!node.Files.ContainsKey(name))
{
node.Files[name] = new FilesystemEntry();
}
return node.Files[name];
}
public void InsertDirectory(string p, bool unpack)
{
FilesystemEntry node = SearchNodeFromPath(p);
node.Files = node.Files ?? new Dictionary<string, FilesystemEntry>();
node.Unpacked = unpack;
}
public void InsertFile(string path, bool shouldUnpack, CrawledFileType file)
{
var dirName = Path.GetDirectoryName(path);
var dirNode = SearchNodeFromPath(dirName);
var node = SearchNodeFromPath(path);
long size = 0;
if (file.Stat is FileInfo fileInfo)
{
size = fileInfo.Length;
}
else
{
throw new Exception($"{path}: stat is not a file");
}
if (shouldUnpack || dirNode.Unpacked == true)
{
node.Size = size;
node.Unpacked = true;
node.Integrity = IntegrityHelper.GetFileIntegrity(path);
return;
}
// Check that the file size does not exceed UINT32_MAX
if (size > UINT32_MAX)
{
throw new Exception($"{path}: file size cannot be larger than 4.2GB");
}
node.Size = size;
node.Offset = _offset.ToString();
node.Integrity = IntegrityHelper.GetFileIntegrity(path);
if (!Extensions.IsWindowsPlatform() && (file.Stat.Attributes & FileAttributes.Hidden) != 0)
{
node.Executable = true;
}
_offset += size;
}
#endregion
}
}
@@ -0,0 +1,160 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using AsarSharp.Utils;
namespace AsarSharp.AsarFileSystem
{
public class CrawledFileType
{
public FileType Type { get; set; }
public FileSystemInfo Stat { get; set; }
public TransformedFile Transformed { get; set; }
}
public class TransformedFile
{
public string Path { get; set; }
public FileSystemInfo Stat { get; set; }
}
public enum FileType
{
File,
Directory,
Link
}
public static class FileSystemCrawler
{
public static CrawledFileType DetermineFileType(string filename)
{
var fileInfo = new FileInfo(filename);
if (fileInfo.Exists)
{
return new CrawledFileType { Type = FileType.File, Stat = fileInfo };
}
var directoryInfo = new DirectoryInfo(filename);
if (directoryInfo.Exists)
{
return new CrawledFileType { Type = FileType.Directory, Stat = directoryInfo };
}
var linkInfo = new FileInfo(filename);
if (linkInfo.Exists && (linkInfo.Attributes & FileAttributes.ReparsePoint) == FileAttributes.ReparsePoint)
{
return new CrawledFileType { Type = FileType.Link, Stat = linkInfo };
}
return null;
}
public static (List<string> filenames, Dictionary<string, CrawledFileType> metadata) CrawlFileSystem(string dir)
{
var metadata = new Dictionary<string, CrawledFileType>();
var crawled = CrawlIterative(dir);
var results = crawled.Select(filename => new { filename, type = DetermineFileType(filename) }).ToList();
var links = new List<string>();
var filenames = new List<string>();
foreach (var result in results.Where(result => result.type != null))
{
metadata[result.filename] = result.type;
if (result.type.Type == FileType.Link)
{
links.Add(result.filename);
}
filenames.Add(result.filename);
}
var filteredFilenames = new List<string>();
foreach (var filename in filenames)
{
var exactLinkIndex = links.FindIndex(link => filename == link);
var isValid = true;
for (var i = 0; i < links.Count; i++)
{
if (i == exactLinkIndex)
{
continue;
}
var link = links[i];
var isFileWithinSymlinkDir = filename.StartsWith(link, StringComparison.OrdinalIgnoreCase);
var relativePath = Extensions.GetRelativePath(link, Path.GetDirectoryName(filename) ?? string.Empty);
if (isFileWithinSymlinkDir && !relativePath.StartsWith("..", StringComparison.Ordinal))
{
isValid = false;
break;
}
}
if (isValid)
{
filteredFilenames.Add(filename);
}
}
return (filteredFilenames, metadata);
}
// (File order is not important!!!)
public static List<string> CrawlIterative(string dir)
{
var result = new List<string>();
var stack = new Stack<string>();
string basePath = Extensions.GetBasePath(dir);
if (!Directory.Exists(basePath))
return result;
// Add only the base directory to the stack, but not to the result
stack.Push(basePath);
while (stack.Count > 0)
{
string currentDir = stack.Pop();
try
{
// Add all files from the current directory
result.AddRange(Directory.GetFiles(currentDir, "*", SearchOption.TopDirectoryOnly));
// Add subdirectories to the results and to the stack
foreach (var directory in Directory.GetDirectories(currentDir, "*",
SearchOption.TopDirectoryOnly))
{
// Add subdirectories to the result
if (directory != basePath) // Do not add a base directory
{
result.Add(directory);
}
// Add to the stack for processing
stack.Push(directory);
}
}
catch (UnauthorizedAccessException)
{
// Skip directories to which there is no access
continue;
}
}
return result;
}
}
}
@@ -0,0 +1,49 @@
using System.Collections.Generic;
using AsarSharp.Integrity;
using Newtonsoft.Json;
namespace AsarSharp.AsarFileSystem
{
public class FilesystemEntry
{
[JsonProperty("files")]
public Dictionary<string, FilesystemEntry> Files { get; set; }
[JsonProperty("executable")]
public bool? Executable { get; set; }
[JsonProperty("size")]
public long? Size { get; set; }
[JsonProperty("offset")]
public string Offset { get; set; }
[JsonProperty("unpacked")]
public bool? Unpacked { get; set; }
[JsonProperty("integrity")]
public IntegrityHelper.FileIntegrity Integrity { get; set; }
[JsonProperty("link")]
public string Link { get; set; }
[JsonIgnore]
public bool IsDirectory => Files != null;
[JsonIgnore]
public bool IsFile => Size.HasValue;
[JsonIgnore]
public bool IsLink => Link != null;
public override string ToString()
{
return $"Offset: {Offset}, Size: {Size} Unpacked: {Unpacked}";
}
public bool ShouldSerializeUnpacked()
{
return Unpacked == true;
}
}
}
+70
View File
@@ -0,0 +1,70 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props"
Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')"/>
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{BEAA604A-402A-4387-8903-A53FC913A26E}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>AsarSharp</RootNamespace>
<AssemblyName>AsarSharp</AssemblyName>
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\packages\Newtonsoft.Json.13.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="System"/>
<Reference Include="System.Core"/>
<Reference Include="System.Data"/>
<Reference Include="System.Xml"/>
</ItemGroup>
<ItemGroup>
<Compile Include="AsarCreator.cs" />
<Compile Include="AsarExtractor.cs" />
<Compile Include="AsarFileSystem\Disk.cs" />
<Compile Include="AsarFileSystem\FileSystem.cs" />
<Compile Include="AsarFileSystem\FileSystemCrawler.cs" />
<Compile Include="AsarFileSystem\FilesystemEntry.cs" />
<Compile Include="Integrity\IntegrityHelper.cs" />
<Compile Include="PickleTools\Pickle.cs" />
<Compile Include="PickleTools\PickleIterator.cs" />
<Compile Include="Properties\AssemblyInfo.cs"/>
<Compile Include="Utils\Extensions.cs" />
<Compile Include="Utils\NativeMethods.cs" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets"/>
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>
+69
View File
@@ -0,0 +1,69 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Security.Cryptography;
using Newtonsoft.Json;
namespace AsarSharp.Integrity
{
public static class IntegrityHelper
{
private const string ALGORITHM = "SHA256";
// 4MB default block size
private const int BLOCK_SIZE = 4 * 1024 * 1024;
public class FileIntegrity
{
[JsonProperty("algorithm")]
public string Algorithm { get; set; }
[JsonProperty("hash")]
public string Hash { get; set; }
[JsonProperty("blockSize")]
public int BlockSize { get; set; }
[JsonProperty("blocks")]
public List<string> Blocks { get; set; }
}
public static FileIntegrity GetFileIntegrity(string path)
{
using (var fileStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))
using(var fileHash = SHA256.Create())
{
var blockHashes = new List<string>();
var buffer = new byte[BLOCK_SIZE];
int bytesRead;
while ((bytesRead = fileStream.Read(buffer, 0, BLOCK_SIZE)) > 0)
{
var block = new byte[bytesRead];
Array.Copy(buffer, block, bytesRead);
blockHashes.Add(HashBlock(block));
fileHash.TransformBlock(block, 0, block.Length, null, 0);
}
fileHash.TransformFinalBlock(Array.Empty<byte>(), 0, 0);
return new FileIntegrity
{
Algorithm = ALGORITHM,
Hash = BitConverter.ToString(fileHash.Hash).Replace("-", "").ToLowerInvariant(),
BlockSize = BLOCK_SIZE,
Blocks = blockHashes,
};
}
}
private static string HashBlock(byte[] block)
{
using (var sha256 = SHA256.Create())
{
var hash = sha256.ComputeHash(block);
return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant();
}
}
}
}
+425
View File
@@ -0,0 +1,425 @@
using System;
using System.Text;
namespace AsarSharp.PickleTools
{
public class Pickle
{
public const int SIZE_INT32 = 4;
public const int SIZE_UINT32 = 4;
public const int SIZE_INT64 = 8;
public const int SIZE_UINT64 = 8;
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;
// Maximum value for read-only
public const long CAPACITY_READ_ONLY = 9007199254740992;
private byte[] _header;
private int _headerSize;
private long _capacityAfterHeader;
private int _writeOffset;
private Pickle(byte[] buffer = null)
{
if (buffer != null)
{
_header = buffer;
_headerSize = buffer.Length - GetPayloadSize();
_capacityAfterHeader = CAPACITY_READ_ONLY;
_writeOffset = 0;
if (_headerSize > buffer.Length)
{
_headerSize = 0;
}
if (_headerSize != AlignInt(_headerSize, SIZE_UINT32))
{
_headerSize = 0;
}
if (_headerSize == 0)
{
_header = new byte[0];
}
}
else
{
_header = new byte[0];
_headerSize = SIZE_UINT32;
_capacityAfterHeader = 0;
_writeOffset = 0;
Resize(PAYLOAD_UNIT);
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 int GetHeaderSize()
{
return _headerSize;
}
public PickleIterator CreateIterator()
{
return new PickleIterator(this);
}
/// <summary>
/// Converts Pickle to a byte array
/// </summary>
public byte[] ToBuffer()
{
int resultSize = _headerSize + GetPayloadSize();
byte[] result = new byte[resultSize];
Array.Copy(_header, 0, result, 0, resultSize);
return result;
}
public bool WriteBool(bool value)
{
return WriteInt(value ? 1 : 0);
}
public bool WriteInt(int value)
{
EnsureCapacity(SIZE_INT32);
var dataLength = AlignInt(SIZE_INT32, SIZE_UINT32);
var newSize = _writeOffset + dataLength;
if (newSize > _capacityAfterHeader)
{
Resize(Math.Max((int)_capacityAfterHeader * 2, newSize));
}
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;
}
public bool WriteUInt32(uint value)
{
EnsureCapacity(SIZE_UINT32);
var dataLength = AlignInt(SIZE_UINT32, SIZE_UINT32);
var newSize = _writeOffset + dataLength;
if (newSize > _capacityAfterHeader)
{
Resize(Math.Max((int)_capacityAfterHeader * 2, newSize));
}
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;
if (newSize > _capacityAfterHeader)
{
Resize(Math.Max((int)_capacityAfterHeader * 2, newSize));
}
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;
}
public bool WriteUInt64(ulong value)
{
EnsureCapacity(SIZE_UINT64);
var dataLength = AlignInt(SIZE_UINT64, SIZE_UINT32);
var newSize = _writeOffset + dataLength;
if (newSize > _capacityAfterHeader)
{
Resize(Math.Max((int)_capacityAfterHeader * 2, newSize));
}
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;
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;
}
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;
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;
}
SetPayloadSize(newSize);
_writeOffset = newSize;
return true;
}
public bool WriteString(string value)
{
byte[] strBytes = Encoding.UTF8.GetBytes(value);
int length = strBytes.Length;
if (!WriteInt(length))
{
return false;
}
var dataLength = AlignInt(length, SIZE_UINT32);
var newSize = _writeOffset + dataLength;
if (newSize > _capacityAfterHeader)
{
Resize(Math.Max((int)_capacityAfterHeader * 2, newSize));
}
Array.Copy(strBytes, 0, _header, _headerSize + _writeOffset, length);
var endOffset = _headerSize + _writeOffset + length;
for (int i = endOffset; i < endOffset + dataLength - length; i++)
{
_header[i] = 0;
}
SetPayloadSize(newSize);
_writeOffset = newSize;
return true;
}
public void SetPayloadSize(int payloadSize)
{
WriteUInt32LE((uint)payloadSize, 0);
}
public int GetPayloadSize()
{
return (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);
_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));
}
}
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);
}
}
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);
}
}
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);
}
}
#endregion
}
}
+101
View File
@@ -0,0 +1,101 @@
using System;
using System.Text;
namespace AsarSharp.PickleTools
{
public class PickleIterator
{
private readonly byte[] _payload;
private readonly int _payloadOffset;
private int _readIndex;
private readonly int _endIndex;
public PickleIterator(Pickle pickle)
{
_payload = pickle.GetHeader();
_payloadOffset = pickle.GetHeaderSize();
_readIndex = 0;
_endIndex = pickle.GetPayloadSize();
}
public bool ReadBool()
{
return ReadInt() != 0;
}
public int ReadInt()
{
return ReadBytes(Pickle.SIZE_INT32, BitConverter.ToInt32);
}
public uint ReadUInt32()
{
return ReadBytes(Pickle.SIZE_UINT32, BitConverter.ToUInt32);
}
public long ReadInt64()
{
return ReadBytes(Pickle.SIZE_INT64, BitConverter.ToInt64);
}
public ulong ReadUInt64()
{
return ReadBytes(Pickle.SIZE_UINT64, BitConverter.ToUInt64);
}
public float ReadFloat()
{
return ReadBytes(Pickle.SIZE_FLOAT, BitConverter.ToSingle);
}
public double ReadDouble()
{
return ReadBytes(Pickle.SIZE_DOUBLE, BitConverter.ToDouble);
}
public string ReadString()
{
int length = ReadInt();
return Encoding.UTF8.GetString(ReadBytes(length));
}
private T ReadBytes<T>(int length, Func<byte[], int, T> converter)
{
int readPayloadOffset = GetReadPayloadOffsetAndAdvance(length);
return converter(_payload, readPayloadOffset);
}
private byte[] ReadBytes(int length)
{
int readPayloadOffset = GetReadPayloadOffsetAndAdvance(length);
byte[] result = new byte[length];
Array.Copy(_payload, readPayloadOffset, result, 0, length);
return result;
}
private int GetReadPayloadOffsetAndAdvance(int length)
{
if (length > _endIndex - _readIndex)
{
_readIndex = _endIndex;
throw new InvalidOperationException($"Failed to read data with length of {length}");
}
int readPayloadOffset = _payloadOffset + _readIndex;
Advance(length);
return readPayloadOffset;
}
private void Advance(int size)
{
int alignedSize = Pickle.AlignInt(size, Pickle.SIZE_UINT32);
if (_endIndex - _readIndex < alignedSize)
{
_readIndex = _endIndex;
}
else
{
_readIndex += alignedSize;
}
}
}
}
+35
View File
@@ -0,0 +1,35 @@
using System.Reflection;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("AsarSharp")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("AsarSharp")]
[assembly: AssemblyCopyright("Copyright © 2025")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("BEAA604A-402A-4387-8903-A53FC913A26E")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
+145
View File
@@ -0,0 +1,145 @@
using System;
using System.IO;
using System.Runtime.InteropServices;
namespace AsarSharp.Utils
{
internal static class Extensions
{
public static string GetRelativePath(string relativeTo, string path)
{
if (string.IsNullOrEmpty(relativeTo))
throw new ArgumentNullException(nameof(relativeTo));
if (string.IsNullOrEmpty(path))
throw new ArgumentNullException(nameof(path));
var fullRelativeTo = Path.GetFullPath(relativeTo);
var fullPath = Path.GetFullPath(path);
if (string.Equals(fullRelativeTo, fullPath, StringComparison.OrdinalIgnoreCase))
return "";
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() : ""));
var relativeUri = relativeToUri.MakeRelativeUri(pathUri);
var relativePath = Uri.UnescapeDataString(relativeUri.ToString())
.Replace('/', Path.DirectorySeparatorChar);
return relativePath.TrimEnd(Path.DirectorySeparatorChar);
}
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
{
FileName = "chmod",
Arguments = $"{permission} \"{filePath}\"",
UseShellExecute = false,
RedirectStandardOutput = true,
CreateNoWindow = true
}
};
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
: NativeMethods.SymLinkFlag.File);
return;
}
// In Unix systems we use the corresponding system call
var process = new System.Diagnostics.Process
{
StartInfo = new System.Diagnostics.ProcessStartInfo
{
FileName = "ln",
Arguments = $"-s \"{linkTarget}\" \"{linkPath}\"",
UseShellExecute = false,
RedirectStandardOutput = true,
CreateNoWindow = true
}
};
process.Start();
process.WaitForExit();
}
public static bool IsWindowsPlatform()
{
return Environment.OSVersion.Platform == PlatformID.Win32NT;
}
}
}
+17
View File
@@ -0,0 +1,17 @@
using System.Runtime.InteropServices;
namespace AsarSharp.Utils
{
internal static class NativeMethods
{
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool CreateSymbolicLink(string lpSymlinkFileName, string lpTargetFileName, SymLinkFlag dwFlags);
public enum SymLinkFlag
{
File = 0,
Directory = 1,
AllowUnprivilegedCreate = 2
}
}
}
+4
View File
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Newtonsoft.Json" version="13.0.3" targetFramework="net48" />
</packages>