Example #1
0
        public Patch(string pattern, string replacement, PatchMode mode = PatchMode.Any, int count = 0)
        {
            this.Mode = mode;

            this.Pattern = pattern;
            this.Replacement = replacement;

            this.ApplyTargetCount = count;
        }
Example #2
0
        // -------------------------------------------------------------------
        // Constructor
        // -------------------------------------------------------------------
        public Patch(string pattern, string replacement, PatchMode mode = PatchMode.Any, int count = 0, string[] fileFilters = null)
        {
            this.Mode = mode;

            this.Pattern = pattern;
            this.Replacement = replacement;

            if (fileFilters != null)
            {
                this.FileFilters = new List<string>(fileFilters);
            }
        }
Example #3
0
        public static AssetInjector <IAssetDataForImage, IAssetDataForImage> injectTileInto(this Texture2D t, string assetName, Range targetTileIndex, Range sourceTileIndex, int tileWidth = 16, int tileHeight = 16, PatchMode mode = PatchMode.Replace)
        {
            Func <IAssetDataForImage, IAssetDataForImage> merger = new Func <IAssetDataForImage, IAssetDataForImage>(delegate(IAssetDataForImage asset)
            {
                for (int i = 0; i < sourceTileIndex.length; i++)
                {
                    Rectangle source = Game1.getSourceRectForStandardTileSheet(t, sourceTileIndex[i], tileWidth, tileHeight);
                    Rectangle target = Game1.getSourceRectForStandardTileSheet(asset.Data, targetTileIndex[i], tileWidth, tileHeight);
                    asset.PatchImage(t, source, target, mode);
                }
                return(asset);
            });

            return(new AssetInjector <IAssetDataForImage, IAssetDataForImage>(assetName, merger).injectEdit());
        }
Example #4
0
 public PatchExpression(string pattern, string replacement, PatchMode mode = PatchMode.Any, int count = 0, RegexOptions options = RegexOptions.None)
     : base(pattern, replacement, mode, count)
 {
     this.Expression = new Regex(pattern, options);
     this.TargetGroup = 1;
 }
Example #5
0
 /*********
 ** Public methods
 *********/
 /// <summary>Construct an instance.</summary>
 /// <param name="logName">A unique name for this patch shown in log messages.</param>
 /// <param name="contentPack">The content pack which requested the patch.</param>
 /// <param name="assetName">The normalised asset name to intercept.</param>
 /// <param name="conditions">The conditions which determine whether this patch should be applied.</param>
 /// <param name="fromAsset">The asset key to load from the content pack instead.</param>
 /// <param name="fromArea">The sprite area from which to read an image.</param>
 /// <param name="toArea">The sprite area to overwrite.</param>
 /// <param name="patchMode">Indicates how the image should be patched.</param>
 /// <param name="monitor">Encapsulates monitoring and logging.</param>
 /// <param name="normaliseAssetName">Normalise an asset name.</param>
 public EditImagePatch(string logName, ManagedContentPack contentPack, ITokenString assetName, IEnumerable <Condition> conditions, ITokenString fromAsset, Rectangle fromArea, Rectangle toArea, PatchMode patchMode, IMonitor monitor, Func <string, string> normaliseAssetName)
     : base(logName, PatchType.EditImage, contentPack, assetName, conditions, normaliseAssetName, fromAsset: fromAsset)
 {
     this.FromArea  = fromArea != Rectangle.Empty ? fromArea : null as Rectangle?;
     this.ToArea    = toArea != Rectangle.Empty ? toArea : null as Rectangle?;
     this.PatchMode = patchMode;
     this.Monitor   = monitor;
 }
 public static bool PatchImage(IAssetDataForImage __instance, Texture2D source, Rectangle?sourceArea, Rectangle?targetArea, PatchMode patchMode)
 {
     if (__instance.Data is Texture2DWrapper wrapper)
     {
         if (sourceArea.HasValue)
         {
             source = Texture2DWrapper.Crop(source, sourceArea.Value);
         }
         source = Texture2DWrapper.ScaleUp(source, wrapper.Scale);
         if (targetArea.HasValue)
         {
             targetArea = Texture2DWrapper.MultiplyRect(targetArea.Value, wrapper.Scale);
         }
         PropertyInfo Data = __instance.GetType().GetProperty("Data");
         Data.SetValue(__instance, wrapper.Wrapped);
         __instance.PatchImage(source, null, targetArea, patchMode);
         Data.SetValue(__instance, wrapper);
         return(false);
     }
     return(true);
 }
Example #7
0
        /// <summary>
        /// Initializes a new instance of the <see cref="SInject"/> class.
        /// </summary>
        /// <param name="assemblyLocation">The assembly location.</param>
        /// <param name="snkCertificatePath">The location of snk certificate</param>
        /// <param name="mode">The mode.</param>
        /// <exception cref="System.Exception"></exception>
        public SInjection(string assemblyLocation, string snkCertificatePath = null, PatchMode mode = PatchMode.Release)
        {
            Log.Configure("SInject");
            _assemblyLocation = assemblyLocation;
            _snkCertificatePath = snkCertificatePath;
            _mode = mode;
            Log.Write("Assembly being Injected {0}", assemblyLocation);

            //  if (IsAssemblyInExcludedList(Path.GetFileName(assemblyLocation))) return;

            if (!File.Exists(assemblyLocation))
                throw new Exception(string.Format("Assembly at location {0} doesn't exist", assemblyLocation));

            _assemblyDefinition = AssemblyDefinition.ReadAssembly(assemblyLocation,
                                                                  GetReaderParameters());
        }
