using System.IO.Compression; using MetaforceInstaller.Core.Models; namespace MetaforceInstaller.Core.Services; public class ZipScrapper { public static InstallationParts PeekFiles(ZipArchive archive) { return new InstallationParts { AndroidContentPath = FindAndroidContent(archive), OculusClientPath = FindOculusClient(archive), PicoClientPath = FindPicoClient(archive), AndroidAdminPath = FindAndroidAdmin(archive), WindowsAdminPath = FindPcAdmin(archive), WindowsContentPath = FindWindowsContent(archive), WindowsServerPath = FindServer(archive), }; } /// /// Extracts ZIP archive to a unique folder based on installation GUID /// /// ZIP archive to extract /// Base storage path /// Unique GUID for this installation /// Progress reporter /// Full path to the extracted folder public static string ExtractZip( ZipArchive archive, string baseOutputPath, Guid installationGuid, IProgress? progress = null) { // Create unique folder for this installation var installationFolder = Path.Combine(baseOutputPath, installationGuid.ToString()); Directory.CreateDirectory(installationFolder); var entries = archive.Entries.Where(e => !string.IsNullOrEmpty(e.Name)).ToList(); var totalEntries = entries.Count; var processedEntries = 0; foreach (var entry in entries) { var destinationPath = Path.Combine(installationFolder, entry.FullName); var destinationDir = Path.GetDirectoryName(destinationPath); if (!string.IsNullOrEmpty(destinationDir)) { Directory.CreateDirectory(destinationDir); } entry.ExtractToFile(destinationPath, overwrite: true); processedEntries++; progress?.Report((double)processedEntries / totalEntries * 100); } return installationFolder; } /// /// Updates InstallationParts paths to reflect the actual extracted location /// public static InstallationParts UpdatePathsAfterExtraction( InstallationParts parts, string extractedFolderPath) { return new InstallationParts { AndroidContentPath = UpdatePath(parts.AndroidContentPath, extractedFolderPath), OculusClientPath = UpdatePath(parts.OculusClientPath, extractedFolderPath), PicoClientPath = UpdatePath(parts.PicoClientPath, extractedFolderPath), AndroidAdminPath = UpdatePath(parts.AndroidAdminPath, extractedFolderPath), WindowsAdminPath = UpdatePath(parts.WindowsAdminPath, extractedFolderPath), WindowsContentPath = UpdatePath(parts.WindowsContentPath, extractedFolderPath), WindowsServerPath = UpdatePath(parts.WindowsServerPath, extractedFolderPath), }; } private static string? UpdatePath(string? relativePath, string basePath) { if (string.IsNullOrEmpty(relativePath)) return null; return Path.Combine(basePath, relativePath); } private static string? FindPicoClient(ZipArchive archive) { return FindEntry(archive, name: "MetaforcePico", extension: ".apk"); } private static string? FindOculusClient(ZipArchive archive) { return FindEntry(archive, name: "MetaforceOculus", extension: ".apk"); } private static string? FindAndroidAdmin(ZipArchive archive) { return FindEntry(archive, name: "MetaforceAdmin", extension: ".apk"); } private static string? FindAndroidContent(ZipArchive archive) { return FindEntry(archive, name: "Content_Android", extension: ".zip"); } private static string? FindWindowsContent(ZipArchive archive) { return FindEntry(archive, name: "Content_StandaloneWindows", extension: ".zip"); } private static string? FindPcAdmin(ZipArchive archive) { return FindExecutable(archive, "MetaforceAdminPC"); } private static string? FindServer(ZipArchive archive) { return FindExecutable(archive, "MetaforceServer"); } /// /// Finds an entry in archive by name and extension /// private static string? FindEntry(ZipArchive archive, string name, string extension) { var entry = archive.Entries.FirstOrDefault(e => e.Name.Contains(name, StringComparison.OrdinalIgnoreCase) && e.Name.EndsWith(extension, StringComparison.OrdinalIgnoreCase)); return entry?.FullName; } /// /// Finds an executable in archive, excluding crash handlers /// private static string? FindExecutable(ZipArchive archive, string containsName) { var entry = archive.Entries.FirstOrDefault(e => e.FullName.Contains(containsName, StringComparison.OrdinalIgnoreCase) && e.Name.EndsWith(".exe", StringComparison.OrdinalIgnoreCase) && !e.Name.Contains("UnityCrashHandler", StringComparison.OrdinalIgnoreCase) && !e.Name.Contains("crashpad_handler", StringComparison.OrdinalIgnoreCase)); return entry?.FullName; } }