migrate from AWS to Azure

This commit migrates from subdomains to paths (due to the cost of a wildcard HTTPS certificate on Azure), adds a web project to redirect the old subdomains from AWS to Azure, and removes AWS-specific hacks.
This commit is contained in:
Jesse Plamondon-Willard 2019-12-01 21:55:20 -05:00
parent 8766a79408
commit 5f532c259d
No known key found for this signature in database
GPG Key ID: CF8B1456B3E29F49
33 changed files with 311 additions and 238 deletions

View File

@ -24,7 +24,7 @@ Exact steps which reproduce the bug, if possible. For example:
4. Error occurs.
**Log file**
Upload your SMAPI log to https://log.smapi.io and post a link here.
Upload your SMAPI log to https://smapi.io/log and post a link here.
**Screenshots**
If applicable, add screenshots to help explain your problem.

5
.gitignore vendored
View File

@ -28,4 +28,7 @@ _ReSharper*/
appsettings.Development.json
# AWS generated files
src/SMAPI.Web/aws-beanstalk-tools-defaults.json
src/SMAPI.Web.LegacyRedirects/aws-beanstalk-tools-defaults.json
# Azure generated files
src/SMAPI.Web/Properties/PublishProfiles/smapi-web-release - Web Deploy.pubxml

View File

