/// <summary> /// Given a stanza and an open zipfile, returns all files that would be installed /// for this stanza. /// /// If a KSP instance is provided, it will be used to generate output paths, otherwise these will be null. /// /// Throws a BadInstallLocationKraken if the install stanza targets an /// unknown install location (eg: not GameData, Ships, etc) /// /// Throws a BadMetadataKraken if the stanza resulted in no files being returned. /// </summary> internal static List <InstallableFile> FindInstallableFiles(ModuleInstallDescriptor stanza, ZipFile zipfile, KSP ksp) { string installDir; bool makeDirs; var files = new List <InstallableFile> (); if (stanza.install_to == "GameData") { installDir = ksp == null ? null : ksp.GameData(); makeDirs = true; } else if (stanza.install_to == "Ships") { installDir = ksp == null ? null : ksp.Ships(); makeDirs = false; // Don't allow directory creation in ships directory } else if (stanza.install_to == "Tutorial") { installDir = ksp == null ? null : ksp.Tutorial(); makeDirs = true; } else if (stanza.install_to == "GameRoot") { installDir = ksp == null ? null : ksp.GameDir(); makeDirs = false; } else { throw new BadInstallLocationKraken("Unknown install_to " + stanza.install_to); } // O(N^2) solution, as we're walking the zipfile for each stanza. // Surely there's a better way, although this is fast enough we may not care. foreach (ZipEntry entry in zipfile) { // Skips things not prescribed by our install stanza. if (!stanza.IsWanted(entry.Name)) { continue; } // Prepare our file info. InstallableFile file_info = new InstallableFile(); file_info.source = entry; file_info.makedir = makeDirs; file_info.destination = null; // If we have a place to install it, fill that in... if (installDir != null) { // Get the full name of the file. string outputName = entry.Name; // Update our file info with the install location file_info.destination = TransformOutputName(stanza.file, outputName, installDir); } files.Add(file_info); } // If we have no files, then something is wrong! (KSP-CKAN/CKAN#93) if (files.Count == 0) { // We have null as the first argument here, because we don't know which module we're installing throw new BadMetadataKraken(null, String.Format("No files found in {0} to install!", stanza.file)); } return(files); }
/// <summary> /// Given a stanza and an open zipfile, returns all files that would be installed /// for this stanza. /// /// If a KSP instance is provided, it will be used to generate output paths, otherwise these will be null. /// /// Throws a BadInstallLocationKraken if the install stanza targets an /// unknown install location (eg: not GameData, Ships, etc) /// /// Throws a BadMetadataKraken if the stanza resulted in no files being returned. /// </summary> /// <exception cref="BadInstallLocationKraken">Thrown when the installation path is not valid according to the spec.</exception> internal static List <InstallableFile> FindInstallableFiles(ModuleInstallDescriptor stanza, ZipFile zipfile, KSP ksp) { string installDir; bool makeDirs; var files = new List <InstallableFile> (); // Normalize the path before doing everything else // TODO: This really should happen in the ModuleInstallDescriptor itself. stanza.install_to = KSPPathUtils.NormalizePath(stanza.install_to); // Convert our stanza to a standard `file` type. This is a no-op if it's // already the basic type. stanza = stanza.ConvertFindToFile(zipfile); if (stanza.install_to == "GameData" || stanza.install_to.StartsWith("GameData/")) { // The installation path can be either "GameData" or a sub-directory of "GameData" // but it cannot contain updirs if (stanza.install_to.Contains("/../") || stanza.install_to.EndsWith("/..")) { throw new BadInstallLocationKraken("Invalid installation path: " + stanza.install_to); } string subDir = stanza.install_to.Substring("GameData".Length); // remove "GameData" subDir = subDir.StartsWith("/") ? subDir.Substring(1) : subDir; // remove a "/" at the beginning, if present // Add the extracted subdirectory to the path of KSP's GameData installDir = ksp == null ? null : (KSPPathUtils.NormalizePath(ksp.GameData() + "/" + subDir)); makeDirs = true; } else if (stanza.install_to.StartsWith("Ships")) { // Don't allow directory creation in ships directory makeDirs = false; switch (stanza.install_to) { case "Ships": installDir = ksp == null ? null : ksp.Ships(); break; case "Ships/VAB": installDir = ksp == null ? null : ksp.ShipsVab(); break; case "Ships/SPH": installDir = ksp == null ? null : ksp.ShipsSph(); break; default: throw new BadInstallLocationKraken("Unknown install_to " + stanza.install_to); } } else { switch (stanza.install_to) { case "Tutorial": installDir = ksp == null ? null : ksp.Tutorial(); makeDirs = true; break; case "Scenarios": installDir = ksp == null ? null : ksp.Scenarios(); makeDirs = true; break; case "GameRoot": installDir = ksp == null ? null : ksp.GameDir(); makeDirs = false; break; default: throw new BadInstallLocationKraken("Unknown install_to " + stanza.install_to); } } // O(N^2) solution, as we're walking the zipfile for each stanza. // Surely there's a better way, although this is fast enough we may not care. foreach (ZipEntry entry in zipfile) { // Skips things not prescribed by our install stanza. if (!stanza.IsWanted(entry.Name)) { continue; } // Prepare our file info. InstallableFile file_info = new InstallableFile { source = entry, makedir = makeDirs, destination = null }; // If we have a place to install it, fill that in... if (installDir != null) { // Get the full name of the file. string outputName = entry.Name; // Update our file info with the install location file_info.destination = TransformOutputName(stanza.file, outputName, installDir); } files.Add(file_info); } // If we have no files, then something is wrong! (KSP-CKAN/CKAN#93) if (files.Count == 0) { // We have null as the first argument here, because we don't know which module we're installing throw new BadMetadataKraken(null, String.Format("No files found in {0} to install!", stanza.file)); } return(files); }
/// <summary> /// Given a stanza and an open zipfile, returns all files that would be installed /// for this stanza. /// /// If a KSP instance is provided, it will be used to generate output paths, otherwise these will be null. /// /// Throws a BadInstallLocationKraken if the install stanza targets an /// unknown install location (eg: not GameData, Ships, etc) /// /// Throws a BadMetadataKraken if the stanza resulted in no files being returned. /// </summary> /// <exception cref="BadInstallLocationKraken">Thrown when the installation path is not valid according to the spec.</exception> internal static List<InstallableFile> FindInstallableFiles(ModuleInstallDescriptor stanza, ZipFile zipfile, KSP ksp) { string installDir; bool makeDirs; var files = new List<InstallableFile> (); // Normalize the path before doing everything else // TODO: This really should happen in the ModuleInstallDescriptor itself. stanza.install_to = KSPPathUtils.NormalizePath(stanza.install_to); // Convert our stanza to a standard `file` type. This is a no-op if it's // already the basic type. stanza = stanza.ConvertFindToFile(zipfile); if (stanza.install_to == "GameData" || stanza.install_to.StartsWith("GameData/")) { // The installation path can be either "GameData" or a sub-directory of "GameData" // but it cannot contain updirs if (stanza.install_to.Contains("/../") || stanza.install_to.EndsWith("/..")) throw new BadInstallLocationKraken("Invalid installation path: " + stanza.install_to); string subDir = stanza.install_to.Substring("GameData".Length); // remove "GameData" subDir = subDir.StartsWith("/") ? subDir.Substring(1) : subDir; // remove a "/" at the beginning, if present // Add the extracted subdirectory to the path of KSP's GameData installDir = ksp == null ? null : (KSPPathUtils.NormalizePath(ksp.GameData() + "/" + subDir)); makeDirs = true; } else if (stanza.install_to.StartsWith("Ships")) { // Don't allow directory creation in ships directory makeDirs = false; switch (stanza.install_to) { case "Ships": installDir = ksp == null ? null : ksp.Ships(); break; case "Ships/VAB": installDir = ksp == null ? null : ksp.ShipsVab(); break; case "Ships/SPH": installDir = ksp == null ? null : ksp.ShipsSph(); break; case "Ships/@thumbs": installDir = ksp == null ? null : ksp.ShipsThumbs(); break; case "Ships/@thumbs/VAB": installDir = ksp == null ? null : ksp.ShipsThumbsVAB(); break; case "Ships/@thumbs/SPH": installDir = ksp == null ? null : ksp.ShipsThumbsSPH(); break; default: throw new BadInstallLocationKraken("Unknown install_to " + stanza.install_to); } } else switch (stanza.install_to) { case "Tutorial": installDir = ksp == null ? null : ksp.Tutorial(); makeDirs = true; break; case "Scenarios": installDir = ksp == null ? null : ksp.Scenarios(); makeDirs = true; break; case "GameRoot": installDir = ksp == null ? null : ksp.GameDir(); makeDirs = false; break; default: throw new BadInstallLocationKraken("Unknown install_to " + stanza.install_to); } // O(N^2) solution, as we're walking the zipfile for each stanza. // Surely there's a better way, although this is fast enough we may not care. foreach (ZipEntry entry in zipfile) { // Skips things not prescribed by our install stanza. if (! stanza.IsWanted(entry.Name)) { continue; } // Prepare our file info. InstallableFile file_info = new InstallableFile { source = entry, makedir = makeDirs, destination = null }; // If we have a place to install it, fill that in... if (installDir != null) { // Get the full name of the file. string outputName = entry.Name; // Update our file info with the install location file_info.destination = TransformOutputName(stanza.file, outputName, installDir); } files.Add(file_info); } // If we have no files, then something is wrong! (KSP-CKAN/CKAN#93) if (files.Count == 0) { // We have null as the first argument here, because we don't know which module we're installing throw new BadMetadataKraken(null, String.Format("No files found in {0} to install!", stanza.file)); } return files; }
/// <summary> /// Given an open zipfile, returns all files that would be installed /// for this stanza. /// /// If a KSP instance is provided, it will be used to generate output paths, otherwise these will be null. /// /// Throws a BadInstallLocationKraken if the install stanza targets an /// unknown install location (eg: not GameData, Ships, etc) /// /// Throws a BadMetadataKraken if the stanza resulted in no files being returned. /// </summary> /// <exception cref="BadInstallLocationKraken">Thrown when the installation path is not valid according to the spec.</exception> public List <InstallableFile> FindInstallableFiles(ZipFile zipfile, GameInstance ksp) { string installDir; var files = new List <InstallableFile>(); // Normalize the path before doing everything else string install_to = CKANPathUtils.NormalizePath(this.install_to); // The installation path cannot contain updirs if (install_to.Contains("/../") || install_to.EndsWith("/..")) { throw new BadInstallLocationKraken("Invalid installation path: " + install_to); } if (ksp == null) { installDir = null; } else if (install_to == ksp.game.PrimaryModDirectoryRelative || install_to.StartsWith($"{ksp.game.PrimaryModDirectoryRelative}/")) { // The installation path can be either "GameData" or a sub-directory of "GameData" string subDir = install_to.Substring(ksp.game.PrimaryModDirectoryRelative.Length); // remove "GameData" subDir = subDir.StartsWith("/") ? subDir.Substring(1) : subDir; // remove a "/" at the beginning, if present // Add the extracted subdirectory to the path of KSP's GameData installDir = CKANPathUtils.NormalizePath(ksp.game.PrimaryModDirectory(ksp) + "/" + subDir); } else { switch (install_to) { case "GameRoot": installDir = ksp.GameDir(); break; default: if (ksp.game.AllowInstallationIn(install_to, out string path)) { installDir = ksp.ToAbsoluteGameDir(path); } else { throw new BadInstallLocationKraken("Unknown install_to " + install_to); } break; } } EnsurePattern(); // `find` is supposed to match the "topmost" folder. Find it. var shortestMatch = find == null ? (int?)null : zipfile.Cast <ZipEntry>() .Select(entry => inst_pattern.Match(entry.Name.Replace('\\', '/'))) .Where(match => match.Success) .DefaultIfEmpty() .Min(match => match?.Index); // O(N^2) solution, as we're walking the zipfile for each stanza. // Surely there's a better way, although this is fast enough we may not care. foreach (ZipEntry entry in zipfile) { // Skips dirs and things not prescribed by our install stanza. if (!IsWanted(entry.Name, shortestMatch)) { continue; } // Prepare our file info. InstallableFile file_info = new InstallableFile { source = entry, makedir = false, destination = null }; // If we have a place to install it, fill that in... if (installDir != null) { // Get the full name of the file. // Update our file info with the install location file_info.destination = TransformOutputName( ksp.game, entry.Name, installDir, @as); file_info.makedir = AllowDirectoryCreation( ksp.game, ksp?.ToRelativeGameDir(file_info.destination) ?? file_info.destination); } files.Add(file_info); } // If we have no files, then something is wrong! (KSP-CKAN/CKAN#93) if (files.Count == 0) { // We have null as the first argument here, because we don't know which module we're installing throw new BadMetadataKraken(null, String.Format("No files found matching {0} to install!", DescribeMatch())); } return(files); }
/// <summary> /// Given an open zipfile, returns all files that would be installed /// for this stanza. /// /// If a KSP instance is provided, it will be used to generate output paths, otherwise these will be null. /// /// Throws a BadInstallLocationKraken if the install stanza targets an /// unknown install location (eg: not GameData, Ships, etc) /// /// Throws a BadMetadataKraken if the stanza resulted in no files being returned. /// </summary> /// <exception cref="BadInstallLocationKraken">Thrown when the installation path is not valid according to the spec.</exception> public List <InstallableFile> FindInstallableFiles(ZipFile zipfile, KSP ksp) { string installDir; bool makeDirs; var files = new List <InstallableFile>(); // Normalize the path before doing everything else string install_to = KSPPathUtils.NormalizePath(this.install_to); if (install_to == "GameData" || install_to.StartsWith("GameData/")) { // The installation path can be either "GameData" or a sub-directory of "GameData" // but it cannot contain updirs if (install_to.Contains("/../") || install_to.EndsWith("/..")) { throw new BadInstallLocationKraken("Invalid installation path: " + install_to); } string subDir = install_to.Substring("GameData".Length); // remove "GameData" subDir = subDir.StartsWith("/") ? subDir.Substring(1) : subDir; // remove a "/" at the beginning, if present // Add the extracted subdirectory to the path of KSP's GameData installDir = ksp == null ? null : (KSPPathUtils.NormalizePath(ksp.GameData() + "/" + subDir)); makeDirs = true; } else if (install_to.StartsWith("Ships")) { // Don't allow directory creation in ships directory makeDirs = false; switch (install_to) { case "Ships": installDir = ksp?.Ships(); break; case "Ships/VAB": installDir = ksp?.ShipsVab(); break; case "Ships/SPH": installDir = ksp?.ShipsSph(); break; case "Ships/@thumbs": installDir = ksp?.ShipsThumbs(); break; case "Ships/@thumbs/VAB": installDir = ksp?.ShipsThumbsVAB(); break; case "Ships/@thumbs/SPH": installDir = ksp?.ShipsThumbsSPH(); break; default: throw new BadInstallLocationKraken("Unknown install_to " + install_to); } } else { switch (install_to) { case "Tutorial": installDir = ksp?.Tutorial(); makeDirs = true; break; case "Scenarios": installDir = ksp?.Scenarios(); makeDirs = true; break; case "Missions": installDir = ksp?.Missions(); makeDirs = true; break; case "GameRoot": installDir = ksp?.GameDir(); makeDirs = false; break; default: throw new BadInstallLocationKraken("Unknown install_to " + install_to); } } EnsurePattern(); // `find` is supposed to match the "topmost" folder. Find it. var shortestMatch = find == null ? (int?)null : zipfile.Cast <ZipEntry>() .Select(entry => inst_pattern.Match(entry.Name.Replace('\\', '/'))) .Where(match => match.Success) .Min(match => match.Index); // O(N^2) solution, as we're walking the zipfile for each stanza. // Surely there's a better way, although this is fast enough we may not care. foreach (ZipEntry entry in zipfile) { // Skips dirs and things not prescribed by our install stanza. if (!IsWanted(entry.Name, shortestMatch)) { continue; } // Prepare our file info. InstallableFile file_info = new InstallableFile { source = entry, makedir = makeDirs, destination = null }; // If we have a place to install it, fill that in... if (installDir != null) { // Get the full name of the file. // Update our file info with the install location file_info.destination = TransformOutputName( entry.Name, installDir, @as); } files.Add(file_info); } // If we have no files, then something is wrong! (KSP-CKAN/CKAN#93) if (files.Count == 0) { // We have null as the first argument here, because we don't know which module we're installing throw new BadMetadataKraken(null, String.Format("No files found matching {0} to install!", DescribeMatch())); } return(files); }