Skip to content

Commit

Permalink
Merge branch 'develop' into stable
Browse files Browse the repository at this point in the history
  • Loading branch information
Pathoschild committed Aug 26, 2023
2 parents 0ce39e2 + c517b91 commit 31777f0
Show file tree
Hide file tree
Showing 44 changed files with 750 additions and 357 deletions.
2 changes: 1 addition & 1 deletion build/common.targets
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ repo. It imports the other MSBuild files as needed.
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<!--set general build properties -->
<Version>3.18.4</Version>
<Version>3.18.5</Version>
<Product>SMAPI</Product>
<LangVersion>latest</LangVersion>
<AssemblySearchPaths>$(AssemblySearchPaths);{GAC}</AssemblySearchPaths>
Expand Down
23 changes: 23 additions & 0 deletions docs/release-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,29 @@
_If needed, you can update to SMAPI 3.16.0 first and then install the latest version._
-->

## 3.18.5
Released 26 August 2023 for Stardew Valley 1.5.6 or later.

* For players:
* Reduced startup time when many mods are rewritten for compatibility.
* Fixed app icon on Linux/macOS, and for some players on Windows (thanks to Datrio!).
* Fixed error if you copy a null field into `config.user.json`.
* Fixed installer creating a "null" file in its folder.
* Fixed installer moving bundled mods back to their default location on update. It now correctly updates their existing folder instead.

* For mod authors:
* Updated dependencies, including [Mono.Cecil](https://github.com/jbevain/cecil) 0.11.4 → 0.11.5 (see [changes](https://github.com/jbevain/cecil/releases/tag/0.11.5)).
* Fixed NPC warp cache not updated when a map edit changes door warps (thanks to atravita!).

* For the web UI:
* Viewing an uploaded log/JSON file within 15 days of expiry now auto-renews it, to allow for discussions that last longer than the default 30-day expiry.
* Fixed log parser's summary skipping mods if some have no description.
* Fixed log parser no longer ignoring Error Handler in newer SMAPI-on-Android versions (thanks to AnotherPillow!).

* For SMAPI developers:
* Overhauled compatibility rewriters.
_This allows simpler, more robust, and more flexible crossplatform rewrites (e.g. on Android) and prepares for the upcoming Stardew Valley 1.6. See remarks on the new `ReplaceReferencesRewriter` for more info._

## 3.18.4
Released 24 June 2023 for Stardew Valley 1.5.6 or later.

Expand Down
59 changes: 44 additions & 15 deletions src/SMAPI.Installer/InteractiveInstaller.cs
Original file line number Diff line number Diff line change
Expand Up @@ -460,22 +460,51 @@ public void Run(string[] args)
continue;
}

// find target folder
// get mod info
string modId = sourceMod.Manifest.UniqueID;
string modName = sourceMod.Manifest.Name;
DirectoryInfo fromDir = sourceMod.Directory;

// get target path
// ReSharper disable once ConditionalAccessQualifierIsNonNullableAccordingToAPIContract -- avoid error if the Mods folder has invalid mods, since they're not validated yet
ModFolder? targetMod = targetMods.FirstOrDefault(p => p.Manifest?.UniqueID?.Equals(sourceMod.Manifest.UniqueID, StringComparison.OrdinalIgnoreCase) == true);
DirectoryInfo defaultTargetFolder = new(Path.Combine(paths.ModsPath, sourceMod.Directory.Name));
DirectoryInfo targetFolder = targetMod?.Directory ?? defaultTargetFolder;
this.PrintDebug(targetFolder.FullName == defaultTargetFolder.FullName
? $" adding {sourceMod.Manifest.Name}..."
: $" adding {sourceMod.Manifest.Name} to {Path.Combine(paths.ModsDir.Name, PathUtilities.GetRelativePath(paths.ModsPath, targetFolder.FullName))}..."
);

// remove existing folder
if (targetFolder.Exists)
this.InteractivelyDelete(targetFolder.FullName, allowUserInput);

