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(); 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 { // Unpacked entries already live on disk next to the archive in // ".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); } 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); } } } }