Skip to content

Commit ad9cb48

Browse files
committed
add integration with Generic Mod Config Menu
1 parent a00592e commit ad9cb48

27 files changed

Lines changed: 1566 additions & 87 deletions

docs/README.md

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -58,22 +58,22 @@ contributing translations.
5858

5959
locale | status
6060
----------- | :----------------
61-
default | [fully translated](../src/SMAPI/i18n/default.json)
62-
Chinese | [fully translated](../src/SMAPI/i18n/zh.json)
63-
French | [fully translated](../src/SMAPI/i18n/fr.json)
64-
German | [fully translated](../src/SMAPI/i18n/de.json)
65-
Hungarian | [fully translated](../src/SMAPI/i18n/hu.json)
66-
Indonesian | [fully translated](../src/SMAPI/i18n/id.json)
67-
Italian | [fully translated](../src/SMAPI/i18n/it.json)
68-
Japanese | [fully translated](../src/SMAPI/i18n/ja.json)
69-
Korean | [fully translated](../src/SMAPI/i18n/ko.json)
70-
[Polish] | [fully translated](../src/SMAPI/i18n/pl.json)
71-
Portuguese | [fully translated](../src/SMAPI/i18n/pt.json)
72-
Russian | [fully translated](../src/SMAPI/i18n/ru.json)
73-
Spanish | [fully translated](../src/SMAPI/i18n/es.json)
74-
[Thai] | [fully translated](../src/SMAPI/i18n/th.json)
75-
Turkish | [fully translated](../src/SMAPI/i18n/tr.json)
76-
[Ukrainian] | [fully translated](../src/SMAPI/i18n/uk.json)
61+
default | [partly translated](../src/SMAPI/i18n/default.json)
62+
Chinese | [partly translated](../src/SMAPI/i18n/zh.json)
63+
French | [partly translated](../src/SMAPI/i18n/fr.json)
64+
German | [partly translated](../src/SMAPI/i18n/de.json)
65+
Hungarian | [partly translated](../src/SMAPI/i18n/hu.json)
66+
Indonesian | [partly translated](../src/SMAPI/i18n/id.json)
67+
Italian | [partly translated](../src/SMAPI/i18n/it.json)
68+
Japanese | [partly translated](../src/SMAPI/i18n/ja.json)
69+
Korean | [partly translated](../src/SMAPI/i18n/ko.json)
70+
[Polish] | [partly translated](../src/SMAPI/i18n/pl.json)
71+
Portuguese | [partly translated](../src/SMAPI/i18n/pt.json)
72+
Russian | [partly translated](../src/SMAPI/i18n/ru.json)
73+
Spanish | [partly translated](../src/SMAPI/i18n/es.json)
74+
[Thai] | [partly translated](../src/SMAPI/i18n/th.json)
75+
Turkish | [partly translated](../src/SMAPI/i18n/tr.json)
76+
[Ukrainian] | [partly translated](../src/SMAPI/i18n/uk.json)
7777

7878
[Polish]: https://www.nexusmods.com/stardewvalley/mods/3616
7979
[Thai]: https://www.nexusmods.com/stardewvalley/mods/7052