@ -63,7 +63,7 @@
<Copy SourceFiles="$(CompiledModsPath)\SaveBackup\SaveBackup.pdb" DestinationFolder="$(PackagePath)\bundle\Mods\SaveBackup" />
<Copy SourceFiles="$(CompiledModsPath)\SaveBackup\manifest.json" DestinationFolder="$(PackagePath)\bundle\Mods\SaveBackup" />
<!-- fix errors on Linux/Mac (sample: https://log.smapi.io/mMdFUpgB) -->
<!-- fix errors on Linux/Mac (sample: https://smapi.io/log/mMdFUpgB) -->
<Copy Condition="$(OS) != 'Windows_NT'" SourceFiles="$(RootPath)\build\lib\System.Numerics.dll" DestinationFolder="$(PackagePath)\bundle\smapi-internal" />
<Copy Condition="$(OS) != 'Windows_NT'" SourceFiles="$(RootPath)\build\lib\System.Runtime.Caching.dll" DestinationFolder="$(PackagePath)\bundle\smapi-internal" />

View File

@ -110,7 +110,7 @@ For modders:
* Clicking a mod link now automatically adds it to the visible mods if the list is filtered.
* JSON validator:
* Added JSON validator at [json.smapi.io](https://json.smapi.io), which lets you validate a JSON file against predefined mod formats.
* Added JSON validator at [smapi.io/json](https://smapi.io/json), which lets you validate a JSON file against predefined mod formats.
* Added support for the `manifest.json` format.
* Added support for the Content Patcher format (thanks to TehPers!).
* Added support for referencing a schema in a JSON Schema-compatible text editor.
@ -373,7 +373,7 @@ Released 19 November 2018 for Stardew Valley 1.3.32.
* Updated compatibility list.
* For the web UI:
* Added a [mod compatibility page](https://mods.smapi.io) and [privacy page](https://smapi.io/privacy).
* Added a [mod compatibility page](https://smapi.io/mods) and [privacy page](https://smapi.io/privacy).
* The log parser now has a separate filter for game messages.
* The log parser now shows content pack authors (thanks to danvolchek!).
* Tweaked log parser UI (thanks to danvolchek!).
@ -557,7 +557,7 @@ Released 11 April 2018 for Stardew Valley 1.2.301.2.33.
* Fixed error when two content packs use different capitalization for the same required mod ID.
* Fixed rare crash if the game duplicates an item.
* For the [log parser](https://log.smapi.io):
* For the [log parser](https://smapi.io/log):
* Tweaked UI.
## 2.5.4
@ -569,7 +569,7 @@ Released 26 March 2018 for Stardew Valley 1.2.301.2.33.
* Fixed error when mods remove an asset editor/loader.
* Fixed minimum game version incorrectly increased in SMAPI 2.5.3.
* For the [log parser](https://log.smapi.io):
* For the [log parser](https://smapi.io/log):
* Fixed error when log text contains certain tokens.
* For modders:
@ -591,7 +591,7 @@ Released 13 March 2018 for Stardew Valley ~~1.2.30~~1.2.33.
* Fixed Linux ["magic number is wrong" errors](https://github.com/mono/mono/issues/6752) by changing default terminal order.
* Updated compatibility list and added update checks for more mods.
* For the [log parser](https://log.smapi.io):
* For the [log parser](https://smapi.io/log):
* Fixed incorrect filtering in some cases.
* Fixed error if mods have duplicate names.
* Fixed parse bugs if a mod has no author name.
@ -605,7 +605,7 @@ Released 25 February 2018 for Stardew Valley 1.2.301.2.33.
* For modders:
* Fixed issue where replacing an asset through `asset.AsImage()` or `asset.AsDictionary()` didn't take effect.
* For the [log parser](https://log.smapi.io):
* For the [log parser](https://smapi.io/log):
* Fixed blank page after uploading a log in some cases.
## 2.5.1
@ -636,7 +636,7 @@ Released 24 February 2018 for Stardew Valley 1.2.301.2.33.
* Fixed unhelpful error when a translation file has duplicate keys due to case-insensitivity.
* Fixed some JSON field names being case-sensitive.
* For the [log parser](https://log.smapi.io):
* For the [log parser](https://smapi.io/log):
* Added support for SMAPI 2.5 content packs.
* Reduced download size when viewing a parsed log with repeated errors.
* Improved parse error handling.
@ -657,7 +657,7 @@ Released 24 January 2018 for Stardew Valley 1.2.301.2.33.
* Fixed intermittent errors (e.g. 'collection has been modified') with some mods when loading a save.
* Fixed compatibility with Linux Terminator terminal.
* For the [log parser](https://log.smapi.io):
* For the [log parser](https://smapi.io/log):
* Fixed error parsing logs with zero installed mods.
* For modders:
@ -692,7 +692,7 @@ Released 26 December 2017 for Stardew Valley 1.2.301.2.33.
* Fixed issue where a mod could change the cursor position reported to other mods.
* Updated compatibility list.
* For the [log parser](https://log.smapi.io):
* For the [log parser](https://smapi.io/log):
* Fixed broken favicon.
## 2.2
@ -706,7 +706,7 @@ Released 02 December 2017 for Stardew Valley 1.2.301.2.33.
* Improved error when a mod has an invalid `EntryDLL` filename format.
* Updated compatibility list.
* For the [log parser](https://log.smapi.io):
* For the [log parser](https://smapi.io/log):
* Logs no longer expire after a week.
* Fixed error when uploading very large logs.
* Slightly improved the UI.
@ -721,7 +721,7 @@ Released 02 December 2017 for Stardew Valley 1.2.301.2.33.
Released 01 November 2017 for Stardew Valley 1.2.301.2.33.
* For players:
* Added a [log parser](https://log.smapi.io) site.
* Added a [log parser](https://smapi.io/log) site.
* Added better Steam instructions to the SMAPI installer.
* Renamed the bundled _TrainerMod_ to _ConsoleCommands_ to make its purpose clearer.
* Removed the game's test messages from the console log.

View File

@ -14,13 +14,13 @@ and update check API.
## Log parser
The log parser provides a web UI for uploading, parsing, and sharing SMAPI logs. The logs are
persisted in a compressed form to Pastebin. The log parser lives at https://log.smapi.io.
persisted in a compressed form to Pastebin. The log parser lives at https://smapi.io/log.
## JSON validator
### Overview
The JSON validator provides a web UI for uploading and sharing JSON files, and validating them as
plain JSON or against a predefined format like `manifest.json` or Content Patcher's `content.json`.
The JSON validator lives at https://json.smapi.io.
The JSON validator lives at https://smapi.io/json.
### Schema file format
Schema files are defined in `wwwroot/schemas` using the [JSON Schema](https://json-schema.org/)
@ -111,7 +111,7 @@ format | schema URL
## Web API
### Overview
SMAPI provides a web API at `api.smapi.io` for use by SMAPI and external tools. The URL includes a
SMAPI provides a web API at `smapi.io/api` for use by SMAPI and external tools. The URL includes a
`{version}` token, which is the SMAPI version for backwards compatibility. This API is publicly
accessible but not officially released; it may change at any time.
@ -184,7 +184,7 @@ may be useful to external tools.
Example request:
```js
POST https://api.smapi.io/v3.0/mods
POST https://smapi.io/api/v3.0/mods
{
"mods": [
{
@ -350,8 +350,7 @@ To launch the environment:
mongod --dbpath C:\dev\smapi-cache
```
2. Launch `SMAPI.Web` from Visual Studio to run a local version of the site.
<small>(Local URLs will use HTTP instead of HTTPS, and subdomains will become routes, like
`log.smapi.io` &rarr; `localhost:59482/log`.)</small>
<small>(Local URLs will use HTTP instead of HTTPS.)</small>
### Production environment
A production environment includes the web servers and cache database hosted online for public
@ -367,7 +366,6 @@ Initial setup:
------------------------------- | -----------------
`LogParser:PastebinDevKey` | The [Pastebin developer key](https://pastebin.com/api#1) used to authenticate with the Pastebin API.
`LogParser:PastebinUserKey` | The [Pastebin user key](https://pastebin.com/api#8) used to authenticate with the Pastebin API. Can be left blank to post anonymously.
`LogParser:SectionUrl` | The root URL of the log page, like `https://log.smapi.io/`.
`ModUpdateCheck:GitHubPassword` | The password with which to authenticate to GitHub when fetching release info.
`ModUpdateCheck:GitHubUsername` | The username with which to authenticate to GitHub when fetching release info.
`MongoDB:Host` | The hostname for the MongoDB instance.

View File

@ -0,0 +1,33 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Pathoschild.Http.Client;
using StardewModdingAPI.Toolkit.Framework.Clients.WebApi;
namespace SMAPI.Web.LegacyRedirects.Controllers
{
/// <summary>Provides an API to perform mod update checks.</summary>
[ApiController]
[Produces("application/json")]
[Route("api/v{version}/mods")]
public class ModsApiController : Controller
{
/*********
** Public methods
*********/
/// <summary>Fetch version metadata for the given mods.</summary>
/// <param name="model">The mod search criteria.</param>
[HttpPost]
public async Task<IEnumerable<ModEntryModel>> PostAsync([FromBody] ModSearchModel model)
{
using IClient client = new FluentClient("https://smapi.io/api");
Startup.ConfigureJsonNet(client.Formatters.JsonFormatter.SerializerSettings);
return await client
.PostAsync(this.Request.Path)
.WithBody(model)
.AsArray<ModEntryModel>();
}
}
}

View File

@ -0,0 +1,37 @@
using System;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Rewrite;
namespace SMAPI.Web.LegacyRedirects.Framework
{
/// <summary>Rewrite requests to prepend the subdomain portion (if any) to the path.</summary>
/// <remarks>Derived from <a href="https://stackoverflow.com/a/44526747/262123" />.</remarks>
internal class LambdaRewriteRule : IRule
{
/*********
** Accessors
*********/
/// <summary>Rewrite an HTTP request if needed.</summary>
private readonly Action<RewriteContext, HttpRequest, HttpResponse> Rewrite;
/*********
** Public methods
*********/
/// <summary>Construct an instance.</summary>
/// <param name="rewrite">Rewrite an HTTP request if needed.</param>
public LambdaRewriteRule(Action<RewriteContext, HttpRequest, HttpResponse> rewrite)
{
this.Rewrite = rewrite ?? throw new ArgumentNullException(nameof(rewrite));
}
/// <summary>Applies the rule. Implementations of ApplyRule should set the value for <see cref="RewriteContext.Result" /> (defaults to RuleResult.ContinueRules).</summary>
/// <param name="context">The rewrite context.</param>
public void ApplyRule(RewriteContext context)
{
HttpRequest request = context.HttpContext.Request;
HttpResponse response = context.HttpContext.Response;
this.Rewrite(context, request, response);
}
}
}

View File

@ -0,0 +1,23 @@
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
namespace SMAPI.Web.LegacyRedirects
{
/// <summary>The main app entry point.</summary>
public class Program
{
/*********
** Public methods
*********/
/// <summary>The main app entry point.</summary>
/// <param name="args">The command-line arguments.</param>
public static void Main(string[] args)
{
Host
.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(builder => builder.UseStartup<Startup>())
.Build()
.Run();
}
}
}

View File

@ -0,0 +1,29 @@
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:52756",
"sslPort": 0
}
},
"$schema": "http://json.schemastore.org/launchsettings.json",
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"SMAPI.Web.LegacyRedirects": {
"commandName": "Project",
"launchBrowser": true,
"launchUrl": "/",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "https://localhost:5001;http://localhost:5000"
}
}
}

View File

@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp3.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<Content Remove="aws-beanstalk-tools-defaults.json" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.0.0" />
<PackageReference Include="Pathoschild.Http.FluentClient" Version="3.3.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\SMAPI.Toolkit.CoreInterfaces\SMAPI.Toolkit.CoreInterfaces.csproj" />
<ProjectReference Include="..\SMAPI.Toolkit\SMAPI.Toolkit.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,94 @@
using System.Net;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Rewrite;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Newtonsoft.Json;
using SMAPI.Web.LegacyRedirects.Framework;
using StardewModdingAPI.Toolkit.Serialization;
namespace SMAPI.Web.LegacyRedirects
{
/// <summary>The web app startup configuration.</summary>
public class Startup
{
/*********
** Public methods
*********/
/// <summary>The method called by the runtime to add services to the container.</summary>
/// <param name="services">The service injection container.</param>
public void ConfigureServices(IServiceCollection services)
{
services
.AddControllers()
.AddNewtonsoftJson(options => Startup.ConfigureJsonNet(options.SerializerSettings));
}
/// <summary>The method called by the runtime to configure the HTTP request pipeline.</summary>
/// <param name="app">The application builder.</param>
/// <param name="env">The hosting environment.</param>
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
app.UseDeveloperExceptionPage();
app
.UseRewriter(this.GetRedirectRules())
.UseRouting()
.UseAuthorization()
.UseEndpoints(endpoints => endpoints.MapControllers());
}
/// <summary>Configure a Json.NET serializer.</summary>
/// <param name="settings">The serializer settings to edit.</param>
internal static void ConfigureJsonNet(JsonSerializerSettings settings)
{
foreach (JsonConverter converter in new JsonHelper().JsonSettings.Converters)
settings.Converters.Add(converter);
settings.Formatting = Formatting.Indented;
settings.NullValueHandling = NullValueHandling.Ignore;
}
/*********
** Private methods
*********/
/// <summary>Get the redirect rules to apply.</summary>
private RewriteOptions GetRedirectRules()
{
var redirects = new RewriteOptions();
redirects.Add(
new LambdaRewriteRule((context, request, response) =>
{
string host = request.Host.Host;
// map API requests to proxy
// This is needed because the low-level HTTP client SMAPI uses for Linux/Mac compatibility doesn't support redirects.
if (host == "api.smapi.io")
{
request.Path = $"/api{request.Path}";
return;
}
// redirect other requests to Azure
string newRoot = host switch
{
"api.smapi.io" => "smapi.io/api",
"json.smapi.io" => "smapi.io/json",
"log.smapi.io" => "smapi.io/log",
"mods.smapi.io" => "smapi.io/mods",
_ => "smapi.io"
};
response.StatusCode = (int)HttpStatusCode.PermanentRedirect;
response.Headers["Location"] = $"{(request.IsHttps ? "https" : "http")}://{newRoot}{request.PathBase}{request.Path}{request.QueryString}";
context.Result = RuleResult.EndResponse;
})
);
return redirects;
}
}
}

View File

@ -5,14 +5,12 @@ using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Schema;
using StardewModdingAPI.Web.Framework;
using StardewModdingAPI.Web.Framework.Clients.Pastebin;
using StardewModdingAPI.Web.Framework.Compression;
using StardewModdingAPI.Web.Framework.ConfigModels;
using StardewModdingAPI.Web.ViewModels.JsonValidator;
namespace StardewModdingAPI.Web.Controllers
@ -23,18 +21,12 @@ namespace StardewModdingAPI.Web.Controllers
/*********
** Fields
*********/
/// <summary>The site config settings.</summary>
private readonly SiteConfig Config;
/// <summary>The underlying Pastebin client.</summary>
private readonly IPastebinClient Pastebin;
/// <summary>The underlying text compression helper.</summary>
private readonly IGzipHelper GzipHelper;
/// <summary>The section URL for the schema validator.</summary>
private string SectionUrl => this.Config.JsonValidatorUrl;
/// <summary>The supported JSON schemas (names indexed by ID).</summary>
private readonly IDictionary<string, string> SchemaFormats = new Dictionary<string, string>
{
@ -57,12 +49,10 @@ namespace StardewModdingAPI.Web.Controllers
** Constructor
***/
/// <summary>Construct an instance.</summary>
/// <param name="siteConfig">The context config settings.</param>
/// <param name="pastebin">The Pastebin API client.</param>
/// <param name="gzipHelper">The underlying text compression helper.</param>
public JsonValidatorController(IOptions<SiteConfig> siteConfig, IPastebinClient pastebin, IGzipHelper gzipHelper)
public JsonValidatorController(IPastebinClient pastebin, IGzipHelper gzipHelper)
{
this.Config = siteConfig.Value;
this.Pastebin = pastebin;
this.GzipHelper = gzipHelper;
}
@ -81,7 +71,7 @@ namespace StardewModdingAPI.Web.Controllers
{
schemaName = this.NormalizeSchemaName(schemaName);
var result = new JsonValidatorModel(this.SectionUrl, id, schemaName, this.SchemaFormats);
var result = new JsonValidatorModel(id, schemaName, this.SchemaFormats);
if (string.IsNullOrWhiteSpace(id))
return this.View("Index", result);
@ -142,7 +132,7 @@ namespace StardewModdingAPI.Web.Controllers
public async Task<ActionResult> PostAsync(JsonValidatorRequestModel request)
{
if (request == null)
return this.View("Index", new JsonValidatorModel(this.SectionUrl, null, null, this.SchemaFormats).SetUploadError("The request seems to be invalid."));
return this.View("Index", new JsonValidatorModel(null, null, this.SchemaFormats).SetUploadError("The request seems to be invalid."));
// normalize schema name
string schemaName = this.NormalizeSchemaName(request.SchemaName);
@ -150,7 +140,7 @@ namespace StardewModdingAPI.Web.Controllers
// get raw log text
string input = request.Content;
if (string.IsNullOrWhiteSpace(input))
return this.View("Index", new JsonValidatorModel(this.SectionUrl, null, schemaName, this.SchemaFormats).SetUploadError("The JSON file seems to be empty."));
return this.View("Index", new JsonValidatorModel(null, schemaName, this.SchemaFormats).SetUploadError("The JSON file seems to be empty."));
// upload log
input = this.GzipHelper.CompressString(input);
@ -158,12 +148,10 @@ namespace StardewModdingAPI.Web.Controllers
// handle errors
if (!result.Success)
return this.View("Index", new JsonValidatorModel(this.SectionUrl, result.ID, schemaName, this.SchemaFormats).SetUploadError($"Pastebin error: {result.Error ?? "unknown error"}"));
return this.View("Index", new JsonValidatorModel(result.ID, schemaName, this.SchemaFormats).SetUploadError($"Pastebin error: {result.Error ?? "unknown error"}"));
// redirect to view
UriBuilder uri = new UriBuilder(new Uri(this.SectionUrl));
uri.Path = $"{uri.Path.TrimEnd('/')}/{schemaName}/{result.ID}";
return this.Redirect(uri.Uri.ToString());
return this.Redirect(this.Url.Action("Index", "LogParser", new { schemaName = schemaName, id = result.ID }));
}

View File

@ -27,9 +27,6 @@ namespace StardewModdingAPI.Web.Controllers
/*********
** Fields
*********/
/// <summary>The site config settings.</summary>
private readonly SiteConfig SiteConfig;
/// <summary>The API client settings.</summary>
private readonly ApiClientsConfig ClientsConfig;
@ -47,13 +44,11 @@ namespace StardewModdingAPI.Web.Controllers
** Constructor
***/
/// <summary>Construct an instance.</summary>
/// <param name="siteConfig">The context config settings.</param>
/// <param name="clientsConfig">The API client settings.</param>
/// <param name="pastebin">The Pastebin API client.</param>
/// <param name="gzipHelper">The underlying text compression helper.</param>
public LogParserController(IOptions<SiteConfig> siteConfig, IOptions<ApiClientsConfig> clientsConfig, IPastebinClient pastebin, IGzipHelper gzipHelper)
public LogParserController(IOptions<ApiClientsConfig> clientsConfig, IPastebinClient pastebin, IGzipHelper gzipHelper)
{
this.SiteConfig = siteConfig.Value;
this.ClientsConfig = clientsConfig.Value;
this.Pastebin = pastebin;
this.GzipHelper = gzipHelper;
@ -103,9 +98,7 @@ namespace StardewModdingAPI.Web.Controllers
return this.View("Index", this.GetModel(null, uploadError: uploadResult.UploadError));
// redirect to view
UriBuilder uri = new UriBuilder(new Uri(this.SiteConfig.LogParserUrl));
uri.Path = $"{uri.Path.TrimEnd('/')}/{uploadResult.ID}";
return this.Redirect(uri.Uri.ToString());
return this.Redirect(this.Url.Action("Index", "LogParser", new { id = uploadResult.ID }));
}
@ -217,10 +210,9 @@ namespace StardewModdingAPI.Web.Controllers
/// <param name="uploadError">An error which occurred while uploading the log.</param>
private LogParserModel GetModel(string pasteID, DateTime? expiry = null, string uploadWarning = null, string uploadError = null)
{
string sectionUrl = this.SiteConfig.LogParserUrl;
Platform? platform = this.DetectClientPlatform();
return new LogParserModel(sectionUrl, pasteID, platform)
return new LogParserModel(pasteID, platform)
{
UploadWarning = uploadWarning,
UploadError = uploadError,

View File

@ -49,9 +49,6 @@ namespace StardewModdingAPI.Web.Controllers
/// <summary>The internal mod metadata list.</summary>
private readonly ModDatabase ModDatabase;
/// <summary>The web URL for the compatibility list.</summary>
private readonly string CompatibilityPageUrl;
/*********
** Public methods
@ -70,7 +67,6 @@ namespace StardewModdingAPI.Web.Controllers
{
this.ModDatabase = new ModToolkit().GetModDatabase(Path.Combine(environment.WebRootPath, "SMAPI.metadata.json"));
ModUpdateCheckConfig config = configProvider.Value;
this.CompatibilityPageUrl = config.CompatibilityPageUrl;
this.WikiCache = wikiCache;
this.ModCache = modCache;
@ -205,7 +201,7 @@ namespace StardewModdingAPI.Web.Controllers
// get unofficial version
if (wikiEntry?.Compatibility.UnofficialVersion != null && this.IsNewer(wikiEntry.Compatibility.UnofficialVersion, main?.Version) && this.IsNewer(wikiEntry.Compatibility.UnofficialVersion, optional?.Version))
unofficial = new ModEntryVersionModel(wikiEntry.Compatibility.UnofficialVersion, $"{this.CompatibilityPageUrl}/#{wikiEntry.Anchor}");
unofficial = new ModEntryVersionModel(wikiEntry.Compatibility.UnofficialVersion, $"{this.Url.Action("Index", "Mods")}#{wikiEntry.Anchor}");
// get unofficial version for beta
if (wikiEntry?.HasBetaInfo == true)
@ -215,7 +211,7 @@ namespace StardewModdingAPI.Web.Controllers
if (wikiEntry.BetaCompatibility.UnofficialVersion != null)
{
unofficialForBeta = (wikiEntry.BetaCompatibility.UnofficialVersion != null && this.IsNewer(wikiEntry.BetaCompatibility.UnofficialVersion, main?.Version) && this.IsNewer(wikiEntry.BetaCompatibility.UnofficialVersion, optional?.Version))
? new ModEntryVersionModel(wikiEntry.BetaCompatibility.UnofficialVersion, $"{this.CompatibilityPageUrl}/#{wikiEntry.Anchor}")
? new ModEntryVersionModel(wikiEntry.BetaCompatibility.UnofficialVersion, $"{this.Url.Action("Index", "Mods")}#{wikiEntry.Anchor}")
: null;
}
else

View File

@ -1,54 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using Microsoft.Extensions.Configuration;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace StardewModdingAPI.Web.Framework
{
/// <summary>Reads configuration values from the AWS Beanstalk environment properties file (if present).</summary>
/// <remarks>This is a workaround for AWS Beanstalk injection not working with .NET Core apps.</remarks>
internal class BeanstalkEnvPropsConfigProvider : ConfigurationProvider, IConfigurationSource
{
/*********
** Fields
*********/
/// <summary>The absolute path to the container configuration file on an Amazon EC2 instance.</summary>
private const string ContainerConfigPath = @"C:\Program Files\Amazon\ElasticBeanstalk\config\containerconfiguration";
/*********
** Public methods
*********/
/// <summary>Build the configuration provider for this source.</summary>
/// <param name="builder">The configuration builder.</param>
public IConfigurationProvider Build(IConfigurationBuilder builder)
{
return new BeanstalkEnvPropsConfigProvider();
}
/// <summary>Load the environment properties.</summary>
public override void Load()
{
this.Data = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
// get Beanstalk config file
FileInfo file = new FileInfo(BeanstalkEnvPropsConfigProvider.ContainerConfigPath);
if (!file.Exists)
return;
// parse JSON
JObject jsonRoot = (JObject)JsonConvert.DeserializeObject(File.ReadAllText(file.FullName));
if (jsonRoot["iis"]?["env"] is JArray jsonProps)
{
foreach (string prop in jsonProps.Values<string>())
{
string[] parts = prop.Split('=', 2); // key=value
if (parts.Length == 2)
this.Data[parts[0]] = parts[1];
}
}
}
}
}

View File

@ -11,8 +11,5 @@ namespace StardewModdingAPI.Web.Framework.ConfigModels
/// <summary>The number of minutes failed update checks should be cached before refetching them.</summary>
public int ErrorCacheMinutes { get; set; }
/// <summary>The web URL for the wiki compatibility list.</summary>
public string CompatibilityPageUrl { get; set; }
}
}

View File

@ -6,18 +6,6 @@ namespace StardewModdingAPI.Web.Framework.ConfigModels
/*********
** Accessors
*********/
/// <summary>The root URL for the app.</summary>
public string RootUrl { get; set; }
/// <summary>The root URL for the log parser.</summary>
public string LogParserUrl { get; set; }
/// <summary>The root URL for the JSON validator.</summary>
public string JsonValidatorUrl { get; set; }
/// <summary>The root URL for the mod list.</summary>
public string ModListUrl { get; set; }
/// <summary>Whether to show SMAPI beta versions on the main page, if any.</summary>
public bool BetaEnabled { get; set; }

View File

@ -1,48 +0,0 @@
using System;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Rewrite;
namespace StardewModdingAPI.Web.Framework.RewriteRules
{
/// <summary>Rewrite requests to prepend the subdomain portion (if any) to the path.</summary>
/// <remarks>Derived from <a href="https://stackoverflow.com/a/44526747/262123" />.</remarks>
internal class ConditionalRewriteSubdomainRule : IRule
{
/*********
** Accessors
*********/
/// <summary>A predicate which indicates when the rule should be applied.</summary>
private readonly Func<HttpRequest, bool> ShouldRewrite;
/*********
** Public methods
*********/
/// <summary>Construct an instance.</summary>
/// <param name="shouldRewrite">A predicate which indicates when the rule should be applied.</param>
public ConditionalRewriteSubdomainRule(Func<HttpRequest, bool> shouldRewrite = null)
{
this.ShouldRewrite = shouldRewrite ?? (req => true);
}
/// <summary>Applies the rule. Implementations of ApplyRule should set the value for <see cref="RewriteContext.Result" /> (defaults to RuleResult.ContinueRules).</summary>
/// <param name="context">The rewrite context.</param>
public void ApplyRule(RewriteContext context)
{
HttpRequest request = context.HttpContext.Request;
// check condition
if (!this.ShouldRewrite(request))
return;
// get host parts
string host = request.Host.Host;
string[] parts = host.Split('.');
if (parts.Length < 2)
return;
// prepend to path
request.Path = $"/{parts[0]}{request.Path}";
}
}
}

View File

@ -48,7 +48,7 @@ namespace StardewModdingAPI.Web
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.Add(new BeanstalkEnvPropsConfigProvider())
.AddEnvironmentVariables()
.Build();
}
@ -173,8 +173,7 @@ namespace StardewModdingAPI.Web
.UseCors(policy => policy
.AllowAnyHeader()
.AllowAnyMethod()
.WithOrigins("https://smapi.io", "https://*.smapi.io", "https://*.edge.smapi.io")
.SetIsOriginAllowedToAllowWildcardSubdomains()
.WithOrigins("https://smapi.io")
)
.UseRewriter(this.GetRedirectRules())
.UseStaticFiles() // wwwroot folder
@ -202,23 +201,13 @@ namespace StardewModdingAPI.Web
shouldRewrite: req =>
req.Host.Host != "localhost"
&& !req.Path.StartsWithSegments("/api")
&& !req.Host.Host.StartsWith("api.")
));
// convert subdomain.smapi.io => smapi.io/subdomain for routing
redirects.Add(new ConditionalRewriteSubdomainRule(
shouldRewrite: req =>
req.Host.Host != "localhost"
&& (req.Host.Host.StartsWith("api.") || req.Host.Host.StartsWith("json.") || req.Host.Host.StartsWith("log.") || req.Host.Host.StartsWith("mods."))
&& !req.Path.StartsWithSegments("/content")
&& !req.Path.StartsWithSegments("/favicon.ico")
));
// shortcut redirects
redirects.Add(new RedirectToUrlRule(@"^/3\.0\.?$", "https://stardewvalleywiki.com/Modding:Migrate_to_SMAPI_3.0"));
redirects.Add(new RedirectToUrlRule(@"^/(?:buildmsg|package)(?:/?(.*))$", "https://github.com/Pathoschild/SMAPI/blob/develop/docs/technical/mod-package.md#$1")); // buildmsg deprecated, remove when SDV 1.4 is released
redirects.Add(new RedirectToUrlRule(@"^/community\.?$", "https://stardewvalleywiki.com/Modding:Community"));
redirects.Add(new RedirectToUrlRule(@"^/compat\.?$", "https://mods.smapi.io"));
redirects.Add(new RedirectToUrlRule(@"^/compat\.?$", "https://smapi.io/mods"));
redirects.Add(new RedirectToUrlRule(@"^/docs\.?$", "https://stardewvalleywiki.com/Modding:Index"));
redirects.Add(new RedirectToUrlRule(@"^/install\.?$", "https://stardewvalleywiki.com/Modding:Player_Guide/Getting_Started#Install_SMAPI"));
redirects.Add(new RedirectToUrlRule(@"^/troubleshoot(.*)$", "https://stardewvalleywiki.com/Modding:Player_Guide/Troubleshooting$1"));

View File

@ -9,9 +9,6 @@ namespace StardewModdingAPI.Web.ViewModels.JsonValidator
/*********
** Accessors
*********/
/// <summary>The root URL for the log parser controller.</summary>
public string SectionUrl { get; set; }
/// <summary>The paste ID.</summary>
public string PasteID { get; set; }
@ -44,13 +41,11 @@ namespace StardewModdingAPI.Web.ViewModels.JsonValidator
public JsonValidatorModel() { }
/// <summary>Construct an instance.</summary>
/// <param name="sectionUrl">The root URL for the log parser controller.</param>
/// <param name="pasteID">The paste ID.</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>
public JsonValidatorModel(string sectionUrl, string pasteID, string schemaName, IDictionary<string, string> schemaFormats)
public JsonValidatorModel(string pasteID, string schemaName, IDictionary<string, string> schemaFormats)
{
this.SectionUrl = sectionUrl;
this.PasteID = pasteID;
this.SchemaName = schemaName;
this.SchemaFormats = schemaFormats;

View File

@ -20,9 +20,6 @@ namespace StardewModdingAPI.Web.ViewModels
/*********
** Accessors
*********/
/// <summary>The root URL for the log parser controller.</summary>
public string SectionUrl { get; set; }
/// <summary>The paste ID.</summary>
public string PasteID { get; set; }
@ -55,12 +52,10 @@ namespace StardewModdingAPI.Web.ViewModels
public LogParserModel() { }
/// <summary>Construct an instance.</summary>
/// <param name="sectionUrl">The root URL for the log parser controller.</param>
/// <param name="pasteID">The paste ID.</param>
/// <param name="platform">The viewer's detected OS, if known.</param>
public LogParserModel(string sectionUrl, string pasteID, Platform? platform)
public LogParserModel(string pasteID, Platform? platform)
{
this.SectionUrl = sectionUrl;
this.PasteID = pasteID;
this.DetectedPlatform = platform;
this.ParsedLog = null;

View File

@ -44,14 +44,14 @@
}
<div><a href="https://stardewvalleywiki.com/Modding:Player_Guide" class="secondary-cta">Player guide</a></div>
<div class="sublinks">
<a href="https://github.com/Pathoschild/SMAPI">source code</a> | <a href="@(new UriBuilder(SiteConfig.Value.RootUrl) { Path = "privacy" }.Uri)">privacy</a>
<a href="https://github.com/Pathoschild/SMAPI">source code</a> | <a href="@Url.Action("Privacy", "Index")">privacy</a>
</div>
<img id="pufferchick" src="Content/images/pufferchick.png" />
</div>
<h2 id="help">Get help</h2>
<ul>
<li><a href="@SiteConfig.Value.ModListUrl">Mod compatibility list</a></li>
<li><a href="@Url.Action("Index", "Mods")">Mod compatibility list</a></li>
<li>Get help <a href="https://smapi.io/community">on Discord or in the forums</a></li>
</ul>
@ -61,7 +61,7 @@
<div class="github-description">
@Html.Raw(Markdig.Markdown.ToHtml(Model.StableVersion.Description))
</div>
<p>See the <a href="https://github.com/Pathoschild/SMAPI/blob/develop/docs/release-notes.md#release-notes">release notes</a> and <a href="@SiteConfig.Value.ModListUrl">mod compatibility list</a> for more info.</p>
<p>See the <a href="https://github.com/Pathoschild/SMAPI/blob/develop/docs/release-notes.md#release-notes">release notes</a> and <a href="@Url.Action("Index", "Mods")">mod compatibility list</a> for more info.</p>
}
else
{
@ -70,13 +70,13 @@ else
<div class="github-description">
@Html.Raw(Markdig.Markdown.ToHtml(Model.StableVersion.Description))
</div>
<p>See the <a href="https://github.com/Pathoschild/SMAPI/blob/develop/docs/release-notes.md#release-notes">release notes</a> and <a href="@SiteConfig.Value.ModListUrl">mod compatibility list</a> for more info.</p>
<p>See the <a href="https://github.com/Pathoschild/SMAPI/blob/develop/docs/release-notes.md#release-notes">release notes</a> and <a href="@Url.Action("Index", "Mods")">mod compatibility list</a> for more info.</p>
<h3>SMAPI @Model.BetaVersion.Version?</h3>
<div class="github-description">
@Html.Raw(Markdig.Markdown.ToHtml(Model.BetaVersion.Description))
</div>
<p>See the <a href="https://github.com/Pathoschild/SMAPI/blob/develop/docs/release-notes.md#release-notes">release notes</a> and <a href="@SiteConfig.Value.ModListUrl">mod compatibility list</a> for more info.</p>
<p>See the <a href="https://github.com/Pathoschild/SMAPI/blob/develop/docs/release-notes.md#release-notes">release notes</a> and <a href="@Url.Action("Index", "Mods")">mod compatibility list</a> for more info.</p>
}
<h2 id="donate">Support SMAPI ♥</h2>

View File

@ -8,7 +8,7 @@
<link rel="stylesheet" href="~/Content/css/privacy.css" />
}
&larr; <a href="@SiteConfig.Value.RootUrl">back to SMAPI page</a>
&larr; <a href="@Url.Action("Index", "Index")">back to SMAPI page</a>
<p>SMAPI is an <a href="https://github.com/Pathoschild/SMAPI">open-source</a> and non-profit project. Your privacy is important, so this page explains what information SMAPI uses and transmits. <strong>This page is informational only, it's not a legal document.</strong></p>
@ -34,7 +34,7 @@
</ol>
<h3>Log parser</h3>
<p>The <a href="https://log.smapi.io/">log parser page</a> lets you store a log file for analysis and sharing. The log data is stored indefinitely in an obfuscated form as unlisted pastes in <a href="https://pastebin.com/">Pastebin</a>. No personal information is stored by the log parser beyond what you choose to upload, but see <em><a href="#web-logging">web logging</a></em> and the <a href="https://pastebin.com/doc_privacy_statement">Pastebin Privacy Statement</a>.</p>
<p>The <a href="https://smapi.io/log">log parser page</a> lets you store a log file for analysis and sharing. The log data is stored indefinitely in an obfuscated form as unlisted pastes in <a href="https://pastebin.com/">Pastebin</a>. No personal information is stored by the log parser beyond what you choose to upload, but see <em><a href="#web-logging">web logging</a></em> and the <a href="https://pastebin.com/doc_privacy_statement">Pastebin Privacy Statement</a>.</p>
<h3>Multiplayer sync</h3>
<p>As part of its multiplayer API, SMAPI transmits basic context to players you connect to (mainly your OS, SMAPI version, game version, and installed mods). This is used to enable multiplayer features like inter-mod messages, compatibility checks, etc. Although this information is normally hidden from players, it may be visible due to mods or configuration changes.</p>

View File

@ -3,8 +3,8 @@
@{
// get view data
string curPageUrl = new Uri(new Uri(Model.SectionUrl), $"{Model.SchemaName}/{Model.PasteID}").ToString();
string newUploadUrl = Model.SchemaName != null ? new Uri(new Uri(Model.SectionUrl), Model.SchemaName).ToString() : Model.SectionUrl;
string curPageUrl = this.Url.Action("Index", "JsonValidator", new { schemaName = Model.SchemaName, id = Model.PasteID });
string newUploadUrl = this.Url.Action("Index", "JsonValidator", new { schemaName = Model.SchemaName });
string schemaDisplayName = null;
bool isValidSchema = Model.SchemaName != null && Model.SchemaFormats.TryGetValue(Model.SchemaName, out schemaDisplayName) && schemaDisplayName != "None";
@ -34,8 +34,8 @@
<script src="https://cdn.jsdelivr.net/gh/tmont/sunlight@1.22.0/src/lang/sunlight.javascript.min.js" crossorigin="anonymous"></script>
<script src="~/Content/js/json-validator.js"></script>
<script>
$(function () {
smapi.jsonValidator(@Json.Serialize(Model.SectionUrl), @Json.Serialize(Model.PasteID));
$(function() {
smapi.jsonValidator(@Json.Serialize(this.Url.Action("Index", "JsonValidator")), @Json.Serialize(Model.PasteID));
});
</script>
}
@ -70,7 +70,7 @@ else if (Model.PasteID != null)
@if (Model.Content == null)
{
<h2>Upload a JSON file</h2>
<form action="@Model.SectionUrl" method="post">
<form action="@this.Url.Action("PostAsync", "JsonValidator")" method="post">
<ol>
<li>
Choose the JSON format:<br />

View File

@ -32,7 +32,7 @@
showSections: @Json.Serialize(Enum.GetNames(typeof(LogSection)).ToDictionary(section => section, section => false), noFormatting),
showLevels: @Json.Serialize(defaultFilters, noFormatting),
enableFilters: @Json.Serialize(!Model.ShowRaw)
}, '@Model.SectionUrl');
}, '@this.Url.Action("Index", "LogParser")');
});
</script>
}
@ -49,8 +49,8 @@ else if (Model.ParseError != null)
{
<div class="banner error" v-pre>
<strong>Oops, couldn't parse that log. (Make sure you upload the log file, not the console text.)</strong><br />
Share this URL when asking for help: <code>@(new Uri(new Uri(Model.SectionUrl), Model.PasteID))</code><br />
(Or <a href="@Model.SectionUrl">upload a new log</a>.)<br />
Share this URL when asking for help: <code>https://@this.Context.Request.Host.ToUriComponent()@this.Url.Action("Index", "LogParser", new { id = Model.PasteID }))</code><br />
(Or <a href="@this.Url.Action("Index", "LogParser")">upload a new log</a>.)<br />
<br />
<small v-pre>Error details: @Model.ParseError</small>
</div>
@ -58,8 +58,8 @@ else if (Model.ParseError != null)
else if (Model.ParsedLog?.IsValid == true)
{
<div class="banner success" v-pre>
<strong>Share this link to let someone else see the log:</strong> <code>@(new Uri(new Uri(Model.SectionUrl), Model.PasteID))</code><br />
(Or <a href="@Model.SectionUrl">upload a new log</a>.)
<strong>Share this link to let someone else see the log:</strong> <code>https://@this.Context.Request.Host.ToUriComponent()@this.Url.Action("Index", "LogParser", new { id = Model.PasteID })</code><br />
(Or <a href="@this.Url.Action("Index", "LogParser")">upload a new log</a>.)
</div>
}
@ -127,7 +127,7 @@ else if (Model.ParsedLog?.IsValid == true)
</div>
<h2>How do I share my log?</h2>
<form action="@Model.SectionUrl" method="post">
<form action="@this.Url.Action("PostAsync", "LogParser")" method="post">
<ol>
<li>
Drag the file onto this textbox (or paste the text in):<br />
@ -322,12 +322,12 @@ else if (Model.ParsedLog?.IsValid == true)
}
</table>
<small><a href="@(new Uri(new Uri(Model.SectionUrl), Model.PasteID))?raw=true">view raw log</a></small>
<small><a href="@this.Url.Action("Index", "LogParser", new { id = Model.PasteID })?raw=true">view raw log</a></small>
}
else
{
<pre v-pre>@Model.ParsedLog.RawText</pre>
<small><a href="@(new Uri(new Uri(Model.SectionUrl), Model.PasteID))">view parsed log</a></small>
<small><a href="@this.Url.Action("Index", "LogParser", new { id = Model.PasteID })">view parsed log</a></small>
}
</div>
}

View File

@ -15,15 +15,15 @@
<div id="sidebar">
<h4>SMAPI</h4>
<ul>
<li><a href="@SiteConfig.Value.RootUrl">About SMAPI</a></li>
<li><a href="@Url.Action("Index", "Index")">About SMAPI</a></li>
<li><a href="https://stardewvalleywiki.com/Modding:Index">Modding docs</a></li>
</ul>
<h4>Tools</h4>
<ul>
<li><a href="@SiteConfig.Value.ModListUrl">Mod compatibility</a></li>
<li><a href="@SiteConfig.Value.LogParserUrl">Log parser</a></li>
<li><a href="@SiteConfig.Value.JsonValidatorUrl">JSON validator</a></li>
<li><a href="@Url.Action("Index", "Mods")">Mod compatibility</a></li>
<li><a href="@Url.Action("Index", "LogParser")">Log parser</a></li>
<li><a href="@Url.Action("Index", "JsonValidator")">JSON validator</a></li>
</ul>
</div>
<div id="content-column">

View File

@ -9,10 +9,6 @@
*/
{
"Site": {
"RootUrl": "http://localhost:59482/",
"ModListUrl": "http://localhost:59482/mods/",
"LogParserUrl": "http://localhost:59482/log/",
"JsonValidatorUrl": "http://localhost:59482/json/",
"BetaEnabled": false,
"BetaBlurb": null
},

View File

@ -16,10 +16,6 @@
},
"Site": {
"RootUrl": null,
"ModListUrl": null,
"LogParserUrl": null,
"JsonValidatorUrl": null,
"BetaEnabled": null,
"BetaBlurb": null
},
@ -72,7 +68,6 @@
"ModUpdateCheck": {
"SuccessCacheMinutes": 60,
"ErrorCacheMinutes": 5,
"CompatibilityPageUrl": "https://mods.smapi.io"
"ErrorCacheMinutes": 5
}
}

View File

@ -78,6 +78,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SMAPI.Toolkit.CoreInterface
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SMAPI.Web", "SMAPI.Web\SMAPI.Web.csproj", "{80EFD92F-728F-41E0-8A5B-9F6F49A91899}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SMAPI.Web.LegacyRedirects", "SMAPI.Web.LegacyRedirects\SMAPI.Web.LegacyRedirects.csproj", "{159AA5A5-35C2-488C-B23F-1613C80594AE}"
EndProject
Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
SMAPI.Internal\SMAPI.Internal.projitems*{85208f8d-6fd1-4531-be05-7142490f59fe}*SharedItemsImports = 13
@ -131,6 +133,10 @@ Global
{80EFD92F-728F-41E0-8A5B-9F6F49A91899}.Debug|Any CPU.Build.0 = Debug|Any CPU
{80EFD92F-728F-41E0-8A5B-9F6F49A91899}.Release|Any CPU.ActiveCfg = Release|Any CPU
{80EFD92F-728F-41E0-8A5B-9F6F49A91899}.Release|Any CPU.Build.0 = Release|Any CPU
{159AA5A5-35C2-488C-B23F-1613C80594AE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{159AA5A5-35C2-488C-B23F-1613C80594AE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{159AA5A5-35C2-488C-B23F-1613C80594AE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{159AA5A5-35C2-488C-B23F-1613C80594AE}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View File

@ -87,7 +87,7 @@ namespace StardewModdingAPI.Framework.ModLoading
updateUrls.Add(mod.DataRecord.AlternativeUrl);
// default update URL
updateUrls.Add("https://mods.smapi.io");
updateUrls.Add("https://smapi.io/mods");
// build error
string error = $"{reasonPhrase}. Please check for a ";

View File

@ -23,7 +23,7 @@ namespace StardewModdingAPI.Framework.Models
#endif
[nameof(UseBetaChannel)] = Constants.ApiVersion.IsPrerelease(),
[nameof(GitHubProjectName)] = "Pathoschild/SMAPI",
[nameof(WebApiBaseUrl)] = "https://api.smapi.io",
[nameof(WebApiBaseUrl)] = "https://smapi.io/api/",
[nameof(VerboseLogging)] = false,
[nameof(LogNetworkTraffic)] = false,
[nameof(DumpMetadata)] = false

View File

@ -299,7 +299,7 @@ namespace StardewModdingAPI.Framework
if (File.Exists(Constants.FatalCrashMarker))
{
this.Monitor.Log("The game crashed last time you played. That can be due to bugs in the game, but if it happens repeatedly you can ask for help here: https://community.playstarbound.com/threads/108375/.", LogLevel.Error);
this.Monitor.Log("If you ask for help, make sure to share your SMAPI log: https://log.smapi.io.", LogLevel.Error);
this.Monitor.Log("If you ask for help, make sure to share your SMAPI log: https://smapi.io/log.", LogLevel.Error);
this.Monitor.Log("Press any key to delete the crash data and continue playing.", LogLevel.Info);
Console.ReadKey();
File.Delete(Constants.FatalCrashLog);
@ -962,7 +962,7 @@ namespace StardewModdingAPI.Framework
}
catch (IncompatibleInstructionException) // details already in trace logs
{
string[] updateUrls = new[] { modDatabase.GetModPageUrlFor(manifest.UniqueID), "https://mods.smapi.io" }.Where(p => p != null).ToArray();
string[] updateUrls = new[] { modDatabase.GetModPageUrlFor(manifest.UniqueID), "https://smapi.io/mods" }.Where(p => p != null).ToArray();
errorReasonPhrase = $"it's no longer compatible. Please check for a new version at {string.Join(" or ", updateUrls)}";
return false;
}

View File

@ -52,7 +52,7 @@ The default values are mirrored in StardewModdingAPI.Framework.Models.SConfig to
* Note: the protocol will be changed to http:// on Linux/Mac due to OpenSSL issues with the
* game's bundled Mono.
*/
"WebApiBaseUrl": "https://api.smapi.io",
"WebApiBaseUrl": "https://smapi.io/api/",
/**
* Whether SMAPI should log network traffic (may be very verbose). Best combined with VerboseLogging, which includes network metadata.