Example #8
0
        /// <summary>Load one patch from a content pack's <c>content.json</c> file.</summary>
        /// <param name="pack">The content pack being loaded.</param>
        /// <param name="entry">The change to load.</param>
        /// <param name="tokenContext">The tokens available for this content pack.</param>
        /// <param name="migrator">The migrator which validates and migrates content pack data.</param>
        /// <param name="logSkip">The callback to invoke with the error reason if loading it fails.</param>
        private bool LoadPatch(ManagedContentPack pack, PatchConfig entry, IContext tokenContext, IMigration migrator, Action <string> logSkip)
        {
            bool TrackSkip(string reason, bool warn = true)
            {
                reason = reason.TrimEnd('.', ' ');
                this.PatchManager.AddPermanentlyDisabled(new DisabledPatch(entry.LogName, entry.Action, entry.Target, pack, reason));
                if (warn)
                {
                    logSkip(reason + '.');
                }
                return(false);
            }

            try
            {
                // normalise patch fields
                if (entry.When == null)
                {
                    entry.When = new InvariantDictionary <string>();
                }

                // parse action
                if (!Enum.TryParse(entry.Action, true, out PatchType action))
                {
                    return(TrackSkip(string.IsNullOrWhiteSpace(entry.Action)
                        ? $"must set the {nameof(PatchConfig.Action)} field"
                        : $"invalid {nameof(PatchConfig.Action)} value '{entry.Action}', expected one of: {string.Join(", ", Enum.GetNames(typeof(PatchType)))}"
                                     ));
                }

                // parse target asset
                ITokenString assetName;
                {
                    if (string.IsNullOrWhiteSpace(entry.Target))
                    {
                        return(TrackSkip($"must set the {nameof(PatchConfig.Target)} field"));
                    }
                    if (!this.TryParseStringTokens(entry.Target, tokenContext, migrator, out string error, out assetName))
                    {
                        return(TrackSkip($"the {nameof(PatchConfig.Target)} is invalid: {error}"));
                    }
                }

                // parse 'enabled'
                bool enabled = true;
                {
                    if (entry.Enabled != null && !this.TryParseEnabled(entry.Enabled, tokenContext, migrator, out string error, out enabled))
                    {
                        return(TrackSkip($"invalid {nameof(PatchConfig.Enabled)} value '{entry.Enabled}': {error}"));
                    }
                }

                // parse conditions
                IList <Condition> conditions;
                {
                    if (!this.TryParseConditions(entry.When, tokenContext, migrator, out conditions, out string error))
                    {
                        return(TrackSkip($"the {nameof(PatchConfig.When)} field is invalid: {error}"));
                    }
                }

                // get patch instance
                IPatch patch;
                switch (action)
                {
                // load asset
                case PatchType.Load:
                {
                    // init patch
                    if (!this.TryPrepareLocalAsset(pack, entry.FromFile, tokenContext, migrator, out string error, out ITokenString fromAsset))
                    {
                        return(TrackSkip(error));
                    }
                    patch = new LoadPatch(entry.LogName, pack, assetName, conditions, fromAsset, this.Helper.Content.NormaliseAssetName);
                }
                break;

                // edit data
                case PatchType.EditData:
                {
                    // validate
                    if (entry.Entries == null && entry.Fields == null && entry.MoveEntries == null)
                    {
                        return(TrackSkip($"one of {nameof(PatchConfig.Entries)}, {nameof(PatchConfig.Fields)}, or {nameof(PatchConfig.MoveEntries)} must be specified for an '{action}' change"));
                    }

                    // parse entries
                    List <EditDataPatchRecord> entries = new List <EditDataPatchRecord>();
                    if (entry.Entries != null)
                    {
                        foreach (KeyValuePair <string, JToken> pair in entry.Entries)
                        {
                            if (!this.TryParseStringTokens(pair.Key, tokenContext, migrator, out string keyError, out ITokenString key))
                            {
                                return(TrackSkip($"{nameof(PatchConfig.Entries)} > '{key}' key is invalid: {keyError}"));
                            }
                            if (!this.TryParseJsonTokens(pair.Value, tokenContext, migrator, out string error, out TokenisableJToken value))
                            {
                                return(TrackSkip($"{nameof(PatchConfig.Entries)} > '{key}' value is invalid: {error}"));
                            }

                            entries.Add(new EditDataPatchRecord(key, value));
                        }
                    }

                    // parse fields
                    List <EditDataPatchField> fields = new List <EditDataPatchField>();
                    if (entry.Fields != null)
                    {
                        foreach (KeyValuePair <string, IDictionary <string, JToken> > recordPair in entry.Fields)
                        {
                            // parse entry key
                            if (!this.TryParseStringTokens(recordPair.Key, tokenContext, migrator, out string keyError, out ITokenString key))
                            {
                                return(TrackSkip($"{nameof(PatchConfig.Fields)} > entry {recordPair.Key} is invalid: {keyError}"));
                            }

                            // parse fields
                            foreach (var fieldPair in recordPair.Value)
                            {
                                // parse field key
                                if (!this.TryParseStringTokens(fieldPair.Key, tokenContext, migrator, out string fieldError, out ITokenString fieldKey))
                                {
                                    return(TrackSkip($"{nameof(PatchConfig.Fields)} > entry {recordPair.Key} > field {fieldPair.Key} key is invalid: {fieldError}"));
                                }

                                // parse value
                                if (!this.TryParseJsonTokens(fieldPair.Value, tokenContext, migrator, out string valueError, out TokenisableJToken value))
                                {
                                    return(TrackSkip($"{nameof(PatchConfig.Fields)} > entry {recordPair.Key} > field {fieldKey} is invalid: {valueError}"));
                                }
                                if (value?.Value is JValue jValue && jValue.Value <string>()?.Contains("/") == true)
                                {
                                    return(TrackSkip($"{nameof(PatchConfig.Fields)} > entry {recordPair.Key} > field {fieldKey} is invalid: value can't contain field delimiter character '/'"));
                                }

                                fields.Add(new EditDataPatchField(key, fieldKey, value));
                            }
                        }
                    }

                    // parse move entries
                    List <EditDataPatchMoveRecord> moveEntries = new List <EditDataPatchMoveRecord>();
                    if (entry.MoveEntries != null)
                    {
                        foreach (PatchMoveEntryConfig moveEntry in entry.MoveEntries)
                        {
                            // validate
                            string[] targets = new[] { moveEntry.BeforeID, moveEntry.AfterID, moveEntry.ToPosition };
                            if (string.IsNullOrWhiteSpace(moveEntry.ID))
                            {
                                return(TrackSkip($"{nameof(PatchConfig.MoveEntries)} > move entry is invalid: must specify an {nameof(PatchMoveEntryConfig.ID)} value"));
                            }
                            if (targets.All(string.IsNullOrWhiteSpace))
                            {
                                return(TrackSkip($"{nameof(PatchConfig.MoveEntries)} > entry '{moveEntry.ID}' is invalid: must specify one of {nameof(PatchMoveEntryConfig.ToPosition)}, {nameof(PatchMoveEntryConfig.BeforeID)}, or {nameof(PatchMoveEntryConfig.AfterID)}"));
                            }
                            if (targets.Count(p => !string.IsNullOrWhiteSpace(p)) > 1)
                            {
                                return(TrackSkip($"{nameof(PatchConfig.MoveEntries)} > entry '{moveEntry.ID}' is invalid: must specify only one of {nameof(PatchMoveEntryConfig.ToPosition)}, {nameof(PatchMoveEntryConfig.BeforeID)}, and {nameof(PatchMoveEntryConfig.AfterID)}"));
                            }

                            // parse IDs
                            if (!this.TryParseStringTokens(moveEntry.ID, tokenContext, migrator, out string idError, out ITokenString moveId))
                            {
                                return(TrackSkip($"{nameof(PatchConfig.MoveEntries)} > entry '{moveEntry.ID}' > {nameof(PatchMoveEntryConfig.ID)} is invalid: {idError}"));
                            }
                            if (!this.TryParseStringTokens(moveEntry.BeforeID, tokenContext, migrator, out string beforeIdError, out ITokenString beforeId))
                            {
                                return(TrackSkip($"{nameof(PatchConfig.MoveEntries)} > entry '{moveEntry.ID}' > {nameof(PatchMoveEntryConfig.BeforeID)} is invalid: {beforeIdError}"));
                            }
                            if (!this.TryParseStringTokens(moveEntry.AfterID, tokenContext, migrator, out string afterIdError, out ITokenString afterId))
                            {
                                return(TrackSkip($"{nameof(PatchConfig.MoveEntries)} > entry '{moveEntry.ID}' > {nameof(PatchMoveEntryConfig.AfterID)} is invalid: {afterIdError}"));
                            }

                            // parse position
                            MoveEntryPosition toPosition = MoveEntryPosition.None;
                            if (!string.IsNullOrWhiteSpace(moveEntry.ToPosition) && (!Enum.TryParse(moveEntry.ToPosition, true, out toPosition) || toPosition == MoveEntryPosition.None))
                            {
                                return(TrackSkip($"{nameof(PatchConfig.MoveEntries)} > entry '{moveEntry.ID}' > {nameof(PatchMoveEntryConfig.ToPosition)} is invalid: must be one of {nameof(MoveEntryPosition.Bottom)} or {nameof(MoveEntryPosition.Top)}"));
                            }

                            // create move entry
                            moveEntries.Add(new EditDataPatchMoveRecord(moveId, beforeId, afterId, toPosition));
                        }
                    }

                    // save
                    patch = new EditDataPatch(entry.LogName, pack, assetName, conditions, entries, fields, moveEntries, this.Monitor, this.Helper.Content.NormaliseAssetName);
                }
                break;

                // edit image
                case PatchType.EditImage:
                {
                    // read patch mode
                    PatchMode patchMode = PatchMode.Replace;
                    if (!string.IsNullOrWhiteSpace(entry.PatchMode) && !Enum.TryParse(entry.PatchMode, true, out patchMode))
                    {
                        return(TrackSkip($"the {nameof(PatchConfig.PatchMode)} is invalid. Expected one of these values: [{string.Join(", ", Enum.GetNames(typeof(PatchMode)))}]"));
                    }

                    // save
                    if (!this.TryPrepareLocalAsset(pack, entry.FromFile, tokenContext, migrator, out string error, out ITokenString fromAsset))
                    {
                        return(TrackSkip(error));
                    }
                    patch = new EditImagePatch(entry.LogName, pack, assetName, conditions, fromAsset, entry.FromArea, entry.ToArea, patchMode, this.Monitor, this.Helper.Content.NormaliseAssetName);
                }
                break;

                // edit map
                case PatchType.EditMap:
                {
                    // read map asset
                    if (!this.TryPrepareLocalAsset(pack, entry.FromFile, tokenContext, migrator, out string error, out ITokenString fromAsset))
                    {
                        return(TrackSkip(error));
                    }

                    // validate
                    if (entry.ToArea == Rectangle.Empty)
                    {
                        return(TrackSkip($"must specify {nameof(entry.ToArea)} (use \"Action\": \"Load\" if you want to replace the whole map file)"));
                    }

                    // save
                    patch = new EditMapPatch(entry.LogName, pack, assetName, conditions, fromAsset, entry.FromArea, entry.ToArea, this.Monitor, this.Helper.Content.NormaliseAssetName);
                }
                break;

                default:
                    return(TrackSkip($"unsupported patch type '{action}'"));
                }

                // skip if not enabled
                // note: we process the patch even if it's disabled, so any errors are caught by the modder instead of only failing after the patch is enabled.
                if (!enabled)
                {
                    return(TrackSkip($"{nameof(PatchConfig.Enabled)} is false", warn: false));
                }

                // save patch
                this.PatchManager.Add(patch);
                return(true);
            }
            catch (Exception ex)
            {
                return(TrackSkip($"error reading info. Technical details:\n{ex}"));
            }
        }
Example #9
0
        public static IEnumerable <CodeInstruction> PatchBySequence(IEnumerable <CodeInstruction> instructions, IEnumerable <CodeInstruction> targetSequence, IEnumerable <CodeInstruction> patchSequence, PatchMode patchMode = PatchMode.AFTER, CheckMode checkMode = CheckMode.ALWAYS, bool showDebugOutput = false)
        {
            List <CodeInstruction> Instructions = instructions.ToList(); //create new list to be modified and returned.

            CodeInstruction targetStart = targetSequence.ElementAt(0);
            int             targetSize  = targetSequence.Count();

            for (int i = 0; i < Instructions.Count; i++)                             //Check every Instruction in the given list if it is the correct Instruction set
            {
                bool targetSequenceStillFits = i + targetSize <= Instructions.Count; //calculate if target sequence fits in Instructions.

                if (targetSequenceStillFits)                                         //stop if not enough lines capable of fitting target sequence
                {
                    bool foundTargetSequence = true;

                    for (int x = 0; x < targetSize && foundTargetSequence; x++) //compare each element of the new sequence to the old to see if it is the same. stop for loop early if the targetsequence
                    {
                        foundTargetSequence = Instructions[i + x].opcode.Equals(targetSequence.ElementAt(x).opcode);
                        if (checkMode != CheckMode.NEVER)//if specified checking params are set appropriately, check opperand. CheckMode enum comes into play here.
                        {
                            foundTargetSequence = foundTargetSequence &&
                                                  (
                                ((Instructions[i + x].operand == null || checkMode == CheckMode.NONNULL) && targetSequence.ElementAt(x).operand == null) ||
                                Instructions[i + x].operand.Equals(targetSequence.ElementAt(x).operand)
                                                  );
                        }

                        if (showDebugOutput && foundTargetSequence)
                        {
                            Logger.Info($"Found {targetSequence.ElementAt(x).opcode} at {i + x}");
                        }
                    }

                    if (foundTargetSequence) //If the TargetSequence was found in the Instructions, Replace at the i index.
                    {
                        if (patchMode == PatchMode.BEFORE || patchMode == PatchMode.AFTER)
                        {
                            int indexToInsertAt = patchMode == PatchMode.AFTER ? i + targetSize : i;
                            Instructions.InsertRange(indexToInsertAt, patchSequence.Select(c => c.FullClone()));
                        }
                        else if (patchMode == PatchMode.REPLACE)
                        {
                            Instructions.RemoveRange(i, targetSize);
                            Instructions.InsertRange(i, patchSequence.Select(c => c.FullClone()));
                        }
                        else
                        {
                            throw new ArgumentException($"Argument PatchMode patchMode == {patchMode}; invalid value!");
                        }

                        break;
                    }
                }
                else //if targetsequence didn't fit in what was left of array (couldn't find target sequence)
                {
                    StringBuilder sb = new StringBuilder();

                    sb.AppendLine($"Failed to patch by sequence: couldn't find target sequence.  This might be okay in certain cases.");

                    // Cut down the stack trace because it's 20 lines of unhelpful reflection internals.
                    // Show enough to figure out which mod + transpiler method is causing this:
                    sb.AppendLine($"Stack Trace:");
                    string[] stackTrace = new System.Diagnostics.StackTrace().ToString().Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None);
                    for (int lineNumber = 0; lineNumber < 2; lineNumber++)
                    {
                        sb.AppendLine(stackTrace[lineNumber]);
                    }

                    Logger.Info(sb.ToString());
                    break;
                }
            }

            return(Instructions.AsEnumerable());
        }