// copy files
this.RecursiveCopy(sourceMod.Directory, paths.ModsDir, filter: this.ShouldCopy);
ModFolder? targetMod = targetMods.FirstOrDefault(p => p.Manifest?.UniqueID?.Equals(modId, StringComparison.OrdinalIgnoreCase) == true);
DirectoryInfo targetDir = new(Path.Combine(paths.ModsPath, fromDir.Name)); // replace existing folder if possible

// if we found the mod in a custom location, replace that copy
if (targetMod != null && targetMod.Directory.FullName != targetDir.FullName)
{
targetDir = targetMod.Directory;
DirectoryInfo parentDir = targetDir.Parent!;

this.PrintDebug($" adding {modName} to {Path.Combine(paths.ModsDir.Name, PathUtilities.GetRelativePath(paths.ModsPath, targetDir.FullName))}...");

if (targetDir.Name != fromDir.Name)
{
// in case the user does weird things like swap folder names, rename the bundled
// mod in a unique staging folder within the temporary package:
string stagingPath = Path.Combine(fromDir.Parent!.Parent!.FullName, $"renamed-mod-{fromDir.Name}");
Directory.CreateDirectory(stagingPath);
fromDir.MoveTo(
Path.Combine(stagingPath, targetDir.Name)
);
}

this.InteractivelyDelete(targetDir.FullName, allowUserInput);
this.RecursiveCopy(fromDir, parentDir, filter: this.ShouldCopy);
}

// else add it to default location
else
{
DirectoryInfo parentDir = targetDir.Parent!;

this.PrintDebug($" adding {modName}...");

if (targetDir.Exists)
this.InteractivelyDelete(targetDir.FullName, allowUserInput);

this.RecursiveCopy(fromDir, parentDir, filter: this.ShouldCopy);
}
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/SMAPI.Installer/assets/install on Windows.bat
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ SET installerDir="%~dp0"
REM make sure we're not running within a zip folder
REM The error level is usually 0 (install dir contains temp path), 1 (it doesn't), or 9009 (findstr doesn't exist due to a Windows issue).
REM If the command doesn't exist, just skip this check.
echo %installerDir% | findstr /C:"%TEMP%" 1>nul 2>null
echo %installerDir% | findstr /C:"%TEMP%" 1>nul 2>nul
if %ERRORLEVEL% EQU 0 (
echo Oops! It looks like you're running the installer from inside a zip file. Make sure you unzip the download first.
echo.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="3.10.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.2" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.1" />
<PackageReference Include="NUnit" Version="3.13.3" />
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" PrivateAssets="all" IncludeAssets="runtime; build; native; contentfiles; analyzers; buildtransitive" />
</ItemGroup>
Expand Down
4 changes: 2 additions & 2 deletions src/SMAPI.Mods.ConsoleCommands/manifest.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
{
"Name": "Console Commands",
"Author": "SMAPI",
"Version": "3.18.4",
"Version": "3.18.5",
"Description": "Adds SMAPI console commands that let you manipulate the game.",
"UniqueID": "SMAPI.ConsoleCommands",
"EntryDll": "ConsoleCommands.dll",
"MinimumApiVersion": "3.18.4"
"MinimumApiVersion": "3.18.5"
}
4 changes: 2 additions & 2 deletions src/SMAPI.Mods.ErrorHandler/manifest.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
{
"Name": "Error Handler",
"Author": "SMAPI",
"Version": "3.18.4",
"Version": "3.18.5",
"Description": "Handles some common vanilla errors to log more useful info or avoid breaking the game.",
"UniqueID": "SMAPI.ErrorHandler",
"EntryDll": "ErrorHandler.dll",
"MinimumApiVersion": "3.18.4"
"MinimumApiVersion": "3.18.5"
}
4 changes: 2 additions & 2 deletions src/SMAPI.Mods.SaveBackup/manifest.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
{
"Name": "Save Backup",
"Author": "SMAPI",
"Version": "3.18.4",
"Version": "3.18.5",
"Description": "Automatically backs up all your saves once per day into its folder.",
"UniqueID": "SMAPI.SaveBackup",
"EntryDll": "SaveBackup.dll",
"MinimumApiVersion": "3.18.4"
"MinimumApiVersion": "3.18.5"
}
6 changes: 3 additions & 3 deletions src/SMAPI.Tests/SMAPI.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.11.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.2" />
<PackageReference Include="Moq" Version="4.18.4" />
<PackageReference Include="FluentAssertions" Version="6.12.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.1" />
<PackageReference Include="Moq" Version="4.20.69" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="NUnit" Version="3.13.3" />
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" PrivateAssets="all" IncludeAssets="runtime; build; native; contentfiles; analyzers; buildtransitive" />
Expand Down
2 changes: 1 addition & 1 deletion src/SMAPI.Toolkit/SMAPI.Toolkit.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<Import Project="..\..\build\common.targets" />

<ItemGroup>
<PackageReference Include="HtmlAgilityPack" Version="1.11.48" />
<PackageReference Include="HtmlAgilityPack" Version="1.11.52" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Pathoschild.Http.FluentClient" Version="4.3.0" />
<PackageReference Include="System.Management" Version="5.0.0" Condition="'$(OS)' == 'Windows_NT'" />
Expand Down
8 changes: 4 additions & 4 deletions src/SMAPI.Web/Controllers/JsonValidatorController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ internal class JsonValidatorController : Controller
};

