Merge branch 'develop' of https://github.com/Pathoschild/SMAPI into harmony2
Conflicts: src/SMAPI/Constants.cs src/SMAPI/Framework/SCore.cs src/SMAPI/SMAPI.csproj
This commit is contained in:
commit
2bcee41151
|
@ -30,9 +30,6 @@ _ReSharper*/
|
||||||
# sensitive files
|
# sensitive files
|
||||||
appsettings.Development.json
|
appsettings.Development.json
|
||||||
|
|
||||||
# generated build files
|
|
||||||
build/0Harmony.*
|
|
||||||
|
|
||||||
# Azure generated files
|
# Azure generated files
|
||||||
src/SMAPI.Web/Properties/PublishProfiles/*.pubxml
|
src/SMAPI.Web/Properties/PublishProfiles/*.pubxml
|
||||||
|
|
||||||
|
|
Binary file not shown.
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
<!--set properties -->
|
<!--set properties -->
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Version>3.5.0</Version>
|
<Version>3.6.1</Version>
|
||||||
<Product>SMAPI</Product>
|
<Product>SMAPI</Product>
|
||||||
|
|
||||||
<LangVersion>latest</LangVersion>
|
<LangVersion>latest</LangVersion>
|
||||||
|
|
|
@ -1,45 +1,58 @@
|
||||||
← [README](README.md)
|
← [README](README.md)
|
||||||
|
|
||||||
# Release notes
|
# Release notes
|
||||||
## Upcoming release + 1
|
<!--
|
||||||
|
## Future release
|
||||||
* For modders:
|
* For modders:
|
||||||
* Migrated to Harmony 2.0 (see [_migrate to Harmony 2.0_](https://stardewvalleywiki.com/Modding:Migrate_to_Harmony_2.0) for more info).
|
* Migrated to Harmony 2.0 (see [_migrate to Harmony 2.0_](https://stardewvalleywiki.com/Modding:Migrate_to_Harmony_2.0) for more info).
|
||||||
* Added `harmony_summary` console command which lists all current Harmony patches, optionally with a search filter.
|
-->
|
||||||
|
|
||||||
|
## 3.6.1
|
||||||
|
Released 21 June 2020 for Stardew Valley 1.4.1 or later.
|
||||||
|
|
||||||
|
* Fixed event priority sorting.
|
||||||
|
|
||||||
|
## 3.6
|
||||||
|
Released 20 June 2020 for Stardew Valley 1.4.1 or later.
|
||||||
|
|
||||||
## Upcoming release
|
|
||||||
* For players:
|
* For players:
|
||||||
* Mod warnings are now listed alphabetically.
|
* Added crossplatform compatibility for mods which use the `[HarmonyPatch(type)]` attribute.
|
||||||
|
* Added experimental option to reduce startup time when loading mod DLLs (thanks to ZaneYork!). Enable `RewriteInParallel` in the `smapi-internal/config.json` to try it.
|
||||||
|
* Reduced processing time when a mod loads many unpacked images (thanks to Entoarox!).
|
||||||
|
* Mod load warnings are now listed alphabetically.
|
||||||
* MacOS files starting with `._` are now ignored and can no longer cause skipped mods.
|
* MacOS files starting with `._` are now ignored and can no longer cause skipped mods.
|
||||||
* Simplified paranoid warning logs and reduced their log level.
|
* Simplified paranoid warning logs and reduced their log level.
|
||||||
* Reduced startup time when loading mod DLLs (thanks to ZaneYork!).
|
|
||||||
* Reduced processing time when a mod loads many unpacked images (thanks to Entoarox!).
|
|
||||||
* Fixed `BadImageFormatException` error detection.
|
|
||||||
* Fixed black maps on Android for mods which use `.tmx` files.
|
* Fixed black maps on Android for mods which use `.tmx` files.
|
||||||
|
* Fixed `BadImageFormatException` error detection.
|
||||||
|
* Fixed `reload_i18n` command not reloading content pack translations.
|
||||||
|
|
||||||
* For the web UI:
|
* For the web UI:
|
||||||
* Added GitHub licenses to mod compatibility list.
|
* Added GitHub licenses to mod compatibility list.
|
||||||
|
* Improved JSON validator:
|
||||||
|
* added SMAPI `i18n` schema;
|
||||||
|
* editing an uploaded file now remembers the selected schema;
|
||||||
|
* changed default schema to plain JSON.
|
||||||
* Updated ModDrop URLs.
|
* Updated ModDrop URLs.
|
||||||
* Internal changes to improve performance and reliability.
|
* Internal changes to improve performance and reliability.
|
||||||
|
|
||||||
* For modders:
|
* For modders:
|
||||||
* Added [event priorities](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Events#Custom_priority) (thanks to spacechase0!).
|
* Added [event priorities](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Events#Custom_priority) (thanks to spacechase0!).
|
||||||
* Added [update subkeys](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Update_checks#Update_subkeys).
|
* Added [update subkeys](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Update_checks#Update_subkeys).
|
||||||
|
* Added [a custom build of Harmony](https://github.com/Pathoschild/Harmony#readme) to provide more useful stack traces in error logs.
|
||||||
|
* Added `harmony_summary` console command to list or search current Harmony patches.
|
||||||
* Added `Multiplayer.PeerConnected` event.
|
* Added `Multiplayer.PeerConnected` event.
|
||||||
* Added ability to override update keys from the compatibility list.
|
* Added support for overriding update keys from the wiki compatibility list.
|
||||||
* Harmony mods which use the `[HarmonyPatch(type)]` attribute now work crossplatform. Previously SMAPI couldn't rewrite types in custom attributes for compatibility.
|
* Improved mod rewriting for compatibility to support more cases (e.g. custom attributes and generic types).
|
||||||
* Improved mod rewriting for compatibility:
|
* Fixed `helper.Reflection` blocking access to game methods/properties intercepted by SMAPI.
|
||||||
* Fixed rewriting types in custom attributes.
|
|
||||||
* Fixed rewriting generic types to method references.
|
|
||||||
* Fixed `helper.Reflection` blocking access to game methods/properties that were extended by SMAPI.
|
|
||||||
* Fixed asset propagation for Gil's portraits.
|
* Fixed asset propagation for Gil's portraits.
|
||||||
* Fixed `.pdb` files ignored for error stack traces for mods rewritten by SMAPI.
|
* Fixed `.pdb` files ignored for error stack traces when mods are rewritten by SMAPI.
|
||||||
* Fixed `ModMessageReceived` event handlers not tracked for performance monitoring.
|
* Fixed `ModMessageReceived` event handlers not tracked for performance monitoring.
|
||||||
|
|
||||||
* For SMAPI developers:
|
* For SMAPI developers:
|
||||||
* Added support for bundling a custom Harmony build for upcoming use.
|
|
||||||
* Eliminated MongoDB storage in the web services, which complicated the code unnecessarily. The app still uses an abstract interface for storage, so we can wrap a distributed cache in the future if needed.
|
* Eliminated MongoDB storage in the web services, which complicated the code unnecessarily. The app still uses an abstract interface for storage, so we can wrap a distributed cache in the future if needed.
|
||||||
* Overhauled update checks to simplify individual clients, centralize common logic, and enable upcoming features.
|
* Overhauled update checks to simplify mod site integrations, centralize common logic, and enable upcoming features.
|
||||||
* Merged the separate legacy redirects app on AWS into the main app on Azure.
|
* Merged the separate legacy redirects app on AWS into the main app on Azure.
|
||||||
|
* Changed SMAPI's Harmony ID from `io.smapi` to `SMAPI` for readability in Harmony summaries.
|
||||||
|
|
||||||
## 3.5
|
## 3.5
|
||||||
Released 27 April 2020 for Stardew Valley 1.4.1 or later.
|
Released 27 April 2020 for Stardew Valley 1.4.1 or later.
|
||||||
|
|
|
@ -58,7 +58,7 @@ SMAPI uses a small number of conditional compilation constants, which you can se
|
||||||
flag | purpose
|
flag | purpose
|
||||||
---- | -------
|
---- | -------
|
||||||
`SMAPI_FOR_WINDOWS` | Whether SMAPI is being compiled on Windows for players on Windows. Set automatically in `crossplatform.targets`.
|
`SMAPI_FOR_WINDOWS` | Whether SMAPI is being compiled on Windows for players on Windows. Set automatically in `crossplatform.targets`.
|
||||||
`HARMONY_2` | Whether to enable experimental Harmony 2.0 support. Existing Harmony 1._x_ mods will be rewritten automatically for compatibility.
|
`HARMONY_2` | Whether to enable experimental Harmony 2.0 support and rewrite existing Harmony 1._x_ mods for compatibility. Note that you need to replace `build/0Harmony.dll` with a Harmony 2.0 build (or switch to a package reference) to use this flag.
|
||||||
|
|
||||||
## For SMAPI developers
|
## For SMAPI developers
|
||||||
### Compiling from source
|
### Compiling from source
|
||||||
|
@ -81,8 +81,8 @@ To prepare a crossplatform SMAPI release, you'll need to compile it on two platf
|
||||||
[crossplatforming info](https://stardewvalleywiki.com/Modding:Modder_Guide/Test_and_Troubleshoot#Testing_on_all_platforms)
|
[crossplatforming info](https://stardewvalleywiki.com/Modding:Modder_Guide/Test_and_Troubleshoot#Testing_on_all_platforms)
|
||||||
on the wiki for the first-time setup.
|
on the wiki for the first-time setup.
|
||||||
|
|
||||||
1. Update the version number in `.root/build/common.targets` and `Constants::Version`. Make sure
|
1. Update the version numbers in `build/common.targets`, `Constants`, and the `manifest.json` for
|
||||||
you use a [semantic version](https://semver.org). Recommended format:
|
bundled mods. Make sure you use a [semantic version](https://semver.org). Recommended format:
|
||||||
|
|
||||||
build type | format | example
|
build type | format | example
|
||||||
:--------- | :----------------------- | :------
|
:--------- | :----------------------- | :------
|
||||||
|
@ -102,14 +102,10 @@ on the wiki for the first-time setup.
|
||||||
3. Rename the folders to `SMAPI <version> installer` and `SMAPI <version> installer for developers`.
|
3. Rename the folders to `SMAPI <version> installer` and `SMAPI <version> installer for developers`.
|
||||||
4. Zip the two folders.
|
4. Zip the two folders.
|
||||||
|
|
||||||
### Using a custom Harmony build
|
### Custom Harmony build
|
||||||
The official SMAPI releases include [a custom build of Harmony](https://github.com/Pathoschild/Harmony),
|
SMAPI uses [a custom build of Harmony](https://github.com/Pathoschild/Harmony#readme), which is
|
||||||
but compiling from source will use the official build. To use a custom build, put `0Harmony.dll` in
|
included in the `build` folder. To use a different build, just replace `0Harmony.dll` in that
|
||||||
the `build` folder and it'll be referenced automatically.
|
folder.
|
||||||
|
|
||||||
Note that Harmony merges its dependencies into `0Harmony.dll` when compiled in release mode. To use
|
|
||||||
a debug build of Harmony, you'll need to manually copy those dependencies into your game's
|
|
||||||
`smapi-internal` folder.
|
|
||||||
|
|
||||||
## Release notes
|
## Release notes
|
||||||
See [release notes](../release-notes.md).
|
See [release notes](../release-notes.md).
|
||||||
|
|
|
@ -110,8 +110,9 @@ Available schemas:
|
||||||
|
|
||||||
format | schema URL
|
format | schema URL
|
||||||
------ | ----------
|
------ | ----------
|
||||||
[SMAPI `manifest.json`](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Manifest) | https://smapi.io/schemas/manifest.json
|
[SMAPI: `manifest.json`](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Manifest) | https://smapi.io/schemas/manifest.json
|
||||||
[Content Patcher `content.json`](https://github.com/Pathoschild/StardewMods/tree/develop/ContentPatcher#readme) | https://smapi.io/schemas/content-patcher.json
|
[SMAPI: translations (`i18n` folder)](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Translation) | https://smapi.io/schemas/i18n.json
|
||||||
|
[Content Patcher: `content.json`](https://github.com/Pathoschild/StardewMods/tree/develop/ContentPatcher#readme) | https://smapi.io/schemas/content-patcher.json
|
||||||
|
|
||||||
## Web API
|
## Web API
|
||||||
### Overview
|
### Overview
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
{
|
{
|
||||||
"Name": "Console Commands",
|
"Name": "Console Commands",
|
||||||
"Author": "SMAPI",
|
"Author": "SMAPI",
|
||||||
"Version": "3.5.0",
|
"Version": "3.6.1",
|
||||||
"Description": "Adds SMAPI console commands that let you manipulate the game.",
|
"Description": "Adds SMAPI console commands that let you manipulate the game.",
|
||||||
"UniqueID": "SMAPI.ConsoleCommands",
|
"UniqueID": "SMAPI.ConsoleCommands",
|
||||||
"EntryDll": "ConsoleCommands.dll",
|
"EntryDll": "ConsoleCommands.dll",
|
||||||
"MinimumApiVersion": "3.5.0"
|
"MinimumApiVersion": "3.6.1"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
{
|
{
|
||||||
"Name": "Save Backup",
|
"Name": "Save Backup",
|
||||||
"Author": "SMAPI",
|
"Author": "SMAPI",
|
||||||
"Version": "3.5.0",
|
"Version": "3.6.1",
|
||||||
"Description": "Automatically backs up all your saves once per day into its folder.",
|
"Description": "Automatically backs up all your saves once per day into its folder.",
|
||||||
"UniqueID": "SMAPI.SaveBackup",
|
"UniqueID": "SMAPI.SaveBackup",
|
||||||
"EntryDll": "SaveBackup.dll",
|
"EntryDll": "SaveBackup.dll",
|
||||||
"MinimumApiVersion": "3.5.0"
|
"MinimumApiVersion": "3.6.1"
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,12 +27,13 @@ namespace StardewModdingAPI.Web.Controllers
|
||||||
private readonly IDictionary<string, string> SchemaFormats = new Dictionary<string, string>
|
private readonly IDictionary<string, string> SchemaFormats = new Dictionary<string, string>
|
||||||
{
|
{
|
||||||
["none"] = "None",
|
["none"] = "None",
|
||||||
["manifest"] = "Manifest",
|
["manifest"] = "SMAPI: manifest",
|
||||||
|
["i18n"] = "SMAPI: translations (i18n)",
|
||||||
["content-patcher"] = "Content Patcher"
|
["content-patcher"] = "Content Patcher"
|
||||||
};
|
};
|
||||||
|
|
||||||
/// <summary>The schema ID to use if none was specified.</summary>
|
/// <summary>The schema ID to use if none was specified.</summary>
|
||||||
private string DefaultSchemaID = "manifest";
|
private string DefaultSchemaID = "none";
|
||||||
|
|
||||||
/// <summary>A token in an error message which indicates that the child errors should be displayed instead.</summary>
|
/// <summary>A token in an error message which indicates that the child errors should be displayed instead.</summary>
|
||||||
private readonly string TransparentToken = "$transparent";
|
private readonly string TransparentToken = "$transparent";
|
||||||
|
@ -57,16 +58,22 @@ namespace StardewModdingAPI.Web.Controllers
|
||||||
/// <summary>Render the schema validator UI.</summary>
|
/// <summary>Render the schema validator UI.</summary>
|
||||||
/// <param name="schemaName">The schema name with which to validate the JSON, or 'edit' to return to the edit screen.</param>
|
/// <param name="schemaName">The schema name with which to validate the JSON, or 'edit' to return to the edit screen.</param>
|
||||||
/// <param name="id">The stored file ID.</param>
|
/// <param name="id">The stored file ID.</param>
|
||||||
|
/// <param name="operation">The operation to perform for the selected log ID. This can be 'edit', or any other value to view.</param>
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
[Route("json")]
|
[Route("json")]
|
||||||
[Route("json/{schemaName}")]
|
[Route("json/{schemaName}")]
|
||||||
[Route("json/{schemaName}/{id}")]
|
[Route("json/{schemaName}/{id}")]
|
||||||
public async Task<ViewResult> Index(string schemaName = null, string id = null)
|
[Route("json/{schemaName}/{id}/{operation}")]
|
||||||
|
public async Task<ViewResult> Index(string schemaName = null, string id = null, string operation = null)
|
||||||
{
|
{
|
||||||
|
// parse arguments
|
||||||
schemaName = this.NormalizeSchemaName(schemaName);
|
schemaName = this.NormalizeSchemaName(schemaName);
|
||||||
|
bool hasId = !string.IsNullOrWhiteSpace(id);
|
||||||
|
bool isEditView = !hasId || operation?.Trim().ToLower() == "edit";
|
||||||
|
|
||||||
var result = new JsonValidatorModel(id, schemaName, this.SchemaFormats);
|
// build result model
|
||||||
if (string.IsNullOrWhiteSpace(id))
|
var result = this.GetModel(id, schemaName, isEditView);
|
||||||
|
if (!hasId)
|
||||||
return this.View("Index", result);
|
return this.View("Index", result);
|
||||||
|
|
||||||
// fetch raw JSON
|
// fetch raw JSON
|
||||||
|
@ -76,7 +83,7 @@ namespace StardewModdingAPI.Web.Controllers
|
||||||
result.SetContent(file.Content, expiry: file.Expiry, uploadWarning: file.Warning);
|
result.SetContent(file.Content, expiry: file.Expiry, uploadWarning: file.Warning);
|
||||||
|
|
||||||
// skip parsing if we're going to the edit screen
|
// skip parsing if we're going to the edit screen
|
||||||
if (schemaName?.ToLower() == "edit")
|
if (isEditView)
|
||||||
return this.View("Index", result);
|
return this.View("Index", result);
|
||||||
|
|
||||||
// parse JSON
|
// parse JSON
|
||||||
|
@ -130,7 +137,7 @@ namespace StardewModdingAPI.Web.Controllers
|
||||||
public async Task<ActionResult> PostAsync(JsonValidatorRequestModel request)
|
public async Task<ActionResult> PostAsync(JsonValidatorRequestModel request)
|
||||||
{
|
{
|
||||||
if (request == null)
|
if (request == null)
|
||||||
return this.View("Index", this.GetModel(null, null).SetUploadError("The request seems to be invalid."));
|
return this.View("Index", this.GetModel(null, null, isEditView: true).SetUploadError("The request seems to be invalid."));
|
||||||
|
|
||||||
// normalize schema name
|
// normalize schema name
|
||||||
string schemaName = this.NormalizeSchemaName(request.SchemaName);
|
string schemaName = this.NormalizeSchemaName(request.SchemaName);
|
||||||
|
@ -138,12 +145,12 @@ namespace StardewModdingAPI.Web.Controllers
|
||||||
// get raw text
|
// get raw text
|
||||||
string input = request.Content;
|
string input = request.Content;
|
||||||
if (string.IsNullOrWhiteSpace(input))
|
if (string.IsNullOrWhiteSpace(input))
|
||||||
return this.View("Index", this.GetModel(null, schemaName).SetUploadError("The JSON file seems to be empty."));
|
return this.View("Index", this.GetModel(null, schemaName, isEditView: true).SetUploadError("The JSON file seems to be empty."));
|
||||||
|
|
||||||
// upload file
|
// upload file
|
||||||
UploadResult result = await this.Storage.SaveAsync(input);
|
UploadResult result = await this.Storage.SaveAsync(input);
|
||||||
if (!result.Succeeded)
|
if (!result.Succeeded)
|
||||||
return this.View("Index", this.GetModel(result.ID, schemaName).SetUploadError(result.UploadError));
|
return this.View("Index", this.GetModel(result.ID, schemaName, isEditView: true).SetContent(input, null).SetUploadError(result.UploadError));
|
||||||
|
|
||||||
// redirect to view
|
// redirect to view
|
||||||
return this.Redirect(this.Url.PlainAction("Index", "JsonValidator", new { schemaName = schemaName, id = result.ID }));
|
return this.Redirect(this.Url.PlainAction("Index", "JsonValidator", new { schemaName = schemaName, id = result.ID }));
|
||||||
|
@ -156,9 +163,10 @@ namespace StardewModdingAPI.Web.Controllers
|
||||||
/// <summary>Build a JSON validator model.</summary>
|
/// <summary>Build a JSON validator model.</summary>
|
||||||
/// <param name="pasteID">The stored file ID.</param>
|
/// <param name="pasteID">The stored file ID.</param>
|
||||||
/// <param name="schemaName">The schema name with which the JSON was validated.</param>
|
/// <param name="schemaName">The schema name with which the JSON was validated.</param>
|
||||||
private JsonValidatorModel GetModel(string pasteID, string schemaName)
|
/// <param name="isEditView">Whether to show the edit view.</param>
|
||||||
|
private JsonValidatorModel GetModel(string pasteID, string schemaName, bool isEditView)
|
||||||
{
|
{
|
||||||
return new JsonValidatorModel(pasteID, schemaName, this.SchemaFormats);
|
return new JsonValidatorModel(pasteID, schemaName, this.SchemaFormats, isEditView);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Get a normalized schema name, or the <see cref="DefaultSchemaID"/> if blank.</summary>
|
/// <summary>Get a normalized schema name, or the <see cref="DefaultSchemaID"/> if blank.</summary>
|
||||||
|
|
|
@ -10,6 +10,9 @@ namespace StardewModdingAPI.Web.ViewModels.JsonValidator
|
||||||
/*********
|
/*********
|
||||||
** Accessors
|
** Accessors
|
||||||
*********/
|
*********/
|
||||||
|
/// <summary>Whether to show the edit view.</summary>
|
||||||
|
public bool IsEditView { get; set; }
|
||||||
|
|
||||||
/// <summary>The paste ID.</summary>
|
/// <summary>The paste ID.</summary>
|
||||||
public string PasteID { get; set; }
|
public string PasteID { get; set; }
|
||||||
|
|
||||||
|
@ -51,11 +54,13 @@ namespace StardewModdingAPI.Web.ViewModels.JsonValidator
|
||||||
/// <param name="pasteID">The stored file ID.</param>
|
/// <param name="pasteID">The stored file ID.</param>
|
||||||
/// <param name="schemaName">The schema name with which the JSON was validated.</param>
|
/// <param name="schemaName">The schema name with which the JSON was validated.</param>
|
||||||
/// <param name="schemaFormats">The supported JSON schemas (names indexed by ID).</param>
|
/// <param name="schemaFormats">The supported JSON schemas (names indexed by ID).</param>
|
||||||
public JsonValidatorModel(string pasteID, string schemaName, IDictionary<string, string> schemaFormats)
|
/// <param name="isEditView">Whether to show the edit view.</param>
|
||||||
|
public JsonValidatorModel(string pasteID, string schemaName, IDictionary<string, string> schemaFormats, bool isEditView)
|
||||||
{
|
{
|
||||||
this.PasteID = pasteID;
|
this.PasteID = pasteID;
|
||||||
this.SchemaName = schemaName;
|
this.SchemaName = schemaName;
|
||||||
this.SchemaFormats = schemaFormats;
|
this.SchemaFormats = schemaFormats;
|
||||||
|
this.IsEditView = isEditView;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Set the validated content.</summary>
|
/// <summary>Set the validated content.</summary>
|
||||||
|
|
|
@ -9,7 +9,6 @@
|
||||||
string newUploadUrl = this.Url.PlainAction("Index", "JsonValidator", new { schemaName = Model.SchemaName });
|
string newUploadUrl = this.Url.PlainAction("Index", "JsonValidator", new { schemaName = Model.SchemaName });
|
||||||
string schemaDisplayName = null;
|
string schemaDisplayName = null;
|
||||||
bool isValidSchema = Model.SchemaName != null && Model.SchemaFormats.TryGetValue(Model.SchemaName, out schemaDisplayName) && schemaDisplayName?.ToLower() != "none";
|
bool isValidSchema = Model.SchemaName != null && Model.SchemaFormats.TryGetValue(Model.SchemaName, out schemaDisplayName) && schemaDisplayName?.ToLower() != "none";
|
||||||
bool isEditView = Model.Content == null || Model.SchemaName?.ToLower() == "edit";
|
|
||||||
|
|
||||||
// build title
|
// build title
|
||||||
ViewData["Title"] = "JSON validator";
|
ViewData["Title"] = "JSON validator";
|
||||||
|
@ -63,7 +62,7 @@ else if (Model.ParseError != null)
|
||||||
<small v-pre>Error details: @Model.ParseError</small>
|
<small v-pre>Error details: @Model.ParseError</small>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
else if (!isEditView && Model.PasteID != null)
|
else if (!Model.IsEditView && Model.PasteID != null)
|
||||||
{
|
{
|
||||||
<div class="banner success">
|
<div class="banner success">
|
||||||
<strong>Share this link to let someone else see this page:</strong> <code>@curPageUrl</code><br />
|
<strong>Share this link to let someone else see this page:</strong> <code>@curPageUrl</code><br />
|
||||||
|
@ -84,7 +83,7 @@ else if (!isEditView && Model.PasteID != null)
|
||||||
}
|
}
|
||||||
|
|
||||||
@* upload new file *@
|
@* upload new file *@
|
||||||
@if (isEditView)
|
@if (Model.IsEditView)
|
||||||
{
|
{
|
||||||
<h2>Upload a JSON file</h2>
|
<h2>Upload a JSON file</h2>
|
||||||
<form action="@this.Url.PlainAction("PostAsync", "JsonValidator")" method="post">
|
<form action="@this.Url.PlainAction("PostAsync", "JsonValidator")" method="post">
|
||||||
|
@ -112,7 +111,7 @@ else if (!isEditView && Model.PasteID != null)
|
||||||
}
|
}
|
||||||
|
|
||||||
@* validation results *@
|
@* validation results *@
|
||||||
@if (!isEditView)
|
@if (!Model.IsEditView)
|
||||||
{
|
{
|
||||||
<div id="output">
|
<div id="output">
|
||||||
@if (Model.UploadError == null)
|
@if (Model.UploadError == null)
|
||||||
|
@ -158,7 +157,7 @@ else if (!isEditView && Model.PasteID != null)
|
||||||
{
|
{
|
||||||
<option value="@pair.Key" selected="@(Model.SchemaName == pair.Key)">@pair.Value</option>
|
<option value="@pair.Key" selected="@(Model.SchemaName == pair.Key)">@pair.Value</option>
|
||||||
}
|
}
|
||||||
</select>) or <a href="@(this.Url.PlainAction("Index", "JsonValidator", new { id = this.Model.PasteID, schemaName = "edit" }))">edit this file</a>.
|
</select>) or <a href="@(this.Url.PlainAction("Index", "JsonValidator", new { id = this.Model.PasteID, schemaName = this.Model.SchemaName, operation = "edit" }))">edit this file</a>.
|
||||||
</div>
|
</div>
|
||||||
<pre id="raw-content" class="sunlight-highlight-javascript">@Model.Content</pre>
|
<pre id="raw-content" class="sunlight-highlight-javascript">@Model.Content</pre>
|
||||||
|
|
||||||
|
|
|
@ -8,8 +8,8 @@
|
||||||
|
|
||||||
TimeSpan staleAge = DateTimeOffset.UtcNow - Model.LastUpdated;
|
TimeSpan staleAge = DateTimeOffset.UtcNow - Model.LastUpdated;
|
||||||
|
|
||||||
bool hasBeta = true; // Model.BetaVersion != null;
|
bool hasBeta = Model.BetaVersion != null;
|
||||||
string betaLabel = "SMAPI 3.6 only"; //"SDV @Model.BetaVersion only";
|
string betaLabel = "SDV @Model.BetaVersion only";
|
||||||
}
|
}
|
||||||
@section Head {
|
@section Head {
|
||||||
<link rel="stylesheet" href="~/Content/css/mods.css?r=20200218" />
|
<link rel="stylesheet" href="~/Content/css/mods.css?r=20200218" />
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"$id": "https://smapi.io/schemas/i18n.json",
|
||||||
|
"title": "SMAPI i18n file",
|
||||||
|
"description": "A translation file for a SMAPI mod or content pack.",
|
||||||
|
"@documentationUrl": "https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Translation",
|
||||||
|
"type": "object",
|
||||||
|
|
||||||
|
"properties": {
|
||||||
|
"$schema": {
|
||||||
|
"title": "Schema",
|
||||||
|
"description": "A reference to this JSON schema. Not part of the actual format, but useful for validation tools.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "https://smapi.io/schemas/manifest.json"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string",
|
||||||
|
"@errorMessages": {
|
||||||
|
"type": "Invalid property. Translation files can only contain text property values."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -24,7 +24,7 @@ namespace StardewModdingAPI
|
||||||
** Public
|
** Public
|
||||||
****/
|
****/
|
||||||
/// <summary>SMAPI's current semantic version.</summary>
|
/// <summary>SMAPI's current semantic version.</summary>
|
||||||
public static ISemanticVersion ApiVersion { get; } = new Toolkit.SemanticVersion("3.6.0");
|
public static ISemanticVersion ApiVersion { get; } = new Toolkit.SemanticVersion("3.6.1");
|
||||||
|
|
||||||
/// <summary>The minimum supported version of Stardew Valley.</summary>
|
/// <summary>The minimum supported version of Stardew Valley.</summary>
|
||||||
public static ISemanticVersion MinimumGameVersion { get; } = new GameVersion("1.4.5");
|
public static ISemanticVersion MinimumGameVersion { get; } = new GameVersion("1.4.5");
|
||||||
|
@ -56,6 +56,14 @@ namespace StardewModdingAPI
|
||||||
/****
|
/****
|
||||||
** Internal
|
** Internal
|
||||||
****/
|
****/
|
||||||
|
/// <summary>Whether SMAPI was compiled in debug mode.</summary>
|
||||||
|
internal const bool IsDebugBuild =
|
||||||
|
#if DEBUG
|
||||||
|
true;
|
||||||
|
#else
|
||||||
|
false;
|
||||||
|
#endif
|
||||||
|
|
||||||
/// <summary>The URL of the SMAPI home page.</summary>
|
/// <summary>The URL of the SMAPI home page.</summary>
|
||||||
internal const string HomePageUrl = "https://smapi.io";
|
internal const string HomePageUrl = "https://smapi.io";
|
||||||
|
|
||||||
|
|
|
@ -1,16 +1,27 @@
|
||||||
#if HARMONY_2
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
#if HARMONY_2
|
||||||
using HarmonyLib;
|
using HarmonyLib;
|
||||||
|
#else
|
||||||
|
using Harmony;
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace StardewModdingAPI.Framework.Commands
|
namespace StardewModdingAPI.Framework.Commands
|
||||||
{
|
{
|
||||||
/// <summary>The 'harmony_summary' SMAPI console command.</summary>
|
/// <summary>The 'harmony_summary' SMAPI console command.</summary>
|
||||||
internal class HarmonySummaryCommand : IInternalCommand
|
internal class HarmonySummaryCommand : IInternalCommand
|
||||||
{
|
{
|
||||||
|
#if !HARMONY_2
|
||||||
|
/*********
|
||||||
|
** Fields
|
||||||
|
*********/
|
||||||
|
/// <summary>The Harmony instance through which to fetch patch info.</summary>
|
||||||
|
private readonly HarmonyInstance HarmonyInstance = HarmonyInstance.Create($"SMAPI.{nameof(HarmonySummaryCommand)}");
|
||||||
|
#endif
|
||||||
|
|
||||||
/*********
|
/*********
|
||||||
** Accessors
|
** Accessors
|
||||||
*********/
|
*********/
|
||||||
|
@ -45,7 +56,16 @@ namespace StardewModdingAPI.Framework.Commands
|
||||||
foreach (var ownerGroup in match.PatchTypesByOwner.OrderBy(p => p.Key))
|
foreach (var ownerGroup in match.PatchTypesByOwner.OrderBy(p => p.Key))
|
||||||
{
|
{
|
||||||
var sortedTypes = ownerGroup.Value
|
var sortedTypes = ownerGroup.Value
|
||||||
.OrderBy(p => p switch { PatchType.Prefix => 0, PatchType.Postfix => 1, PatchType.Finalizer => 2, PatchType.Transpiler => 3, _ => 4 });
|
.OrderBy(p => p switch
|
||||||
|
{
|
||||||
|
PatchType.Prefix => 0,
|
||||||
|
PatchType.Postfix => 1,
|
||||||
|
#if HARMONY_2
|
||||||
|
PatchType.Finalizer => 2,
|
||||||
|
#endif
|
||||||
|
PatchType.Transpiler => 3,
|
||||||
|
_ => 4
|
||||||
|
});
|
||||||
|
|
||||||
result.AppendLine($" - {ownerGroup.Key} ({string.Join(", ", sortedTypes).ToLower()})");
|
result.AppendLine($" - {ownerGroup.Key} ({string.Join(", ", sortedTypes).ToLower()})");
|
||||||
}
|
}
|
||||||
|
@ -91,15 +111,26 @@ namespace StardewModdingAPI.Framework.Commands
|
||||||
/// <summary>Get all current Harmony patches.</summary>
|
/// <summary>Get all current Harmony patches.</summary>
|
||||||
private IEnumerable<SearchResult> GetAllPatches()
|
private IEnumerable<SearchResult> GetAllPatches()
|
||||||
{
|
{
|
||||||
|
#if HARMONY_2
|
||||||
foreach (MethodBase method in Harmony.GetAllPatchedMethods())
|
foreach (MethodBase method in Harmony.GetAllPatchedMethods())
|
||||||
|
#else
|
||||||
|
foreach (MethodBase method in this.HarmonyInstance.GetPatchedMethods())
|
||||||
|
#endif
|
||||||
{
|
{
|
||||||
// get metadata for method
|
// get metadata for method
|
||||||
|
#if HARMONY_2
|
||||||
HarmonyLib.Patches patchInfo = Harmony.GetPatchInfo(method);
|
HarmonyLib.Patches patchInfo = Harmony.GetPatchInfo(method);
|
||||||
|
#else
|
||||||
|
Harmony.Patches patchInfo = this.HarmonyInstance.GetPatchInfo(method);
|
||||||
|
#endif
|
||||||
|
|
||||||
IDictionary<PatchType, IReadOnlyCollection<Patch>> patchGroups = new Dictionary<PatchType, IReadOnlyCollection<Patch>>
|
IDictionary<PatchType, IReadOnlyCollection<Patch>> patchGroups = new Dictionary<PatchType, IReadOnlyCollection<Patch>>
|
||||||
{
|
{
|
||||||
[PatchType.Prefix] = patchInfo.Prefixes,
|
[PatchType.Prefix] = patchInfo.Prefixes,
|
||||||
[PatchType.Postfix] = patchInfo.Postfixes,
|
[PatchType.Postfix] = patchInfo.Postfixes,
|
||||||
|
#if HARMONY_2
|
||||||
[PatchType.Finalizer] = patchInfo.Finalizers,
|
[PatchType.Finalizer] = patchInfo.Finalizers,
|
||||||
|
#endif
|
||||||
[PatchType.Transpiler] = patchInfo.Transpilers
|
[PatchType.Transpiler] = patchInfo.Transpilers
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -129,8 +160,10 @@ namespace StardewModdingAPI.Framework.Commands
|
||||||
/// <summary>A postfix patch.</summary>
|
/// <summary>A postfix patch.</summary>
|
||||||
Postfix,
|
Postfix,
|
||||||
|
|
||||||
|
#if HARMONY_2
|
||||||
/// <summary>A finalizer patch.</summary>
|
/// <summary>A finalizer patch.</summary>
|
||||||
Finalizer,
|
Finalizer,
|
||||||
|
#endif
|
||||||
|
|
||||||
/// <summary>A transpiler patch.</summary>
|
/// <summary>A transpiler patch.</summary>
|
||||||
Transpiler
|
Transpiler
|
||||||
|
@ -167,4 +200,3 @@ namespace StardewModdingAPI.Framework.Commands
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
|
@ -47,7 +47,7 @@ namespace StardewModdingAPI.Framework.Events
|
||||||
if (!(obj is ManagedEventHandler<TEventArgs> other))
|
if (!(obj is ManagedEventHandler<TEventArgs> other))
|
||||||
throw new ArgumentException("Can't compare to an unrelated object type.");
|
throw new ArgumentException("Can't compare to an unrelated object type.");
|
||||||
|
|
||||||
int priorityCompare = this.Priority.CompareTo(other.Priority);
|
int priorityCompare = -this.Priority.CompareTo(other.Priority); // higher value = sort first
|
||||||
return priorityCompare != 0
|
return priorityCompare != 0
|
||||||
? priorityCompare
|
? priorityCompare
|
||||||
: this.RegistrationOrder.CompareTo(other.RegistrationOrder);
|
: this.RegistrationOrder.CompareTo(other.RegistrationOrder);
|
||||||
|
|
|
@ -76,9 +76,10 @@ namespace StardewModdingAPI.Framework.ModLoading
|
||||||
/// <param name="mod">The mod for which the assembly is being loaded.</param>
|
/// <param name="mod">The mod for which the assembly is being loaded.</param>
|
||||||
/// <param name="assemblyPath">The assembly file path.</param>
|
/// <param name="assemblyPath">The assembly file path.</param>
|
||||||
/// <param name="assumeCompatible">Assume the mod is compatible, even if incompatible code is detected.</param>
|
/// <param name="assumeCompatible">Assume the mod is compatible, even if incompatible code is detected.</param>
|
||||||
|
/// <param name="rewriteInParallel">Whether to enable experimental parallel rewriting.</param>
|
||||||
/// <returns>Returns the rewrite metadata for the preprocessed assembly.</returns>
|
/// <returns>Returns the rewrite metadata for the preprocessed assembly.</returns>
|
||||||
/// <exception cref="IncompatibleInstructionException">An incompatible CIL instruction was found while rewriting the assembly.</exception>
|
/// <exception cref="IncompatibleInstructionException">An incompatible CIL instruction was found while rewriting the assembly.</exception>
|
||||||
public Assembly Load(IModMetadata mod, string assemblyPath, bool assumeCompatible)
|
public Assembly Load(IModMetadata mod, string assemblyPath, bool assumeCompatible, bool rewriteInParallel)
|
||||||
{
|
{
|
||||||
// get referenced local assemblies
|
// get referenced local assemblies
|
||||||
AssemblyParseResult[] assemblies;
|
AssemblyParseResult[] assemblies;
|
||||||
|
@ -108,7 +109,7 @@ namespace StardewModdingAPI.Framework.ModLoading
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// rewrite assembly
|
// rewrite assembly
|
||||||
bool changed = this.RewriteAssembly(mod, assembly.Definition, loggedMessages, logPrefix: " ");
|
bool changed = this.RewriteAssembly(mod, assembly.Definition, loggedMessages, logPrefix: " ", rewriteInParallel);
|
||||||
|
|
||||||
// detect broken assembly reference
|
// detect broken assembly reference
|
||||||
foreach (AssemblyNameReference reference in assembly.Definition.MainModule.AssemblyReferences)
|
foreach (AssemblyNameReference reference in assembly.Definition.MainModule.AssemblyReferences)
|
||||||
|
@ -279,9 +280,10 @@ namespace StardewModdingAPI.Framework.ModLoading
|
||||||
/// <param name="assembly">The assembly to rewrite.</param>
|
/// <param name="assembly">The assembly to rewrite.</param>
|
||||||
/// <param name="loggedMessages">The messages that have already been logged for this mod.</param>
|
/// <param name="loggedMessages">The messages that have already been logged for this mod.</param>
|
||||||
/// <param name="logPrefix">A string to prefix to log messages.</param>
|
/// <param name="logPrefix">A string to prefix to log messages.</param>
|
||||||
|
/// <param name="rewriteInParallel">Whether to enable experimental parallel rewriting.</param>
|
||||||
/// <returns>Returns whether the assembly was modified.</returns>
|
/// <returns>Returns whether the assembly was modified.</returns>
|
||||||
/// <exception cref="IncompatibleInstructionException">An incompatible CIL instruction was found while rewriting the assembly.</exception>
|
/// <exception cref="IncompatibleInstructionException">An incompatible CIL instruction was found while rewriting the assembly.</exception>
|
||||||
private bool RewriteAssembly(IModMetadata mod, AssemblyDefinition assembly, HashSet<string> loggedMessages, string logPrefix)
|
private bool RewriteAssembly(IModMetadata mod, AssemblyDefinition assembly, HashSet<string> loggedMessages, string logPrefix, bool rewriteInParallel)
|
||||||
{
|
{
|
||||||
ModuleDefinition module = assembly.MainModule;
|
ModuleDefinition module = assembly.MainModule;
|
||||||
string filename = $"{assembly.Name.Name}.dll";
|
string filename = $"{assembly.Name.Name}.dll";
|
||||||
|
@ -330,7 +332,7 @@ namespace StardewModdingAPI.Framework.ModLoading
|
||||||
return rewritten;
|
return rewritten;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
bool anyRewritten = rewriter.RewriteModule();
|
bool anyRewritten = rewriter.RewriteModule(rewriteInParallel);
|
||||||
|
|
||||||
// handle rewrite flags
|
// handle rewrite flags
|
||||||
foreach (IInstructionHandler handler in handlers)
|
foreach (IInstructionHandler handler in handlers)
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
@ -56,15 +57,20 @@ namespace StardewModdingAPI.Framework.ModLoading.Framework
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Rewrite the loaded module code.</summary>
|
/// <summary>Rewrite the loaded module code.</summary>
|
||||||
|
/// <param name="rewriteInParallel">Whether to enable experimental parallel rewriting.</param>
|
||||||
/// <returns>Returns whether the module was modified.</returns>
|
/// <returns>Returns whether the module was modified.</returns>
|
||||||
public bool RewriteModule()
|
public bool RewriteModule(bool rewriteInParallel)
|
||||||
{
|
{
|
||||||
int typesChanged = 0;
|
IEnumerable<TypeDefinition> types = this.Module.GetTypes().Where(type => type.BaseType != null); // skip special types like <Module>
|
||||||
Exception exception = null;
|
|
||||||
|
|
||||||
Parallel.ForEach(
|
// experimental parallel rewriting
|
||||||
source: this.Module.GetTypes().Where(type => type.BaseType != null), // skip special types like <Module>
|
// This may cause intermittent startup errors and is disabled by default: https://github.com/Pathoschild/SMAPI/issues/721
|
||||||
body: type =>
|
if (rewriteInParallel)
|
||||||
|
{
|
||||||
|
int typesChanged = 0;
|
||||||
|
Exception exception = null;
|
||||||
|
|
||||||
|
Parallel.ForEach(types, type =>
|
||||||
{
|
{
|
||||||
if (exception != null)
|
if (exception != null)
|
||||||
return;
|
return;
|
||||||
|
@ -72,50 +78,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Framework
|
||||||
bool changed = false;
|
bool changed = false;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
changed |= this.RewriteCustomAttributes(type.CustomAttributes);
|
changed = this.RewriteTypeDefinition(type);
|
||||||
changed |= this.RewriteGenericParameters(type.GenericParameters);
|
|
||||||
|
|
||||||
foreach (InterfaceImplementation @interface in type.Interfaces)
|
|
||||||
changed |= this.RewriteTypeReference(@interface.InterfaceType, newType => @interface.InterfaceType = newType);
|
|
||||||
|
|
||||||
if (type.BaseType.FullName != "System.Object")
|
|
||||||
changed |= this.RewriteTypeReference(type.BaseType, newType => type.BaseType = newType);
|
|
||||||
|
|
||||||
foreach (MethodDefinition method in type.Methods)
|
|
||||||
{
|
|
||||||
changed |= this.RewriteTypeReference(method.ReturnType, newType => method.ReturnType = newType);
|
|
||||||
changed |= this.RewriteGenericParameters(method.GenericParameters);
|
|
||||||
changed |= this.RewriteCustomAttributes(method.CustomAttributes);
|
|
||||||
|
|
||||||
foreach (ParameterDefinition parameter in method.Parameters)
|
|
||||||
changed |= this.RewriteTypeReference(parameter.ParameterType, newType => parameter.ParameterType = newType);
|
|
||||||
|
|
||||||
foreach (var methodOverride in method.Overrides)
|
|
||||||
changed |= this.RewriteMethodReference(methodOverride);
|
|
||||||
|
|
||||||
if (method.HasBody)
|
|
||||||
{
|
|
||||||
foreach (VariableDefinition variable in method.Body.Variables)
|
|
||||||
changed |= this.RewriteTypeReference(variable.VariableType, newType => variable.VariableType = newType);
|
|
||||||
|
|
||||||
// check CIL instructions
|
|
||||||
ILProcessor cil = method.Body.GetILProcessor();
|
|
||||||
Collection<Instruction> instructions = cil.Body.Instructions;
|
|
||||||
for (int i = 0; i < instructions.Count; i++)
|
|
||||||
{
|
|
||||||
var instruction = instructions[i];
|
|
||||||
if (instruction.OpCode.Code == Code.Nop)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
changed |= this.RewriteInstruction(instruction, cil, newInstruction =>
|
|
||||||
{
|
|
||||||
changed = true;
|
|
||||||
cil.Replace(instruction, newInstruction);
|
|
||||||
instruction = newInstruction;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
@ -124,18 +87,90 @@ namespace StardewModdingAPI.Framework.ModLoading.Framework
|
||||||
|
|
||||||
if (changed)
|
if (changed)
|
||||||
Interlocked.Increment(ref typesChanged);
|
Interlocked.Increment(ref typesChanged);
|
||||||
}
|
});
|
||||||
);
|
|
||||||
|
|
||||||
return exception == null
|
return exception == null
|
||||||
? typesChanged > 0
|
? typesChanged > 0
|
||||||
: throw new Exception($"Rewriting {this.Module.Name} failed.", exception);
|
: throw new Exception($"Rewriting {this.Module.Name} failed.", exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
// non-parallel rewriting
|
||||||
|
{
|
||||||
|
bool changed = false;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
foreach (var type in types)
|
||||||
|
changed |= this.RewriteTypeDefinition(type);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
throw new Exception($"Rewriting {this.Module.Name} failed.", ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*********
|
/*********
|
||||||
** Private methods
|
** Private methods
|
||||||
*********/
|
*********/
|
||||||
|
/// <summary>Rewrite a loaded type definition.</summary>
|
||||||
|
/// <param name="type">The type definition to rewrite.</param>
|
||||||
|
/// <returns>Returns whether the type was modified.</returns>
|
||||||
|
private bool RewriteTypeDefinition(TypeDefinition type)
|
||||||
|
{
|
||||||
|
bool changed = false;
|
||||||
|
|
||||||
|
changed |= this.RewriteCustomAttributes(type.CustomAttributes);
|
||||||
|
changed |= this.RewriteGenericParameters(type.GenericParameters);
|
||||||
|
|
||||||
|
foreach (InterfaceImplementation @interface in type.Interfaces)
|
||||||
|
changed |= this.RewriteTypeReference(@interface.InterfaceType, newType => @interface.InterfaceType = newType);
|
||||||
|
|
||||||
|
if (type.BaseType.FullName != "System.Object")
|
||||||
|
changed |= this.RewriteTypeReference(type.BaseType, newType => type.BaseType = newType);
|
||||||
|
|
||||||
|
foreach (MethodDefinition method in type.Methods)
|
||||||
|
{
|
||||||
|
changed |= this.RewriteTypeReference(method.ReturnType, newType => method.ReturnType = newType);
|
||||||
|
changed |= this.RewriteGenericParameters(method.GenericParameters);
|
||||||
|
changed |= this.RewriteCustomAttributes(method.CustomAttributes);
|
||||||
|
|
||||||
|
foreach (ParameterDefinition parameter in method.Parameters)
|
||||||
|
changed |= this.RewriteTypeReference(parameter.ParameterType, newType => parameter.ParameterType = newType);
|
||||||
|
|
||||||
|
foreach (var methodOverride in method.Overrides)
|
||||||
|
changed |= this.RewriteMethodReference(methodOverride);
|
||||||
|
|
||||||
|
if (method.HasBody)
|
||||||
|
{
|
||||||
|
foreach (VariableDefinition variable in method.Body.Variables)
|
||||||
|
changed |= this.RewriteTypeReference(variable.VariableType, newType => variable.VariableType = newType);
|
||||||
|
|
||||||
|
// check CIL instructions
|
||||||
|
ILProcessor cil = method.Body.GetILProcessor();
|
||||||
|
Collection<Instruction> instructions = cil.Body.Instructions;
|
||||||
|
for (int i = 0; i < instructions.Count; i++)
|
||||||
|
{
|
||||||
|
var instruction = instructions[i];
|
||||||
|
if (instruction.OpCode.Code == Code.Nop)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
changed |= this.RewriteInstruction(instruction, cil, newInstruction =>
|
||||||
|
{
|
||||||
|
changed = true;
|
||||||
|
cil.Replace(instruction, newInstruction);
|
||||||
|
instruction = newInstruction;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>Rewrite a CIL instruction if needed.</summary>
|
/// <summary>Rewrite a CIL instruction if needed.</summary>
|
||||||
/// <param name="instruction">The current CIL instruction.</param>
|
/// <param name="instruction">The current CIL instruction.</param>
|
||||||
/// <param name="cil">The CIL instruction processor.</param>
|
/// <param name="cil">The CIL instruction processor.</param>
|
||||||
|
|
|
@ -15,12 +15,8 @@ namespace StardewModdingAPI.Framework.Models
|
||||||
private static readonly IDictionary<string, object> DefaultValues = new Dictionary<string, object>
|
private static readonly IDictionary<string, object> DefaultValues = new Dictionary<string, object>
|
||||||
{
|
{
|
||||||
[nameof(CheckForUpdates)] = true,
|
[nameof(CheckForUpdates)] = true,
|
||||||
[nameof(ParanoidWarnings)] =
|
[nameof(ParanoidWarnings)] = Constants.IsDebugBuild,
|
||||||
#if DEBUG
|
[nameof(RewriteInParallel)] = Constants.IsDebugBuild,
|
||||||
true,
|
|
||||||
#else
|
|
||||||
false,
|
|
||||||
#endif
|
|
||||||
[nameof(UseBetaChannel)] = Constants.ApiVersion.IsPrerelease(),
|
[nameof(UseBetaChannel)] = Constants.ApiVersion.IsPrerelease(),
|
||||||
[nameof(GitHubProjectName)] = "MartyrPher/SMAPI-Android-Installer",
|
[nameof(GitHubProjectName)] = "MartyrPher/SMAPI-Android-Installer",
|
||||||
[nameof(WebApiBaseUrl)] = "https://smapi.io/api/",
|
[nameof(WebApiBaseUrl)] = "https://smapi.io/api/",
|
||||||
|
@ -48,6 +44,9 @@ namespace StardewModdingAPI.Framework.Models
|
||||||
/// <summary>Whether to check for newer versions of SMAPI and mods on startup.</summary>
|
/// <summary>Whether to check for newer versions of SMAPI and mods on startup.</summary>
|
||||||
public bool CheckForUpdates { get; set; }
|
public bool CheckForUpdates { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Whether to enable experimental parallel rewriting.</summary>
|
||||||
|
public bool RewriteInParallel { get; set; } = (bool)SConfig.DefaultValues[nameof(SConfig.RewriteInParallel)];
|
||||||
|
|
||||||
/// <summary>Whether to add a section to the 'mod issues' list for mods which which directly use potentially sensitive .NET APIs like file or shell access.</summary>
|
/// <summary>Whether to add a section to the 'mod issues' list for mods which which directly use potentially sensitive .NET APIs like file or shell access.</summary>
|
||||||
public bool ParanoidWarnings { get; set; } = (bool)SConfig.DefaultValues[nameof(SConfig.ParanoidWarnings)];
|
public bool ParanoidWarnings { get; set; } = (bool)SConfig.DefaultValues[nameof(SConfig.ParanoidWarnings)];
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ using System;
|
||||||
#if HARMONY_2
|
#if HARMONY_2
|
||||||
using HarmonyLib;
|
using HarmonyLib;
|
||||||
#else
|
#else
|
||||||
|
using MonoMod.RuntimeDetour;
|
||||||
using Harmony;
|
using Harmony;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -34,6 +35,13 @@ namespace StardewModdingAPI.Framework.Patching
|
||||||
#if HARMONY_2
|
#if HARMONY_2
|
||||||
Harmony harmony = new Harmony("SMAPI");
|
Harmony harmony = new Harmony("SMAPI");
|
||||||
#else
|
#else
|
||||||
|
if (!HarmonyDetourBridge.Initialized && Constants.HarmonyEnabled)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
HarmonyDetourBridge.Init();
|
||||||
|
}
|
||||||
|
catch { Constants.HarmonyEnabled = false; }
|
||||||
|
}
|
||||||
HarmonyInstance harmony = HarmonyInstance.Create("SMAPI");
|
HarmonyInstance harmony = HarmonyInstance.Create("SMAPI");
|
||||||
#endif
|
#endif
|
||||||
foreach (IHarmonyPatch patch in patches)
|
foreach (IHarmonyPatch patch in patches)
|
||||||
|
|
|
@ -347,6 +347,8 @@ namespace StardewModdingAPI.Framework
|
||||||
// add headers
|
// add headers
|
||||||
if (this.Settings.DeveloperMode)
|
if (this.Settings.DeveloperMode)
|
||||||
this.Monitor.Log($"You have SMAPI for developers, so the console will be much more verbose. You can disable developer mode by installing the non-developer version of SMAPI, or by editing {Constants.ApiConfigPath}.", LogLevel.Info);
|
this.Monitor.Log($"You have SMAPI for developers, so the console will be much more verbose. You can disable developer mode by installing the non-developer version of SMAPI, or by editing {Constants.ApiConfigPath}.", LogLevel.Info);
|
||||||
|
if (this.Settings.RewriteInParallel)
|
||||||
|
this.Monitor.Log($"You enabled experimental parallel rewriting. This may result in faster startup times, but intermittent startup errors. You can disable it by reinstalling SMAPI or editing {Constants.ApiConfigPath}.", LogLevel.Info);
|
||||||
if (!this.Settings.CheckForUpdates)
|
if (!this.Settings.CheckForUpdates)
|
||||||
this.Monitor.Log($"You configured SMAPI to not check for updates. Running an old version of SMAPI is not recommended. You can enable update checks by reinstalling SMAPI or editing {Constants.ApiConfigPath}.", LogLevel.Warn);
|
this.Monitor.Log($"You configured SMAPI to not check for updates. Running an old version of SMAPI is not recommended. You can enable update checks by reinstalling SMAPI or editing {Constants.ApiConfigPath}.", LogLevel.Warn);
|
||||||
//if (!this.Monitor.WriteToConsole)
|
//if (!this.Monitor.WriteToConsole)
|
||||||
|
@ -489,9 +491,7 @@ namespace StardewModdingAPI.Framework
|
||||||
this.Monitor.Log("Type 'help' for help, or 'help <cmd>' for a command's usage", LogLevel.Info);
|
this.Monitor.Log("Type 'help' for help, or 'help <cmd>' for a command's usage", LogLevel.Info);
|
||||||
this.GameInstance.CommandManager
|
this.GameInstance.CommandManager
|
||||||
.Add(new HelpCommand(this.GameInstance.CommandManager), this.Monitor)
|
.Add(new HelpCommand(this.GameInstance.CommandManager), this.Monitor)
|
||||||
#if HARMONY_2
|
|
||||||
.Add(new HarmonySummaryCommand(), this.Monitor)
|
.Add(new HarmonySummaryCommand(), this.Monitor)
|
||||||
#endif
|
|
||||||
.Add(new ReloadI18nCommand(this.ReloadTranslations), this.Monitor);
|
.Add(new ReloadI18nCommand(this.ReloadTranslations), this.Monitor);
|
||||||
|
|
||||||
// update window titles
|
// update window titles
|
||||||
|
@ -540,9 +540,7 @@ namespace StardewModdingAPI.Framework
|
||||||
this.Monitor.Log("Type 'help' for help, or 'help <cmd>' for a command's usage", LogLevel.Info);
|
this.Monitor.Log("Type 'help' for help, or 'help <cmd>' for a command's usage", LogLevel.Info);
|
||||||
this.GameInstance.CommandManager
|
this.GameInstance.CommandManager
|
||||||
.Add(new HelpCommand(this.GameInstance.CommandManager), this.Monitor)
|
.Add(new HelpCommand(this.GameInstance.CommandManager), this.Monitor)
|
||||||
#if HARMONY_2
|
|
||||||
.Add(new HarmonySummaryCommand(), this.Monitor)
|
.Add(new HarmonySummaryCommand(), this.Monitor)
|
||||||
#endif
|
|
||||||
.Add(new ReloadI18nCommand(this.ReloadTranslations), this.Monitor);
|
.Add(new ReloadI18nCommand(this.ReloadTranslations), this.Monitor);
|
||||||
|
|
||||||
// start handling command line input
|
// start handling command line input
|
||||||
|
@ -1013,7 +1011,7 @@ namespace StardewModdingAPI.Framework
|
||||||
Assembly modAssembly;
|
Assembly modAssembly;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
modAssembly = assemblyLoader.Load(mod, assemblyPath, assumeCompatible: true);//mod.DataRecord?.Status == ModStatus.AssumeCompatible);
|
modAssembly = assemblyLoader.Load(mod, assemblyPath, assumeCompatible: true/*mod.DataRecord?.Status == ModStatus.AssumeCompatible*/, rewriteInParallel: this.Settings.RewriteInParallel);
|
||||||
this.ModRegistry.TrackAssemblies(mod, modAssembly);
|
this.ModRegistry.TrackAssemblies(mod, modAssembly);
|
||||||
}
|
}
|
||||||
catch (IncompatibleInstructionException) // details already in trace logs
|
catch (IncompatibleInstructionException) // details already in trace logs
|
||||||
|
@ -1310,7 +1308,7 @@ namespace StardewModdingAPI.Framework
|
||||||
/// <summary>Reload translations for all mods.</summary>
|
/// <summary>Reload translations for all mods.</summary>
|
||||||
private void ReloadTranslations()
|
private void ReloadTranslations()
|
||||||
{
|
{
|
||||||
this.ReloadTranslations(this.ModRegistry.GetAll(contentPacks: false));
|
this.ReloadTranslations(this.ModRegistry.GetAll());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Reload translations for the given mods.</summary>
|
/// <summary>Reload translations for the given mods.</summary>
|
||||||
|
|
|
@ -33,18 +33,30 @@ copy all the settings, or you may cause bugs due to overridden changes in future
|
||||||
*/
|
*/
|
||||||
"DeveloperMode": true,
|
"DeveloperMode": true,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to enable experimental parallel rewriting when SMAPI is loading mods. This can
|
||||||
|
* reduce startup time when you have many mods installed, but is experimental and may cause
|
||||||
|
* intermittent startup errors.
|
||||||
|
*
|
||||||
|
* When this is commented out, it'll be true for local debug builds and false otherwise.
|
||||||
|
*/
|
||||||
|
//"RewriteInParallel": false,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether to add a section to the 'mod issues' list for mods which directly use potentially
|
* Whether to add a section to the 'mod issues' list for mods which directly use potentially
|
||||||
* sensitive .NET APIs like file or shell access. Note that many mods do this legitimately as
|
* sensitive .NET APIs like file or shell access. Note that many mods do this legitimately as
|
||||||
* part of their normal functionality, so these warnings are meaningless without further
|
* part of their normal functionality, so these warnings are meaningless without further
|
||||||
* investigation. When this is commented out, it'll be true for local debug builds and false
|
* investigation.
|
||||||
* otherwise.
|
*
|
||||||
|
* When this is commented out, it'll be true for local debug builds and false otherwise.
|
||||||
*/
|
*/
|
||||||
//"ParanoidWarnings": true,
|
//"ParanoidWarnings": true,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether SMAPI should show newer beta versions as an available update. When this is commented
|
* Whether SMAPI should show newer beta versions as an available update.
|
||||||
* out, it'll be true if the current SMAPI version is beta, and false otherwise.
|
*
|
||||||
|
* When this is commented out, it'll be true if the current SMAPI version is beta, and false
|
||||||
|
* otherwise.
|
||||||
*/
|
*/
|
||||||
//"UseBetaChannel": true,
|
//"UseBetaChannel": true,
|
||||||
|
|
||||||
|
|
|
@ -34,9 +34,9 @@
|
||||||
<DebugType>pdbonly</DebugType>
|
<DebugType>pdbonly</DebugType>
|
||||||
<Optimize>true</Optimize>
|
<Optimize>true</Optimize>
|
||||||
<OutputPath>bin\Release\</OutputPath>
|
<OutputPath>bin\Release\</OutputPath>
|
||||||
<!-- <DefineConstants>TRACE;DEBUG;ANDROID_TARGET_GOOGLE</DefineConstants>-->
|
<!-- <DefineConstants>ANDROID_TARGET_GOOGLE</DefineConstants>-->
|
||||||
<!-- <DefineConstants>TRACE;DEBUG;ANDROID_TARGET_SAMSUNG</DefineConstants>-->
|
<!-- <DefineConstants>ANDROID_TARGET_SAMSUNG</DefineConstants>-->
|
||||||
<DefineConstants>TRACE;DEBUG;ANDROID_TARGET_GOOGLE;HARMONY_1</DefineConstants>
|
<DefineConstants>ANDROID_TARGET_GOOGLE;HARMONY_1</DefineConstants>
|
||||||
<ErrorReport>prompt</ErrorReport>
|
<ErrorReport>prompt</ErrorReport>
|
||||||
<WarningLevel>4</WarningLevel>
|
<WarningLevel>4</WarningLevel>
|
||||||
<LangVersion>8.0</LangVersion>
|
<LangVersion>8.0</LangVersion>
|
||||||
|
@ -104,7 +104,13 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Reference Include="0Harmony">
|
<Reference Include="0Harmony">
|
||||||
<HintPath>..\Loader\libs\0Harmony.dll</HintPath>
|
<HintPath>..\..\build\0Harmony.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="MonoMod.RuntimeDetour">
|
||||||
|
<HintPath>..\Loader\libs\MonoMod.RuntimeDetour.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="MonoMod.Utils">
|
||||||
|
<HintPath>..\Loader\libs\MonoMod.Utils.dll</HintPath>
|
||||||
</Reference>
|
</Reference>
|
||||||
<Reference Include="MonoGame.Framework">
|
<Reference Include="MonoGame.Framework">
|
||||||
<HintPath>..\Loader\libs\MonoGame.Framework.dll</HintPath>
|
<HintPath>..\Loader\libs\MonoGame.Framework.dll</HintPath>
|
||||||
|
|
Loading…
Reference in New Issue