Example #10
0
        /// <summary>Load one patch from a content pack's <c>content.json</c> file.</summary>
        /// <param name="pack">The content pack being loaded.</param>
        /// <param name="entry">The change to load.</param>
        /// <param name="config">The content pack's config values.</param>
        /// <param name="logSkip">The callback to invoke with the error reason if loading it fails.</param>
        private bool LoadPatch(IContentPack pack, PatchConfig entry, InvariantDictionary <ConfigField> config, Action <string> logSkip)
        {
            bool TrackSkip(string reason, bool warn = true)
            {
                this.PatchManager.AddPermanentlyDisabled(new DisabledPatch(entry.LogName, entry.Action, entry.Target, pack, reason));
                if (warn)
                {
                    logSkip(reason);
                }
                return(false);
            }

            try
            {
                // normalise patch fields
                if (entry.When == null)
                {
                    entry.When = new InvariantDictionary <string>();
                }

                // parse action
                if (!Enum.TryParse(entry.Action, true, out PatchType action))
                {
                    return(TrackSkip(string.IsNullOrWhiteSpace(entry.Action)
                        ? $"must set the {nameof(PatchConfig.Action)} field."
                        : $"invalid {nameof(PatchConfig.Action)} value '{entry.Action}', expected one of: {string.Join(", ", Enum.GetNames(typeof(PatchType)))}."
                                     ));
                }

                // parse target asset
                TokenString assetName;
                {
                    if (string.IsNullOrWhiteSpace(entry.Target))
                    {
                        return(TrackSkip($"must set the {nameof(PatchConfig.Target)} field."));
                    }
                    if (!this.TryParseTokenString(entry.Target, config, out string error, out TokenStringBuilder builder))
                    {
                        return(TrackSkip($"the {nameof(PatchConfig.Target)} is invalid: {error}"));
                    }
                    assetName = builder.Build();
                }

                // parse 'enabled'
                bool enabled = true;
                {
                    if (entry.Enabled != null && !this.TryParseBoolean(entry.Enabled, config, out string error, out enabled))
                    {
                        return(TrackSkip($"invalid {nameof(PatchConfig.Enabled)} value '{entry.Enabled}': {error}"));
                    }
                }

                // apply config
                foreach (string key in config.Keys)
                {
                    if (entry.When.TryGetValue(key, out string values))
                    {
                        InvariantHashSet expected = this.PatchManager.ParseCommaDelimitedField(values);
                        if (!expected.Intersect(config[key].Value).Any())
                        {
                            return(TrackSkip($"disabled: config field '{key}' must have one of '{string.Join(", ", expected)}', but found '{string.Join(", ", config[key].Value)}'.", warn: false));
                        }

                        entry.When.Remove(key);
                    }
                }

                // parse conditions
                ConditionDictionary conditions;
                {
                    if (!this.PatchManager.TryParseConditions(entry.When, out conditions, out string error))
                    {
                        return(TrackSkip($"the {nameof(PatchConfig.When)} field is invalid: {error}."));
                    }
                }

                // get patch instance
                IPatch patch;
                switch (action)
                {
                // load asset
                case PatchType.Load:
                {
                    // init patch
                    if (!this.TryPrepareLocalAsset(pack, entry.FromFile, config, conditions, out string error, out TokenString fromAsset, checkOnly: !enabled))
                    {
                        return(TrackSkip(error));
                    }
                    patch = new LoadPatch(entry.LogName, this.AssetLoader, pack, assetName, conditions, fromAsset, this.Helper.Content.NormaliseAssetName);

                    // detect conflicting loaders
                    if (enabled)
                    {
                        InvariantDictionary <IPatch> conflicts = this.PatchManager.GetConflictingLoaders(patch);
                        if (conflicts.Any())
                        {
                            IEnumerable <string> conflictNames = (from conflict in conflicts
                                                                  orderby conflict.Key
                                                                  select $"'{conflict.Value.LogName}' already loads {conflict.Key}");
                            return(TrackSkip(
                                       $"{nameof(entry.Target)} '{patch.TokenableAssetName.Raw}' conflicts with other load patches ({string.Join(", ", conflictNames)}). Each file can only be loaded by one patch, unless their conditions can never overlap."));
                        }
                    }
                }
                break;

                // edit data
                case PatchType.EditData:
                {
                    // validate
                    if (entry.Entries == null && entry.Fields == null)
                    {
                        return(TrackSkip($"either {nameof(PatchConfig.Entries)} or {nameof(PatchConfig.Fields)} must be specified for a '{action}' change."));
                    }
                    if (entry.Entries != null && entry.Entries.Any(p => string.IsNullOrWhiteSpace(p.Value)))
                    {
                        return(TrackSkip($"the {nameof(PatchConfig.Entries)} can't contain empty values."));
                    }
                    if (entry.Fields != null && entry.Fields.Any(p => p.Value == null || p.Value.Any(n => n.Value == null)))
                    {
                        return(TrackSkip($"the {nameof(PatchConfig.Fields)} can't contain empty values."));
                    }

                    // save
                    patch = new EditDataPatch(entry.LogName, this.AssetLoader, pack, assetName, conditions, entry.Entries, entry.Fields, this.Monitor, this.Helper.Content.NormaliseAssetName);
                }
                break;

                // edit image
                case PatchType.EditImage:
                {
                    // read patch mode
                    PatchMode patchMode = PatchMode.Replace;
                    if (!string.IsNullOrWhiteSpace(entry.PatchMode) && !Enum.TryParse(entry.PatchMode, true, out patchMode))
                    {
                        return(TrackSkip($"the {nameof(PatchConfig.PatchMode)} is invalid. Expected one of these values: [{string.Join(", ", Enum.GetNames(typeof(PatchMode)))}]."));
                    }

                    // save
                    if (!this.TryPrepareLocalAsset(pack, entry.FromFile, config, conditions, out string error, out TokenString fromAsset, checkOnly: !enabled))
                    {
                        return(TrackSkip(error));
                    }
                    patch = new EditImagePatch(entry.LogName, this.AssetLoader, pack, assetName, conditions, fromAsset, entry.FromArea, entry.ToArea, patchMode, this.Monitor, this.Helper.Content.NormaliseAssetName);
                }
                break;

                default:
                    return(TrackSkip($"unsupported patch type '{action}'."));
                }

                // only apply patch when its tokens are available
                HashSet <ConditionKey> tokensUsed = new HashSet <ConditionKey>(patch.GetTokensUsed());
                foreach (ConditionKey key in tokensUsed)
                {
                    if (!patch.Conditions.ContainsKey(key))
                    {
                        patch.Conditions.Add(key, patch.Conditions.GetValidValues(key));
                    }
                }

                // skip if not enabled
                // note: we process the patch even if it's disabled, so any errors are caught by the modder instead of only failing after the patch is enabled.
                if (!enabled)
                {
                    return(TrackSkip($"{nameof(PatchConfig.Enabled)} is false.", warn: false));
                }

                // save patch
                this.PatchManager.Add(patch);
                return(true);
            }
            catch (Exception ex)
            {
                return(TrackSkip($"error reading info. Technical details:\n{ex}"));
            }
        }
Example #11
0
 public TargetMethodInfo(MethodBase oldMethod, MethodInfo newMethod, PatchMode mode)
 {
     this.oldMethod = oldMethod;
     this.newMethod = newMethod;
     this.mode = mode;
 }
Example #12
0
        public static AssetInjector <IAssetDataForImage, IAssetDataForImage> injectInto(this Texture2D t, string assetName, Rectangle?source, Rectangle?target, PatchMode mode = PatchMode.Replace)
        {
            Func <IAssetDataForImage, IAssetDataForImage> merger = new Func <IAssetDataForImage, IAssetDataForImage>(delegate(IAssetDataForImage asset)
            {
                asset.PatchImage(t, source, target, mode);
                return(asset);
            });

            return(new AssetInjector <IAssetDataForImage, IAssetDataForImage>(assetName, merger).injectEdit());
        }
Example #13
0
 public static AssetInjector <IAssetDataForImage, IAssetDataForImage> injectInto(this Texture2D t, string assetName, Point position, PatchMode mode = PatchMode.Replace)
 {
     return(t.injectInto(assetName, null, new Rectangle(position.X, position.Y, t.Width, t.Height), mode));
 }