/// <summary>The schema ID to use if none was specified.</summary>
private string DefaultSchemaID = "none";
private readonly string DefaultSchemaID = "none";

/// <summary>A token in an error message which indicates that the child errors should be displayed instead.</summary>
private readonly string TransparentToken = "$transparent";
Expand Down Expand Up @@ -82,7 +82,7 @@ public async Task<ViewResult> Index(string? schemaName = null, string? id = null
StoredFileInfo file = await this.Storage.GetAsync(id!, renew);
if (string.IsNullOrWhiteSpace(file.Content))
return this.View("Index", result.SetUploadError("The JSON file seems to be empty."));
result.SetContent(file.Content, expiry: file.Expiry, uploadWarning: file.Warning);
result.SetContent(file.Content, oldExpiry: file.OldExpiry, newExpiry: file.NewExpiry, uploadWarning: file.Warning);

// skip parsing if we're going to the edit screen
if (isEditView)
Expand All @@ -108,7 +108,7 @@ public async Task<ViewResult> Index(string? schemaName = null, string? id = null

// format JSON
string formatted = parsed.ToString(Formatting.Indented);
result.SetContent(formatted, expiry: file.Expiry, uploadWarning: file.Warning);
result.SetContent(formatted, oldExpiry: file.OldExpiry, newExpiry: file.NewExpiry, uploadWarning: file.Warning);
parsed = JToken.Parse(formatted); // update line number references
}

Expand Down Expand Up @@ -158,7 +158,7 @@ public async Task<ActionResult> PostAsync(JsonValidatorRequestModel? request)
// upload file
UploadResult result = await this.Storage.SaveAsync(input);
if (!result.Succeeded)
return this.View("Index", this.GetModel(result.ID, schemaName, isEditView: true).SetContent(input, null).SetUploadError(result.UploadError));
return this.View("Index", this.GetModel(result.ID, schemaName, isEditView: true).SetContent(input, null, null).SetUploadError(result.UploadError));

// redirect to view
return this.Redirect(this.Url.PlainAction("Index", "JsonValidator", new { schemaName, id = result.ID })!);
Expand Down
28 changes: 15 additions & 13 deletions src/SMAPI.Web/Controllers/LogParserController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ public async Task<ActionResult> Index(string? id = null, LogViewFormat format =
? new LogParser().Parse(file.Content)
: new ParsedLog { IsValid = false, Error = file.Error };

return this.View("Index", this.GetModel(id, uploadWarning: file.Warning, expiry: file.Expiry).SetResult(log, showRaw: format == LogViewFormat.RawView));
return this.View("Index", this.GetModel(id, uploadWarning: file.Warning, oldExpiry: file.OldExpiry, newExpiry: file.NewExpiry).SetResult(log, showRaw: format == LogViewFormat.RawView));
}

case LogViewFormat.RawDownload:
Expand Down Expand Up @@ -114,43 +114,45 @@ public async Task<ActionResult> PostAsync()
*********/
/// <summary>Build a log parser model.</summary>
/// <param name="pasteID">The stored file ID.</param>
/// <param name="expiry">When the uploaded file will no longer be available.</param>
/// <param name="oldExpiry">When the uploaded file would no longer have been available, before any renewal applied in this request</param>
/// <param name="newExpiry">When the file will no longer be available, after any renewal applied in this request.</param>
/// <param name="uploadWarning">A non-blocking warning while uploading the log.</param>
/// <param name="uploadError">An error which occurred while uploading the log.</param>
private LogParserModel GetModel(string? pasteID, DateTimeOffset? expiry = null, string? uploadWarning = null, string? uploadError = null)
private LogParserModel GetModel(string? pasteID, DateTimeOffset? oldExpiry = null, DateTimeOffset? newExpiry = null, string? uploadWarning = null, string? uploadError = null)
{
Platform? platform = this.DetectClientPlatform();

return new LogParserModel(pasteID, platform)
{
UploadWarning = uploadWarning,
UploadError = uploadError,
Expiry = expiry
OldExpiry = oldExpiry,
NewExpiry = newExpiry
};
}

/// <summary>Detect the viewer's OS.</summary>
/// <returns>Returns the viewer OS if known, else null.</returns>
private Platform? DetectClientPlatform()
{
string userAgent = this.Request.Headers["User-Agent"];
switch (userAgent)
string? userAgent = this.Request.Headers["User-Agent"];

if (userAgent != null)
{
case string ua when ua.Contains("Windows"):
if (userAgent.Contains("Windows"))
return Platform.Windows;

case string ua when ua.Contains("Android"): // check for Android before Linux because Android user agents also contain Linux
if (userAgent.Contains("Android"))
return Platform.Android;

case string ua when ua.Contains("Linux"):
if (userAgent.Contains("Linux"))
return Platform.Linux;

case string ua when ua.Contains("Mac"):
if (userAgent.Contains("Mac"))
return Platform.Mac;

default:
return null;
}

return null;
}
}
}
3 changes: 3 additions & 0 deletions src/SMAPI.Web/Framework/ConfigModels/ApiClientsConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ internal class ApiClientsConfig
/// <summary>The number of days since the blob's last-modified date when it will be deleted.</summary>
public int AzureBlobTempExpiryDays { get; set; }

