public static bool ImportFromDirectory(out Mod mod, out string message) { mod = null; message = ""; CommonOpenFileDialog openDialog = new CommonOpenFileDialog { IsFolderPicker = true, Title = "Open mod folder", Multiselect = false }; if (openDialog.ShowDialog() != CommonFileDialogResult.Ok) { return(false); } CommonSaveFileDialog saveDialog = new CommonSaveFileDialog { Filters = { new CommonFileDialogFilter("Compressed mod", "*.zipmod"), new CommonFileDialogFilter("Compressed folder", "*.zip") }, Title = "Save new .zip file", AlwaysAppendDefaultExtension = true }; if (saveDialog.ShowDialog() != CommonFileDialogResult.Ok) { return(false); } string sourceDir = openDialog.FileName; string savePath = saveDialog.FileName; if (!savePath.ToLower().EndsWith(".zipmod") && (saveDialog.SelectedFileTypeIndex == 1)) { savePath += ".zipmod"; } if (!savePath.ToLower().EndsWith(".zip") && (saveDialog.SelectedFileTypeIndex == 2)) { savePath += ".zip"; } if (File.Exists(savePath)) { File.Delete(savePath); } ZipFile file = new ZipFile(savePath, Encoding.UTF8); //file.CompressionLevel = Ionic.Zlib.CompressionLevel.None; Manifest manifest = new Manifest { Guid = "<not set>" }; //check for root abdata folder string rootDir = ""; if (Path.GetFileName(sourceDir).ToLower() == "abdata") { rootDir = Path.GetDirectoryName(sourceDir); } else if (Directory.Exists(Path.Combine(sourceDir, "abdata"))) { rootDir = sourceDir; } else { //we have to try and find abdata string potentialDirectory = Directory.EnumerateDirectories(sourceDir, "abdata", SearchOption.AllDirectories).FirstOrDefault() ?? ""; if (potentialDirectory != "") { rootDir = Path.GetDirectoryName(potentialDirectory); } else { //no abdata folder exists message = "Cannot find \"abdata\" folder!"; file.Save(); file.Dispose(); File.Delete(savePath); return(false); } } string GetNewPath(string FullPath) { string newPath = FullPath.Remove(0, rootDir.Length).Trim('\\', '/').Replace('\\', '/'); newPath = newPath.Remove(newPath.LastIndexOf('/') + 1); return(newPath); } void TryAddFolderFromRoot(string prefix) { string totalPath = Path.Combine(rootDir, prefix); if (Directory.Exists(totalPath)) { file.AddDirectoryByName(prefix + "/"); foreach (string subFilePath in Directory.GetFiles(Path.Combine(rootDir, prefix), "*", SearchOption.AllDirectories)) { file.AddFile(subFilePath, GetNewPath(subFilePath)); } foreach (string subDirPath in Directory.GetDirectories(Path.Combine(rootDir, prefix), "*", SearchOption.AllDirectories)) { file.AddDirectoryByName(subDirPath.Remove(0, rootDir.Length).Trim('\\', '/').Replace('\\', '/') + "/"); } } } //need to add abdata with special care file.AddDirectoryByName("abdata/"); TryAddFolderFromRoot("UserData"); //Export csv files if (Directory.Exists(Path.Combine(rootDir, "abdata/list/characustom/"))) { AssetsManager assetsManager = new AssetsManager(); assetsManager.LoadFolder(Path.Combine(rootDir, "abdata/list/characustom/")); foreach (var assetFile in assetsManager.assetsFileList) { bool hasOtherAssets = false; foreach (var asset in assetFile.Objects.Select(x => x.Value)) { switch (asset) { case TextAsset textAsset: string FileName = $"abdata/list/characustom/{Path.GetFileNameWithoutExtension(assetFile.originalPath)}_{textAsset.m_Name}.csv"; file.AddFileWithName(__tempMakeFile(ExportCSV(textAsset.m_Script)), FileName); break; case AssetBundle ab: break; default: hasOtherAssets = true; break; } } //If the list file has assets other than just text lists add it too. It may be necessary for the mod to work. if (hasOtherAssets) { file.AddFile(assetFile.originalPath, GetNewPath(assetFile.originalPath)); } } } //Export studio csv files if (Directory.Exists(Path.Combine(rootDir, "abdata/studio/info/"))) { AssetsManager assetsManager = new AssetsManager(); assetsManager.LoadFolder(Path.Combine(rootDir, "abdata/studio/info/")); foreach (var assetFile in assetsManager.assetsFileList) { bool hasOtherAssets = false; bool directoryAdded = false; foreach (var asset in assetFile.Objects.Select(x => x.Value)) { switch (asset) { case MonoBehaviour monoBehaviour: if (monoBehaviour.m_Script.TryGet(out var monoScript) && monoScript.m_Name == "ExcelData") { if (!directoryAdded) { file.AddDirectoryByName($"abdata/studio/info/{Path.GetFileNameWithoutExtension(assetFile.originalPath)}/"); directoryAdded = true; } string FileName = $"abdata/studio/info/{Path.GetFileNameWithoutExtension(assetFile.originalPath)}/{monoBehaviour.m_Name}.csv"; monoBehaviour.reader.Reset(); file.AddFileWithName(__tempMakeFile(ExportStudioCSV(monoBehaviour.serializedType.m_Nodes, monoBehaviour.reader)), FileName); } break; case AssetBundle ab: case MonoScript ms: break; default: hasOtherAssets = true; break; } } if (hasOtherAssets) { file.AddFile(assetFile.originalPath, GetNewPath(assetFile.originalPath)); } } } //Add the rest of the unity3d files foreach (string subFilePath in Directory.GetFiles(Path.Combine(rootDir, "abdata"), "*", SearchOption.AllDirectories)) { string newPath = GetNewPath(subFilePath); //These are handled elsewhere if (newPath.ToLower().StartsWith("abdata/list/characustom/") || newPath.ToLower().StartsWith("abdata/studio/info/")) { continue; } if (subFilePath.EndsWith(".unity3d")) { file.AddFile(subFilePath, newPath); } } //Add all directories foreach (string subDirPath in Directory.GetDirectories(Path.Combine(rootDir, "abdata"), "*", SearchOption.AllDirectories)) { string newPath = subDirPath.Remove(0, rootDir.Length).Trim('\\', '/').Replace('\\', '/') + "/"; file.AddDirectoryByName(newPath); } //Add manifest file.AddFileWithName(__tempMakeFile(manifest.Export()), "manifest.xml"); file.Save(); file.Dispose(); __deleteAllTempPaths(); mod = new Mod(savePath); return(true); }