Example #14
0
        /// <summary>Overwrite part of the image.</summary>
        /// <param name="source">The image to patch into the content.</param>
        /// <param name="sourceArea">The part of the <paramref name="source"/> to copy (or <c>null</c> to take the whole texture). This must be within the bounds of the <paramref name="source"/> texture.</param>
        /// <param name="targetArea">The part of the content to patch (or <c>null</c> to patch the whole texture). The original content within this area will be erased. This must be within the bounds of the existing spritesheet.</param>
        /// <param name="patchMode">Indicates how an image should be patched.</param>
        /// <exception cref="ArgumentNullException">One of the arguments is null.</exception>
        /// <exception cref="ArgumentOutOfRangeException">The <paramref name="targetArea"/> is outside the bounds of the spritesheet.</exception>
        public void PatchImage(Texture2D source, Rectangle?sourceArea = null, Rectangle?targetArea = null, PatchMode patchMode = PatchMode.Replace)
        {
            // get texture
            Texture2D target = this.Data;

            // get areas
            sourceArea = sourceArea ?? new Rectangle(0, 0, source.Width, source.Height);
            targetArea = targetArea ?? new Rectangle(0, 0, Math.Min(sourceArea.Value.Width, target.Width), Math.Min(sourceArea.Value.Height, target.Height));

            // validate
            if (source == null)
            {
                throw new ArgumentNullException(nameof(source), "Can't patch from a null source texture.");
            }
            if (sourceArea.Value.X < 0 || sourceArea.Value.Y < 0 || sourceArea.Value.Right > source.Width || sourceArea.Value.Bottom > source.Height)
            {
                throw new ArgumentOutOfRangeException(nameof(sourceArea), "The source area is outside the bounds of the source texture.");
            }
            if (targetArea.Value.X < 0 || targetArea.Value.Y < 0 || targetArea.Value.Right > target.Width || targetArea.Value.Bottom > target.Height)
            {
                throw new ArgumentOutOfRangeException(nameof(targetArea), "The target area is outside the bounds of the target texture.");
            }
            if (sourceArea.Value.Width != targetArea.Value.Width || sourceArea.Value.Height != targetArea.Value.Height)
            {
                throw new InvalidOperationException("The source and target areas must be the same size.");
            }

            // get source data
            int pixelCount = sourceArea.Value.Width * sourceArea.Value.Height;

            Color[] sourceData = new Color[pixelCount];
            source.GetData(0, sourceArea, sourceData, 0, pixelCount);

            // merge data in overlay mode
            if (patchMode == PatchMode.Overlay)
            {
                Color[] newData = new Color[targetArea.Value.Width * targetArea.Value.Height];
                target.GetData(0, targetArea, newData, 0, newData.Length);
                for (int i = 0; i < sourceData.Length; i++)
                {
                    Color pixel = sourceData[i];
                    if (pixel.A != 0) // not transparent
                    {
                        newData[i] = pixel;
                    }
                }
                sourceData = newData;
            }

            // patch target texture
            target.SetData(0, targetArea, sourceData, 0, pixelCount);
        }
        public static void PatchExtendedTileSheet(this IAssetDataForImage asset, Texture2D source, Rectangle?sourceArea = null, Rectangle?targetArea = null, PatchMode patchMode = PatchMode.Replace)
        {
            string assetName = asset.AssetName.Replace('/', '\\');

            if (!extendedTextureAssets.ContainsKey(assetName) || !targetArea.HasValue)
            {
                asset.PatchImage(source, sourceArea, targetArea, patchMode);
                return;
            }
            extendedTextures.Remove(extendedTextureAssets[assetName].BaseTileSheet);
            extendedTextureAssets[assetName].BaseTileSheet = asset.Data;
            extendedTextures.Add(extendedTextureAssets[assetName].BaseTileSheet, extendedTextureAssets[assetName]);

            var adjustedTarget = GetAdjustedTileSheetTarget(asset.Data, targetArea.Value);

            //Log.trace("Tilesheet target:" + adjustedTarget.TileSheet + " " + adjustedTarget.Y);
            if (adjustedTarget.TileSheet == 0)
            {
                asset.PatchImage(source, sourceArea, targetArea, patchMode);
                return;
            }

            // Cheaty hack so I don't have to reimplement patch
            var oldData  = asset.Data;
            var dataProp = asset.GetType().GetProperty("Data");

            try
            {
                dataProp.SetValue(asset, GetTileSheet(oldData, adjustedTarget.TileSheet));

                Rectangle r = targetArea.Value;
                r.Y = adjustedTarget.Y;
                //Log.trace($"Ext-patching on {assetName}={extendedTextureAssets[assetName].AssetPath}: {r}/{asset.Data.Width}x{asset.Data.Height}");
                asset.PatchImage(source, sourceArea, r, patchMode);
            }
            finally
            {
                dataProp.SetValue(asset, oldData);
            }
        }
Example #16
0
        public static void PatchImage(IAssetDataForImage __instance, ref Texture2D source, ref Rectangle?sourceArea, Rectangle?targetArea, PatchMode patchMode)
        {
            if (source is ScaledTexture2D scaled)
            {
                var a  = new Rectangle(0, 0, __instance.Data.Width, __instance.Data.Height);
                var s  = new Rectangle(0, 0, source.Width, source.Height);
                var sr = !sourceArea.HasValue ? s : sourceArea.Value;
                var tr = !targetArea.HasValue ? sr : targetArea.Value;

                if (a == tr && patchMode == PatchMode.Replace)
                {
                    __instance.ReplaceWith(source);
                    return;
                }

                if (patchMode == PatchMode.Overlay)
                {
                    scaled.AsOverlay = true;
                }

                if (scaled.AsOverlay)
                {
                    Color[] data = new Color[(int)(tr.Width) * (int)(tr.Height)];
                    __instance.Data.getArea(tr).GetData(data);
                    scaled.SetData <Color>(data);
                }

                if (__instance.Data is MappedTexture2D map)
                {
                    map.Set(tr, source);
                }
                else
                {
                    __instance.ReplaceWith(new MappedTexture2D(__instance.Data, new Dictionary <Rectangle?, Texture2D>()
                    {
                        { tr, source }
                    }));
                }
            }
        }
Example #17
0
        /// <summary>Load one patch from a content pack's <c>content.json</c> file.</summary>
        /// <param name="pack">The content pack being loaded.</param>
        /// <param name="entry">The change to load.</param>
        /// <param name="tokenContext">The tokens available for this content pack.</param>
        /// <param name="migrator">The migrator which validates and migrates content pack data.</param>
        /// <param name="logSkip">The callback to invoke with the error reason if loading it fails.</param>
        private bool LoadPatch(ManagedContentPack pack, PatchConfig entry, IContext tokenContext, IMigration migrator, Action <string> logSkip)
        {
            bool TrackSkip(string reason, bool warn = true)
            {
                this.PatchManager.AddPermanentlyDisabled(new DisabledPatch(entry.LogName, entry.Action, entry.Target, pack, reason));
                if (warn)
                {
                    logSkip(reason);
                }
                return(false);
            }

            try
            {
                // normalise patch fields
                if (entry.When == null)
                {
                    entry.When = new InvariantDictionary <string>();
                }

                // parse action
                if (!Enum.TryParse(entry.Action, true, out PatchType action))
                {
                    return(TrackSkip(string.IsNullOrWhiteSpace(entry.Action)
                        ? $"must set the {nameof(PatchConfig.Action)} field."
                        : $"invalid {nameof(PatchConfig.Action)} value '{entry.Action}', expected one of: {string.Join(", ", Enum.GetNames(typeof(PatchType)))}."
                                     ));
                }

                // parse target asset
                TokenString assetName;
                {
                    if (string.IsNullOrWhiteSpace(entry.Target))
                    {
                        return(TrackSkip($"must set the {nameof(PatchConfig.Target)} field."));
                    }
                    if (!this.TryParseStringTokens(entry.Target, tokenContext, migrator, out string error, out assetName))
                    {
                        return(TrackSkip($"the {nameof(PatchConfig.Target)} is invalid: {error}"));
                    }
                }

                // parse 'enabled'
                bool enabled = true;
                {
                    if (entry.Enabled != null && !this.TryParseEnabled(entry.Enabled, tokenContext, migrator, out string error, out enabled))
                    {
                        return(TrackSkip($"invalid {nameof(PatchConfig.Enabled)} value '{entry.Enabled}': {error}"));
                    }
                }

                // parse conditions
                ConditionDictionary conditions;
                {
                    if (!this.TryParseConditions(entry.When, tokenContext, migrator, out conditions, out string error))
                    {
                        return(TrackSkip($"the {nameof(PatchConfig.When)} field is invalid: {error}."));
                    }
                }

                // get patch instance
                IPatch patch;
                switch (action)
                {
                // load asset
                case PatchType.Load:
                {
                    // init patch
                    if (!this.TryPrepareLocalAsset(pack, entry.FromFile, tokenContext, migrator, out string error, out TokenString fromAsset))
                    {
                        return(TrackSkip(error));
                    }
                    patch = new LoadPatch(entry.LogName, pack, assetName, conditions, fromAsset, this.Helper.Content.NormaliseAssetName);
                }
                break;

                // edit data
                case PatchType.EditData:
                {
                    // validate
                    if (entry.Entries == null && entry.Fields == null)
                    {
                        return(TrackSkip($"either {nameof(PatchConfig.Entries)} or {nameof(PatchConfig.Fields)} must be specified for a '{action}' change."));
                    }

                    // parse entries
                    List <EditDataPatchRecord> entries = new List <EditDataPatchRecord>();
                    if (entry.Entries != null)
                    {
                        foreach (KeyValuePair <string, JToken> pair in entry.Entries)
                        {
                            if (!this.TryParseStringTokens(pair.Key, tokenContext, migrator, out string keyError, out TokenString key))
                            {
                                return(TrackSkip($"{nameof(PatchConfig.Entries)} > '{key}' key is invalid: {keyError}."));
                            }
                            if (!this.TryParseJsonTokens(pair.Value, tokenContext, migrator, out string error, out TokenisableJToken value))
                            {
                                return(TrackSkip($"{nameof(PatchConfig.Entries)} > '{key}' value is invalid: {error}."));
                            }

                            entries.Add(new EditDataPatchRecord(key, value));
                        }
                    }

                    // parse fields
                    List <EditDataPatchField> fields = new List <EditDataPatchField>();
                    if (entry.Fields != null)
                    {
                        foreach (KeyValuePair <string, IDictionary <string, JToken> > recordPair in entry.Fields)
                        {
                            // parse entry key
                            if (!this.TryParseStringTokens(recordPair.Key, tokenContext, migrator, out string keyError, out TokenString key))
                            {
                                return(TrackSkip($"{nameof(PatchConfig.Fields)} > entry {recordPair.Key} is invalid: {keyError}."));
                            }

                            // parse fields
                            foreach (var fieldPair in recordPair.Value)
                            {
                                // parse field key
                                if (!this.TryParseStringTokens(fieldPair.Key, tokenContext, migrator, out string fieldError, out TokenString fieldKey))
                                {
                                    return(TrackSkip($"{nameof(PatchConfig.Fields)} > entry {recordPair.Key} > field {fieldPair.Key} key is invalid: {fieldError}."));
                                }

                                // parse value
                                if (!this.TryParseJsonTokens(fieldPair.Value, tokenContext, migrator, out string valueError, out TokenisableJToken value))
                                {
                                    return(TrackSkip($"{nameof(PatchConfig.Fields)} > entry {recordPair.Key} > field {fieldKey} is invalid: {valueError}."));
                                }
                                if (value?.Value is JValue jValue && jValue.Value <string>()?.Contains("/") == true)
                                {
                                    return(TrackSkip($"{nameof(PatchConfig.Fields)} > entry {recordPair.Key} > field {fieldKey} is invalid: value can't contain field delimiter character '/'."));
                                }

                                fields.Add(new EditDataPatchField(key, fieldKey, value));
                            }
                        }
                    }

                    // save
                    patch = new EditDataPatch(entry.LogName, pack, assetName, conditions, entries, fields, this.Monitor, this.Helper.Content.NormaliseAssetName);
                }
                break;

                // edit image
                case PatchType.EditImage:
                {
                    // read patch mode
                    PatchMode patchMode = PatchMode.Replace;
                    if (!string.IsNullOrWhiteSpace(entry.PatchMode) && !Enum.TryParse(entry.PatchMode, true, out patchMode))
                    {
                        return(TrackSkip($"the {nameof(PatchConfig.PatchMode)} is invalid. Expected one of these values: [{string.Join(", ", Enum.GetNames(typeof(PatchMode)))}]."));
                    }

                    // save
                    if (!this.TryPrepareLocalAsset(pack, entry.FromFile, tokenContext, migrator, out string error, out TokenString fromAsset))
                    {
                        return(TrackSkip(error));
                    }
                    patch = new EditImagePatch(entry.LogName, pack, assetName, conditions, fromAsset, entry.FromArea, entry.ToArea, patchMode, this.Monitor, this.Helper.Content.NormaliseAssetName);
                }
                break;

                default:
                    return(TrackSkip($"unsupported patch type '{action}'."));
                }

                // skip if not enabled
                // note: we process the patch even if it's disabled, so any errors are caught by the modder instead of only failing after the patch is enabled.
                if (!enabled)
                {
                    return(TrackSkip($"{nameof(PatchConfig.Enabled)} is false.", warn: false));
                }

                // save patch
                this.PatchManager.Add(patch);
                return(true);
            }
            catch (Exception ex)
            {
                return(TrackSkip($"error reading info. Technical details:\n{ex}"));
            }
        }