/// <summary>The number of days before expiry within which blob expiry dates should be auto-renewed on access.</summary>
public int AzureBlobTempExpiryAutoRenewalDays { get; set; }


/****
** Chucklefish
Expand Down
2 changes: 1 addition & 1 deletion src/SMAPI.Web/Framework/LogParsing/LogParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ public ParsedLog Parse(string? logText)
string version = match.Groups["version"].Value;
string author = match.Groups["author"].Value;
string description = match.Groups["description"].Value;
string forMod = match.Groups["for"].Value;
string forMod = match.Groups["for"].Value.Trim(); // if there's no mod description, trim newline from ID

if (!mods.TryGetValue(name, out List<LogModInfo>? entries))
mods[name] = entries = new List<LogModInfo>();
Expand Down
5 changes: 3 additions & 2 deletions src/SMAPI.Web/Framework/Storage/IStorageProvider.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System;
using System.Threading.Tasks;

namespace StardewModdingAPI.Web.Framework.Storage
Expand All @@ -13,7 +14,7 @@ internal interface IStorageProvider

/// <summary>Fetch raw text from storage.</summary>
/// <param name="id">The storage ID returned by <see cref="SaveAsync"/>.</param>
/// <param name="renew">Whether to reset the file expiry.</param>
Task<StoredFileInfo> GetAsync(string id, bool renew);
/// <param name="forceRenew">Whether to reset the file expiry.</param>
Task<StoredFileInfo> GetAsync(string id, bool forceRenew);
}
}
Loading

0 comments on commit 31777f0

Please sign in to comment.