docs/release-notes.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
# Release notes
44
## Upcoming release
55
* For players:
6+
* Added in-game config UI via [Generic Mod Config Menu](<https://www.nexusmods.com/stardewvalley/mods/5098>).
67
* SMAPI now uses [automated and attested builds](https://www.patreon.com/posts/automated-builds-148417912) (thanks to DecidedlyHuman)!
78
_This improves the security and transparency of SMAPI builds. Every step to build SMAPI from the public source code is now public and verifiable, with file signatures to let players and tools confirm the build hasn't been tampered with._
89
* SMAPI can now detect known malicious loose files in the `Mods` folder.

src/SMAPI.Internal/ConsoleWriting/ColorfulConsoleWriter.cs

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,15 @@ internal class ColorfulConsoleWriter : IConsoleWriter
1111
/*********
1212
** Fields
1313
*********/
14+
/// <summary>The target platform.</summary>
15+
private readonly Platform Platform;
16+
1417
/// <summary>The console text color for each log level.</summary>
15-
private readonly IDictionary<ConsoleLogLevel, ConsoleColor>? Colors;
18+
private IDictionary<ConsoleLogLevel, ConsoleColor>? Colors;
1619

1720
/// <summary>Whether the current console supports color formatting.</summary>
1821
[MemberNotNullWhen(true, nameof(ColorfulConsoleWriter.Colors))]
19-
private bool SupportsColor { get; }
22+
private bool SupportsColor { get; set; }
2023

2124

2225
/*********
@@ -32,6 +35,16 @@ public ColorfulConsoleWriter(Platform platform)
3235
/// <param name="colorSchemeId">The color scheme ID in <paramref name="colorConfig"/> to use, or <see cref="MonitorColorScheme.AutoDetect"/> to select one automatically.</param>
3336
/// <param name="colorConfig">The colors to use for text written to the SMAPI console.</param>
3437
public ColorfulConsoleWriter(Platform platform, MonitorColorScheme colorSchemeId, Dictionary<MonitorColorScheme, Dictionary<ConsoleLogLevel, ConsoleColor>> colorConfig)
38+
{
39+
this.Platform = platform;
40+
41+
this.SetColors(colorSchemeId, colorConfig);
42+
}
43+
44+
/// <summary>Set the color scheme to apply.</summary>
45+
/// <param name="colorSchemeId">The color scheme ID in <paramref name="colorSchemes"/> to use, or <see cref="MonitorColorScheme.AutoDetect"/> to select one automatically.</param>
46+
/// <param name="colorSchemes">The colors to use for text written to the SMAPI console.</param>
47+
public void SetColors(MonitorColorScheme colorSchemeId, Dictionary<MonitorColorScheme, Dictionary<ConsoleLogLevel, ConsoleColor>> colorSchemes)
3548
{
3649
if (colorSchemeId == MonitorColorScheme.None)
3750
{
@@ -41,7 +54,7 @@ public ColorfulConsoleWriter(Platform platform, MonitorColorScheme colorSchemeId
4154
else
4255
{
4356
this.SupportsColor = this.TestColorSupport();
44-
this.Colors = this.GetConsoleColorScheme(platform, colorSchemeId, colorConfig);
57+
this.Colors = ColorfulConsoleWriter.GetConsoleColorScheme(this.Platform, colorSchemeId, colorSchemes);
4558
}
4659
}
4760

@@ -123,7 +136,7 @@ private bool TestColorSupport()
123136
/// <param name="platform">The target platform.</param>
124137
/// <param name="colorSchemeId">The color scheme ID in <paramref name="colorConfig"/> to use, or <see cref="MonitorColorScheme.AutoDetect"/> to select one automatically.</param>
125138
/// <param name="colorConfig">The colors to use for text written to the SMAPI console.</param>
126-
private IDictionary<ConsoleLogLevel, ConsoleColor> GetConsoleColorScheme(Platform platform, MonitorColorScheme colorSchemeId, Dictionary<MonitorColorScheme, Dictionary<ConsoleLogLevel, ConsoleColor>> colorConfig)
139+
private static IDictionary<ConsoleLogLevel, ConsoleColor> GetConsoleColorScheme(Platform platform, MonitorColorScheme colorSchemeId, Dictionary<MonitorColorScheme, Dictionary<ConsoleLogLevel, ConsoleColor>> colorConfig)
127140
{
128141
// get color scheme ID
129142
if (colorSchemeId == MonitorColorScheme.AutoDetect)

src/SMAPI/Framework/Logging/LogManager.cs

Lines changed: 54 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,12 @@ internal class LogManager : IDisposable
2828
/// <summary>Create a monitor instance given the ID and name.</summary>
2929
private readonly Func<string, string, Monitor> GetMonitorImpl;
3030

31+
/// <summary>The console writer which sends color-coded text to the console.</summary>
32+
private readonly ColorfulConsoleWriter ConsoleWriter;
33+
34+
/// <summary>The monitors managed by SMAPI.</summary>
35+
private readonly List<Monitor> Monitors = [];
36+
3137

3238
/*********
3339
** Accessors
@@ -59,12 +65,9 @@ public LogManager(string logPath, MonitorColorScheme colorSchemeId, Dictionary<M
5965
this.LogFile = new LogFileManager(logPath);
6066

6167
// init monitor
62-
this.GetMonitorImpl = (id, name) => new Monitor(id, name, this.LogFile, colorSchemeId, colorConfig, verboseLogging.Contains("*") || verboseLogging.Contains(id), getScreenIdForLog)
63-
{
64-
WriteToConsole = writeToConsole,
65-
ShowTraceInConsole = isDeveloperMode,
66-
ShowFullStampInConsole = isDeveloperMode
67-
};
68+
this.ConsoleWriter = new ColorfulConsoleWriter(Constants.Platform, colorSchemeId, colorConfig);
69+
this.GetMonitorImpl = (id, name) => this.CreateAndRegisterMonitor(id, name, verboseLogging, getScreenIdForLog, writeToConsole, isDeveloperMode);
70+
6871
this.Monitor = this.GetMonitor("SMAPI", "SMAPI");
6972
this.MonitorForGame = this.GetMonitor("game", "game");
7073

@@ -91,6 +94,19 @@ public void SetConsoleTitle(string title)
9194
Console.Title = title;
9295
}
9396

97+
/// <summary>Apply the SMAPI settings to the log manager and its managed monitors.</summary>
98+
/// <param name="colorSchemeId">The color scheme ID in <paramref name="colorSchemes"/> to use, or <see cref="MonitorColorScheme.AutoDetect"/> to select one automatically.</param>
99+
/// <param name="colorSchemes">The colors to use for text written to the SMAPI console.</param>
100+
/// <param name="verboseLogging">The log contexts for which to enable verbose logging, which may show a lot more information to simplify troubleshooting.</param>
101+
/// <param name="isDeveloperMode">Whether to enable full console output for developers.</param>
102+
public void ApplySettings(MonitorColorScheme colorSchemeId, Dictionary<MonitorColorScheme, Dictionary<ConsoleLogLevel, ConsoleColor>> colorSchemes, HashSet<string> verboseLogging, bool isDeveloperMode)
103+
{
104+
foreach (Monitor monitor in this.Monitors)
105+
this.ApplySettings(monitor, verboseLogging, isDeveloperMode);
106+
107+
this.ConsoleWriter.SetColors(colorSchemeId, colorSchemes);
108+
}
109+
94110
/****
95111
** Console input
96112
****/
@@ -333,6 +349,38 @@ public void Dispose()
333349
/*********
334350
** Protected methods
335351
*********/
352+
/// <summary>Create and register a monitor instance.</summary>
353+
/// <param name="modId">The mod ID, if applicable.</param>
354+
/// <param name="source">The name of the module which logs messages using this instance.</param>
355+
/// <param name="verboseLogging">The log contexts for which to enable verbose logging, which may show a lot more information to simplify troubleshooting.</param>
356+
/// <param name="getScreenIdForLog">Get the screen ID that should be logged to distinguish between players in split-screen mode, if any.</param>
357+
/// <param name="writeToConsole">Whether to write anything to the console. This should be disabled if no console is available.</param>
358+
/// <param name="isDeveloperMode">Whether to enable full console output for developers.</param>
359+
private Monitor CreateAndRegisterMonitor(string modId, string source, HashSet<string> verboseLogging, Func<int?> getScreenIdForLog, bool writeToConsole, bool isDeveloperMode)
360+
{
361+
Monitor monitor = new(modId, source, this.LogFile, this.ConsoleWriter, getScreenIdForLog)
362+
{
363+
WriteToConsole = writeToConsole
364+
};
365+
366+
this.ApplySettings(monitor, verboseLogging, isDeveloperMode);
367+
368+
this.Monitors.Add(monitor);
369+
370+
return monitor;
371+
}
372+
373+
/// <summary>Apply the SMAPI settings to a managed monitor.</summary>
374+
/// <param name="monitor">The monitor to update.</param>
375+
/// <param name="verboseLogging">The log contexts for which to enable verbose logging, which may show a lot more information to simplify troubleshooting.</param>
376+
/// <param name="isDeveloperMode">Whether to enable full console output for developers.</param>
377+
private void ApplySettings(Monitor monitor, HashSet<string> verboseLogging, bool isDeveloperMode)
378+
{
379+
monitor.IsVerbose = verboseLogging.Contains("*") || verboseLogging.Contains(monitor.ModId);
380+
monitor.ShowTraceInConsole = isDeveloperMode;
381+
monitor.ShowFullStampInConsole = isDeveloperMode;
382+
}
383+
336384
/// <summary>Write a summary of mod warnings to the console and log.</summary>
337385
/// <param name="mods">The loaded mods.</param>
338386
/// <param name="skippedMods">The mods which could not be loaded.</param>

src/SMAPI/Framework/Models/SConfig.cs

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -15,20 +15,21 @@ internal class SConfig
1515
/// <summary>The default config values, for fields that should be logged if different.</summary>
1616
private static readonly IDictionary<string, object> DefaultValues = new Dictionary<string, object>
1717
{
18-
[nameof(CheckForUpdates)] = true,
19-
[nameof(CheckForBlacklistUpdates)] = true,
20-
[nameof(CheckContentIntegrity)] = true,
21-
[nameof(ListenForConsoleInput)] = true,
22-
[nameof(ParanoidWarnings)] = Constants.IsDebugBuild,
23-
[nameof(UseBetaChannel)] = Constants.ApiVersion.IsPrerelease(),
24-
[nameof(GitHubProjectName)] = "Pathoschild/SMAPI",
25-
[nameof(WebApiBaseUrl)] = "https://smapi.io/api/",
26-
[nameof(LogNetworkTraffic)] = false,
27-
[nameof(LogTechnicalDetailsForBrokenMods)] = false,
28-
[nameof(RewriteMods)] = true,
29-
[nameof(FixHarmony)] = true,
30-
[nameof(UseCaseInsensitivePaths)] = Constants.Platform is Platform.Android or Platform.Linux,
31-
[nameof(SuppressHarmonyDebugMode)] = true
18+
[nameof(SConfig.CheckForUpdates)] = true,
19+
[nameof(SConfig.CheckForBlacklistUpdates)] = true,
20+
[nameof(SConfig.CheckContentIntegrity)] = true,
21+
[nameof(SConfig.ListenForConsoleInput)] = true,
22+
[nameof(SConfig.ParanoidWarnings)] = Constants.IsDebugBuild,
23+
[nameof(SConfig.UseBetaChannel)] = Constants.ApiVersion.IsPrerelease(),
24+
[nameof(SConfig.GitHubProjectName)] = "Pathoschild/SMAPI",
25+
[nameof(SConfig.WebApiBaseUrl)] = "https://smapi.io/api/",
26+
[nameof(SConfig.LogNetworkTraffic)] = false,
27+
[nameof(SConfig.LogTechnicalDetailsForBrokenMods)] = false,
28+
[nameof(SConfig.RewriteMods)] = true,
29+
[nameof(SConfig.EnableConfigMenu)] = true,
30+
[nameof(SConfig.FixHarmony)] = true,
31+
[nameof(SConfig.UseCaseInsensitivePaths)] = Constants.Platform is Platform.Android or Platform.Linux,
32+
[nameof(SConfig.SuppressHarmonyDebugMode)] = true
3233
};
3334

3435
/// <summary>The default values for <see cref="SuppressUpdateChecks"/>, to log changes if different.</summary>
@@ -46,7 +47,7 @@ internal class SConfig
4647
// Note: properties must be writable to support merging config.user.json into it.
4748
//
4849

49-
/// <summary>Whether to enable development features.</summary>
50+
/// <summary>Whether to show much more info in the SMAPI consoler window, intended for mod developers. Not recommended for most players.</summary>
5051
public bool DeveloperMode { get; set; }
5152

5253
/// <summary>Whether to check for newer versions of SMAPI and mods on startup.</summary>
@@ -61,7 +62,7 @@ internal class SConfig
6162
/// <summary>Whether SMAPI should listen for console input to support console commands.</summary>
6263
public bool ListenForConsoleInput { get; set; }
6364

64-
/// <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>
65+
/// <summary>Whether to add a section to the 'mod issues' list for mods which directly use potentially sensitive .NET APIs like file or shell access.</summary>
6566
public bool ParanoidWarnings { get; set; }
6667

6768
/// <summary>Whether to show beta versions as valid updates.</summary>
@@ -83,6 +84,9 @@ internal class SConfig
8384
/// <summary>Whether SMAPI should rewrite mods for compatibility.</summary>
8485
public bool RewriteMods { get; set; }
8586

87+
/// <summary>Whether SMAPI should add an in-game config menu through Generic Mod Config Menu.</summary>
88+
public bool EnableConfigMenu { get; set; }
89+
8690
/// <summary>Whether to apply fixes to Harmony so it works with Stardew Valley.</summary>
8791
public bool FixHarmony { get; set; }
8892

@@ -130,6 +134,7 @@ internal class SConfig
130134
/// <param name="blacklistUrl"><inheritdoc cref="BlacklistUrl" path="/summary" /></param>
131135
/// <param name="verboseLogging"><inheritdoc cref="VerboseLogging" path="/summary" /></param>
132136
/// <param name="rewriteMods"><inheritdoc cref="RewriteMods" path="/summary" /></param>
137+
/// <param name="enableConfigMenu"><inheritdoc cref="EnableConfigMenu" path="/summary" /></param>
133138
/// <param name="fixHarmony"><inheritdoc cref="FixHarmony" path="/summary" /></param>
134139
/// <param name="useCaseInsensitivePaths"><inheritdoc cref="UseCaseInsensitivePaths" path="/summary" /></param>
135140
/// <param name="logNetworkTraffic"><inheritdoc cref="LogNetworkTraffic" path="/summary" /></param>
@@ -153,6 +158,7 @@ public SConfig(
153158
string blacklistUrl,
154159
string[]? verboseLogging,
155160
bool? rewriteMods,
161+
bool? enableConfigMenu,
156162
bool? fixHarmony,
157163
bool? useCaseInsensitivePaths,
158164
bool? logNetworkTraffic,
@@ -177,6 +183,7 @@ public SConfig(
177183
this.BlacklistUrl = blacklistUrl;
178184
this.VerboseLogging = new HashSet<string>(verboseLogging ?? [], StringComparer.OrdinalIgnoreCase);
179185
this.RewriteMods = rewriteMods ?? (bool)SConfig.DefaultValues[nameof(this.RewriteMods)];
186+
this.EnableConfigMenu = enableConfigMenu ?? (bool)SConfig.DefaultValues[nameof(this.EnableConfigMenu)];
180187
this.FixHarmony = fixHarmony ?? (bool)SConfig.DefaultValues[nameof(this.FixHarmony)];
181188
this.UseCaseInsensitivePaths = useCaseInsensitivePaths ?? (bool)SConfig.DefaultValues[nameof(this.UseCaseInsensitivePaths)];
182189
this.LogNetworkTraffic = logNetworkTraffic ?? (bool)SConfig.DefaultValues[nameof(this.LogNetworkTraffic)];

src/SMAPI/Framework/Monitor.cs

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,6 @@ internal class Monitor : IMonitor
1313
/*********
1414
** Fields
1515
*********/
16-
/// <summary>The mod ID, if applicable.</summary>
17-
private readonly string ModId;
18-
1916
/// <summary>The name of the module which logs messages using this instance.</summary>
2017
private readonly string Source;
2118

@@ -41,6 +38,9 @@ internal class Monitor : IMonitor
4138
/*********
4239
** Accessors
4340
*********/
41+
/// <summary>The mod ID, if applicable.</summary>
42+
public string ModId { get; }
43+
4444
/// <summary>Whether to log basic contextual info (like buttons pressed and menus opened) even if <see cref="IsVerbose"/> is disabled.</summary>
4545
public static bool ForceLogContext { get; set; }
4646

@@ -54,7 +54,11 @@ internal class Monitor : IMonitor
5454
public static LogLevel ContextLogLevel => Monitor.ForceLogContext ? LogLevel.Info : LogLevel.Trace;
5555

5656
/// <inheritdoc />
57-
public bool IsVerbose => field || Monitor.ForceVerboseLoggingForAll || Monitor.ForceVerboseLogging.Contains(this.ModId);
57+
public bool IsVerbose
58+
{
59+
get => field || Monitor.ForceVerboseLoggingForAll || Monitor.ForceVerboseLogging.Contains(this.ModId);
60+
set => field = value;
61+
}
5862

5963
/// <summary>Whether to show the full log stamps (with time/level/logger) in the console. If false, shows a simplified stamp with only the logger.</summary>
6064
internal bool ShowFullStampInConsole { get; set; }
@@ -73,11 +77,9 @@ internal class Monitor : IMonitor
7377
/// <param name="modId">The mod ID, if applicable.</param>
7478
/// <param name="source">The name of the module which logs messages using this instance.</param>
7579
/// <param name="logFile">The log file to which to write messages.</param>
76-
/// <param name="colorSchemeId">The color scheme ID in <paramref name="colorConfig"/> to use, or <see cref="MonitorColorScheme.AutoDetect"/> to select one automatically.</param>
77-
/// <param name="colorConfig">The colors to use for text written to the SMAPI console.</param>
78-
/// <param name="isVerbose">Whether verbose logging is enabled. This enables more detailed diagnostic messages than are normally needed.</param>
80+
/// <param name="consoleWriter">Handles writing text to the console.</param>
7981
/// <param name="getScreenIdForLog">Get the screen ID that should be logged to distinguish between players in split-screen mode, if any.</param>
80-
public Monitor(string modId, string source, LogFileManager logFile, MonitorColorScheme colorSchemeId, Dictionary<MonitorColorScheme, Dictionary<ConsoleLogLevel, ConsoleColor>> colorConfig, bool isVerbose, Func<int?> getScreenIdForLog)
82+
public Monitor(string modId, string source, LogFileManager logFile, IConsoleWriter consoleWriter, Func<int?> getScreenIdForLog)
8183
{
8284
// validate
8385
if (string.IsNullOrWhiteSpace(source))
@@ -87,8 +89,7 @@ public Monitor(string modId, string source, LogFileManager logFile, MonitorColor
8789
this.ModId = modId;
8890
this.Source = source;
8991
this.LogFile = logFile ?? throw new ArgumentNullException(nameof(logFile), "The log file manager cannot be null.");
90-
this.ConsoleWriter = new ColorfulConsoleWriter(Constants.Platform, colorSchemeId, colorConfig);
91-
this.IsVerbose = isVerbose;
92+
this.ConsoleWriter = consoleWriter ?? throw new ArgumentNullException(nameof(consoleWriter), "The console writer cannot be null.");
9293
this.GetScreenIdForLog = getScreenIdForLog;
9394
}
9495

0 commit comments

Comments
 (0)