Example #18
0
        public void Import(string fileName, PatchMode mode)
        {
            Clear();

            string[] fileNameSplit    = fileName.Split(new char[] { '/', '\\' });
            string   fileRelativePath = fileName.Remove(fileName.Length - (fileNameSplit[fileNameSplit.Length - 1].Length), fileNameSplit[fileNameSplit.Length - 1].Length);

            int            minIndex     = allVertices.Length;
            List <Vector3> positions    = new List <Vector3>();
            CollisionPatch currentPatch = new CollisionPatch("<Undefined>");

            StreamReader rd = new StreamReader(fileName);

            while (!rd.EndOfStream)
            {
                string   line  = rd.ReadLine();
                string[] split = line.Split(' ');
                if (split[0] == "mtllib") //Material Library
                {
                    string currentDir = Environment.CurrentDirectory;
                    Environment.CurrentDirectory = System.IO.Path.GetDirectoryName(System.IO.Path.GetFullPath(fileName));
                    CreateMaterialLibrary(line.Remove(0, split[0].Length).Trim());
                    Environment.CurrentDirectory = currentDir;
                }
                else if (split[0] == "o") //Object
                {
                    if (mode == PatchMode.ByObject)
                    {
                        if (currentPatch.triangles.Count > 0)
                        {
                            patches.Add(currentPatch);
                        }
                        currentPatch = new CollisionPatch(split[1]);
                    }
                }
                else if (split[0] == "v") //Vertex Position
                {
                    Vector3 v = cvt.ParseVector3(split, 1);
                    v.X *= -1;
                    positions.Add(v * Settings.importScale);
                }
                else if (split[0] == "usemtl") //Material usage declaration
                {
                    if (mode == PatchMode.ByMaterial)
                    {
                        if (currentPatch.triangles.Count > 0)
                        {
                            foreach (CollisionPatch patch in patches)
                            {
                                if (patch.name == currentPatch.name)
                                {
                                    goto skipMakePatch;
                                }
                            }
                            patches.Add(currentPatch);
                            skipMakePatch :;
                        }
                        foreach (CollisionPatch patch in patches)
                        {
                            if (patch.name == split[1])
                            {
                                currentPatch = patch;
                                goto skipNewPatch;
                            }
                        }
                        currentPatch = new CollisionPatch(split[1]);
                        if (!materials.TryGetValue(split[1], out currentPatch.materialImage))
                        {
                            currentPatch.materialImage = failImage;
                        }
                        skipNewPatch :;
                    }
                }
                else if (split[0] == "f") // Face
                {
                    short i0 = 0, ix = 0;
                    for (int i = 1; i < split.Length; i++)
                    {
                        short    index         = 0;
                        string[] vertexIndices = split[i].Split('/');
                        short.TryParse(vertexIndices[0], out index);
                        index -= 1;

                        if (i == 1)
                        {
                            i0 = index;
                        }
                        if (i >= 3)
                        {
                            CollisionTriangle t;
                            currentPatch.triangles.Add(t = new CollisionTriangle((short)(i0 + minIndex), (short)(ix + minIndex), (short)(index + minIndex)));
                        }
                        ix = index;
                    }
                }
            }
            rd.BaseStream.Close();
            Array.Resize(ref allVertices, allVertices.Length + positions.Count);
            Array.Copy(positions.ToArray(), 0, allVertices, allVertices.Length - positions.Count, positions.Count);

            if (currentPatch.triangles.Count > 0)
            {
                if (mode == PatchMode.ByObject)
                {
                    goto makePatch;
                }
                foreach (CollisionPatch patch in patches)
                {
                    if (patch.name == currentPatch.name)
                    {
                        goto skipMakePatch;
                    }
                }
makePatch:
                patches.Add(currentPatch);
                skipMakePatch :;
            }
            Optimize();
        }
