public int FindNameOrAdd(string name) { if (nameLookupTable.TryGetValue(name, out var index)) { return(index); } addName(name, true); //Don't bother doing a lookup as we just did one. // If this was an issue it'd be a multithreading issue that still could occur and is an // issue in the user code return(names.Count - 1); }
private Dictionary <string, List <string> > getFileSupercedances() { Mod.MEGame game = target.Game; //make dictionary from basegame files var fileListMapping = new CaseInsensitiveDictionary <List <string> >(); var directories = MELoadedFiles.GetEnabledDLC(target).OrderBy(dir => MELoadedFiles.GetMountPriority(dir, target.Game)); foreach (string directory in directories) { var dlc = Path.GetFileName(directory); if (MEDirectories.OfficialDLC(target.Game).Contains(dlc)) { continue; //skip } foreach (string filePath in MELoadedFiles.GetCookedFiles(target.Game, directory, false)) { string fileName = Path.GetFileName(filePath); if (fileName != null && fileName.RepresentsPackageFilePath()) { if (fileListMapping.TryGetValue(fileName, out var supercedingList)) { supercedingList.Insert(0, dlc); } else { fileListMapping[fileName] = new List <string>(new[] { dlc }); } } } } return(fileListMapping); }
private static NameWrapper AddNameToUniqueStrings(string name, Dictionary <string, string> dontShortenStrings, CaseInsensitiveDictionary <StringWrapper> uniqueStrings) { NameWrapper nameWrapper = new NameWrapper(); List <string> nameStrings = StringUtils.SplitAndReallyRemoveEmptyEntries(name, Utils.NAME_SEPARATOR, StringComparison.OrdinalIgnoreCase); foreach (string nameString in nameStrings) { bool canShorten = !dontShortenStrings.ContainsKey(nameString); StringWrapper stringWrapper; if (uniqueStrings.TryGetValue(nameString, out stringWrapper)) { DebugUtils.AssertDebuggerBreak(stringWrapper.CanShorten == canShorten); } else { stringWrapper = new StringWrapper(nameString, canShorten); uniqueStrings.Add(nameString, stringWrapper); } nameWrapper.AddString(stringWrapper); } return(nameWrapper); }
/// <summary> /// Gets a list of superceding package files from the DLC of the game. Only files in DLC mods are returned /// </summary> /// <param name="target">Target to get supercedances for</param> /// <returns>Dictionary mapping filename to list of DLCs that contain that file, in order of highest priority to lowest</returns> public static Dictionary <string, List <string> > GetFileSupercedances(GameTarget target, string[] additionalExtensionsToInclude = null) { //make dictionary from basegame files var fileListMapping = new CaseInsensitiveDictionary <List <string> >(); var directories = MELoadedFiles.GetEnabledDLCFolders(target.Game, target.TargetPath).OrderBy(dir => MELoadedFiles.GetMountPriority(dir, target.Game)).ToList(); foreach (string directory in directories) { var dlc = Path.GetFileName(directory); if (MEDirectories.OfficialDLC(target.Game).Contains(dlc)) { continue; //skip } foreach (string filePath in MELoadedFiles.GetCookedFiles(target.Game, directory, false, additionalExtensions: additionalExtensionsToInclude)) { string fileName = Path.GetFileName(filePath); if (fileName != null && fileName.RepresentsPackageFilePath() || (additionalExtensionsToInclude != null && additionalExtensionsToInclude.Contains(Path.GetExtension(fileName)))) { if (fileListMapping.TryGetValue(fileName, out var supercedingList)) { supercedingList.Insert(0, dlc); } else { fileListMapping[fileName] = new List <string>(new[] { dlc }); } } } } return(fileListMapping); }
private static Log GetLogger(string name = null) { var logName = name ?? Assembly.GetCallingAssembly().GetName().Name; Log log; lock (LoggerLock) { if (Loggers.TryGetValue(logName, out log)) { return(log); } log = new Log(logName); Loggers.Add(logName, log); } return(log); }
public TestMessageResult DoAllMessageValuesMatchDataTypes(CaseInsensitiveDictionary <object> messageKeyDictionary, CaseInsensitiveDictionary <SchemaObject> contractDictionary, bool allowSubset = false) { var testMessageResult = new TestMessageResult(); // loop through each property in the message Dictionary - trying to parse the values into the data type in the contract foreach (KeyValuePair <string, object> kv in messageKeyDictionary) { // if the contract doesn't supply the datatype for the parameter -> then the message doesn't match the contract. contractDictionary.TryGetValue(kv.Key, out var propertySchemaObject); if (propertySchemaObject != null) { ChecksForMessage(propertySchemaObject, kv, testMessageResult, allowSubset); } } return(testMessageResult); }
private Mod GenerateCompatibilityPackForFiles(List <string> dlcModList, List <string> filesToBePatched, SevenZipExtractor uiArchive) { dlcModList = dlcModList.Select(x => { var tpmi = ThirdPartyServices.GetThirdPartyModInfo(x, MEGame.ME3); if (tpmi == null) { return(x); } return(tpmi.modname); }).ToList(); string dlcs = string.Join(@"\n - ", dlcModList); StarterKitOptions sko = new StarterKitOptions { ModName = M3L.GetString(M3L.string_guiCompatibilityPack), ModDescription = M3L.GetString(M3L.string_interp_compatPackModDescription, dlcs, DateTime.Now.ToString()), ModDeveloper = App.AppVersionHR, ModDLCFolderName = UI_MOD_NAME, ModGame = MEGame.ME3, ModInternalName = @"UI Mod Compatibility Pack", ModInternalTLKID = 1420400890, ModMountFlag = EMountFileFlag.ME3_SPOnly_NoSaveFileDependency, ModMountPriority = 31050, ModURL = null, ModModuleNumber = 0 }; Mod generatedMod = null; void modGenerated(Mod mod) { generatedMod = mod; lock (modGeneratedSignaler) { Monitor.Pulse(modGeneratedSignaler); } } void skUicallback(string text) { ActionSubstring = text; } StarterKitGeneratorWindow.CreateStarterKitMod(sko, skUicallback, modGenerated); lock (modGeneratedSignaler) { Monitor.Wait(modGeneratedSignaler); } //Mod has been generated. string outputPath = Path.Combine(generatedMod.ModPath, @"DLC_MOD_" + UI_MOD_NAME, @"CookedPCConsole"); ActionString = M3L.GetString(M3L.string_preparingUiLibrary); ActionSubstring = M3L.GetString(M3L.string_decompressingData); int done = 0; CaseInsensitiveDictionary <byte[]> uiLibraryData = new CaseInsensitiveDictionary <byte[]>(); var filesToDecompress = uiArchive.ArchiveFileData.Where(x => x.FileName.EndsWith(@".swf")).ToList(); foreach (var f in filesToDecompress) { MemoryStream decompressedStream = new MemoryStream(); uiArchive.ExtractFile(f.Index, decompressedStream); string fname = f.FileName.Substring(f.FileName.IndexOf('\\') + 1); fname = fname.Substring(0, fname.IndexOf(@".swf", StringComparison.InvariantCultureIgnoreCase)); uiLibraryData[fname] = decompressedStream.ToArray(); done++; Percent = getPercent(done, filesToDecompress.Count); } ActionString = M3L.GetString(M3L.string_patchingFiles); Percent = 0; done = 0; string singlesuffix = M3L.GetString(M3L.string_singularFile); string pluralsuffix = M3L.GetString(M3L.string_pluralFiles); ActionSubstring = M3L.GetString(M3L.string_interp_patchedXY, done.ToString(), done == 1 ? singlesuffix : pluralsuffix); foreach (var file in filesToBePatched) { var package = MEPackageHandler.OpenMEPackage(file); var guiExports = package.Exports.Where(x => !x.IsDefaultObject && x.ClassName == @"GFxMovieInfo").ToList(); if (guiExports.Count > 0) { //potential item needing replacement //Check GUI library to see if we have anything. foreach (var export in guiExports) { if (uiLibraryData.TryGetValue(export.GetFullPath, out var newData)) { //Patching this export. var exportProperties = export.GetProperties(); var rawData = exportProperties.GetProp <ArrayProperty <ByteProperty> >(@"RawData"); rawData.Clear(); rawData.AddRange(newData.Select(x => new ByteProperty(x))); //This will be terribly slow. Need to port over new ME3Exp binary data handler export.WriteProperties(exportProperties); } else { Debug.WriteLine(@"Not patching gui export, file not in library: " + export.GetFullPath); } } var outpath = Path.Combine(outputPath, Path.GetFileName(package.FilePath)); if (package.IsModified) { Log.Information(@"Saving patched package to " + outpath); package.save(outpath); done++; ActionSubstring = M3L.GetString(M3L.string_interp_patchedXY, done.ToString(), done == 1 ? singlesuffix : pluralsuffix); } else { done++; Log.Information(@"File was patched but data did not change! " + outpath); } Percent = getPercent(done, filesToBePatched.Count); } else { throw new Exception($@"Error: {Path.GetFileName(file)} doesn't contain GUI exports! This shouldn't have been possible."); } } return(generatedMod); }
public void ApplyChanges(GameTarget gameTarget, CaseInsensitiveDictionary <string> loadedFiles, Mod associatedMod, ref int numMergesCompleted, int numTotalMerges, Action <int, int, string, string> mergeProgressDelegate = null) { List <string> targetFiles = new List <string>(); if (ApplyToAllLocalizations) { var targetnameBase = Path.GetFileNameWithoutExtension(FileName); var targetExtension = Path.GetExtension(FileName); var localizations = StarterKitGeneratorWindow.GetLanguagesForGame(associatedMod.Game); // Ensure end name is not present on base foreach (var l in localizations) { if (targetnameBase.EndsWith($@"_{l.filecode}", StringComparison.InvariantCultureIgnoreCase)) { targetnameBase = targetnameBase.Substring(0, targetnameBase.Length - (l.filecode.Length + 1)); //_FileCode } } foreach (var l in localizations) { var targetname = $@"{targetnameBase}_{l.filecode}{targetExtension}"; if (loadedFiles.TryGetValue(targetname, out var fullpath)) { targetFiles.Add(fullpath); } else { Log.Warning($@"File not found in game: {targetname}, skipping..."); numMergesCompleted++; mergeProgressDelegate?.Invoke(numMergesCompleted, numMergesCompleted, null, null); } } } else { if (loadedFiles.TryGetValue(FileName, out var fullpath)) { targetFiles.Add(fullpath); } else { Log.Warning($@"File not found in game: {FileName}, skipping..."); numMergesCompleted++; mergeProgressDelegate?.Invoke(numMergesCompleted, numMergesCompleted, null, null); } } MergeAssetCache1 mac = new MergeAssetCache1(); foreach (var f in targetFiles) { Log.Information($@"Opening package {f}"); #if DEBUG Stopwatch sw = Stopwatch.StartNew(); #endif // Open as memorystream as we need to hash this file for tracking using MemoryStream ms = new MemoryStream(File.ReadAllBytes(f)); var existingMD5 = Utilities.CalculateMD5(ms); var package = MEPackageHandler.OpenMEPackageFromStream(ms, f); #if DEBUG Debug.WriteLine($@"Opening package {f} took {sw.ElapsedMilliseconds} ms"); #endif foreach (var pc in MergeChanges) { pc.ApplyChanges(package, mac, associatedMod, gameTarget); } var track = package.IsModified; if (package.IsModified) { Log.Information($@"Saving package {package.FilePath}"); #if DEBUG sw.Restart(); #endif package.Save(savePath: f, compress: true); #if DEBUG Debug.WriteLine($@"Saving package {f} took {sw.ElapsedMilliseconds} ms"); #endif } else { Log.Information($@"Package {package.FilePath} was not modified. This change is likely already installed, not saving package"); } numMergesCompleted++; mergeProgressDelegate?.Invoke(numMergesCompleted, numTotalMerges, track ? existingMD5 : null, track ? f : null); } }
/// <summary> /// Checks mods for updates. ForceUpdateCheck will force the mod to validate against the server (essentially repair mode). It is not used for rate limiting! /// </summary> /// <param name="modsToCheck">Mods to have server send information about</param> /// <param name="forceUpdateCheck">Force update check regardless of version</param> /// <returns></returns> public static List <ModUpdateInfo> CheckForModUpdates(List <Mod> modsToCheck, bool forceUpdateCheck, Action <string> updateStatusCallback = null) { string updateFinalRequest = UpdaterServiceManifestEndpoint; bool first = true; foreach (var mod in modsToCheck) { if (mod.ModModMakerID > 0) { //Modmaker style if (first) { updateFinalRequest += "?"; first = false; } else { updateFinalRequest += "&"; } updateFinalRequest += "modmakerupdatecode[]=" + mod.ModModMakerID; } else if (mod.ModClassicUpdateCode > 0) { //Classic style if (first) { updateFinalRequest += "?"; first = false; } else { updateFinalRequest += "&"; } updateFinalRequest += "classicupdatecode[]=" + mod.ModClassicUpdateCode; } else if (mod.NexusModID > 0 && mod.NexusUpdateCheck) { //Nexus style if (first) { updateFinalRequest += "?"; first = false; } else { updateFinalRequest += "&"; } updateFinalRequest += "nexusupdatecode[]=" + mod.Game.ToString().Substring(2) + "-" + mod.NexusModID; } //else if (mod.NexusModID > 0) //{ // //Classic style // if (first) // { // updateFinalRequest += "?"; // first = false; // } // else // { // updateFinalRequest += "&"; // } // updateFinalRequest += "nexusupdatecode[]=" + mod.ModClassicUpdateCode; //} } using var wc = new System.Net.WebClient(); try { Debug.WriteLine(updateFinalRequest); string updatexml = wc.DownloadStringAwareOfEncoding(updateFinalRequest); XElement rootElement = XElement.Parse(updatexml); #region classic mods var modUpdateInfos = new List <ModUpdateInfo>(); var classicUpdateInfos = (from e in rootElement.Elements("mod") select new ModUpdateInfo { changelog = (string)e.Attribute("changelog"), versionstr = (string)e.Attribute("version"), updatecode = (int)e.Attribute("updatecode"), serverfolder = (string)e.Attribute("folder"), sourceFiles = (from f in e.Elements("sourcefile") select new SourceFile { lzmahash = (string)f.Attribute("lzmahash"), hash = (string)f.Attribute("hash"), size = (int)f.Attribute("size"), lzmasize = (int)f.Attribute("lzmasize"), relativefilepath = f.Value, timestamp = (Int64?)f.Attribute("timestamp") ?? (Int64)0 }).ToList(), blacklistedFiles = e.Elements("blacklistedfile").Select(x => x.Value).ToList() }).ToList(); // CALCULATE UPDATE DELTA CaseInsensitiveDictionary <USFileInfo> hashMap = new CaseInsensitiveDictionary <USFileInfo>(); //Used to rename files foreach (var modUpdateInfo in classicUpdateInfos) { modUpdateInfo.ResolveVersionVar(); //Calculate update information var matchingMod = modsToCheck.FirstOrDefault(x => x.ModClassicUpdateCode == modUpdateInfo.updatecode); if (matchingMod != null && (forceUpdateCheck || matchingMod.ParsedModVersion < modUpdateInfo.version)) { // The following line is left so we know that it was at one point considered implemented. // This prevents updating copies of the same mod in the library. Cause it's just kind of a bandwidth waste. //modsToCheck.Remove(matchingMod); //This makes it so we don't feed in multiple same-mods. For example, nexus check on 3 Project Variety downloads modUpdateInfo.mod = matchingMod; modUpdateInfo.SetLocalizedInfo(); string modBasepath = matchingMod.ModPath; double i = 0; List <string> references = null; try { references = matchingMod.GetAllRelativeReferences(true); } catch { // There's an error. Underlying disk state may have changed since we originally loaded the mod } if (references == null || !matchingMod.ValidMod) { // The mod failed to load. We should just index everything the // references will not be dfully parsed. var localFiles = Directory.GetFiles(matchingMod.ModPath, "*", SearchOption.AllDirectories); references = localFiles.Select(x => x.Substring(matchingMod.ModPath.Length + 1)).ToList(); } int total = references.Count; // Index existing files foreach (var v in references) { updateStatusCallback?.Invoke( $"Indexing {modUpdateInfo.mod.ModName} for updates {(int)(i * 100 / total)}%"); i++; var fpath = Path.Combine(matchingMod.ModPath, v); if (fpath.RepresentsPackageFilePath()) { // We need to make sure it's decompressed var qPackage = MEPackageHandler.QuickOpenMEPackage(fpath); if (qPackage.IsCompressed) { CLog.Information( $" >> Decompressing compressed package for update comparison check: {fpath}", Settings.LogModUpdater); qPackage = MEPackageHandler.OpenMEPackage(fpath); MemoryStream tStream = new MemoryStream(); tStream = qPackage.SaveToStream(false); hashMap[v] = new USFileInfo() { MD5 = Utilities.CalculateMD5(tStream), CompressedMD5 = Utilities.CalculateMD5(fpath), Filesize = tStream.Length, RelativeFilepath = v }; continue; } } hashMap[v] = new USFileInfo() { MD5 = Utilities.CalculateMD5(fpath), Filesize = new FileInfo(fpath).Length, RelativeFilepath = v }; } i = 0; total = modUpdateInfo.sourceFiles.Count; foreach (var serverFile in modUpdateInfo.sourceFiles) { Log.Information($@"Checking {serverFile.relativefilepath} for update applicability"); updateStatusCallback?.Invoke( $"Calculating update delta for {modUpdateInfo.mod.ModName} {(int)(i * 100 / total)}%"); i++; bool calculatedOp = false; if (hashMap.TryGetValue(serverFile.relativefilepath, out var indexInfo)) { if (indexInfo.MD5 == serverFile.hash) { CLog.Information(@" >> File is up to date", Settings.LogModUpdater); calculatedOp = true; } else if (indexInfo.CompressedMD5 != null && indexInfo.CompressedMD5 == serverFile.hash) { CLog.Information(@" >> Compressed package file is up to date", Settings.LogModUpdater); calculatedOp = true; } } if (!calculatedOp) { // File is missing or hash was wrong. We should try to map it to another existing file // to save bandwidth var existingFilesThatMatchServerHash = hashMap.Where(x => x.Value.MD5 == serverFile.hash || (x.Value.CompressedMD5 != null && x.Value.CompressedMD5 == serverFile.hash)) .ToList(); if (existingFilesThatMatchServerHash.Any()) { CLog.Information( $" >> Server file can be cloned from local file {existingFilesThatMatchServerHash[0].Value.RelativeFilepath} as it has same hash", Settings.LogModUpdater); modUpdateInfo.cloneOperations[serverFile] = existingFilesThatMatchServerHash[0] .Value; // Server file can be sourced from the value } else if (indexInfo == null) { // we don't have file hashed (new file) CLog.Information( $" >> Applicable for updates, File does not exist locally", Settings.LogModUpdater); modUpdateInfo.applicableUpdates.Add(serverFile); } else { // Existing file has wrong hash CLog.Information($" >> Applicable for updates, hash has changed", Settings.LogModUpdater); modUpdateInfo.applicableUpdates.Add(serverFile); } } } foreach (var blacklistedFile in modUpdateInfo.blacklistedFiles) { var blLocalFile = Path.Combine(modBasepath, blacklistedFile); if (File.Exists(blLocalFile)) { Log.Information(@"Blacklisted file marked for deletion: " + blLocalFile); modUpdateInfo.filesToDelete.Add(blLocalFile); } } // alphabetize files modUpdateInfo.applicableUpdates.Sort(x => x.relativefilepath); //Files to remove calculation var modFiles = Directory.GetFiles(modBasepath, "*", SearchOption.AllDirectories) .Select(x => x.Substring(modBasepath.Length + 1)).ToList(); var additionalFilesToDelete = modFiles.Except( modUpdateInfo.sourceFiles.Select(x => x.relativefilepath), StringComparer.InvariantCultureIgnoreCase).Distinct().ToList(); modUpdateInfo.filesToDelete.AddRange( additionalFilesToDelete); //Todo: Add security check here to prevent malicious modUpdateInfo.TotalBytesToDownload = modUpdateInfo.applicableUpdates.Sum(x => x.lzmasize); } } modUpdateInfos.AddRange(classicUpdateInfos); #endregion #region modmaker mods var modmakerModUpdateInfos = (from e in rootElement.Elements("modmakermod") select new ModMakerModUpdateInfo { ModMakerId = (int)e.Attribute("id"), versionstr = (string)e.Attribute("version"), PublishDate = DateTime.ParseExact((string)e.Attribute("publishdate"), "yyyy-MM-dd", CultureInfo.InvariantCulture), changelog = (string)e.Attribute("changelog") }).ToList(); modUpdateInfos.AddRange(modmakerModUpdateInfos); #endregion #region Nexus Mod Third Party var nexusModsUpdateInfo = (from e in rootElement.Elements("nexusmod") select new NexusModUpdateInfo { NexusModsId = (int)e.Attribute("id"), GameId = (int)e.Attribute("game"), versionstr = (string)e.Attribute("version"), UpdatedTime = DateTimeOffset.FromUnixTimeSeconds((long)e.Attribute("updated_timestamp")) .DateTime }).ToList(); modUpdateInfos.AddRange(nexusModsUpdateInfo); #endregion return(modUpdateInfos); } catch (Exception e) { Log.Error("Error checking for mod updates: " + App.FlattenException(e)); Crashes.TrackError(e, new Dictionary <string, string>() { { "Update check URL", updateFinalRequest } }); } return(null); }
public static void BuildBioPGlobal(GameTarget target) { M3MergeDLC.RemoveMergeDLC(target); var loadedFiles = MELoadedFiles.GetFilesLoadedInGame(target.Game, gameRootOverride: target.TargetPath); //var mergeFiles = loadedFiles.Where(x => // x.Key.StartsWith(@"BioH_") && x.Key.Contains(@"_DLC_MOD_") && x.Key.EndsWith(@".pcc") && !x.Key.Contains(@"_LOC_") && !x.Key.Contains(@"_Explore.")); Log.Information($@"SQMMERGE: Building BioP_Global"); var appearanceInfo = new CaseInsensitiveDictionary <List <SquadmateInfoSingle> >(); int appearanceId = 255; // starting int currentConditional = STARTING_OUTFIT_CONDITIONAL; // Scan squadmate merge files var sqmSupercedances = M3Directories.GetFileSupercedances(target, new[] { @".sqm" }); if (sqmSupercedances.TryGetValue(SQUADMATE_MERGE_MANIFEST_FILE, out var infoList)) { infoList.Reverse(); foreach (var dlc in infoList) { Log.Information($@"SQMMERGE: Processing {dlc}"); var jsonFile = Path.Combine(M3Directories.GetDLCPath(target), dlc, target.Game.CookedDirName(), SQUADMATE_MERGE_MANIFEST_FILE); var infoPackage = JsonConvert.DeserializeObject <SquadmateMergeInfo>(File.ReadAllText(jsonFile)); if (!infoPackage.Validate(dlc, target, loadedFiles)) { continue; // skip this } // Enumerate all outfits listed for a single squadmate foreach (var outfit in infoPackage.Outfits) { List <SquadmateInfoSingle> list; // See if we already have an outfit list for this squadmate, maybe from another mod... if (!appearanceInfo.TryGetValue(outfit.HenchName, out list)) { list = new List <SquadmateInfoSingle>(); appearanceInfo[outfit.HenchName] = list; } outfit.ConditionalIndex = currentConditional++; // This is always incremented, so it might appear out of order in game files depending on how mod order is processed, that should be okay though. outfit.AppearanceId = appearanceId++; // may need adjusted outfit.DLCName = dlc; list.Add(outfit); Log.Information($@"SQMMERGE: ConditionalIndex for {outfit.HenchName} appearanceid {outfit.AppearanceId}: {outfit.ConditionalIndex}"); } } } if (appearanceInfo.Any()) { var biopGlobal = MEPackageHandler.OpenMEPackage(loadedFiles[@"BioP_Global.pcc"]); var lsk = biopGlobal.Exports.FirstOrDefault(x => x.ClassName == @"LevelStreamingKismet"); var persistentLevel = biopGlobal.FindExport(@"TheWorld.PersistentLevel"); // Clone LevelStreamingKismets foreach (var sqm in appearanceInfo.Values) { foreach (var outfit in sqm) { var fName = outfit.HenchPackage; var newLSK = EntryCloner.CloneEntry(lsk); newLSK.WriteProperty(new NameProperty(fName, @"PackageName")); if (target.Game.IsGame3()) { // Game 3 has _Explore files too fName += @"_Explore"; newLSK = EntryCloner.CloneEntry(lsk); newLSK.WriteProperty(new NameProperty(fName, @"PackageName")); } } } // Update BioWorldInfo // Doesn't have consistent number so we can't find it by instanced full path var bioWorldInfo = biopGlobal.Exports.FirstOrDefault(x => x.ClassName == @"BioWorldInfo"); var props = bioWorldInfo.GetProperties(); // Update Plot Streaming var plotStreaming = props.GetProp <ArrayProperty <StructProperty> >(@"PlotStreaming"); foreach (var sqm in appearanceInfo.Values) { foreach (var outfit in sqm) { // find item to add to buildPlotElementObject(plotStreaming, outfit, target.Game, false); if (target.Game.IsGame3()) { buildPlotElementObject(plotStreaming, outfit, target.Game, true); } } } // Update StreamingLevels var streamingLevels = props.GetProp <ArrayProperty <ObjectProperty> >(@"StreamingLevels"); streamingLevels.ReplaceAll(biopGlobal.Exports.Where(x => x.ClassName == @"LevelStreamingKismet").Select(x => new ObjectProperty(x))); bioWorldInfo.WriteProperties(props); M3MergeDLC.GenerateMergeDLC(target, Guid.NewGuid()); // Save BioP_Global into DLC var cookedDir = Path.Combine(M3Directories.GetDLCPath(target), M3MergeDLC.MERGE_DLC_FOLDERNAME, target.Game.CookedDirName()); var outP = Path.Combine(cookedDir, @"BioP_Global.pcc"); biopGlobal.Save(outP); // Generate conditionals file if (target.Game.IsGame3()) { CNDFile cnd = new CNDFile(); cnd.ConditionalEntries = new List <CNDFile.ConditionalEntry>(); foreach (var sqm in appearanceInfo.Values) { foreach (var outfit in sqm) { var scText = $@"(plot.ints[{GetSquadmateOutfitInt(outfit.HenchName, target.Game)}] == i{outfit.MemberAppearanceValue})"; var compiled = ME3ConditionalsCompiler.Compile(scText); cnd.ConditionalEntries.Add(new CNDFile.ConditionalEntry() { Data = compiled, ID = outfit.ConditionalIndex }); } } cnd.ToFile(Path.Combine(cookedDir, $@"Conditionals{M3MergeDLC.MERGE_DLC_FOLDERNAME}.cnd")); } else if (target.Game.IsGame2()) { var startupF = Path.Combine(cookedDir, $@"Startup_{M3MergeDLC.MERGE_DLC_FOLDERNAME}.pcc"); var startup = MEPackageHandler.OpenMEPackageFromStream(Utilities.GetResourceStream( $@"MassEffectModManagerCore.modmanager.mergedlc.{target.Game}.Startup_{M3MergeDLC.MERGE_DLC_FOLDERNAME}.pcc")); var conditionalClass = startup.FindExport($@"PlotManager{M3MergeDLC.MERGE_DLC_FOLDERNAME}.BioAutoConditionals"); // Add Conditional Functions FileLib fl = new FileLib(startup); bool initialized = fl.Initialize(new RelativePackageCache() { RootPath = M3Directories.GetBioGamePath(target) }); if (!initialized) { throw new Exception( $@"FileLib for script update could not initialize, cannot install conditionals"); } var funcToClone = startup.FindExport($@"PlotManager{M3MergeDLC.MERGE_DLC_FOLDERNAME}.BioAutoConditionals.TemplateFunction"); foreach (var sqm in appearanceInfo.Values) { foreach (var outfit in sqm) { var func = EntryCloner.CloneEntry(funcToClone); func.ObjectName = $@"F{outfit.ConditionalIndex}"; func.indexValue = 0; var scText = new StreamReader(Utilities.GetResourceStream( $@"MassEffectModManagerCore.modmanager.squadmates.{target.Game}.HasOutfitOnConditional.txt")) .ReadToEnd(); scText = scText.Replace(@"%CONDITIONALNUM%", outfit.ConditionalIndex.ToString()); scText = scText.Replace(@"%SQUADMATEOUTFITPLOTINT%", outfit.AppearanceId.ToString()); scText = scText.Replace(@"%OUTFITINDEX%", outfit.MemberAppearanceValue.ToString()); (_, MessageLog log) = UnrealScriptCompiler.CompileFunction(func, scText, fl); if (log.AllErrors.Any()) { Log.Error($@"Error compiling function {func.InstancedFullPath}:"); foreach (var l in log.AllErrors) { Log.Error(l.Message); } throw new Exception(M3L.GetString(M3L.string_interp_errorCompilingConditionalFunction, func, string.Join('\n', log.AllErrors.Select(x => x.Message)))); } } } // Relink the conditionals chain UClass uc = ObjectBinary.From <UClass>(conditionalClass); uc.UpdateLocalFunctions(); uc.UpdateChildrenChain(); conditionalClass.WriteBinary(uc); startup.Save(startupF); } // Add startup package, member appearances if (target.Game.IsGame2()) { var bioEngine = Path.Combine(cookedDir, @"BIOEngine.ini"); var ini = DuplicatingIni.LoadIni(bioEngine); var startupSection = ini.GetOrAddSection(@"Engine.StartupPackages"); startupSection.Entries.Add(new DuplicatingIni.IniEntry(@"+DLCStartupPackage", $@"Startup_{M3MergeDLC.MERGE_DLC_FOLDERNAME}")); startupSection.Entries.Add(new DuplicatingIni.IniEntry(@"+Package", $@"PlotManager{M3MergeDLC.MERGE_DLC_FOLDERNAME}")); ini.WriteToFile(bioEngine); } else if (target.Game.IsGame3()) { var mergeCoalFile = Path.Combine(M3Directories.GetDLCPath(target), M3MergeDLC.MERGE_DLC_FOLDERNAME, target.Game.CookedDirName(), $@"Default_{M3MergeDLC.MERGE_DLC_FOLDERNAME}.bin"); var mergeCoal = CoalescedConverter.DecompileGame3ToMemory(new MemoryStream(File.ReadAllBytes(mergeCoalFile))); // Member appearances var bioUiDoc = XDocument.Parse(mergeCoal[@"BIOUI.xml"]); foreach (var sqm in appearanceInfo.Values) { foreach (var outfit in sqm) { var entry = new Game3CoalescedValueEntry() { Section = @"sfxgame.sfxguidata_teamselect", Name = @"selectappearances", Type = 3, Value = StringStructParser.BuildCommaSeparatedSplitValueList(outfit.ToPropertyDictionary(), @"AvailableImage", @"HighlightImage", @"DeadImage", @"SilhouetteImage") }; Game3CoalescedHelper.AddArrayEntry(bioUiDoc, entry); } } mergeCoal[@"BIOUI.xml"] = bioUiDoc.ToString(); // Dynamic load mapping var bioEngineDoc = XDocument.Parse(mergeCoal[@"BIOEngine.xml"]); foreach (var sqm in appearanceInfo.Values) { foreach (var outfit in sqm) { // // * <Section name="sfxgame.sfxengine"> // <Property name="dynamicloadmapping"> // <Value type="3">(ObjectName="BIOG_GesturesConfigDLC.RuntimeData",SeekFreePackageName="GesturesConfigDLC")</Value> var entry = new Game3CoalescedValueEntry() { Section = @"sfxgame.sfxengine", Name = @"dynamicloadmapping", Type = 3 }; entry.Values.Add($"(ObjectName=\"{outfit.AvailableImage}\",SeekFreePackageName=\"SFXHenchImages_{outfit.DLCName}\")"); // do not localize entry.Values.Add($"(ObjectName=\"{outfit.HighlightImage}\",SeekFreePackageName=\"SFXHenchImages_{outfit.DLCName}\")"); // do not localize Game3CoalescedHelper.AddArrayEntry(bioEngineDoc, entry); } } mergeCoal[@"BIOEngine.xml"] = bioEngineDoc.ToString(); CoalescedConverter.CompileFromMemory(mergeCoal).WriteToFile(mergeCoalFile); } } }
public async Task RunAsync() { var lineWithCommand = new Regex($"^{_context.CommandPrefix} (\\S+)(?: (\\S+))*$"); using (var stream = new StreamReader(new MemoryStream(Encoding.UTF8.GetBytes(_context.InputFile)))) { while (!stream.EndOfStream) { var line = await stream.ReadLineAsync(); if (line == null) { continue; } var commandCheck = lineWithCommand.Match(line); if (commandCheck.Success) { var groupEnumerator = commandCheck.Groups.GetEnumerator(); groupEnumerator.MoveNext(); groupEnumerator.MoveNext(); var group = groupEnumerator.Current as Group; if (group == null) { continue; } var command = group.Value; if (command == null) { continue; } if (!_instructions.TryGetValue(command, out var instruction)) { continue; } var parameters = new List <string>(); while (groupEnumerator.MoveNext()) { var value = (groupEnumerator.Current as Group)?.Value; if (!string.IsNullOrWhiteSpace(value)) { parameters.Add(value ?? throw new InvalidOperationException()); } } if (!_activeInstructions.ContainsKey(instruction.Command)) { _activeInstructions.Add(instruction.Command, instruction); } await instruction.InitializeInstructionAsync(_context, parameters.ToArray()); } else { foreach (var inst in _activeInstructions.Values) { await inst.HandleLineAsync(line); } } } } }