From 5b1ad78595d598c251fd608837ad0568c11c4991 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 14 Feb 2024 23:32:54 -0500 Subject: [PATCH] add warning for clarity when a runtime migration fails --- .../Migrations/BaseRuntimeMigration.cs | 13 ++- .../Migrations/Internal/IEditAssetMigrator.cs | 7 +- ...gration_2_0.ForBigCraftablesInformation.cs | 5 +- .../Migrations/Migration_2_0.ForBlueprints.cs | 5 +- .../Migrations/Migration_2_0.ForBoots.cs | 5 +- .../Migrations/Migration_2_0.ForCrops.cs | 5 +- .../Migrations/Migration_2_0.ForLocations.cs | 4 +- .../Migration_2_0.ForNpcDispositions.cs | 5 +- .../Migration_2_0.ForObjectInformation.cs | 4 +- .../Framework/Patches/EditDataPatch.cs | 97 +++++++++++++------ ContentPatcher/docs/author-migration-guide.md | 19 +++- 11 files changed, 118 insertions(+), 51 deletions(-) diff --git a/ContentPatcher/Framework/Migrations/BaseRuntimeMigration.cs b/ContentPatcher/Framework/Migrations/BaseRuntimeMigration.cs index 87d61a401..3a9618b14 100644 --- a/ContentPatcher/Framework/Migrations/BaseRuntimeMigration.cs +++ b/ContentPatcher/Framework/Migrations/BaseRuntimeMigration.cs @@ -67,9 +67,18 @@ public virtual bool TryApplyEditPatch(IPatch patch, IAssetData asset, out str { if (migrator.AppliesTo(patch.TargetAssetBeforeRedirection ?? asset.Name)) { - if (migrator.TryApplyEditPatch(editPatch, asset, out error)) - return true; + // log warning if runtime migration has issues + bool hasLoggedWarning = false; + void OnWarning(string warning, IMonitor monitor) + { + if (!hasLoggedWarning) + monitor.Log($"Data patch \"{patch.Path}\" reported warnings when applying runtime migration {this.Version}. (For the mod author: see https://smapi.io/cp-migrate to avoid runtime migrations.)", LogLevel.Warn); + hasLoggedWarning = true; + } + // apply + if (migrator.TryApplyEditPatch(editPatch, asset, OnWarning, out error)) + return true; if (error != null) return false; } diff --git a/ContentPatcher/Framework/Migrations/Internal/IEditAssetMigrator.cs b/ContentPatcher/Framework/Migrations/Internal/IEditAssetMigrator.cs index dfc3b715b..bf64258be 100644 --- a/ContentPatcher/Framework/Migrations/Internal/IEditAssetMigrator.cs +++ b/ContentPatcher/Framework/Migrations/Internal/IEditAssetMigrator.cs @@ -1,3 +1,4 @@ +using System; using System.Diagnostics.CodeAnalysis; using ContentPatcher.Framework.Conditions; using ContentPatcher.Framework.Patches; @@ -19,6 +20,10 @@ internal interface IEditAssetMigrator bool TryApplyLoadPatch(LoadPatch patch, IAssetName assetName, [NotNullWhen(true)] ref T? asset, out string? error); /// - bool TryApplyEditPatch(EditDataPatch patch, IAssetData asset, out string? error); + /// + /// + /// A callback to invoke before logging a warning message. The warning message is still logged after calling it. + /// + bool TryApplyEditPatch(EditDataPatch patch, IAssetData asset, Action onWarning, out string? error); } } diff --git a/ContentPatcher/Framework/Migrations/Migration_2_0.ForBigCraftablesInformation.cs b/ContentPatcher/Framework/Migrations/Migration_2_0.ForBigCraftablesInformation.cs index 985131e45..e111dcfdd 100644 --- a/ContentPatcher/Framework/Migrations/Migration_2_0.ForBigCraftablesInformation.cs +++ b/ContentPatcher/Framework/Migrations/Migration_2_0.ForBigCraftablesInformation.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using ContentPatcher.Framework.Migrations.Internal; @@ -54,12 +55,12 @@ public bool TryApplyLoadPatch(LoadPatch patch, IAssetName assetName, [NotNull } /// - public bool TryApplyEditPatch(EditDataPatch patch, IAssetData asset, out string? error) + public bool TryApplyEditPatch(EditDataPatch patch, IAssetData asset, Action onWarning, out string? error) { var data = asset.GetData>(); Dictionary tempData = this.GetOldFormat(data); Dictionary tempDataBackup = new(tempData); - patch.Edit>(new FakeAssetData(asset, this.GetOldAssetName(asset.Name), tempData)); + patch.Edit>(new FakeAssetData(asset, this.GetOldAssetName(asset.Name), tempData), onWarning); this.MergeIntoNewFormat(data, tempData, tempDataBackup); error = null; diff --git a/ContentPatcher/Framework/Migrations/Migration_2_0.ForBlueprints.cs b/ContentPatcher/Framework/Migrations/Migration_2_0.ForBlueprints.cs index 97b0cf521..be52b6932 100644 --- a/ContentPatcher/Framework/Migrations/Migration_2_0.ForBlueprints.cs +++ b/ContentPatcher/Framework/Migrations/Migration_2_0.ForBlueprints.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Text; @@ -55,12 +56,12 @@ public bool TryApplyLoadPatch(LoadPatch patch, IAssetName assetName, [NotNull } /// - public bool TryApplyEditPatch(EditDataPatch patch, IAssetData asset, out string? error) + public bool TryApplyEditPatch(EditDataPatch patch, IAssetData asset, Action onWarning, out string? error) { var data = asset.GetData>(); Dictionary tempData = this.GetOldFormat(data); Dictionary tempDataBackup = new(tempData); - patch.Edit>(new FakeAssetData(asset, this.GetOldAssetName(asset.Name), tempData)); + patch.Edit>(new FakeAssetData(asset, this.GetOldAssetName(asset.Name), tempData), onWarning); this.MergeIntoNewFormat(data, tempData, tempDataBackup); error = null; diff --git a/ContentPatcher/Framework/Migrations/Migration_2_0.ForBoots.cs b/ContentPatcher/Framework/Migrations/Migration_2_0.ForBoots.cs index 151a0dcd4..518b600c0 100644 --- a/ContentPatcher/Framework/Migrations/Migration_2_0.ForBoots.cs +++ b/ContentPatcher/Framework/Migrations/Migration_2_0.ForBoots.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using ContentPatcher.Framework.Migrations.Internal; @@ -45,10 +46,10 @@ public bool TryApplyLoadPatch(LoadPatch patch, IAssetName assetName, [NotNull } /// - public bool TryApplyEditPatch(EditDataPatch patch, IAssetData asset, out string? error) + public bool TryApplyEditPatch(EditDataPatch patch, IAssetData asset, Action onWarning, out string? error) { var data = (Dictionary)asset.Data; - patch.Edit>(asset); + patch.Edit>(asset, onWarning); this.MigrateData(data); error = null; diff --git a/ContentPatcher/Framework/Migrations/Migration_2_0.ForCrops.cs b/ContentPatcher/Framework/Migrations/Migration_2_0.ForCrops.cs index 217871fe5..e436d6ad1 100644 --- a/ContentPatcher/Framework/Migrations/Migration_2_0.ForCrops.cs +++ b/ContentPatcher/Framework/Migrations/Migration_2_0.ForCrops.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; @@ -51,12 +52,12 @@ public bool TryApplyLoadPatch(LoadPatch patch, IAssetName assetName, [NotNull } /// - public bool TryApplyEditPatch(EditDataPatch patch, IAssetData asset, out string? error) + public bool TryApplyEditPatch(EditDataPatch patch, IAssetData asset, Action onWarning, out string? error) { var data = asset.GetData>(); Dictionary tempData = this.GetOldFormat(data); Dictionary tempDataBackup = new(tempData); - patch.Edit>(new FakeAssetData(asset, asset.Name, tempData)); + patch.Edit>(new FakeAssetData(asset, asset.Name, tempData), onWarning); this.MergeIntoNewFormat(data, tempData, tempDataBackup); error = null; diff --git a/ContentPatcher/Framework/Migrations/Migration_2_0.ForLocations.cs b/ContentPatcher/Framework/Migrations/Migration_2_0.ForLocations.cs index 0b12d0d1d..ba639ce74 100644 --- a/ContentPatcher/Framework/Migrations/Migration_2_0.ForLocations.cs +++ b/ContentPatcher/Framework/Migrations/Migration_2_0.ForLocations.cs @@ -77,13 +77,13 @@ public bool TryApplyLoadPatch(LoadPatch patch, IAssetName assetName, [NotNull } /// - public bool TryApplyEditPatch(EditDataPatch patch, IAssetData asset, out string? error) + public bool TryApplyEditPatch(EditDataPatch patch, IAssetData asset, Action onWarning, out string? error) { var assetData = asset.GetData>(); Dictionary tempData = this.GetOldFormat(assetData, out HashSet skippedDueToNoData); Dictionary tempDataBackup = new(tempData); - patch.Edit>(new FakeAssetData(asset, asset.Name, tempData)); + patch.Edit>(new FakeAssetData(asset, asset.Name, tempData), onWarning); this.MergeIntoNewFormat(assetData, tempData, tempDataBackup, skippedDueToNoData, patch.ContentPack.Manifest.UniqueID); error = null; diff --git a/ContentPatcher/Framework/Migrations/Migration_2_0.ForNpcDispositions.cs b/ContentPatcher/Framework/Migrations/Migration_2_0.ForNpcDispositions.cs index 89afd3729..3c75eab4b 100644 --- a/ContentPatcher/Framework/Migrations/Migration_2_0.ForNpcDispositions.cs +++ b/ContentPatcher/Framework/Migrations/Migration_2_0.ForNpcDispositions.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Text; @@ -55,12 +56,12 @@ public bool TryApplyLoadPatch(LoadPatch patch, IAssetName assetName, [NotNull } /// - public bool TryApplyEditPatch(EditDataPatch patch, IAssetData asset, out string? error) + public bool TryApplyEditPatch(EditDataPatch patch, IAssetData asset, Action onWarning, out string? error) { var data = asset.GetData>(); Dictionary tempData = this.GetOldFormat(data); Dictionary tempDataBackup = new(tempData); - patch.Edit>(new FakeAssetData(asset, this.GetOldAssetName(asset.Name), tempData)); + patch.Edit>(new FakeAssetData(asset, this.GetOldAssetName(asset.Name), tempData), onWarning); this.MergeIntoNewFormat(data, tempData, tempDataBackup); error = null; diff --git a/ContentPatcher/Framework/Migrations/Migration_2_0.ForObjectInformation.cs b/ContentPatcher/Framework/Migrations/Migration_2_0.ForObjectInformation.cs index 453ae2238..408a95944 100644 --- a/ContentPatcher/Framework/Migrations/Migration_2_0.ForObjectInformation.cs +++ b/ContentPatcher/Framework/Migrations/Migration_2_0.ForObjectInformation.cs @@ -57,12 +57,12 @@ public bool TryApplyLoadPatch(LoadPatch patch, IAssetName assetName, [NotNull } /// - public bool TryApplyEditPatch(EditDataPatch patch, IAssetData asset, out string? error) + public bool TryApplyEditPatch(EditDataPatch patch, IAssetData asset, Action onWarning, out string? error) { var data = asset.GetData>(); Dictionary tempData = this.GetOldFormat(data); Dictionary tempDataBackup = new(tempData); - patch.Edit>(new FakeAssetData(asset, this.GetOldAssetName(asset.Name), tempData)); + patch.Edit>(new FakeAssetData(asset, this.GetOldAssetName(asset.Name), tempData), onWarning); this.MergeIntoNewFormat(data, tempData, tempDataBackup, patch.ContentPack.Manifest.UniqueID); error = null; diff --git a/ContentPatcher/Framework/Patches/EditDataPatch.cs b/ContentPatcher/Framework/Patches/EditDataPatch.cs index 1bc6e763d..d04258338 100644 --- a/ContentPatcher/Framework/Patches/EditDataPatch.cs +++ b/ContentPatcher/Framework/Patches/EditDataPatch.cs @@ -179,11 +179,19 @@ public override bool UpdateContext(IContext context) /// public override void Edit(IAssetData asset) + { + this.Edit(asset, onWarning: null); + } + + /// + /// + /// A callback to invoke before logging a warning message. The warning message is still logged after calling it. + public void Edit(IAssetData asset, Action? onWarning) { // get editor if (!this.EditorFactory.TryGetEditorFor(asset.Data, out IKeyValueEditor? editor)) { - this.WarnForPatch(this.GetEditorNotCompatibleError("the target asset", asset.Data, entryExists: true)); + this.WarnForPatch(this.GetEditorNotCompatibleError("the target asset", asset.Data, entryExists: true), onWarning); return; } @@ -218,7 +226,7 @@ public override void Edit(IAssetData asset) if (!this.EditorFactory.TryGetEditorFor(data, out editor)) { - this.WarnForPatch(this.GetEditorNotCompatibleError($"the field '{string.Join("' > '", path)}'", data, entryExists: parentEditor.HasEntry(key))); + this.WarnForPatch(this.GetEditorNotCompatibleError($"the field '{string.Join("' > '", path)}'", data, entryExists: parentEditor.HasEntry(key)), onWarning); return; } } @@ -226,7 +234,7 @@ public override void Edit(IAssetData asset) // apply edits char fieldDelimiter = this.GetStringFieldDelimiter(asset); - this.ApplyEdits(editor, fieldDelimiter); + this.ApplyEdits(editor, fieldDelimiter, onWarning); } /// @@ -310,17 +318,19 @@ private bool TryLoadFile(ITokenString fromFile, IContext context, [NotNullWhen(t /// Apply the patch to a data asset. /// The asset editor to apply. /// The field delimiter for the data asset's string values, if applicable. - private void ApplyEdits(IKeyValueEditor editor, char fieldDelimiter) + /// A callback to invoke before logging a warning message. The warning message is still logged after calling it. + private void ApplyEdits(IKeyValueEditor editor, char fieldDelimiter, Action? onWarning) { - this.ApplyRecords(editor); - this.ApplyFields(editor, fieldDelimiter); - this.ApplyTextOperations(editor, fieldDelimiter); - this.ApplyMoveEntries(editor); + this.ApplyRecords(editor, onWarning); + this.ApplyFields(editor, fieldDelimiter, onWarning); + this.ApplyTextOperations(editor, fieldDelimiter, onWarning); + this.ApplyMoveEntries(editor, onWarning); } /// Apply entry overwrites to the data asset. /// The asset editor to apply. - private void ApplyRecords(IKeyValueEditor editor) + /// A callback to invoke before logging a warning message. The warning message is still logged after calling it. + private void ApplyRecords(IKeyValueEditor editor, Action? onWarning) { int i = 0; foreach (EditDataPatchRecord record in this.Records) @@ -337,16 +347,16 @@ private void ApplyRecords(IKeyValueEditor editor) if (!editor.CanAddEntries && !editor.HasEntry(key)) { if (editor is ModelKeyValueEditor modelEditor) - this.WarnForRecord(i, $"this asset is a data model, which doesn't allow adding new entries. The entry '{record.Key.Value}' isn't defined in the model, must be one of: {string.Join(", ", modelEditor.FieldNames)}."); + this.WarnForRecord(i, $"this asset is a data model, which doesn't allow adding new entries. The entry '{record.Key.Value}' isn't defined in the model, must be one of: {string.Join(", ", modelEditor.FieldNames)}.", onWarning); else - this.WarnForRecord(i, $"this asset doesn't allow adding new entries, and the entry '{record.Key.Value}' isn't defined in the model."); + this.WarnForRecord(i, $"this asset doesn't allow adding new entries, and the entry '{record.Key.Value}' isn't defined in the model.", onWarning); continue; } if (targetType is null) { // This shouldn't happen in practice per the remarks on IKeyValueEditor.GetEntryType. // If you're here because it did happen, sorry! - this.WarnForRecord(i, $"this asset doesn't have a type for entry '{record.Key.Value}', so new entries can't be added."); + this.WarnForRecord(i, $"this asset doesn't have a type for entry '{record.Key.Value}', so new entries can't be added.", onWarning); continue; } @@ -365,7 +375,7 @@ private void ApplyRecords(IKeyValueEditor editor) } catch (Exception ex) { - this.WarnForRecord(i, $"failed converting {(fromType == JTokenType.Object ? "entry" : $"{fromType} value")} to the expected type '{targetType.FullName}': {ex.Message}."); + this.WarnForRecord(i, $"failed converting {(fromType == JTokenType.Object ? "entry" : $"{fromType} value")} to the expected type '{targetType.FullName}': {ex.Message}.", onWarning); continue; } @@ -376,7 +386,7 @@ private void ApplyRecords(IKeyValueEditor editor) } catch (Exception ex) { - this.WarnForRecord(i, $"failed setting '{key}' entry: {ex.Message}"); + this.WarnForRecord(i, $"failed setting '{key}' entry: {ex.Message}", onWarning); continue; } } @@ -385,7 +395,8 @@ private void ApplyRecords(IKeyValueEditor editor) /// Apply field overwrites to the data asset. /// The asset editor to apply. /// The field delimiter for the data asset's string values, if applicable. - private void ApplyFields(IKeyValueEditor editor, char fieldDelimiter) + /// A callback to invoke before logging a warning message. The warning message is still logged after calling it. + private void ApplyFields(IKeyValueEditor editor, char fieldDelimiter, Action? onWarning) { foreach (IGrouping recordGroup in this.Fields.GroupByIgnoreCase(p => p.EntryKey.Value!)) { @@ -396,7 +407,7 @@ private void ApplyFields(IKeyValueEditor editor, char fieldDelimiter) // skip if doesn't exist if (!editor.HasEntry(key)) { - this.WarnForField($"there's no record matching key '{key}' under {nameof(PatchConfig.Fields)}."); + this.WarnForField($"there's no record matching key '{key}' under {nameof(PatchConfig.Fields)}.", onWarning); continue; } @@ -408,12 +419,12 @@ private void ApplyFields(IKeyValueEditor editor, char fieldDelimiter) { if (!int.TryParse(field.FieldKey.Value, out int index)) { - this.WarnForField($"record '{key}' under {nameof(PatchConfig.Fields)} is a string, so it requires a field index between 0 and {actualFields.Length - 1} (received \"{field.FieldKey}\" instead))."); + this.WarnForField($"record '{key}' under {nameof(PatchConfig.Fields)} is a string, so it requires a field index between 0 and {actualFields.Length - 1} (received \"{field.FieldKey}\" instead)).", onWarning); continue; } if (index < 0 || index > actualFields.Length - 1) { - this.WarnForField($"record '{key}' under {nameof(PatchConfig.Fields)} has no field with index {index} (must be 0 to {actualFields.Length - 1})."); + this.WarnForField($"record '{key}' under {nameof(PatchConfig.Fields)} has no field with index {index} (must be 0 to {actualFields.Length - 1}).", onWarning); continue; } @@ -438,25 +449,33 @@ private void ApplyFields(IKeyValueEditor editor, char fieldDelimiter) /// Apply text operations to the data asset. /// The asset editor to apply. /// The field delimiter for the data asset's string values, if applicable. - private void ApplyTextOperations(IKeyValueEditor editor, char fieldDelimiter) + /// A callback to invoke before logging a warning message. The warning message is still logged after calling it. + private void ApplyTextOperations(IKeyValueEditor editor, char fieldDelimiter, Action? onWarning) { for (int i = 0; i < this.TextOperations.Length; i++) { if (!this.TryApplyTextOperation(this.TextOperations[i], editor, fieldDelimiter, out string? error)) - this.Monitor.Log($"Can't apply data patch \"{this.Path} > text operation #{i}\" to {this.TargetAsset}: {error}", LogLevel.Warn); + { + string warning = $"Can't apply data patch \"{this.Path} > text operation #{i}\" to {this.TargetAsset}: {error}"; + onWarning?.Invoke(warning, this.Monitor); + this.Monitor.Log(warning, LogLevel.Warn); + } } } /// Apply entry moves to the data asset. /// The asset editor to apply. - private void ApplyMoveEntries(IKeyValueEditor editor) + /// A callback to invoke before logging a warning message. The warning message is still logged after calling it. + private void ApplyMoveEntries(IKeyValueEditor editor, Action? onWarning) { if (!this.MoveRecords.Any()) return; if (!editor.CanMoveEntries) { - this.Monitor.LogOnce($"Can't move records for \"{this.Path}\" > {nameof(PatchConfig.MoveEntries)}: target asset '{this.TargetAsset}' isn't an ordered list.", LogLevel.Warn); + string warning = $"Can't move records for \"{this.Path}\" > {nameof(PatchConfig.MoveEntries)}: target asset '{this.TargetAsset}' isn't an ordered list."; + onWarning?.Invoke(warning, this.Monitor); + this.Monitor.LogOnce(warning, LogLevel.Warn); return; } @@ -491,24 +510,29 @@ private void ApplyMoveEntries(IKeyValueEditor editor) // log error if (result != MoveResult.Success) { + string logMessage; + switch (result) { case MoveResult.TargetNotFound: - this.Monitor.LogOnce($"Can't move {errorLabel}: no entry with ID '{key}' exists.", LogLevel.Warn); + logMessage = $"Can't move {errorLabel}: no entry with ID '{key}' exists."; break; case MoveResult.AnchorNotFound: - this.Monitor.LogOnce($"Can't move {errorLabel}: no entry with ID '{anchorKey}' exists.", LogLevel.Warn); + logMessage = $"Can't move {errorLabel}: no entry with ID '{anchorKey}' exists."; break; case MoveResult.AnchorIsMain: - this.Monitor.LogOnce($"Can't move {errorLabel}: can't move entry relative to itself.", LogLevel.Warn); + logMessage = $"Can't move {errorLabel}: can't move entry relative to itself."; break; default: - this.Monitor.LogOnce($"Can't move {errorLabel}: an unknown error occurred.", LogLevel.Warn); + logMessage = $"Can't move {errorLabel}: an unknown error occurred."; break; } + + onWarning?.Invoke(logMessage, this.Monitor); + this.Monitor.LogOnce(logMessage, LogLevel.Warn); } } } @@ -610,24 +634,33 @@ private char GetStringFieldDelimiter(IAssetInfo asset) /// Log a warning for an issue when applying the patch. /// The message to log. - private void WarnForPatch(string message) + /// A callback to invoke before logging a warning message. The warning message is still logged after calling it. + private void WarnForPatch(string message, Action? onWarning) { - this.Monitor.Log($"Can't apply data patch \"{this.Path}\" to {this.TargetAsset}: {message}", LogLevel.Warn); + string warning = $"Can't apply data patch \"{this.Path}\" to {this.TargetAsset}: {message}"; + onWarning?.Invoke(warning, this.Monitor); + this.Monitor.Log(warning, LogLevel.Warn); } /// Log a warning for an issue when applying a field edit. /// The message to log. - private void WarnForField(string message) + /// A callback to invoke before logging a warning message. The warning message is still logged after calling it. + private void WarnForField(string message, Action? onWarning) { - this.Monitor.Log($"Can't apply data patch \"{this.Path}\" to {this.TargetAsset}: {message}", LogLevel.Warn); + string warning = $"Can't apply data patch \"{this.Path}\" to {this.TargetAsset}: {message}"; + onWarning?.Invoke(warning, this.Monitor); + this.Monitor.Log(warning, LogLevel.Warn); } /// Log a warning for an issue when applying a record. /// The index of the record in the list. /// The message to log. - private void WarnForRecord(int index, string message) + /// A callback to invoke before logging a warning message. The warning message is still logged after calling it. + private void WarnForRecord(int index, string message, Action? onWarning) { - this.Monitor.Log($"Can't apply data patch \"{this.Path} > entry #{index}\" to {this.TargetAsset}: {message}", LogLevel.Warn); + string warning = $"Can't apply data patch \"{this.Path} > entry #{index}\" to {this.TargetAsset}: {message}"; + onWarning?.Invoke(warning, this.Monitor); + this.Monitor.Log(warning, LogLevel.Warn); } diff --git a/ContentPatcher/docs/author-migration-guide.md b/ContentPatcher/docs/author-migration-guide.md index 44057e3fa..7fc9551d6 100644 --- a/ContentPatcher/docs/author-migration-guide.md +++ b/ContentPatcher/docs/author-migration-guide.md @@ -63,12 +63,27 @@ Just set the `Format` field to the latest version shown in the [author guide](au then review the sections below for any changes you need to make. If a version isn't listed on this page, there's nothing else to change for that version. -> [!TIP] -> Feel free to [ask on Discord](https://smapi.io/community#Discord) if you need help! + + +
+ Why does my content pack show "reported warnings when applying runtime migration 2.0.0" in the + SMAPI console? +
+
+ +Your content pack has a `Format` version from before Stardew Valley 1.6, so Content Patcher tried +to migrate your content pack to the new asset format and failed. + +You can fix it by: +1. Setting `"Format": "2.0.0"` in your `content.json`. +2. Updating your content pack to the latest Content Patcher and Stardew Valley format (see below).
+> [!TIP] +> Feel free to [ask on Discord](https://smapi.io/community#Discord) if you need help! + ## Migration guides These changes only apply when you set the `Format` version in your `content.json` to the listed version or higher. See [release notes](release-notes.md) for a full list of changes.