Example #19
0
 public TargetMethodAttribute(Type targetClass, string targetMethodName, Type[] genericArguemtns, PatchMode patchMode, params Type[] argumentTypes) : this(targetClass, targetMethodName, patchMode, argumentTypes) =>
        //        /// <summary>
        //        /// Hooks the console title.
        //        /// </summary> TODO: this might actually be needed to avoid using the XNA shims
        //        public void HookConsoleTitle()
        //        {
        //            var method = Terraria.Main.Methods.Single(x => x.Name == "DedServ");
        //            var callback = API.GameWindow.Methods.First(m => m.Name == "SetTitle");
        //
        ////            var il = method.Body.GetILProcessor();
        //
        //            var replacement = _asm.MainModule.Import(callback);
        //            foreach (var ins in method.Body.Instructions
        //                .Where(x => x.OpCode == OpCodes.Call
        //                    && x.Operand is MethodReference
        //                    && (x.Operand as MethodReference).DeclaringType.FullName == "System.Console"
        //                    && (x.Operand as MethodReference).Name == "set_Title"))
        //            {
        //                ins.Operand = replacement;
        //            }
        //        }
        /// <summary>
        /// Hooks start of the application for plugins to be loaded
        /// </summary>
        /// <param name="mode">Mode.</param>
        public void HookProgramStart(PatchMode mode)
        {
            if (mode == PatchMode.Server)
            {
                var method = Terraria.WindowsLaunch.Methods.Single(x => x.Name == "Main");
                var callback = API.MainCallback.Methods.First(m => m.Name == "OnProgramStarted");

                var il = method.Body.GetILProcessor();

                var ret = il.Create(OpCodes.Ret);
                var call = il.Create(OpCodes.Call, _asm.MainModule.Import(callback));
                var first = method.Body.Instructions.First();

                il.InsertBefore(first, il.Create(OpCodes.Ldarg_0));
                il.InsertBefore(first, call);
                il.InsertBefore(first, il.Create(OpCodes.Brtrue_S, first));
                il.InsertBefore(first, ret);
            }
            else if (mode == PatchMode.Client)
            {
                var method = Terraria.Program.Methods.Single(x => x.Name == "LaunchGame");
                var callback = API.MainCallback.Methods.First(m => m.Name == "OnProgramStarted");

                var il = method.Body.GetILProcessor();

                var ret = il.Create(OpCodes.Ret);
                var call = il.Create(OpCodes.Call, _asm.MainModule.Import(callback));
                var first = method.Body.Instructions.First();

                il.InsertBefore(first, il.Create(OpCodes.Ldarg_0));
                il.InsertBefore(first, call);
                il.InsertBefore(first, il.Create(OpCodes.Brtrue_S, first));
                il.InsertBefore(first, ret);
            }
        }
        /*********
        ** Public methods
        *********/
        /// <summary>Construct an instance.</summary>
        /// <param name="logName">A unique name for this patch shown in log messages.</param>
        /// <param name="contentPack">The content pack which requested the patch.</param>
        /// <param name="assetName">The normalized asset name to intercept.</param>
        /// <param name="conditions">The conditions which determine whether this patch should be applied.</param>
        /// <param name="fromAsset">The asset key to load from the content pack instead.</param>
        /// <param name="fromArea">The sprite area from which to read an image.</param>
        /// <param name="toArea">The sprite area to overwrite.</param>
        /// <param name="patchMode">Indicates how the image should be patched.</param>
        /// <param name="monitor">Encapsulates monitoring and logging.</param>
        /// <param name="normalizeAssetName">Normalize an asset name.</param>
        public EditImagePatch(string logName, ManagedContentPack contentPack, IManagedTokenString assetName, IEnumerable <Condition> conditions, IManagedTokenString fromAsset, TokenRectangle fromArea, TokenRectangle toArea, PatchMode patchMode, IMonitor monitor, Func <string, string> normalizeAssetName)
            : base(logName, PatchType.EditImage, contentPack, assetName, conditions, normalizeAssetName, fromAsset: fromAsset)
        {
            this.FromArea  = fromArea;
            this.ToArea    = toArea;
            this.PatchMode = patchMode;
            this.Monitor   = monitor;

            this.Contextuals
            .Add(fromArea)
            .Add(toArea);
        }
        //        /// <summary>
        //        /// Allows eclipse to be started at any time
        //        /// </summary>
        //        //TODO mark as a feature, not a requirement for a functional OTA
        //        public void HookEclipse()
        //        {
        //            var mth = Terraria.Main.Methods.Single(x => x.Name == "UpdateTime");
        //            var field = API.MainCallback.Fields.Single(x => x.Name == "StartEclipse");
        //
        //            var il = mth.Body.GetILProcessor();
        //            var start = il.Body.Instructions.Single(x =>
        //                x.OpCode == OpCodes.Ldsfld
        //                            && x.Operand is FieldReference
        //                            && (x.Operand as FieldReference).Name == "hardMode"
        //                            && x.Previous.OpCode == OpCodes.Call
        //                            && x.Previous.Operand is MethodReference
        //                            && (x.Previous.Operand as MethodReference).Name == "StartInvasion"
        //                        );
        //
        //            //Preserve
        //            var old = start.Operand as FieldReference;
        //
        //            //Replace with ours to keep the IL inline
        //            start.Operand = _asm.MainModule.Import(field);
        //            //Readd the preserved
        //            il.InsertAfter(start, il.Create(OpCodes.Ldsfld, old));
        //
        //            //Now find the target instruction if the value is true
        //            var startEclipse = il.Body.Instructions.Single(x =>
        //                x.OpCode == OpCodes.Stsfld
        //                                   && x.Operand is FieldReference
        //                                   && (x.Operand as FieldReference).Name == "eclipse"
        //                                   && x.Next.OpCode == OpCodes.Ldsfld
        //                                   && x.Next.Operand is FieldReference
        //                                   && (x.Next.Operand as FieldReference).Name == "eclipse"
        //                               ).Previous;
        //            il.InsertAfter(start, il.Create(OpCodes.Brtrue, startEclipse));
        //
        //            //Since all we care about is setting the StartEclipse to TRUE; we need to be able to disable once done.
        //            il.InsertAfter(startEclipse.Next, il.Create(OpCodes.Stsfld, start.Operand as FieldReference));
        //            il.InsertAfter(startEclipse.Next, il.Create(OpCodes.Ldc_I4_0));
        //        }
        //        public void HookBloodMoon()
        //        {
        //            var mth = Terraria.Main.Methods.Single(x => x.Name == "UpdateTime");
        //            var field = API.MainCallback.Fields.Single(x => x.Name == "StartBloodMoon");
        //            //return;
        //            var il = mth.Body.GetILProcessor();
        //            var start = il.Body.Instructions.Single(x =>
        //                x.OpCode == OpCodes.Ldsfld
        //                            && x.Operand is FieldReference
        //                            && (x.Operand as FieldReference).Name == "spawnEye"
        //                            && x.Next.Next.OpCode == OpCodes.Ldsfld
        //                            && x.Next.Next.Operand is FieldReference
        //                            && (x.Next.Next.Operand as FieldReference).Name == "moonPhase"
        //                        );
        //
        //            //Preserve
        //            var old = start.Operand as FieldReference;
        //            var target = start.Next as Instruction;
        //
        //            //Replace with ours to keep the IL inline
        //            start.Operand = _asm.MainModule.Import(field);
        //            //Readd the preserved
        //            il.InsertAfter(start, il.Create(OpCodes.Ldsfld, old));
        //
        //            //Now find the target instruction if the value is true
        //            Instruction begin = start.Next;
        //            var i = 12;
        //            while (i > 0)
        //            {
        //                i--;
        //                begin = begin.Next;
        //            }
        //            il.InsertAfter(start, il.Create(OpCodes.Brtrue, begin));
        //
        //            //Since all we care about is setting the StartBloodMoon to TRUE; we need to be able to disable once done.
        //            var startBloodMoon = il.Body.Instructions.Single(x =>
        //                x.OpCode == OpCodes.Ldsfld
        //                                     && x.Operand is FieldReference
        //                                     && (x.Operand as FieldReference).Name == "bloodMoon"
        //                                     && x.Next.Next.OpCode == OpCodes.Ldsfld
        //                                     && x.Next.Next.Operand is FieldReference
        //                                     && (x.Next.Next.Operand as FieldReference).Name == "netMode"
        //                                 );
        //            il.InsertAfter(startBloodMoon.Next, il.Create(OpCodes.Stsfld, start.Operand as FieldReference));
        //            il.InsertAfter(startBloodMoon.Next, il.Create(OpCodes.Ldc_I4_0));
        //        }
        /// <summary>
        /// Saves the patched assembly
        /// </summary>
        /// <param name="mode">Mode.</param>
        /// <param name="fileName">File name.</param>
        /// <param name="apiBuild">API build.</param>
        /// <param name="tdsmUID">Tdsm user interface.</param>
        /// <param name="name">Name.</param>
        /// <param name="swapOTA">If set to <c>true</c> swap OT.</param>
        public void Save(PatchMode mode, string fileName, int apiBuild, string tdsmUID, string name, bool swapOTA = false)
        {
            if (mode == PatchMode.Server)
            {
                //Ensure the name is updated to the new one
                _asm.Name = new AssemblyNameDefinition(name, new Version(0, 0, apiBuild, 0));
                _asm.MainModule.Name = fileName;

                //Change the uniqueness from what Terraria has, to something different (that way vanilla isn't picked up by assembly resolutions)
                //                var g = _asm.CustomAttributes.Single(x => x.AttributeType.Name == "GuidAttribute");

                for (var x = 0; x < _asm.CustomAttributes.Count; x++)
                {
                    if (_asm.CustomAttributes[x].AttributeType.Name == "GuidAttribute")
                    {
                        _asm.CustomAttributes[x].ConstructorArguments[0] =
                        new CustomAttributeArgument(_asm.CustomAttributes[x].ConstructorArguments[0].Type, tdsmUID);
                    }
                    else if (_asm.CustomAttributes[x].AttributeType.Name == "AssemblyTitleAttribute")
                    {
                        _asm.CustomAttributes[x].ConstructorArguments[0] =
                        new CustomAttributeArgument(_asm.CustomAttributes[x].ConstructorArguments[0].Type, name);
                    }
                    else if (_asm.CustomAttributes[x].AttributeType.Name == "AssemblyProductAttribute")
                    {
                        _asm.CustomAttributes[x].ConstructorArguments[0] =
                        new CustomAttributeArgument(_asm.CustomAttributes[x].ConstructorArguments[0].Type, name);
                    }
                    //else if (_asm.CustomAttributes[x].AttributeType.Name == "AssemblyFileVersionAttribute")
                    //{
                    //    _asm.CustomAttributes[x].ConstructorArguments[0] =
                    //        new CustomAttributeArgument(_asm.CustomAttributes[x].ConstructorArguments[0].Type, "1.0.0.0");
                    //}
                }
            }

            //            foreach (var mod in _asm.Modules)
            //            {
            //                for (var x = mod.Resources.Count - 1; x >= 0; x--)
            //                {
            //                    mod.Resources.RemoveAt(x);
            //                }
            //            }

            //_asm.Write(fileName);
            using (var fs = File.OpenWrite(fileName))
            {
                _asm.Write(fs);
                fs.Flush();
                fs.Close();
            }

            if (swapOTA) SwapOTAReferences();
        }
Example #23
0
 /*********
 ** Public methods
 *********/
 /// <summary>Construct an instance.</summary>
 /// <param name="logName">A unique name for this patch shown in log messages.</param>
 /// <param name="assetLoader">Handles loading assets from content packs.</param>
 /// <param name="contentPack">The content pack which requested the patch.</param>
 /// <param name="assetName">The normalised asset name to intercept.</param>
 /// <param name="conditions">The conditions which determine whether this patch should be applied.</param>
 /// <param name="fromLocalAsset">The asset key to load from the content pack instead.</param>
 /// <param name="fromArea">The sprite area from which to read an image.</param>
 /// <param name="toArea">The sprite area to overwrite.</param>
 /// <param name="patchMode">Indicates how the image should be patched.</param>
 /// <param name="monitor">Encapsulates monitoring and logging.</param>
 /// <param name="normaliseAssetName">Normalise an asset name.</param>
 public EditImagePatch(string logName, AssetLoader assetLoader, IContentPack contentPack, TokenString assetName, ConditionDictionary conditions, TokenString fromLocalAsset, Rectangle fromArea, Rectangle toArea, PatchMode patchMode, IMonitor monitor, Func <string, string> normaliseAssetName)
     : base(logName, PatchType.EditImage, assetLoader, contentPack, assetName, conditions, normaliseAssetName)
 {
     this.FromLocalAsset = fromLocalAsset;
     this.FromArea       = fromArea != Rectangle.Empty ? fromArea : null as Rectangle?;
     this.ToArea         = toArea != Rectangle.Empty ? toArea : null as Rectangle?;
     this.PatchMode      = patchMode;
     this.Monitor        = monitor;
 }
 /// <summary>
 /// Onward from this specific instruction is client code, and code afterwards returns for the server. (Odd i know)
 /// However, if we dont return and skipMenu is set, the server will crash
 /// </summary>
 public void SkipMenu(PatchMode mode)
 {
     if (mode != PatchMode.Server) throw new Exception("SkipMenu is a server-only fix");
     var initialise = Terraria.Main.Methods.Single(x => x.Name == "Initialize");
     var loc = initialise.Body.Instructions
         .Where(x => x.OpCode == OpCodes.Ldsfld && x.Operand is FieldDefinition)
         //.Select(x => x.Operand as FieldDefinition)
         .Single(x => (x.Operand as FieldDefinition).Name == "skipMenu");
     var il = initialise.Body.GetILProcessor();
     il.InsertBefore(loc, il.Create(OpCodes.Ret));
 }
Example #25
0
    public static bool PatchImage(IAssetDataForImage __instance, XTexture2D source, XRectangle?sourceArea, XRectangle?targetArea, PatchMode patchMode)
    {
        if (!Config.SMAPI.ApplyPatchEnabled)
        {
            return(true);
        }

        // get texture
        if (source is null)
        {
            throw new ArgumentNullException(nameof(source), "Can't patch from a null source texture.");
        }

        XTexture2D target = __instance.Data;

        // get areas
        sourceArea ??= new(0, 0, source.Width, source.Height);
        targetArea ??= new(0, 0, Math.Min(sourceArea.Value.Width, target.Width), Math.Min(sourceArea.Value.Height, target.Height));

        // validate
        if (!source.Bounds.Contains(sourceArea.Value))
        {
            throw new ArgumentOutOfRangeException(nameof(sourceArea), "The source area is outside the bounds of the source texture.");
        }
        if (!target.Bounds.Contains(targetArea.Value))
        {
            throw new ArgumentOutOfRangeException(nameof(targetArea), "The target area is outside the bounds of the target texture.");
        }
        if (sourceArea.Value.Size != targetArea.Value.Size)
        {
            throw new InvalidOperationException("The source and target areas must be the same size.");
        }

        if (GL.Texture2DExt.CopyTexture(source, sourceArea.Value, target, targetArea.Value, patchMode))
        {
            return(false);
        }

        // get source data
        int pixelCount = sourceArea.Value.Width * sourceArea.Value.Height;
        var sourceData = GC.AllocateUninitializedArray <XColor>(pixelCount);

        source.GetData(0, sourceArea, sourceData, 0, pixelCount);

        // merge data in overlay mode
        if (patchMode == PatchMode.Overlay)
        {
            // get target data
            var targetData = GC.AllocateUninitializedArray <XColor>(pixelCount);
            target.GetData(0, targetArea, targetData, 0, pixelCount);

            // merge pixels
            for (int i = 0; i < sourceData.Length; i++)
            {
                var above = sourceData[i];
                var below = targetData[i];

                // shortcut transparency
                if (above.A < MinOpacity)
                {
                    sourceData[i] = below;
                    continue;
                }
                if (below.A < MinOpacity)
                {
                    sourceData[i] = above;
                    continue;
                }

                // merge pixels
                // This performs a conventional alpha blend for the pixels, which are already
                // premultiplied by the content pipeline. The formula is derived from
                // https://shawnhargreaves.com/blog/premultiplied-alpha.html.
                float alphaBelow = 1 - (above.A / 255f);
                sourceData[i] = new XColor(
                    (int)(above.R + (below.R * alphaBelow)),               // r
                    (int)(above.G + (below.G * alphaBelow)),               // g
                    (int)(above.B + (below.B * alphaBelow)),               // b
                    Math.Max(above.A, below.A)                             // a
                    );
            }
        }

        // patch target texture
        target.SetData(0, targetArea, sourceData, 0, pixelCount);
        return(false);
    }
    public void ApplyPatch(string filePath, PatchMode mode)
    {
        var strs = File.ReadAllLines(filePath, Encoding.UTF8);

        string lastSection = null;

        for (int i = 0; i < strs.Length; i++)
        {
            var str = strs[i];
            if (str.Length < 1)
            {
                continue;
            }

            var comm = str.IndexOf("//");
            if (comm > -1)
            {
                str = str.Substring(0, comm);
                if (str.Length < 1)
                {
                    continue;
                }
            }

            var sub = str.TrimStart();
            if (sub.Length < 1)
            {
                continue;
            }

            if (sub[0] == '[')
            {
                var cbi = sub.IndexOf(']', 1);
                if (cbi > 0)
                {
                    var section = sub.Substring(0, cbi + 1);
                    lastSection = section;
                    if (!Data.ContainsKey(section))
                    {
                        Data[section] = new List <Parameter>();
                    }
                }
            }
            else if (lastSection != null)
            {
                var esi = str.IndexOf('=', 1);
                if (esi > 0)
                {
                    string    name = str.Substring(0, esi), value = str.Substring(esi + 1);
                    Parameter param;
                    if ((mode == PatchMode.Addon) || ((param = SeekParam(Data[lastSection], name)) == null))
                    {
                        Data[lastSection].Add(new Parameter {
                            Name = name, Value = value
                        });
                    }
                    else
                    {
                        var strRep = value.IndexOf("%s%");
                        if (strRep > -1)
                        {
                            var hasBraces = ((param.Value[0] == '"') && (param.Value[param.Value.Length - 1] == '"'));
                            if (hasBraces)
                            {
                                param.Value = param.Value.Substring(0, param.Value.Length - 1);
                            }
                            param.Value = $"{value.Substring(0, strRep)}{param.Value}{value.Substring(strRep + 3)}{(hasBraces ? "\"" : "")}";
                        }
                        else
                        {
                            param.Value = value;
                        }
                    }
                }
            }
        }
    }
Example #27
0
        /// <summary>Overwrite part of the image.</summary>
        /// <param name="source">The image to patch into the content.</param>
        /// <param name="sourceArea">The part of the <paramref name="source"/> to copy (or <c>null</c> to take the whole texture). This must be within the bounds of the <paramref name="source"/> texture.</param>
        /// <param name="targetArea">The part of the content to patch (or <c>null</c> to patch the whole texture). The original content within this area will be erased. This must be within the bounds of the existing spritesheet.</param>
        /// <param name="patchMode">Indicates how an image should be patched.</param>
        /// <exception cref="ArgumentNullException">One of the arguments is null.</exception>
        /// <exception cref="ArgumentOutOfRangeException">The <paramref name="targetArea"/> is outside the bounds of the spritesheet.</exception>
        public void PatchImage(Texture2D source, Rectangle?sourceArea = null, Rectangle?targetArea = null, PatchMode patchMode = PatchMode.Replace)
        {
            // get texture
            if (source == null)
            {
                throw new ArgumentNullException(nameof(source), "Can't patch from a null source texture.");
            }
            Texture2D target = this.Data;

            // get areas
            sourceArea = sourceArea ?? new Rectangle(0, 0, source.Width, source.Height);
            targetArea = targetArea ?? new Rectangle(0, 0, Math.Min(sourceArea.Value.Width, target.Width), Math.Min(sourceArea.Value.Height, target.Height));

            // validate
            if (sourceArea.Value.X < 0 || sourceArea.Value.Y < 0 || sourceArea.Value.Right > source.Width || sourceArea.Value.Bottom > source.Height)
            {
                throw new ArgumentOutOfRangeException(nameof(sourceArea), "The source area is outside the bounds of the source texture.");
            }
            if (targetArea.Value.X < 0 || targetArea.Value.Y < 0 || targetArea.Value.Right > target.Width || targetArea.Value.Bottom > target.Height)
            {
                throw new ArgumentOutOfRangeException(nameof(targetArea), "The target area is outside the bounds of the target texture.");
            }
            if (sourceArea.Value.Width != targetArea.Value.Width || sourceArea.Value.Height != targetArea.Value.Height)
            {
                throw new InvalidOperationException("The source and target areas must be the same size.");
            }

            // get source data
            int pixelCount = sourceArea.Value.Width * sourceArea.Value.Height;

            Color[] sourceData = new Color[pixelCount];
            source.GetData(0, sourceArea, sourceData, 0, pixelCount);

            // merge data in overlay mode
            if (patchMode == PatchMode.Overlay)
            {
                // get target data
                Color[] targetData = new Color[pixelCount];
                target.GetData(0, targetArea, targetData, 0, pixelCount);

                // merge pixels
                Color[] newData = new Color[targetArea.Value.Width * targetArea.Value.Height];
                target.GetData(0, targetArea, newData, 0, newData.Length);
                for (int i = 0; i < sourceData.Length; i++)
                {
                    Color above = sourceData[i];
                    Color below = targetData[i];

                    // shortcut transparency
                    if (above.A < AssetDataForImage.MinOpacity)
                    {
                        continue;
                    }
                    if (below.A < AssetDataForImage.MinOpacity)
                    {
                        newData[i] = above;
                        continue;
                    }

                    // merge pixels
                    // This performs a conventional alpha blend for the pixels, which are already
                    // premultiplied by the content pipeline. The formula is derived from
                    // https://blogs.msdn.microsoft.com/shawnhar/2009/11/06/premultiplied-alpha/.
                    // Note: don't use named arguments here since they're different between
                    // Linux/Mac and Windows.
                    float alphaBelow = 1 - (above.A / 255f);
                    newData[i] = new Color(
                        (int)(above.R + (below.R * alphaBelow)), // r
                        (int)(above.G + (below.G * alphaBelow)), // g
                        (int)(above.B + (below.B * alphaBelow)), // b
                        Math.Max(above.A, below.A)               // a
                        );
                }
                sourceData = newData;
            }

            // patch target texture
            target.SetData(0, targetArea, sourceData, 0, pixelCount);
        }
Example #28
0
        /*********
        ** Public methods
        *********/
        /// <summary>Construct an instance.</summary>
        /// <param name="path">The path to the patch from the root content file.</param>
        /// <param name="assetName">The normalized asset name to intercept.</param>
        /// <param name="conditions">The conditions which determine whether this patch should be applied.</param>
        /// <param name="fromAsset">The asset key to load from the content pack instead.</param>
        /// <param name="fromArea">The sprite area from which to read an image.</param>
        /// <param name="toArea">The sprite area to overwrite.</param>
        /// <param name="patchMode">Indicates how the image should be patched.</param>
        /// <param name="updateRate">When the patch should be updated.</param>
        /// <param name="contentPack">The content pack which requested the patch.</param>
        /// <param name="parentPatch">The parent patch for which this patch was loaded, if any.</param>
        /// <param name="monitor">Encapsulates monitoring and logging.</param>
        /// <param name="normalizeAssetName">Normalize an asset name.</param>
        public EditImagePatch(LogPathBuilder path, IManagedTokenString assetName, IEnumerable <Condition> conditions, IManagedTokenString fromAsset, TokenRectangle fromArea, TokenRectangle toArea, PatchMode patchMode, UpdateRate updateRate, ManagedContentPack contentPack, IPatch parentPatch, IMonitor monitor, Func <string, string> normalizeAssetName)
            : base(
                path: path,
                type: PatchType.EditImage,
                assetName: assetName,
                conditions: conditions,
                normalizeAssetName: normalizeAssetName,
                fromAsset: fromAsset,
                updateRate: updateRate,
                contentPack: contentPack,
                parentPatch: parentPatch
                )
        {
            this.FromArea  = fromArea;
            this.ToArea    = toArea;
            this.PatchMode = patchMode;
            this.Monitor   = monitor;

            this.Contextuals
            .Add(fromArea)
            .Add(toArea);
        }
Example #29
0
        /// <summary>Load one patch from a content pack's <c>content.json</c> file.</summary>
        /// <param name="pack">The content pack being loaded.</param>
        /// <param name="contentConfig">The content pack's config.</param>
        /// <param name="entry">The change to load.</param>
        /// <param name="tokenContext">The tokens available for this content pack.</param>
        /// <param name="latestFormatVersion">The latest format version.</param>
        /// <param name="minumumTokenVersions">The minimum format versions for newer condition types.</param>
        /// <param name="logSkip">The callback to invoke with the error reason if loading it fails.</param>
        private bool LoadPatch(ManagedContentPack pack, ContentConfig contentConfig, PatchConfig entry, IContext tokenContext, ISemanticVersion latestFormatVersion, InvariantDictionary <ISemanticVersion> minumumTokenVersions, Action <string> logSkip)
        {
            bool TrackSkip(string reason, bool warn = true)
            {
                this.PatchManager.AddPermanentlyDisabled(new DisabledPatch(entry.LogName, entry.Action, entry.Target, pack, reason));
                if (warn)
                {
                    logSkip(reason);
                }
                return(false);
            }

            try
            {
                // normalise patch fields
                if (entry.When == null)
                {
                    entry.When = new InvariantDictionary <string>();
                }

                // parse action
                if (!Enum.TryParse(entry.Action, true, out PatchType action))
                {
                    return(TrackSkip(string.IsNullOrWhiteSpace(entry.Action)
                        ? $"must set the {nameof(PatchConfig.Action)} field."
                        : $"invalid {nameof(PatchConfig.Action)} value '{entry.Action}', expected one of: {string.Join(", ", Enum.GetNames(typeof(PatchType)))}."
                                     ));
                }

                // parse target asset
                TokenString assetName;
                {
                    if (string.IsNullOrWhiteSpace(entry.Target))
                    {
                        return(TrackSkip($"must set the {nameof(PatchConfig.Target)} field."));
                    }
                    if (!this.TryParseTokenString(entry.Target, tokenContext, out string error, out assetName))
                    {
                        return(TrackSkip($"the {nameof(PatchConfig.Target)} is invalid: {error}"));
                    }
                }

                // parse 'enabled'
                bool enabled = true;
                {
                    if (entry.Enabled != null && !this.TryParseEnabled(entry.Enabled, tokenContext, out string error, out enabled))
                    {
                        return(TrackSkip($"invalid {nameof(PatchConfig.Enabled)} value '{entry.Enabled}': {error}"));
                    }
                }

                // parse conditions
                ConditionDictionary conditions;
                {
                    if (!this.TryParseConditions(entry.When, tokenContext, contentConfig.Format, latestFormatVersion, minumumTokenVersions, out conditions, out string error))
                    {
                        return(TrackSkip($"the {nameof(PatchConfig.When)} field is invalid: {error}."));
                    }
                }

                // get patch instance
                IPatch patch;
                switch (action)
                {
                // load asset
                case PatchType.Load:
                {
                    // init patch
                    if (!this.TryPrepareLocalAsset(pack, entry.FromFile, tokenContext, out string error, out TokenString fromAsset))
                    {
                        return(TrackSkip(error));
                    }
                    patch = new LoadPatch(entry.LogName, pack, assetName, conditions, fromAsset, this.Helper.Content.NormaliseAssetName);
                }
                break;

                // edit data
                case PatchType.EditData:
                {
                    // validate
                    if (entry.Entries == null && entry.Fields == null)
                    {
                        return(TrackSkip($"either {nameof(PatchConfig.Entries)} or {nameof(PatchConfig.Fields)} must be specified for a '{action}' change."));
                    }
                    if (entry.Entries != null && entry.Entries.Any(p => p.Value != null && p.Value.Trim() == ""))
                    {
                        return(TrackSkip($"the {nameof(PatchConfig.Entries)} can't contain empty values."));
                    }
                    if (entry.Fields != null && entry.Fields.Any(p => p.Value == null || p.Value.Any(n => n.Value == null)))
                    {
                        return(TrackSkip($"the {nameof(PatchConfig.Fields)} can't contain empty values."));
                    }

                    // parse entries
                    IDictionary <string, TokenString> entries = new Dictionary <string, TokenString>();
                    if (entry.Entries != null)
                    {
                        foreach (KeyValuePair <string, string> pair in entry.Entries)
                        {
                            string key = pair.Key;
                            if (!this.TryParseTokenString(pair.Value, tokenContext, out string error, out TokenString value))
                            {
                                return(TrackSkip($"the {nameof(PatchConfig.Entries)} > '{key}' entry is invalid: {error}."));
                            }
                            entries[key] = value;
                        }
                    }

                    // parse fields
                    IDictionary <string, IDictionary <int, TokenString> > fields = new Dictionary <string, IDictionary <int, TokenString> >();
                    if (entry.Fields != null)
                    {
                        foreach (var recordPair in entry.Fields)
                        {
                            string key = recordPair.Key;
                            fields[key] = new Dictionary <int, TokenString>();

                            foreach (var fieldPair in recordPair.Value)
                            {
                                int field = fieldPair.Key;
                                if (!this.TryParseTokenString(fieldPair.Value, tokenContext, out string error, out TokenString value))
                                {
                                    return(TrackSkip($"the {nameof(PatchConfig.Fields)} > '{key}' > {field} field is invalid: {error}."));
                                }

                                fields[key][field] = value;
                            }
                        }
                    }

                    // save
                    patch = new EditDataPatch(entry.LogName, pack, assetName, conditions, entries, fields, this.Monitor, this.Helper.Content.NormaliseAssetName);
                }
                break;

                // edit image
                case PatchType.EditImage:
                {
                    // read patch mode
                    PatchMode patchMode = PatchMode.Replace;
                    if (!string.IsNullOrWhiteSpace(entry.PatchMode) && !Enum.TryParse(entry.PatchMode, true, out patchMode))
                    {
                        return(TrackSkip($"the {nameof(PatchConfig.PatchMode)} is invalid. Expected one of these values: [{string.Join(", ", Enum.GetNames(typeof(PatchMode)))}]."));
                    }

                    // save
                    if (!this.TryPrepareLocalAsset(pack, entry.FromFile, tokenContext, out string error, out TokenString fromAsset))
                    {
                        return(TrackSkip(error));
                    }
                    patch = new EditImagePatch(entry.LogName, pack, assetName, conditions, fromAsset, entry.FromArea, entry.ToArea, patchMode, this.Monitor, this.Helper.Content.NormaliseAssetName);
                }
                break;

                default:
                    return(TrackSkip($"unsupported patch type '{action}'."));
                }

                // skip if not enabled
                // note: we process the patch even if it's disabled, so any errors are caught by the modder instead of only failing after the patch is enabled.
                if (!enabled)
                {
                    return(TrackSkip($"{nameof(PatchConfig.Enabled)} is false.", warn: false));
                }

                // save patch
                this.PatchManager.Add(patch);
                return(true);
            }
            catch (Exception ex)
            {
                return(TrackSkip($"error reading info. Technical details:\n{ex}"));
            }
        }
Example #30
0
        public Task PatchAsync(IProgress <int> progress, PatchMode mode, float multiplier)
        {
            return(Task.Run(() =>
            {
                var _multiplier = BitConverter.GetBytes(multiplier);

                for (var i = 0; i < _candidateIds.Count(); i++)
                {
                    var id = _candidateIds[i];
                    var binary = _archive.Get(id);

                    var disassembly = D3DCompiler.Disassemble(binary);
                    if (disassembly.Contains("FP_FadePower"))
                    {
                        // actor - patching these causes glitches with camera-overlap fade
                        progress.Report(i + 1);
                        continue;
                    }

                    bool found = false;
                    byte[] newBinary = (byte[])binary.Clone();

                    foreach (var pos in binary.SigScan(block_sig))
                    {
                        switch (mode)
                        {
                        case PatchMode.DisableDithering:
                            Buffer.BlockCopy(nop_12x, 0, newBinary, pos + block_discard_offset, 12);
                            break;

                        case PatchMode.NarrowDithering:
                            Buffer.BlockCopy(_multiplier, 0, newBinary, pos + block_float_offset, 4);
                            break;
                        }
                        found = true;
                    }

                    foreach (var pos in binary.SigScan(block2_sig))
                    {
                        switch (mode)
                        {
                        case PatchMode.DisableDithering:
                            Buffer.BlockCopy(nop_12x, 0, newBinary, pos + block2_discard_offset, 12);
                            break;

                        case PatchMode.NarrowDithering:
                            Buffer.BlockCopy(_multiplier, 0, newBinary, pos + block2_float_offset, 4);
                            break;
                        }
                        found = true;
                    }

                    if (found)
                    {
                        int[] checksum = DXBCChecksum.DXBCChecksum.CalculateDXBCChecksum(newBinary);
                        Buffer.BlockCopy(checksum, 0, newBinary, 4, 16);
                        _archive.Replace(id, newBinary);
                    }

                    progress.Report(i + 1);
                }
            }));
        }