Merge branch 'develop' into stable

This commit is contained in:
Jesse Plamondon-Willard
2026-01-25 20:08:51 -05:00
55 changed files with 3771 additions and 2266 deletions
+113
View File
@@ -0,0 +1,113 @@
name: Build SMAPI
on:
push:
tags:
- "[0-9]+.[0-9]+.[0-9]+" # stable version tag
branches:
- develop
- build-test
jobs:
build_and_release:
runs-on: ubuntu-latest
permissions:
contents: write # for creating releases
id-token: write # for creating attestations
attestations: write
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Get SMAPI assembly version
id: smapi-version
shell: pwsh
run: |
[xml]$xml = Get-Content build/common.targets
[string]$version = $xml.Project.PropertyGroup.Version
$version = $version.Trim()
$pattern = '^(\d+)\.(\d+)\.(\d+)$'
if ($version -match $pattern) {
$major = $Matches[1]
$minor = $Matches[2]
$patch = $Matches[3]
}
else {
Write-Error "The version '$version' found in build/common.targets did not match the strict x.y.z format. Aborting."
Write-Host 'Debug info:'
Write-Host "Extracted version: $version"
exit 1
}
"smapi_version=$version" | Out-File $env:GITHUB_OUTPUT -Append
"smapi_major=$major" | Out-File $env:GITHUB_OUTPUT -Append
"smapi_minor=$minor" | Out-File $env:GITHUB_OUTPUT -Append
"smapi_patch=$patch" | Out-File $env:GITHUB_OUTPUT -Append
"VERSION=$major.$minor.$patch" | Out-File $env:GITHUB_ENV -Append
Write-Host "Version: $major.$minor.$patch"
- name: Verify SMAPI assembly version matches tag
if: github.ref_type == 'tag'
shell: pwsh
run: |
if ($env:VERSION -ne '${{github.ref_name}}') {
Write-Error "The pushed tag '${{github.ref_name}}' doesn't match the version '$env:VERSION' found in build/common.targets. Aborting."
Write-Host 'Debug info:'
Write-Host 'Tag: ${{github.ref_name}}'
Write-Host "In common.targets: $env:VERSION"
exit 1
}
- name: Update SMAPI assembly version
if: github.ref_type != 'tag'
shell: pwsh
run: |
$updatedPatch = [int]${{steps.smapi-version.outputs.smapi_patch}} + 1
$timestamp = Get-Date -Format 'yyyyMMdd-HHmm'
$updatedVersion = "${{steps.smapi-version.outputs.smapi_major}}.${{steps.smapi-version.outputs.smapi_minor}}.$updatedPatch-alpha.$timestamp"
"VERSION=$updatedVersion" | Out-File $env:GITHUB_ENV -Append
Write-Host "This is a dev version. Building as version: $updatedVersion"
./build/scripts/set-smapi-version.ps1 "$updatedVersion"
Write-Host 'Version updated.'
- name: Checkout game reference assemblies
run: |
git clone --depth 1 https://github.com/StardewModders/mod-reference-assemblies.git "$HOME/StardewValley"
- name: Build SMAPI
shell: pwsh
run: |
Write-Host "Building SMAPI $env:VERSION."
./build/scripts/prepare-install-package.ps1 "$env:VERSION"
- name: Upload installer
uses: actions/upload-artifact@v4
with:
name: 'SMAPI ${{env.VERSION}} installer'
path: 'bin/SMAPI-${{env.VERSION}}-installer.zip'
if-no-files-found: 'error'
- name: Create GitHub release
uses: ncipollo/release-action@v1
if: github.ref_type == 'tag'
with:
artifacts: 'bin/SMAPI-${{env.VERSION}}-installer.zip'
token: '${{secrets.GITHUB_TOKEN}}'
tag: '${{github.ref_name}}'
name: '${{env.VERSION}}'
body: |
Draft.
draft: true
- name: Generate artifact attestations
uses: actions/attest-build-provenance@v2
if: github.ref_type == 'tag'
with:
subject-path: bin/SMAPI-${{env.VERSION}}-installer.zip
+1 -1
View File
@@ -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>4.4.0</Version>
<Version>4.5.0</Version>
<Product>SMAPI</Product>
<LangVersion>latest</LangVersion>
<AssemblySearchPaths>$(AssemblySearchPaths);{GAC}</AssemblySearchPaths>
+66
View File
@@ -0,0 +1,66 @@
#!/usr/bin/env bash
##########
## Read config
##########
# get SMAPI version
version="$1"
if [ $# -eq 0 ]; then
echo "SMAPI release version (like '4.0.0'):"
read version
fi
# get Windows bin path
windowsBinPath="$2"
if [ $# -le 1 ]; then
echo "Windows compiled bin path:"
read windowsBinPath
fi
# installer internal folders
buildFolders=("linux" "macOS" "windows")
##########
## Finalize release package
##########
# move files to Linux filesystem
folderName = "SMAPI $version installer"
echo "Preparing $folderName.zip..."
echo "-------------------------------------------------"
echo "copying '$windowsBinPath/$folderName' to Linux filesystem..."
cp -r "$windowsBinPath/$folderName" .
# fix permissions
echo "fixing permissions..."
find "$folderName" -type d -exec chmod 755 {} \;
find "$folderName" -type f -exec chmod 644 {} \;
find "$folderName" -name "*.sh" -exec chmod 755 {} \;
find "$folderName" -name "*.command" -exec chmod 755 {} \;
find "$folderName" -name "SMAPI.Installer" -exec chmod 755 {} \;
find "$folderName" -name "StardewModdingAPI" -exec chmod 755 {} \;
# convert bundle folder into final 'install.dat' files
for build in ${buildFolders[@]}; do
echo "packaging $folderName/internal/$build/install.dat..."
pushd "$folderName/internal/$build/bundle" > /dev/null
zip "install.dat" * --recurse-paths --quiet
mv install.dat ../
popd > /dev/null
rm -rf "$folderName/internal/$build/bundle"
done
# zip installer
echo "packaging installer..."
zip -9 "$folderName.zip" "$folderName" --recurse-paths --quiet
# move zip back to Windows bin path
echo "moving release zip to $windowsBinPath/$folderName.zip..."
mv "$folderName.zip" "$windowsBinPath"
rm -rf "$folderName"
echo ""
echo ""
echo "Done!"
@@ -1,4 +1,4 @@
function In-Place-Regex {
function In-Place-Regex {
param (
[Parameter(Mandatory)][string]$Path,
[Parameter(Mandatory)][string]$Search,
+289
View File
@@ -0,0 +1,289 @@
#!/usr/bin/env pwsh
#
#
# Note: On Windows, this script *does not* set Linux permissions. The final changes are handled by the
# finalize-install-package.sh file in WSL.
#
#
. "$PSScriptRoot/lib/in-place-regex.ps1"
##########
## Read arguments
##########
$windowsOnly = $false # Windows-only build
$skipBundleDeletion = $false # skip bundle deletion (only applies when using WSL to finalize the build on Windows)
foreach ($arg in $args) {
if ($arg -eq "--windows-only" -and $IsWindows) {
$windowsOnly = $true
}
elseif ($arg -eq "--skip-bundle-deletion") {
$skipBundleDeletion = $true
}
}
##########
## Find the game folder
##########
if ($IsWindows) {
$possibleGamePaths=(
# GOG
"C:\Program Files\GalaxyClient\Games\Stardew Valley",
"C:\Program Files\GOG Galaxy\Games\Stardew Valley",
"C:\Program Files\GOG Games\Stardew Valley",
"C:\Program Files (x86)\GalaxyClient\Games\Stardew Valley",
"C:\Program Files (x86)\GOG Galaxy\Games\Stardew Valley",
"C:\Program Files (x86)\GOG Games\Stardew Valley",
# Steam
"C:\Program Files\Steam\steamapps\common\Stardew Valley",
"C:\Program Files (x86)\Steam\steamapps\common\Stardew Valley"
)
}
else {
$possibleGamePaths=(
# override
"$HOME/StardewValley",
# Linux
"$HOME/GOG Games/Stardew Valley/game",
"$HOME/.steam/steam/steamapps/common/Stardew Valley",
"$HOME/.local/share/Steam/steamapps/common/Stardew Valley",
"$HOME/.var/app/com.valvesoftware.Steam/data/Steam/steamapps/common/Stardew Valley",
# macOS
"/Applications/Stardew Valley.app/Contents/MacOS",
"$HOME/Library/Application Support/Steam/steamapps/common/Stardew Valley/Contents/MacOS"
)
}
$gamePath = ""
foreach ($possibleGamePath in $possibleGamePaths) {
if (Test-Path $possibleGamePath -PathType Container) {
$gamePath = $possibleGamePath
break
}
}
##########
## Preset values
##########
# paths
$bundleModNames = "ConsoleCommands", "SaveBackup"
# build configuration
$buildConfig = "Release"
$framework = "net6.0"
if ($windowsOnly) {
$folders = "windows"
$runtimes = @{ windows = "win-x64" }
$msBuildPlatformNames = @{ windows = "Windows_NT" }
}
else {
$folders = "linux", "macOS", "windows"
$runtimes = @{ linux = "linux-x64"; macOS = "osx-x64"; windows = "win-x64" }
$msBuildPlatformNames = @{ linux = "Unix"; macOS = "OSX"; windows = "Windows_NT" }
}
# version number
$version = $args[0]
if (!$version) {
$version = Read-Host "SMAPI release version (like '4.0.0')"
}
##########
## Move to SMAPI root
##########
Set-Location "$PSScriptRoot/../.."
##########
## Clear old build files
##########
Write-Output "Clearing old builds..."
Write-Output "-------------------------------------------------"
foreach ($path in (Get-ChildItem -Recurse -Include ('bin', 'obj'))) {
Write-Output "$path"
Remove-Item -Recurse -Force "$path"
}
Write-Output ""
##########
## Compile files
##########
. "$PSScriptRoot/set-smapi-version.ps1" "$version"
foreach ($folder in $folders) {
$runtime = $runtimes[$folder]
$msbuildPlatformName = $msBuildPlatformNames[$folder]
Write-Output "Compiling SMAPI for $folder..."
Write-Output "-------------------------------------------------"
dotnet publish src/SMAPI --configuration $buildConfig -v minimal --runtime "$runtime" --framework "$framework" -p:OS="$msbuildPlatformName" -p:TargetFrameworks="$framework" -p:GamePath="$gamePath" -p:CopyToGameFolder="false" --self-contained true
Write-Output ""
Write-Output ""
Write-Output "Compiling installer for $folder..."
Write-Output "-------------------------------------------------"
dotnet publish src/SMAPI.Installer --configuration $buildConfig -v minimal --runtime "$runtime" --framework "$framework" -p:OS="$msbuildPlatformName" -p:TargetFrameworks="$framework" -p:GamePath="$gamePath" -p:CopyToGameFolder="false" --self-contained true
Write-Output ""
Write-Output ""
foreach ($modName in $bundleModNames) {
Write-Output "Compiling $modName for $folder..."
Write-Output "-------------------------------------------------"
dotnet publish src/SMAPI.Mods.$modName --configuration $buildConfig -v minimal --runtime "$runtime" --framework "$framework" -p:OS="$msbuildPlatformName" -p:TargetFrameworks="$framework" -p:GamePath="$gamePath" -p:CopyToGameFolder="false" --self-contained false
Write-Output ""
Write-Output ""
}
}
##########
## Prepare install package
##########
Write-Output "Preparing install package..."
Write-Output "----------------------------"
# init paths
$installAssets = "src/SMAPI.Installer/assets"
$packagePath = "bin/SMAPI installer"
# init structure
foreach ($folder in $folders) {
$folderPath = "$packagePath/internal/$folder/bundle/smapi-internal"
if ($IsWindows) {
# On Windows, mkdir creates parent directories automatically and the --parents argument isn't recognized.
mkdir "$folderPath" > $null
}
else
{
mkdir "$folderPath" --parents
}
}
# copy base installer files
foreach ($name in @("install on Linux.sh", "install on macOS.command", "install on Windows.bat", "README.txt")) {
if ($windowsOnly -and ($name -eq "install on Linux.sh" -or $name -eq "install on macOS.command")) {
continue;
}
Copy-Item "$installAssets/$name" "$packagePath"
}
# copy per-platform files
foreach ($folder in $folders) {
$runtime = $runtimes[$folder]
# get paths
$smapiBin = "src/SMAPI/bin/$buildConfig/$runtime/publish"
$internalPath = "$packagePath/internal/$folder"
$bundlePath = "$internalPath/bundle"
# installer files
Copy-Item "src/SMAPI.Installer/bin/$buildConfig/$runtime/publish/*" "$internalPath" -Recurse
Remove-Item -Recurse -Force "$internalPath/assets"
# runtime config for SMAPI
# This is identical to the one generated by the build, except that the min runtime version is
# set to 6.0.0 (instead of whatever version it was built with) and rollForward is set to latestMinor instead of
# minor.
Copy-Item "$installAssets/runtimeconfig.json" "$bundlePath/StardewModdingAPI.runtimeconfig.json"
# installer DLL config
if ($folder -eq "windows") {
Copy-Item "$installAssets/windows-exe-config.xml" "$packagePath/internal/windows/install.exe.config"
}
# bundle root files
foreach ($name in @("StardewModdingAPI", "StardewModdingAPI.dll", "StardewModdingAPI.xml", "steam_appid.txt")) {
if ($name -eq "StardewModdingAPI" -and $folder -eq "windows") {
$name = "$name.exe"
}
Copy-Item "$smapiBin/$name" "$bundlePath"
}
# bundle i18n
Copy-Item -Recurse "$smapiBin/i18n" "$bundlePath/smapi-internal"
# bundle smapi-internal
foreach ($name in @("0Harmony.dll", "0Harmony.xml", "Markdig.dll", "Mono.Cecil.dll", "Mono.Cecil.Mdb.dll", "Mono.Cecil.Pdb.dll", "MonoMod.Common.dll", "Newtonsoft.Json.dll", "Pathoschild.Http.Client.dll", "Pintail.dll", "TMXTile.dll", "SMAPI.Toolkit.dll", "SMAPI.Toolkit.xml", "SMAPI.Toolkit.CoreInterfaces.dll", "SMAPI.Toolkit.CoreInterfaces.xml", "System.Net.Http.Formatting.dll")) {
Copy-Item "$smapiBin/$name" "$bundlePath/smapi-internal"
}
if ($folder -eq "windows") {
Copy-Item "$smapiBin/VdfConverter.dll" "$bundlePath/smapi-internal"
}
Copy-Item "$smapiBin/SMAPI.blacklist.json" "$bundlePath/smapi-internal/blacklist.json"
Copy-Item "$smapiBin/SMAPI.config.json" "$bundlePath/smapi-internal/config.json"
Copy-Item "$smapiBin/SMAPI.metadata.json" "$bundlePath/smapi-internal/metadata.json"
if ($folder -eq "linux" -or $folder -eq "macOS") {
Copy-Item "$installAssets/unix-launcher.sh" "$bundlePath"
}
else {
Copy-Item "$installAssets/windows-exe-config.xml" "$bundlePath/StardewModdingAPI.exe.config"
}
# copy bundled mods
foreach ($modName in $bundleModNames) {
$fromPath = "src/SMAPI.Mods.$modName/bin/$buildConfig/$runtime/publish"
$targetPath = "$bundlePath/Mods/$modName"
if ($IsWindows) {
# On Windows, mkdir creates parent directories automatically and the --parents argument isn't recognized.
mkdir "$targetPath" > $null
}
else
{
mkdir "$targetPath" --parents
}
Copy-Item "$fromPath/$modName.dll" "$targetPath"
Copy-Item "$fromPath/manifest.json" "$targetPath"
if (Test-Path "$fromPath/i18n" -PathType Container) {
Copy-Item -Recurse "$fromPath/i18n" "$targetPath"
}
}
}
# mark scripts executable
if ($IsWindows) {
Write-Warning "Can't set Unix file permissions on Windows. This may cause issues for Linux/macOS players."
}
else {
ForEach ($path in @("install on Linux.sh", "install on macOS.command", "bundle/unix-launcher.sh")) {
if (Test-Path "$packagePath/$path" -PathType Leaf) {
chmod 755 "$packagePath/$path"
}
}
}
# convert bundle folder into final 'install.dat' files
foreach ($folder in $folders) {
$path = "$packagePath/internal/$folder"
Compress-Archive -Path "$path/bundle/*" -CompressionLevel Optimal -DestinationPath "$path/install.dat"
if (!$skipBundleDeletion) {
Remove-Item -Recurse -Force "$path/bundle"
}
}
###########
### Create release zips
###########
Move-Item "$packagePath" "bin/SMAPI $version installer"
Compress-Archive -Path "bin/SMAPI $version installer" -DestinationPath "bin/SMAPI-$version-installer.zip" -CompressionLevel Optimal
Write-Output ""
Write-Output "Done! Package created in ${pwd.Path}/bin."
+2 -8
View File
@@ -1,10 +1,4 @@
#
#
# This is the PowerShell equivalent of ../unix/set-smapi-version.sh.
# When making changes, both scripts should be updated.
#
#
#!/usr/bin/env pwsh
. "$PSScriptRoot\lib\in-place-regex.ps1"
@@ -15,7 +9,7 @@ if (!$version) {
}
# move to SMAPI root
cd "$PSScriptRoot/../.."
Set-Location "$PSScriptRoot/../.."
# apply changes
In-Place-Regex -Path "build/common.targets" -Search "<Version>.+</Version>" -Replace "<Version>$version</Version>"
-228
View File
@@ -1,228 +0,0 @@
#!/usr/bin/env bash
#
#
# This is the Bash equivalent of ../windows/prepare-install-package.ps1.
# When making changes, both scripts should be updated.
#
#
##########
## Find the game folder
##########
possibleGamePaths=(
# override
"$HOME/StardewValley"
# Linux
"$HOME/GOG Games/Stardew Valley/game"
"$HOME/.steam/steam/steamapps/common/Stardew Valley"
"$HOME/.local/share/Steam/steamapps/common/Stardew Valley"
"$HOME/.var/app/com.valvesoftware.Steam/data/Steam/steamapps/common/Stardew Valley"
# macOS
"/Applications/Stardew Valley.app/Contents/MacOS"
"$HOME/Library/Application Support/Steam/steamapps/common/Stardew Valley/Contents/MacOS"
)
gamePath=""
for possibleGamePath in "${possibleGamePaths[@]}"; do
if [[ -d "$possibleGamePath" ]]; then
gamePath="$possibleGamePath"
break
fi
done
##########
## Preset values
##########
# paths
bundleModNames=("ConsoleCommands" "SaveBackup")
# build configuration
buildConfig="Release"
folders=("linux" "macOS" "windows")
declare -A runtimes=(["linux"]="linux-x64" ["macOS"]="osx-x64" ["windows"]="win-x64")
declare -A msBuildPlatformNames=(["linux"]="Unix" ["macOS"]="OSX" ["windows"]="Windows_NT")
# version number
version="$1"
if [ $# -eq 0 ]; then
echo "SMAPI release version (like '4.0.0'):"
read version
fi
##########
## Move to SMAPI root
##########
cd "`dirname "$0"`/../.."
##########
## Clear old build files
##########
echo "Clearing old builds..."
echo "-------------------------------------------------"
for path in bin */**/bin */**/obj; do
echo "$path"
rm -rf $path
done
echo ""
##########
## Compile files
##########
. ${0%/*}/set-smapi-version.sh "$version"
for folder in ${folders[@]}; do
runtime=${runtimes[$folder]}
msbuildPlatformName=${msBuildPlatformNames[$folder]}
echo "Compiling SMAPI for $folder..."
echo "-------------------------------------------------"
dotnet publish src/SMAPI --configuration $buildConfig -v minimal --runtime "$runtime" -p:OS="$msbuildPlatformName" -p:GamePath="$gamePath" -p:CopyToGameFolder="false" --self-contained true
echo ""
echo ""
echo "Compiling installer for $folder..."
echo "-------------------------------------------------"
dotnet publish src/SMAPI.Installer --configuration $buildConfig -v minimal --runtime "$runtime" -p:OS="$msbuildPlatformName" -p:GamePath="$gamePath" -p:CopyToGameFolder="false" --self-contained true
echo ""
echo ""
for modName in ${bundleModNames[@]}; do
echo "Compiling $modName for $folder..."
echo "-------------------------------------------------"
dotnet publish src/SMAPI.Mods.$modName --configuration $buildConfig -v minimal --runtime "$runtime" -p:OS="$msbuildPlatformName" -p:GamePath="$gamePath" -p:CopyToGameFolder="false" --self-contained false
echo ""
echo ""
done
done
##########
## Prepare install package
##########
echo "Preparing install package..."
echo "-------------------------------------------------"
# init paths
installAssets="src/SMAPI.Installer/assets"
packagePath="bin/SMAPI installer"
packageDevPath="bin/SMAPI installer for developers"
# init structure
for folder in ${folders[@]}; do
mkdir "$packagePath/internal/$folder/bundle/smapi-internal" --parents
done
# copy base installer files
for name in "install on Linux.sh" "install on macOS.command" "install on Windows.bat" "README.txt"; do
cp "$installAssets/$name" "$packagePath"
done
# copy per-platform files
for folder in ${folders[@]}; do
runtime=${runtimes[$folder]}
# get paths
smapiBin="src/SMAPI/bin/$buildConfig/$runtime/publish"
internalPath="$packagePath/internal/$folder"
bundlePath="$internalPath/bundle"
# installer files
cp -r "src/SMAPI.Installer/bin/$buildConfig/$runtime/publish"/* "$internalPath"
rm -rf "$internalPath/assets"
# runtime config for SMAPI
# This is identical to the one generated by the build, except that the min runtime version is
# set to 5.0.0 (instead of whatever version it was built with) and rollForward is set to latestMinor instead of
# minor.
cp "$installAssets/runtimeconfig.json" "$bundlePath/StardewModdingAPI.runtimeconfig.json"
# installer DLL config
if [ $folder == "windows" ]; then
cp "$installAssets/windows-exe-config.xml" "$packagePath/internal/windows/install.exe.config"
fi
# bundle root files
for name in "StardewModdingAPI" "StardewModdingAPI.dll" "StardewModdingAPI.xml" "steam_appid.txt"; do
if [ $name == "StardewModdingAPI" ] && [ $folder == "windows" ]; then
name="$name.exe"
fi
cp "$smapiBin/$name" "$bundlePath"
done
# bundle i18n
cp -r "$smapiBin/i18n" "$bundlePath/smapi-internal"
# bundle smapi-internal
for name in "0Harmony.dll" "0Harmony.xml" "Markdig.dll" "Mono.Cecil.dll" "Mono.Cecil.Mdb.dll" "Mono.Cecil.Pdb.dll" "MonoMod.Common.dll" "Newtonsoft.Json.dll" "Pathoschild.Http.Client.dll" "Pintail.dll" "TMXTile.dll" "SMAPI.Toolkit.dll" "SMAPI.Toolkit.xml" "SMAPI.Toolkit.CoreInterfaces.dll" "SMAPI.Toolkit.CoreInterfaces.xml" "System.Net.Http.Formatting.dll"; do
cp "$smapiBin/$name" "$bundlePath/smapi-internal"
done
cp "$smapiBin/SMAPI.blacklist.json" "$bundlePath/smapi-internal/blacklist.json"
cp "$smapiBin/SMAPI.config.json" "$bundlePath/smapi-internal/config.json"
cp "$smapiBin/SMAPI.metadata.json" "$bundlePath/smapi-internal/metadata.json"
if [ $folder == "linux" ] || [ $folder == "macOS" ]; then
cp "$installAssets/unix-launcher.sh" "$bundlePath"
else
cp "$installAssets/windows-exe-config.xml" "$bundlePath/StardewModdingAPI.exe.config"
fi
# copy bundled mods
for modName in ${bundleModNames[@]}; do
fromPath="src/SMAPI.Mods.$modName/bin/$buildConfig/$runtime/publish"
targetPath="$bundlePath/Mods/$modName"
mkdir "$targetPath" --parents
cp "$fromPath/$modName.dll" "$targetPath"
cp "$fromPath/manifest.json" "$targetPath"
if [ -d "$fromPath/i18n" ]; then
cp -r "$fromPath/i18n" "$targetPath"
fi
done
done
# mark scripts executable
for path in "install on Linux.sh" "install on macOS.command" "bundle/unix-launcher.sh"; do
if [ -f "$packagePath/$path" ]; then
chmod 755 "$packagePath/$path"
fi
done
# split into main + for-dev folders
cp -r "$packagePath" "$packageDevPath"
for folder in ${folders[@]}; do
# disable developer mode in main package
sed --in-place --expression="s/\"DeveloperMode\": true/\"DeveloperMode\": false/" "$packagePath/internal/$folder/bundle/smapi-internal/config.json"
# convert bundle folder into final 'install.dat' files
for path in "$packagePath/internal/$folder" "$packageDevPath/internal/$folder"; do
pushd "$path/bundle" > /dev/null
zip "install.dat" * --recurse-paths --quiet
popd > /dev/null
mv "$path/bundle/install.dat" "$path/install.dat"
rm -rf "$path/bundle"
done
done
##########
## Create release zips
##########
# rename folders
mv "$packagePath" "bin/SMAPI $version installer"
mv "$packageDevPath" "bin/SMAPI $version installer for developers"
# package files
pushd bin > /dev/null
zip -9 "SMAPI $version installer.zip" "SMAPI $version installer" --recurse-paths --quiet
zip -9 "SMAPI $version installer for developers.zip" "SMAPI $version installer for developers" --recurse-paths --quiet
popd > /dev/null
echo ""
echo "Done! Package created in $(pwd)/bin"
-26
View File
@@ -1,26 +0,0 @@
#!/usr/bin/env bash
#
#
# This is the Bash equivalent of ../windows/set-smapi-version.ps1.
# When making changes, both scripts should be updated.
#
#
# get version number
version="$1"
if [ $# -eq 0 ]; then
echo "SMAPI release version (like '4.0.0'):"
read version
fi
# move to SMAPI root
cd "`dirname "$0"`/../.."
# apply changes
sed "s/<Version>.+<\/Version>/<Version>$version<\/Version>/" "build/common.targets" --in-place --regexp-extended
sed "s/RawApiVersion = \".+?\";/RawApiVersion = \"$version\";/" "src/SMAPI/Constants.cs" --in-place --regexp-extended
for modName in "ConsoleCommands" "SaveBackup"; do
sed "s/\"(Version|MinimumApiVersion)\": \".+?\"/\"\1\": \"$version\"/g" "src/SMAPI.Mods.$modName/manifest.json" --in-place --regexp-extended
done
-67
View File
@@ -1,67 +0,0 @@
#!/usr/bin/env bash
##########
## Read config
##########
# get SMAPI version
version="$1"
if [ $# -eq 0 ]; then
echo "SMAPI release version (like '4.0.0'):"
read version
fi
# get Windows bin path
windowsBinPath="$2"
if [ $# -le 1 ]; then
echo "Windows compiled bin path:"
read windowsBinPath
fi
# installer internal folders
buildFolders=("linux" "macOS" "windows")
##########
## Finalize release package
##########
for folderName in "SMAPI $version installer" "SMAPI $version installer for developers"; do
# move files to Linux filesystem
echo "Preparing $folderName.zip..."
echo "-------------------------------------------------"
echo "copying '$windowsBinPath/$folderName' to Linux filesystem..."
cp -r "$windowsBinPath/$folderName" .
# fix permissions
echo "fixing permissions..."
find "$folderName" -type d -exec chmod 755 {} \;
find "$folderName" -type f -exec chmod 644 {} \;
find "$folderName" -name "*.sh" -exec chmod 755 {} \;
find "$folderName" -name "*.command" -exec chmod 755 {} \;
find "$folderName" -name "SMAPI.Installer" -exec chmod 755 {} \;
find "$folderName" -name "StardewModdingAPI" -exec chmod 755 {} \;
# convert bundle folder into final 'install.dat' files
for build in ${buildFolders[@]}; do
echo "packaging $folderName/internal/$build/install.dat..."
pushd "$folderName/internal/$build/bundle" > /dev/null
zip "install.dat" * --recurse-paths --quiet
mv install.dat ../
popd > /dev/null
rm -rf "$folderName/internal/$build/bundle"
done
# zip installer
echo "packaging installer..."
zip -9 "$folderName.zip" "$folderName" --recurse-paths --quiet
# move zip back to Windows bin path
echo "moving release zip to $windowsBinPath/$folderName.zip..."
mv "$folderName.zip" "$windowsBinPath"
rm -rf "$folderName"
echo ""
echo ""
done
echo "Done!"
-257
View File
@@ -1,257 +0,0 @@
#
#
# This is the PowerShell equivalent of ../unix/prepare-install-package.sh, *except* that it doesn't
# set Linux permissions, create the install.dat files, or create the final zip (unless you specify
# --windows-only). Due to limitations in PowerShell, the final changes are handled by the
# windows/finalize-install-package.sh file in WSL.
#
# When making changes, make sure to update ../unix/prepare-install-package.ps1 too.
#
#
. "$PSScriptRoot/lib/in-place-regex.ps1"
##########
## Find the game folder
##########
$possibleGamePaths=(
# GOG
"C:\Program Files\GalaxyClient\Games\Stardew Valley",
"C:\Program Files\GOG Galaxy\Games\Stardew Valley",
"C:\Program Files\GOG Games\Stardew Valley",
"C:\Program Files (x86)\GalaxyClient\Games\Stardew Valley",
"C:\Program Files (x86)\GOG Galaxy\Games\Stardew Valley",
"C:\Program Files (x86)\GOG Games\Stardew Valley",
# Steam
"C:\Program Files\Steam\steamapps\common\Stardew Valley",
"C:\Program Files (x86)\Steam\steamapps\common\Stardew Valley"
)
$gamePath = ""
foreach ($possibleGamePath in $possibleGamePaths) {
if (Test-Path $possibleGamePath -PathType Container) {
$gamePath = $possibleGamePath
break
}
}
##########
## Preset values
##########
# paths
$bundleModNames = "ConsoleCommands", "SaveBackup"
# build configuration
$buildConfig = "Release"
$framework = "net6.0"
$folders = "linux", "macOS", "windows"
$runtimes = @{ linux = "linux-x64"; macOS = "osx-x64"; windows = "win-x64" }
$msBuildPlatformNames = @{ linux = "Unix"; macOS = "OSX"; windows = "Windows_NT" }
# version number
$version = $args[0]
if (!$version) {
$version = Read-Host "SMAPI release version (like '4.0.0')"
}
# Windows-only build
$windowsOnly = $false
foreach ($arg in $args) {
if ($arg -eq "--windows-only") {
$windowsOnly = $true
$folders = "windows"
$runtimes = @{ windows = "win-x64" }
$msBuildPlatformNames = @{ windows = "Windows_NT" }
}
}
##########
## Move to SMAPI root
##########
cd "$PSScriptRoot/../.."
##########
## Clear old build files
##########
echo "Clearing old builds..."
echo "-------------------------------------------------"
foreach ($path in (dir -Recurse -Include ('bin', 'obj'))) {
echo "$path"
rm -Recurse -Force "$path"
}
echo ""
##########
## Compile files
##########
. "$PSScriptRoot/set-smapi-version.ps1" "$version"
foreach ($folder in $folders) {
$runtime = $runtimes[$folder]
$msbuildPlatformName = $msBuildPlatformNames[$folder]
echo "Compiling SMAPI for $folder..."
echo "-------------------------------------------------"
dotnet publish src/SMAPI --configuration $buildConfig -v minimal --runtime "$runtime" --framework "$framework" -p:OS="$msbuildPlatformName" -p:TargetFrameworks="$framework" -p:GamePath="$gamePath" -p:CopyToGameFolder="false" --self-contained true
echo ""
echo ""
echo "Compiling installer for $folder..."
echo "-------------------------------------------------"
dotnet publish src/SMAPI.Installer --configuration $buildConfig -v minimal --runtime "$runtime" --framework "$framework" -p:OS="$msbuildPlatformName" -p:TargetFrameworks="$framework" -p:GamePath="$gamePath" -p:CopyToGameFolder="false" --self-contained true
echo ""
echo ""
foreach ($modName in $bundleModNames) {
echo "Compiling $modName for $folder..."
echo "-------------------------------------------------"
dotnet publish src/SMAPI.Mods.$modName --configuration $buildConfig -v minimal --runtime "$runtime" --framework "$framework" -p:OS="$msbuildPlatformName" -p:TargetFrameworks="$framework" -p:GamePath="$gamePath" -p:CopyToGameFolder="false" --self-contained false
echo ""
echo ""
}
}
##########
## Prepare install package
##########
echo "Preparing install package..."
echo "----------------------------"
# init paths
$installAssets = "src/SMAPI.Installer/assets"
$packagePath = "bin/SMAPI installer"
$packageDevPath = "bin/SMAPI installer for developers"
# init structure
foreach ($folder in $folders) {
mkdir "$packagePath/internal/$folder/bundle/smapi-internal" > $null
}
# copy base installer files
foreach ($name in @("install on Linux.sh", "install on macOS.command", "install on Windows.bat", "README.txt")) {
if ($windowsOnly -and ($name -eq "install on Linux.sh" -or $name -eq "install on macOS.command")) {
continue;
}
cp "$installAssets/$name" "$packagePath"
}
# copy per-platform files
foreach ($folder in $folders) {
$runtime = $runtimes[$folder]
# get paths
$smapiBin = "src/SMAPI/bin/$buildConfig/$runtime/publish"
$internalPath = "$packagePath/internal/$folder"
$bundlePath = "$internalPath/bundle"
# installer files
cp "src/SMAPI.Installer/bin/$buildConfig/$runtime/publish/*" "$internalPath" -Recurse
rm -Recurse -Force "$internalPath/assets"
# runtime config for SMAPI
# This is identical to the one generated by the build, except that the min runtime version is
# set to 5.0.0 (instead of whatever version it was built with) and rollForward is set to latestMinor instead of
# minor.
cp "$installAssets/runtimeconfig.json" "$bundlePath/StardewModdingAPI.runtimeconfig.json"
# installer DLL config
if ($folder -eq "windows") {
cp "$installAssets/windows-exe-config.xml" "$packagePath/internal/windows/install.exe.config"
}
# bundle root files
foreach ($name in @("StardewModdingAPI", "StardewModdingAPI.dll", "StardewModdingAPI.xml", "steam_appid.txt")) {
if ($name -eq "StardewModdingAPI" -and $folder -eq "windows") {
$name = "$name.exe"
}
cp "$smapiBin/$name" "$bundlePath"
}
# bundle i18n
cp -Recurse "$smapiBin/i18n" "$bundlePath/smapi-internal"
# bundle smapi-internal
foreach ($name in @("0Harmony.dll", "0Harmony.xml", "Markdig.dll", "Mono.Cecil.dll", "Mono.Cecil.Mdb.dll", "Mono.Cecil.Pdb.dll", "MonoMod.Common.dll", "Newtonsoft.Json.dll", "Pathoschild.Http.Client.dll", "Pintail.dll", "TMXTile.dll", "SMAPI.Toolkit.dll", "SMAPI.Toolkit.xml", "SMAPI.Toolkit.CoreInterfaces.dll", "SMAPI.Toolkit.CoreInterfaces.xml", "System.Net.Http.Formatting.dll")) {
cp "$smapiBin/$name" "$bundlePath/smapi-internal"
}
if ($folder -eq "windows") {
cp "$smapiBin/VdfConverter.dll" "$bundlePath/smapi-internal"
}
cp "$smapiBin/SMAPI.blacklist.json" "$bundlePath/smapi-internal/blacklist.json"
cp "$smapiBin/SMAPI.config.json" "$bundlePath/smapi-internal/config.json"
cp "$smapiBin/SMAPI.metadata.json" "$bundlePath/smapi-internal/metadata.json"
if ($folder -eq "linux" -or $folder -eq "macOS") {
cp "$installAssets/unix-launcher.sh" "$bundlePath"
}
else {
cp "$installAssets/windows-exe-config.xml" "$bundlePath/StardewModdingAPI.exe.config"
}
# copy bundled mods
foreach ($modName in $bundleModNames) {
$fromPath = "src/SMAPI.Mods.$modName/bin/$buildConfig/$runtime/publish"
$targetPath = "$bundlePath/Mods/$modName"
mkdir "$targetPath" > $null
cp "$fromPath/$modName.dll" "$targetPath"
cp "$fromPath/manifest.json" "$targetPath"
if (Test-Path "$fromPath/i18n" -PathType Container) {
cp -Recurse "$fromPath/i18n" "$targetPath"
}
}
}
# DISABLED: will be handled by Linux script
# mark scripts executable
#ForEach ($path in @("install on Linux.sh", "install on macOS.command", "bundle/unix-launcher.sh")) {
# if (Test-Path "$packagePath/$path" -PathType Leaf) {
# chmod 755 "$packagePath/$path"
# }
#}
# split into main + for-dev folders
cp -Recurse "$packagePath" "$packageDevPath"
foreach ($folder in $folders) {
# disable developer mode in main package
In-Place-Regex -Path "$packagePath/internal/$folder/bundle/smapi-internal/config.json" -Search "`"DeveloperMode`": true" -Replace "`"DeveloperMode`": false"
# convert bundle folder into final 'install.dat' files
if ($windowsOnly)
{
foreach ($path in @("$packagePath/internal/$folder", "$packageDevPath/internal/$folder"))
{
Compress-Archive -Path "$path/bundle/*" -CompressionLevel Optimal -DestinationPath "$path/install.zip"
mv "$path/install.zip" "$path/install.dat"
rm -Recurse -Force "$path/bundle"
}
}
}
###########
### Create release zips
###########
# rename folders
mv "$packagePath" "bin/SMAPI $version installer"
mv "$packageDevPath" "bin/SMAPI $version installer for developers"
# package files
if ($windowsOnly)
{
Compress-Archive -Path "bin/SMAPI $version installer" -DestinationPath "bin/SMAPI $version installer.zip" -CompressionLevel Optimal
Compress-Archive -Path "bin/SMAPI $version installer for developers" -DestinationPath "bin/SMAPI $version installer for developers.zip" -CompressionLevel Optimal
}
echo ""
echo "Done! See docs/technical/smapi.md to create the release zips."
+16 -16
View File
@@ -58,22 +58,22 @@ contributing translations.
locale | status
----------- | :----------------
default | ✓ [fully translated](../src/SMAPI/i18n/default.json)
Chinese | ✓ [fully translated](../src/SMAPI/i18n/zh.json)
French | ✓ [fully translated](../src/SMAPI/i18n/fr.json)
German | ✓ [fully translated](../src/SMAPI/i18n/de.json)
Hungarian | ✓ [fully translated](../src/SMAPI/i18n/hu.json)
Indonesian | ✓ [fully translated](../src/SMAPI/i18n/id.json)
Italian | ✓ [fully translated](../src/SMAPI/i18n/it.json)
Japanese | ✓ [fully translated](../src/SMAPI/i18n/ja.json)
Korean | ✓ [fully translated](../src/SMAPI/i18n/ko.json)
[Polish] | ✓ [fully translated](../src/SMAPI/i18n/pl.json)
Portuguese | ✓ [fully translated](../src/SMAPI/i18n/pt.json)
Russian | ✓ [fully translated](../src/SMAPI/i18n/ru.json)
Spanish | ✓ [fully translated](../src/SMAPI/i18n/es.json)
[Thai] | ✓ [fully translated](../src/SMAPI/i18n/th.json)
Turkish | ✓ [fully translated](../src/SMAPI/i18n/tr.json)
[Ukrainian] | ✓ [fully translated](../src/SMAPI/i18n/uk.json)
default | ↻ [partly translated](../src/SMAPI/i18n/default.json)
Chinese | ↻ [partly translated](../src/SMAPI/i18n/zh.json)
French | ↻ [partly translated](../src/SMAPI/i18n/fr.json)
German | ↻ [partly translated](../src/SMAPI/i18n/de.json)
Hungarian | ↻ [partly translated](../src/SMAPI/i18n/hu.json)
Indonesian | ↻ [partly translated](../src/SMAPI/i18n/id.json)
Italian | ↻ [partly translated](../src/SMAPI/i18n/it.json)
Japanese | ↻ [partly translated](../src/SMAPI/i18n/ja.json)
Korean | ↻ [partly translated](../src/SMAPI/i18n/ko.json)
[Polish] | ↻ [partly translated](../src/SMAPI/i18n/pl.json)
Portuguese | ↻ [partly translated](../src/SMAPI/i18n/pt.json)
Russian | ↻ [partly translated](../src/SMAPI/i18n/ru.json)
Spanish | ↻ [partly translated](../src/SMAPI/i18n/es.json)
[Thai] | ↻ [partly translated](../src/SMAPI/i18n/th.json)
Turkish | ↻ [partly translated](../src/SMAPI/i18n/tr.json)
[Ukrainian] | ↻ [partly translated](../src/SMAPI/i18n/uk.json)
[Polish]: https://www.nexusmods.com/stardewvalley/mods/3616
[Thai]: https://www.nexusmods.com/stardewvalley/mods/7052
File diff suppressed because it is too large Load Diff
+19 -1349
View File
File diff suppressed because it is too large Load Diff
+77 -71
View File
@@ -14,9 +14,11 @@ This document is about SMAPI itself; see also [mod build package](mod-package.md
* [Compile from source code](#compile-from-source-code)
* [Main project](#main-project)
* [Custom Harmony build](#custom-harmony-build)
* [Version format](#version-format)
* [Prepare a release](#prepare-a-release)
* [On any platform](#on-any-platform)
* [On Windows](#on-windows)
* [Automated build pipeline](#automated-build-pipeline)
* [Manual release on any platform](#manual-release-on-any-platform)
* [Manual release On Windows](#manual-release-on-windows)
* [Release notes](#release-notes)
## Customisation
@@ -67,6 +69,15 @@ flag | purpose
---- | -------
`SMAPI_FOR_WINDOWS` | Whether SMAPI is being compiled for Windows; if not set, the code assumes Linux/macOS. Set automatically in `common.targets`.
## Version format
SMAPI uses [semantic versioning](https://semver.org). Typical format:
build type | format | example
:--------- | :----------------------- | :------
dev build | `<version>-alpha.<date>` | `4.0.0-alpha.20251230`
prerelease | `<version>-beta.<date>` | `4.0.0-beta.20251230`
release | `<version>` | `4.0.0`
## Compile from source code
### Main project
Using an official SMAPI release is recommended for most users, but you can compile from source
@@ -80,88 +91,86 @@ the `SMAPI` project with debugging from Visual Studio or Rider should launch SMA
debugger attached, so you can intercept errors and step through the code being executed.
### Custom Harmony build
SMAPI uses [a custom build of Harmony 2.2.2](https://github.com/Pathoschild/Harmony#readme), which
is included in the `build` folder. To use a different build, just replace `0Harmony.dll` in that
SMAPI uses [a custom build of Harmony](https://github.com/Pathoschild/Harmony#readme), which is
included in the `build` folder. To use a different build, just replace `0Harmony.dll` in that
folder before compiling.
## Prepare a release
### On any platform
_This is the unified process that works on any platform. However, it needs a few extra steps on
Windows (e.g. running Steam in WSL); see ['On Windows'](#on-windows) below for an alternative quick
option._
### Automated build pipeline
SMAPI releases can be compiled automatically on GitHub. This is the recommended approach for
official releases, since...
- It eliminates the risk of malware on your computer infecting the release.
- It creates an [attestation](https://docs.github.com/en/actions/concepts/security/artifact-attestations),
so security-savvy users can verify that the release was compiled from the code on GitHub and
hasn't been tampered with.
- It benefits from future workflow improvements like code signing.
#### First-time setup
The typical process is:
1. Commit changes on the `develop` branch.
* _Note: for an alpha version, you're done! All commits on `develop` are compiled into alpha
releases. See the 'Actions' tab on GitHub._
2. Set SMAPI's [version](#version-format) in `build/common.targets`, `src/SMAPI/Constants.cs`, and
each mod's `manifest.json`.
* _You can run `pwsh build/scripts/set-smapi-version.ps1 VERSION_HERE` to do it automatically.
On Linux/macOS, you'll need to [install PowerShell](https://learn.microsoft.com/en-us/powershell/scripting/install/install-powershell)._
3. Update the [release notes](../release-notes.md).
4. Commit.
5. Merge `develop` into `stable`.
6. Tag the merge commit with the version (e.g. `5.0.0`).
7. Check the 'Actions' tab on GitHub for the build, and download its artifacts when it finishes.
### Manual release on any platform
> [!WARNING]
> This lets you manually prepare a release, which is fine for personal use. However, official
> releases should use the [automated build pipeline](#automated-build-pipeline) instead.
> [!TIP]
> This approach works on any platform, but it's a bit complicated on Windows. See also [_Manual
> release on Windows_](#manual-release-on-windows) for a simpler approach.
First-time setup:
1. On Windows only:
1. [Install Windows Subsystem for Linux (WSL)](https://docs.microsoft.com/en-us/windows/wsl/install).
2. Run `sudo apt update` in WSL to update the package list.
3. The rest of the instructions below should be run in WSL.
2. Install the required software:
1. Install the [.NET 5 SDK](https://docs.microsoft.com/en-us/dotnet/core/install/linux-ubuntu).
_For Ubuntu-based systems, you can run `lsb_release -a` to get the Ubuntu version number._
2. [Install Steam](https://linuxconfig.org/how-to-install-steam-on-ubuntu-20-04-focal-fossa-linux).
3. Launch `steam` and install the game like usual.
4. Download and install your preferred IDE. For the [latest standalone Rider
version](https://www.jetbrains.com/help/rider/Installation_guide.html#prerequisites):
```sh
wget "<download url here>" -O rider-install.tar.gz
sudo tar -xzvf rider-install.tar.gz -C /opt
ln -s "/opt/JetBrains Rider-<version>/bin/rider.sh"
./rider.sh
```
3. Clone the SMAPI repo:
2. Follow the rest of the instructions inside WSL.
2. Install...
- [.NET 6 SDK](https://docs.microsoft.com/en-us/dotnet/core/install/linux-ubuntu) (run
`lsb_release -a` if you need the Ubuntu version number);
- [PowerShell](https://learn.microsoft.com/en-us/powershell/scripting/install/install-powershell);
- and Steam (see [Linux instructions](https://linuxconfig.org/how-to-install-steam-on-ubuntu-20-04-focal-fossa-linux)).
3. Launch `steam` and install the game like usual.
4. Clone the SMAPI repo:
```sh
git clone https://github.com/Pathoschild/SMAPI.git
```
### Launch the game
1. Run these commands to start Steam:
```sh
export TERM=xterm
steam
```
2. Launch the game through the Steam UI.
To prepare the release:
1. Run `pwsh build/scripts/prepare-install-package.sh VERSION_HERE` to create the release package in the
root `bin` folder. Make sure you use a [semantic version](#version-format).
### Prepare the release
1. Run `build/unix/prepare-install-package.sh VERSION_HERE` to create the release package in the
root `bin` folder.
### Manual release On Windows
> [!WARNING]
> This lets you manually prepare a release, which is fine for personal use. However, official
> releases should use the [automated build pipeline](#automated-build-pipeline) instead.
Make sure you use a [semantic version](https://semver.org). Recommended format:
> [!TIP]
> To prepare a Windows-only build, you can skip WSL and replace `--skip-bundle-deletion` with `--windows-only`
> when calling `prepare-install-package.ps1`.
build type | format | example
:--------- | :----------------------- | :------
dev build | `<version>-alpha.<date>` | `4.0.0-alpha.20251230`
prerelease | `<version>-beta.<date>` | `4.0.0-beta.20251230`
release | `<version>` | `4.0.0`
### On Windows
_This is the alternative quick process for Windows only. This avoids needing Steam installed on WSL,
and can be used to create Windows-only builds without using WSL at all. See ['on any platform'](#on-any-platform)
above for the unified process._
#### First-time setup
1. Set up Windows Subsystem for Linux (WSL):
1. [Install WSL](https://docs.microsoft.com/en-us/windows/wsl/install).
2. Run `sudo apt update` in WSL to update the package list.
3. The rest of the instructions below should be run in WSL.
2. Install the required software:
1. Install the [.NET 5 SDK](https://dotnet.microsoft.com/download/dotnet/5.0).
2. Install [Stardew Valley](https://www.stardewvalley.net/).
3. Clone the SMAPI repo:
First-time setup:
1. Install...
- [Windows Subsystem for Linux (WSL)](https://docs.microsoft.com/en-us/windows/wsl/install);
- [.NET 6 SDK](https://dotnet.microsoft.com/download/dotnet/5.0);
- [Stardew Valley](https://www.stardewvalley.net/).
2. Clone the SMAPI repo:
```sh
git clone https://github.com/Pathoschild/SMAPI.git
```
### Prepare the release
1. Run `build/windows/prepare-install-package.ps1 VERSION_HERE` in PowerShell to create the release
package folders in the root `bin` folder.
Make sure you use a [semantic version](https://semver.org). Recommended format:
build type | format | example
:--------- | :----------------------- | :------
dev build | `<version>-alpha.<date>` | `4.0.0-alpha.20251230`
prerelease | `<version>-beta.<date>` | `4.0.0-beta.20251230`
release | `<version>` | `4.0.0`
To prepare the release:
1. Run `pwsh build/scripts/prepare-install-package.ps1 VERSION_HERE --skip-bundle-deletion` to
create the release package folders in the root `bin` folder. Make sure you use a [semantic
version](#version-format).
2. Launch WSL and run this script:
```bash
@@ -169,11 +178,8 @@ above for the unified process._
# In WSL, `/mnt/c/example` accesses `C:\example` on the Windows filesystem.
version="4.0.0"
binFolder="/mnt/e/source/_Stardew/SMAPI/bin"
build/windows/finalize-install-package.sh "$version" "$binFolder"
pwsh build/scripts/finalize-install-package.sh "$version" "$binFolder"
```
Note: to prepare a test Windows-only build, you can pass `--windows-only` in the first step and
skip the second one.
## Release notes
See [release notes](../release-notes.md).
+2 -2
View File
@@ -191,8 +191,8 @@ internal class InteractiveInstaller
** show theme selector
****/
// get theme writers
ColorfulConsoleWriter lightBackgroundWriter = new(context.Platform, ColorfulConsoleWriter.GetDefaultColorSchemeConfig(MonitorColorScheme.LightBackground));
ColorfulConsoleWriter darkBackgroundWriter = new(context.Platform, ColorfulConsoleWriter.GetDefaultColorSchemeConfig(MonitorColorScheme.DarkBackground));
ColorfulConsoleWriter lightBackgroundWriter = new(context.Platform, MonitorColorScheme.LightBackground, ColorfulConsoleWriter.GetDefaultColorSchemeConfig(MonitorColorScheme.LightBackground));
ColorfulConsoleWriter darkBackgroundWriter = new(context.Platform, MonitorColorScheme.DarkBackground, ColorfulConsoleWriter.GetDefaultColorSchemeConfig(MonitorColorScheme.DarkBackground));
// print question
this.PrintPlain("Which text looks more readable?");
@@ -1,30 +0,0 @@
using System;
using System.Collections.Generic;
namespace StardewModdingAPI.Internal.ConsoleWriting;
/// <summary>The console color scheme options.</summary>
internal class ColorSchemeConfig
{
/*********
** Accessors
*********/
/// <summary>The default color scheme ID to use, or <see cref="MonitorColorScheme.AutoDetect"/> to select one automatically.</summary>
public MonitorColorScheme UseScheme { get; set; }
/// <summary>The available console color schemes.</summary>
public IDictionary<MonitorColorScheme, IDictionary<ConsoleLogLevel, ConsoleColor>> Schemes { get; set; }
/*********
** Public methods
*********/
/// <summary>Construct an instance.</summary>
/// <param name="useScheme">The default color scheme ID to use, or <see cref="MonitorColorScheme.AutoDetect"/> to select one automatically.</param>
/// <param name="schemes">The available console color schemes.</param>
public ColorSchemeConfig(MonitorColorScheme useScheme, IDictionary<MonitorColorScheme, IDictionary<ConsoleLogLevel, ConsoleColor>> schemes)
{
this.UseScheme = useScheme;
this.Schemes = schemes;
}
}
@@ -11,12 +11,15 @@ internal class ColorfulConsoleWriter : IConsoleWriter
/*********
** Fields
*********/
/// <summary>The target platform.</summary>
private readonly Platform Platform;
/// <summary>The console text color for each log level.</summary>
private readonly IDictionary<ConsoleLogLevel, ConsoleColor>? Colors;
private IDictionary<ConsoleLogLevel, ConsoleColor>? Colors;
/// <summary>Whether the current console supports color formatting.</summary>
[MemberNotNullWhen(true, nameof(ColorfulConsoleWriter.Colors))]
private bool SupportsColor { get; }
private bool SupportsColor { get; set; }
/*********
@@ -25,14 +28,25 @@ internal class ColorfulConsoleWriter : IConsoleWriter
/// <summary>Construct an instance.</summary>
/// <param name="platform">The target platform.</param>
public ColorfulConsoleWriter(Platform platform)
: this(platform, ColorfulConsoleWriter.GetDefaultColorSchemeConfig(MonitorColorScheme.AutoDetect)) { }
: this(platform, MonitorColorScheme.AutoDetect, ColorfulConsoleWriter.GetDefaultColorSchemeConfig(MonitorColorScheme.AutoDetect)) { }
/// <summary>Construct an instance.</summary>
/// <param name="platform">The target platform.</param>
/// <param name="colorSchemeId">The color scheme ID in <paramref name="colorConfig"/> to use, or <see cref="MonitorColorScheme.AutoDetect"/> to select one automatically.</param>
/// <param name="colorConfig">The colors to use for text written to the SMAPI console.</param>
public ColorfulConsoleWriter(Platform platform, ColorSchemeConfig colorConfig)
public ColorfulConsoleWriter(Platform platform, MonitorColorScheme colorSchemeId, Dictionary<MonitorColorScheme, Dictionary<ConsoleLogLevel, ConsoleColor>> colorConfig)
{
if (colorConfig.UseScheme == MonitorColorScheme.None)
this.Platform = platform;
this.SetColors(colorSchemeId, colorConfig);
}
/// <summary>Set the color scheme to apply.</summary>
/// <param name="colorSchemeId">The color scheme ID in <paramref name="colorSchemes"/> to use, or <see cref="MonitorColorScheme.AutoDetect"/> to select one automatically.</param>
/// <param name="colorSchemes">The colors to use for text written to the SMAPI console.</param>
public void SetColors(MonitorColorScheme colorSchemeId, Dictionary<MonitorColorScheme, Dictionary<ConsoleLogLevel, ConsoleColor>> colorSchemes)
{
if (colorSchemeId == MonitorColorScheme.None)
{
this.SupportsColor = false;
this.Colors = null;
@@ -40,7 +54,7 @@ internal class ColorfulConsoleWriter : IConsoleWriter
else
{
this.SupportsColor = this.TestColorSupport();
this.Colors = this.GetConsoleColorScheme(platform, colorConfig);
this.Colors = ColorfulConsoleWriter.GetConsoleColorScheme(this.Platform, colorSchemeId, colorSchemes);
}
}
@@ -73,34 +87,31 @@ internal class ColorfulConsoleWriter : IConsoleWriter
/// <summary>Get the default color scheme config for cases where it's not configurable (e.g. the installer).</summary>
/// <param name="useScheme">The default color scheme ID to use, or <see cref="MonitorColorScheme.AutoDetect"/> to select one automatically.</param>
/// <remarks>The colors here should be kept in sync with the SMAPI config file.</remarks>
public static ColorSchemeConfig GetDefaultColorSchemeConfig(MonitorColorScheme useScheme)
public static Dictionary<MonitorColorScheme, Dictionary<ConsoleLogLevel, ConsoleColor>> GetDefaultColorSchemeConfig(MonitorColorScheme useScheme)
{
return new ColorSchemeConfig(
useScheme: useScheme,
schemes: new Dictionary<MonitorColorScheme, IDictionary<ConsoleLogLevel, ConsoleColor>>
return new Dictionary<MonitorColorScheme, Dictionary<ConsoleLogLevel, ConsoleColor>>
{
[MonitorColorScheme.DarkBackground] = new()
{
[MonitorColorScheme.DarkBackground] = new Dictionary<ConsoleLogLevel, ConsoleColor>
{
[ConsoleLogLevel.Trace] = ConsoleColor.DarkGray,
[ConsoleLogLevel.Debug] = ConsoleColor.DarkGray,
[ConsoleLogLevel.Info] = ConsoleColor.White,
[ConsoleLogLevel.Warn] = ConsoleColor.Yellow,
[ConsoleLogLevel.Error] = ConsoleColor.Red,
[ConsoleLogLevel.Alert] = ConsoleColor.Magenta,
[ConsoleLogLevel.Success] = ConsoleColor.DarkGreen
},
[MonitorColorScheme.LightBackground] = new Dictionary<ConsoleLogLevel, ConsoleColor>
{
[ConsoleLogLevel.Trace] = ConsoleColor.DarkGray,
[ConsoleLogLevel.Debug] = ConsoleColor.DarkGray,
[ConsoleLogLevel.Info] = ConsoleColor.Black,
[ConsoleLogLevel.Warn] = ConsoleColor.DarkYellow,
[ConsoleLogLevel.Error] = ConsoleColor.Red,
[ConsoleLogLevel.Alert] = ConsoleColor.DarkMagenta,
[ConsoleLogLevel.Success] = ConsoleColor.DarkGreen
}
[ConsoleLogLevel.Trace] = ConsoleColor.DarkGray,
[ConsoleLogLevel.Debug] = ConsoleColor.DarkGray,
[ConsoleLogLevel.Info] = ConsoleColor.White,
[ConsoleLogLevel.Warn] = ConsoleColor.Yellow,
[ConsoleLogLevel.Error] = ConsoleColor.Red,
[ConsoleLogLevel.Alert] = ConsoleColor.Magenta,
[ConsoleLogLevel.Success] = ConsoleColor.DarkGreen
},
[MonitorColorScheme.LightBackground] = new()
{
[ConsoleLogLevel.Trace] = ConsoleColor.DarkGray,
[ConsoleLogLevel.Debug] = ConsoleColor.DarkGray,
[ConsoleLogLevel.Info] = ConsoleColor.Black,
[ConsoleLogLevel.Warn] = ConsoleColor.DarkYellow,
[ConsoleLogLevel.Error] = ConsoleColor.Red,
[ConsoleLogLevel.Alert] = ConsoleColor.DarkMagenta,
[ConsoleLogLevel.Success] = ConsoleColor.DarkGreen
}
);
};
}
@@ -123,22 +134,22 @@ internal class ColorfulConsoleWriter : IConsoleWriter
/// <summary>Get the color scheme to use for the current console.</summary>
/// <param name="platform">The target platform.</param>
/// <param name="colorSchemeId">The color scheme ID in <paramref name="colorConfig"/> to use, or <see cref="MonitorColorScheme.AutoDetect"/> to select one automatically.</param>
/// <param name="colorConfig">The colors to use for text written to the SMAPI console.</param>
private IDictionary<ConsoleLogLevel, ConsoleColor> GetConsoleColorScheme(Platform platform, ColorSchemeConfig colorConfig)
private static IDictionary<ConsoleLogLevel, ConsoleColor> GetConsoleColorScheme(Platform platform, MonitorColorScheme colorSchemeId, Dictionary<MonitorColorScheme, Dictionary<ConsoleLogLevel, ConsoleColor>> colorConfig)
{
// get color scheme ID
MonitorColorScheme schemeId = colorConfig.UseScheme;
if (schemeId == MonitorColorScheme.AutoDetect)
if (colorSchemeId == MonitorColorScheme.AutoDetect)
{
schemeId = platform == Platform.Mac
colorSchemeId = platform == Platform.Mac
? MonitorColorScheme.LightBackground // macOS doesn't provide console background color info, but it's usually white.
: ColorfulConsoleWriter.IsDark(Console.BackgroundColor) ? MonitorColorScheme.DarkBackground : MonitorColorScheme.LightBackground;
}
// get colors for scheme
return colorConfig.Schemes.TryGetValue(schemeId, out IDictionary<ConsoleLogLevel, ConsoleColor>? scheme)
return colorConfig.TryGetValue(colorSchemeId, out Dictionary<ConsoleLogLevel, ConsoleColor>? scheme)
? scheme
: throw new NotSupportedException($"Unknown color scheme '{schemeId}'.");
: throw new NotSupportedException($"Unknown color scheme '{colorSchemeId}'.");
}
/// <summary>Get whether a console color should be considered dark, which is subjectively defined as 'white looks better than black on this text'.</summary>
@@ -10,7 +10,6 @@
</PropertyGroup>
<ItemGroup>
<Compile Include="$(MSBuildThisFileDirectory)ConsoleWriting\ColorfulConsoleWriter.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ConsoleWriting\ColorSchemeConfig.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ConsoleWriting\ConsoleLogLevel.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ConsoleWriting\IConsoleWriter.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ConsoleWriting\MonitorColorScheme.cs" />
+2 -2
View File
@@ -1,9 +1,9 @@
{
"Name": "Console Commands",
"Author": "SMAPI",
"Version": "4.4.0",
"Version": "4.5.0",
"Description": "Adds SMAPI console commands that let you manipulate the game.",
"UniqueId": "SMAPI.ConsoleCommands",
"EntryDll": "ConsoleCommands.dll",
"MinimumApiVersion": "4.4.0"
"MinimumApiVersion": "4.5.0"
}
+2 -2
View File
@@ -1,9 +1,9 @@
{
"Name": "Save Backup",
"Author": "SMAPI",
"Version": "4.4.0",
"Version": "4.5.0",
"Description": "Automatically backs up all your saves once per day into its folder.",
"UniqueId": "SMAPI.SaveBackup",
"EntryDll": "SaveBackup.dll",
"MinimumApiVersion": "4.4.0"
"MinimumApiVersion": "4.5.0"
}
@@ -0,0 +1,37 @@
namespace StardewModdingAPI.Toolkit.Framework.ModBlacklistData;
/// <summary>A loose file entry in the <see cref="ModBlacklistModel"/>.</summary>
public class LooseFileBlacklistEntryModel
{
/*********
** Accessors
*********/
/// <summary>The file name to block (if any).</summary>
public string? Name { get; }
/// <summary>The file extension to block (if any), including the dot.</summary>
public string? Extension { get; }
/// <summary>The MD5 hash to block (if any).</summary>
public string? Hash { get; }
/// <summary>A player-friendly explanation of why the mod is blocked and what they should do next.</summary>
public string? Message { get; }
/*********
** Public methods
*********/
/// <summary>Construct an instance.</summary>
/// <param name="name"><inheritdoc cref="Name" path="/summary"/></param>
/// <param name="extension"><inheritdoc cref="Extension" path="/summary"/></param>
/// <param name="hash"><inheritdoc cref="Hash" path="/summary"/></param>
/// <param name="message"><inheritdoc cref="Message" path="/summary"/></param>
public LooseFileBlacklistEntryModel(string? name, string? extension, string? hash, string message)
{
this.Name = name;
this.Extension = extension;
this.Hash = hash;
this.Message = message;
}
}
@@ -1,4 +1,6 @@
using System;
using System.Collections.Generic;
using System.IO;
using StardewModdingAPI.Toolkit.Utilities;
namespace StardewModdingAPI.Toolkit.Framework.ModBlacklistData;
@@ -18,7 +20,7 @@ public class ModBlacklist
*********/
/// <summary>Construct an empty instance.</summary>
public ModBlacklist()
: this(new ModBlacklistModel([])) { }
: this(new ModBlacklistModel([], [])) { }
/// <summary>Construct an instance.</summary>
/// <param name="data">The underlying mod blacklist data.</param>
@@ -30,7 +32,7 @@ public class ModBlacklist
/// <summary>Get the blacklist entry for a mod, if any.</summary>
/// <param name="modId">The unique mod ID.</param>
/// <param name="entryDllPath">The absolute path to the entry DLL, if this is a C# mod.</param>
public ModBlacklistEntryModel? Get(string modId, string? entryDllPath)
public ModBlacklistEntryModel? CheckMod(string modId, string? entryDllPath)
{
string? entryDllHash = null;
@@ -56,4 +58,57 @@ public class ModBlacklist
return null;
}
/// <summary>Scan a folder to detect any files which match the list of malicious files.</summary>
/// <param name="rootPath">The root path to scan.</param>
public IEnumerable<(string FilePath, LooseFileBlacklistEntryModel Match)> CheckLooseFiles(string rootPath)
{
foreach (string path in Directory.EnumerateFiles(rootPath, "*.*", SearchOption.AllDirectories))
{
LooseFileBlacklistEntryModel? blacklistEntry = this.CheckLooseFile(path);
if (blacklistEntry != null)
yield return (path, blacklistEntry);
}
}
/// <summary>Get the blacklist entry for a loose file, if any.</summary>
/// <param name="fullPath">The absolute path to the file to check.</param>
public LooseFileBlacklistEntryModel? CheckLooseFile(string fullPath)
{
string? name = null;
string? extension = null;
string? hash = null;
foreach (LooseFileBlacklistEntryModel entry in this.Blacklist.LooseFileBlacklist)
{
// check file name
if (entry.Name != null)
{
name ??= Path.GetFileName(fullPath);
if (!string.Equals(name, entry.Name, StringComparison.OrdinalIgnoreCase))
continue;
}
// check file extension
if (entry.Extension != null)
{
extension ??= Path.GetExtension(fullPath);
if (!string.Equals(extension, entry.Extension, StringComparison.OrdinalIgnoreCase))
continue;
}
// check hash
if (entry.Hash != null)
{
hash ??= FileUtilities.GetFileHash(fullPath);
if (!string.Equals(hash, entry.Hash, StringComparison.OrdinalIgnoreCase))
continue;
}
return entry;
}
return null;
}
}
@@ -3,15 +3,17 @@ namespace StardewModdingAPI.Toolkit.Framework.ModBlacklistData;
/// <summary>A mod entry in the <see cref="ModBlacklistModel"/>.</summary>
public class ModBlacklistEntryModel
{
/*********
** Accessors
*********/
/// <summary>The manifest IDs to block (if any).</summary>
public string? Id { get; }
/// <summary>The MD5 hashes of the entry DLL to block (if any).</summary>
/// <remarks>Due to the chance of MD5 collisions, this should only be used in addition to the <see cref="Id"/>.</remarks>
/// <summary>The MD5 hash of the entry DLL to block (if any).</summary>
public string? EntryDllHash { get; }
/// <summary>A player-friendly explanation of why the mod is blocked and what they should do next.</summary>
public string Message { get; }
public string? Message { get; }
/*********
@@ -9,14 +9,19 @@ public class ModBlacklistModel
/// <summary>Metadata about malicious or harmful SMAPI mods which are disabled by default.</summary>
public ModBlacklistEntryModel[] Blacklist { get; }
/// <summary>Metadata about individual files which are known to be malicious, and should be blocked. If any file in a folder matches an entry, the entire folder is considered malicious.</summary>
public LooseFileBlacklistEntryModel[] LooseFileBlacklist { get; }
/*********
** Public methods
*********/
/// <summary>Construct an instance.</summary>
/// <param name="blacklist"><inheritdoc cref="Blacklist" path="/summary"/></param>
public ModBlacklistModel(ModBlacklistEntryModel[]? blacklist)
/// <param name="looseFileBlacklist"><inheritdoc cref="LooseFileBlacklist" path="/summary"/></param>
public ModBlacklistModel(ModBlacklistEntryModel[]? blacklist, LooseFileBlacklistEntryModel[]? looseFileBlacklist)
{
this.Blacklist = blacklist ?? [];
this.LooseFileBlacklist = looseFileBlacklist ?? [];
}
}
+5 -11
View File
@@ -57,8 +57,8 @@ internal class IndexController : Controller
{
// choose versions
ReleaseVersion[] versions = await this.GetReleaseVersionsAsync();
ReleaseVersion? stableVersion = versions.LastOrDefault(version => !version.IsForDevs);
ReleaseVersion? stableVersionForDevs = versions.LastOrDefault(version => version.IsForDevs);
ReleaseVersion? stableVersion = versions.LastOrDefault();
ReleaseVersion? stableVersionForDevs = versions.LastOrDefault();
// render view
IndexVersionModel stableVersionModel = stableVersion != null
@@ -123,12 +123,11 @@ internal class IndexController : Controller
if (asset.FileName.StartsWith("Z_"))
continue;
Match match = Regex.Match(asset.FileName, @"SMAPI-(?<version>[\d\.]+(?:-.+)?)-installer(?<forDevs>-for-developers)?.zip");
Match match = Regex.Match(asset.FileName, @"SMAPI-(?<version>[\d\.]+(?:-.+)?)-installer.zip");
if (!match.Success || !SemanticVersion.TryParse(match.Groups["version"].Value, out ISemanticVersion? version))
continue;
bool isForDevs = match.Groups["forDevs"].Success;
yield return new ReleaseVersion(release, asset, version, isForDevs);
yield return new ReleaseVersion(release, asset, version);
}
}
@@ -147,9 +146,6 @@ internal class IndexController : Controller
/// <summary>The SMAPI version.</summary>
public ISemanticVersion Version { get; }
/// <summary>Whether this is a 'for developers' download.</summary>
public bool IsForDevs { get; }
/*********
** Public methods
@@ -158,13 +154,11 @@ internal class IndexController : Controller
/// <param name="release">The underlying GitHub release.</param>
/// <param name="asset">The underlying download asset.</param>
/// <param name="version">The SMAPI version.</param>
/// <param name="isForDevs">Whether this is a 'for developers' download.</param>
public ReleaseVersion(GitRelease release, GitAsset asset, ISemanticVersion version, bool isForDevs)
public ReleaseVersion(GitRelease release, GitAsset asset, ISemanticVersion version)
{
this.Release = release;
this.Asset = asset;
this.Version = version;
this.IsForDevs = isForDevs;
}
}
}
-1
View File
@@ -91,7 +91,6 @@
<h2 id="modcreators">For mod creators</h2>
<ul>
<li><a href="@Model.StableVersion.DevDownloadUrl">SMAPI @Model.StableVersion.Version for developers</a> (includes <a href="https://docs.microsoft.com/en-us/visualstudio/ide/using-intellisense">intellisense</a> and full console output)</li>
<li><a href="https://stardewvalleywiki.com/Modding:Index">Modding documentation</a></li>
<li><a href="https://github.com/Pathoschild/SMAPI">Source code</a></li>
</ul>
+15 -2
View File
@@ -7,12 +7,25 @@
"Blacklist": [
// 2025-07
{
"Id": "OpUlarenous.PortableCommunityCenter", // 'Even Better Portable Community Center' (Nexus:35601, dfd9e53ee3e6fb997c04a2b0c7f49a50)
"Id": "OpUlarenous.PortableCommunityCenter", // 'Even Better Portable Community Center' (Nexus:35601, dfd9e53ee3e6fb997c04a2b0c7f49a50, malicious reupload of legit mod)
"Message": "It downloads malicious code from a remote server and runs it on your computer. SMAPI refused to load the mod, but you should delete this mod and run a complete anti-malware scan of your computer now to be safe."
},
{
"Id": "BrEvoiultion.GiveSpeedInMorning", // 'Give Speed In Morning' (Nexus:35650, 97860c755192a27cdb6c16ec32327774)
"Id": "BrEvoiultion.GiveSpeedInMorning", // 'Give Speed In Morning' (Nexus:35650, 97860c755192a27cdb6c16ec32327774, malicious reupload of legit mod)
"Message": "It downloads malicious code from a remote server and runs it on your computer. SMAPI refused to load the mod, but you should delete this mod and run a complete anti-malware scan of your computer now to be safe."
}
],
/**
* Metadata about individual files which are known to be malicious, and should be blocked.
* If any file in a folder matches an entry, the entire folder is considered malicious.
*/
"LooseFileBlacklist": [
// 2026-01: 'Alchemistry' (ModDrop:1731397, malicious reupload of legit mod)
{
"Name": "Auto_Alchemistry.bat",
"Hash": "8b749727775689a05cc92ab4d624ab8b",
"Message": "It downloads malicious code from a remote server and runs it on your computer. SMAPI refused to load the mod, but you should delete the mod containing this file and run a complete anti-malware scan of your computer now to be safe."
}
]
}
@@ -14,9 +14,9 @@
"title": "Format version",
"description": "The format version. You should always use the latest version to enable the latest features, avoid obsolete behavior, and reduce load times.",
"type": "string",
"pattern": "^2\\.8\\.[0-9]+$",
"pattern": "^2\\.9\\.[0-9]+$",
"@errorMessages": {
"pattern": "Incorrect value '@value'. You should always use the latest format version (currently 2.8.0) to enable the latest features, avoid obsolete behavior, and reduce load times."
"pattern": "Incorrect value '@value'. You should always use the latest format version (currently 2.9.0) to enable the latest features, avoid obsolete behavior, and reduce load times."
}
},
"ConfigSchema": {
@@ -473,7 +473,7 @@
"title": "Patch mode",
"description": "How to apply FromArea to ToArea. Defaults to Replace.",
"type": "string",
"enum": [ "Replace", "Overlay" ],
"enum": [ "Replace", "Overlay", "Mask" ],
"default": "Replace"
},
+7 -10
View File
@@ -9,6 +9,7 @@
<Folder Name="/.root/.github/">
<File Path="../.github/CONTRIBUTING.md" />
<File Path="../.github/SUPPORT.md" />
<File Path="../.github/workflows/build-smapi.yml" />
</Folder>
<Folder Name="/.root/.github/ISSUE_TEMPLATE/">
<File Path="../.github/ISSUE_TEMPLATE/config.yml" />
@@ -19,17 +20,13 @@
<File Path="../build/deploy-local-smapi.targets" />
<File Path="../build/find-game-folder.targets" />
</Folder>
<Folder Name="/.root/build/unix/">
<File Path="../build/unix/prepare-install-package.sh" />
<File Path="../build/unix/set-smapi-version.sh" />
<Folder Name="/.root/build/scripts/">
<File Path="../build/scripts/finalize-install-package.sh" />
<File Path="../build/scripts/prepare-install-package.ps1" />
<File Path="../build/scripts/set-smapi-version.ps1" />
</Folder>
<Folder Name="/.root/build/windows/">
<File Path="../build/windows/finalize-install-package.sh" />
<File Path="../build/windows/prepare-install-package.ps1" />
<File Path="../build/windows/set-smapi-version.ps1" />
</Folder>
<Folder Name="/.root/build/windows/lib/">
<File Path="../build/windows/lib/in-place-regex.ps1" />
<Folder Name="/.root/build/scripts/lib/">
<File Path="../build/scripts/lib/in-place-regex.ps1" />
</Folder>
<Folder Name="/.root/docs/">
<File Path="../docs/mod-build-config.md" />
+3 -3
View File
@@ -49,7 +49,7 @@ internal static class EarlyConstants
internal static int? LogScreenId { get; set; }
/// <summary>SMAPI's current raw semantic version.</summary>
internal static string RawApiVersion = "4.4.0";
internal static string RawApiVersion = "4.5.0";
}
/// <summary>Contains SMAPI's constants and assumptions.</summary>
@@ -108,7 +108,7 @@ public static class Constants
#if DEBUG
true;
#else
false;
false;
#endif
/// <summary>The URL of the SMAPI home page.</summary>
@@ -124,7 +124,7 @@ public static class Constants
internal static string ApiUserConfigPath => Path.Combine(Constants.InternalFilesPath, "config.user.json");
/// <summary>The file path for the per-mods-folder <see cref="ApiConfigPath"/> override file, which is applied over it.</summary>
internal static string ApiModGroupConfigPath => Path.Combine(ModsPath, "SMAPI-config.json");
internal static string ApiModGroupConfigPath => Path.Combine(Constants.ModsPath, "SMAPI-config.json");
/// <summary>The file path for the SMAPI metadata file.</summary>
internal static string ApiMetadataPath => Path.Combine(Constants.InternalFilesPath, "metadata.json");
+56 -7
View File
@@ -28,6 +28,12 @@ internal class LogManager : IDisposable
/// <summary>Create a monitor instance given the ID and name.</summary>
private readonly Func<string, string, Monitor> GetMonitorImpl;
/// <summary>The console writer which sends color-coded text to the console.</summary>
private readonly ColorfulConsoleWriter ConsoleWriter;
/// <summary>The monitors managed by SMAPI.</summary>
private readonly List<Monitor> Monitors = [];
/*********
** Accessors
@@ -47,23 +53,21 @@ internal class LogManager : IDisposable
****/
/// <summary>Construct an instance.</summary>
/// <param name="logPath">The log file path to write.</param>
/// <param name="colorSchemeId">The color scheme ID in <paramref name="colorConfig"/> to use, or <see cref="MonitorColorScheme.AutoDetect"/> to select one automatically.</param>
/// <param name="colorConfig">The colors to use for text written to the SMAPI console.</param>
/// <param name="writeToConsole">Whether to output log messages to the console.</param>
/// <param name="verboseLogging">The log contexts for which to enable verbose logging, which may show a lot more information to simplify troubleshooting.</param>
/// <param name="isDeveloperMode">Whether to enable full console output for developers.</param>
/// <param name="getScreenIdForLog">Get the screen ID that should be logged to distinguish between players in split-screen mode, if any.</param>
public LogManager(string logPath, ColorSchemeConfig colorConfig, bool writeToConsole, HashSet<string> verboseLogging, bool isDeveloperMode, Func<int?> getScreenIdForLog)
public LogManager(string logPath, MonitorColorScheme colorSchemeId, Dictionary<MonitorColorScheme, Dictionary<ConsoleLogLevel, ConsoleColor>> colorConfig, bool writeToConsole, HashSet<string> verboseLogging, bool isDeveloperMode, Func<int?> getScreenIdForLog)
{
// init log file
this.LogFile = new LogFileManager(logPath);
// init monitor
this.GetMonitorImpl = (id, name) => new Monitor(id, name, this.LogFile, colorConfig, verboseLogging.Contains("*") || verboseLogging.Contains(id), getScreenIdForLog)
{
WriteToConsole = writeToConsole,
ShowTraceInConsole = isDeveloperMode,
ShowFullStampInConsole = isDeveloperMode
};
this.ConsoleWriter = new ColorfulConsoleWriter(Constants.Platform, colorSchemeId, colorConfig);
this.GetMonitorImpl = (id, name) => this.CreateAndRegisterMonitor(id, name, verboseLogging, getScreenIdForLog, writeToConsole, isDeveloperMode);
this.Monitor = this.GetMonitor("SMAPI", "SMAPI");
this.MonitorForGame = this.GetMonitor("game", "game");
@@ -90,6 +94,19 @@ internal class LogManager : IDisposable
Console.Title = title;
}
/// <summary>Apply the SMAPI settings to the log manager and its managed monitors.</summary>
/// <param name="colorSchemeId">The color scheme ID in <paramref name="colorSchemes"/> to use, or <see cref="MonitorColorScheme.AutoDetect"/> to select one automatically.</param>
/// <param name="colorSchemes">The colors to use for text written to the SMAPI console.</param>
/// <param name="verboseLogging">The log contexts for which to enable verbose logging, which may show a lot more information to simplify troubleshooting.</param>
/// <param name="isDeveloperMode">Whether to enable full console output for developers.</param>
public void ApplySettings(MonitorColorScheme colorSchemeId, Dictionary<MonitorColorScheme, Dictionary<ConsoleLogLevel, ConsoleColor>> colorSchemes, HashSet<string> verboseLogging, bool isDeveloperMode)
{
foreach (Monitor monitor in this.Monitors)
this.ApplySettings(monitor, verboseLogging, isDeveloperMode);
this.ConsoleWriter.SetColors(colorSchemeId, colorSchemes);
}
/****
** Console input
****/
@@ -332,6 +349,38 @@ internal class LogManager : IDisposable
/*********
** Protected methods
*********/
/// <summary>Create and register a monitor instance.</summary>
/// <param name="modId">The mod ID, if applicable.</param>
/// <param name="source">The name of the module which logs messages using this instance.</param>
/// <param name="verboseLogging">The log contexts for which to enable verbose logging, which may show a lot more information to simplify troubleshooting.</param>
/// <param name="getScreenIdForLog">Get the screen ID that should be logged to distinguish between players in split-screen mode, if any.</param>
/// <param name="writeToConsole">Whether to write anything to the console. This should be disabled if no console is available.</param>
/// <param name="isDeveloperMode">Whether to enable full console output for developers.</param>
private Monitor CreateAndRegisterMonitor(string modId, string source, HashSet<string> verboseLogging, Func<int?> getScreenIdForLog, bool writeToConsole, bool isDeveloperMode)
{
Monitor monitor = new(modId, source, this.LogFile, this.ConsoleWriter, getScreenIdForLog)
{
WriteToConsole = writeToConsole
};
this.ApplySettings(monitor, verboseLogging, isDeveloperMode);
this.Monitors.Add(monitor);
return monitor;
}
/// <summary>Apply the SMAPI settings to a managed monitor.</summary>
/// <param name="monitor">The monitor to update.</param>
/// <param name="verboseLogging">The log contexts for which to enable verbose logging, which may show a lot more information to simplify troubleshooting.</param>
/// <param name="isDeveloperMode">Whether to enable full console output for developers.</param>
private void ApplySettings(Monitor monitor, HashSet<string> verboseLogging, bool isDeveloperMode)
{
monitor.IsVerbose = verboseLogging.Contains("*") || verboseLogging.Contains(monitor.ModId);
monitor.ShowTraceInConsole = isDeveloperMode;
monitor.ShowFullStampInConsole = isDeveloperMode;
}
/// <summary>Write a summary of mod warnings to the console and log.</summary>
/// <param name="mods">The loaded mods.</param>
/// <param name="skippedMods">The mods which could not be loaded.</param>
@@ -43,7 +43,7 @@ internal class ModResolver
if (!File.Exists(entryDllPath))
entryDllPath = null;
blacklistEntry = modBlacklist.Get(manifest.UniqueID, entryDllPath);
blacklistEntry = modBlacklist.CheckMod(manifest.UniqueID, entryDllPath);
}
// parse internal data record (if any)
+57 -20
View File
@@ -15,20 +15,21 @@ internal class SConfig
/// <summary>The default config values, for fields that should be logged if different.</summary>
private static readonly IDictionary<string, object> DefaultValues = new Dictionary<string, object>
{
[nameof(CheckForUpdates)] = true,
[nameof(CheckForBlacklistUpdates)] = true,
[nameof(CheckContentIntegrity)] = true,
[nameof(ListenForConsoleInput)] = true,
[nameof(ParanoidWarnings)] = Constants.IsDebugBuild,
[nameof(UseBetaChannel)] = Constants.ApiVersion.IsPrerelease(),
[nameof(GitHubProjectName)] = "Pathoschild/SMAPI",
[nameof(WebApiBaseUrl)] = "https://smapi.io/api/",
[nameof(LogNetworkTraffic)] = false,
[nameof(LogTechnicalDetailsForBrokenMods)] = false,
[nameof(RewriteMods)] = true,
[nameof(FixHarmony)] = true,
[nameof(UseCaseInsensitivePaths)] = Constants.Platform is Platform.Android or Platform.Linux,
[nameof(SuppressHarmonyDebugMode)] = true
[nameof(SConfig.CheckForUpdates)] = true,
[nameof(SConfig.CheckForBlacklistUpdates)] = true,
[nameof(SConfig.CheckContentIntegrity)] = true,
[nameof(SConfig.ListenForConsoleInput)] = true,
[nameof(SConfig.ParanoidWarnings)] = Constants.IsDebugBuild,
[nameof(SConfig.UseBetaChannel)] = Constants.ApiVersion.IsPrerelease(),
[nameof(SConfig.GitHubProjectName)] = "Pathoschild/SMAPI",
[nameof(SConfig.WebApiBaseUrl)] = "https://smapi.io/api/",
[nameof(SConfig.LogNetworkTraffic)] = false,
[nameof(SConfig.LogTechnicalDetailsForBrokenMods)] = false,
[nameof(SConfig.RewriteMods)] = true,
[nameof(SConfig.EnableConfigMenu)] = true,
[nameof(SConfig.FixHarmony)] = true,
[nameof(SConfig.UseCaseInsensitivePaths)] = Constants.Platform is Platform.Android or Platform.Linux,
[nameof(SConfig.SuppressHarmonyDebugMode)] = true
};
/// <summary>The default values for <see cref="SuppressUpdateChecks"/>, to log changes if different.</summary>
@@ -46,7 +47,7 @@ internal class SConfig
// Note: properties must be writable to support merging config.user.json into it.
//
/// <summary>Whether to enable development features.</summary>
/// <summary>Whether to show much more info in the SMAPI consoler window, intended for mod developers. Not recommended for most players.</summary>
public bool DeveloperMode { get; set; }
/// <summary>Whether to check for newer versions of SMAPI and mods on startup.</summary>
@@ -61,7 +62,7 @@ internal class SConfig
/// <summary>Whether SMAPI should listen for console input to support console commands.</summary>
public bool ListenForConsoleInput { get; set; }
/// <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 directly use potentially sensitive .NET APIs like file or shell access.</summary>
public bool ParanoidWarnings { get; set; }
/// <summary>Whether to show beta versions as valid updates.</summary>
@@ -83,6 +84,9 @@ internal class SConfig
/// <summary>Whether SMAPI should rewrite mods for compatibility.</summary>
public bool RewriteMods { get; set; }
/// <summary>Whether SMAPI should add an in-game config menu through Generic Mod Config Menu.</summary>
public bool EnableConfigMenu { get; set; }
/// <summary>Whether to apply fixes to Harmony so it works with Stardew Valley.</summary>
public bool FixHarmony { get; set; }
@@ -95,8 +99,11 @@ internal class SConfig
/// <summary>Whether to include more technical details about broken mods in the TRACE logs. This is mainly useful for creating compatibility rewriters.</summary>
public bool LogTechnicalDetailsForBrokenMods { get; set; }
/// <summary>The color scheme ID in <see cref="ConsoleColorSchemes"/> to use, or <see cref="MonitorColorScheme.AutoDetect"/> to select one automatically.</summary>
public MonitorColorScheme ConsoleColorScheme { get; set; }
/// <summary>The colors to use for text written to the SMAPI console.</summary>
public ColorSchemeConfig ConsoleColors { get; set; }
public Dictionary<MonitorColorScheme, Dictionary<ConsoleLogLevel, ConsoleColor>> ConsoleColorSchemes { get; set; }
/// <summary>Whether to prevent mods from enabling Harmony's debug mode, which impacts performance and creates a file on your desktop. Debug mode should never be enabled by a released mod.</summary>
public bool SuppressHarmonyDebugMode { get; set; }
@@ -124,18 +131,45 @@ internal class SConfig
/// <param name="useBetaChannel"><inheritdoc cref="UseBetaChannel" path="/summary" /></param>
/// <param name="gitHubProjectName"><inheritdoc cref="GitHubProjectName" path="/summary" /></param>
/// <param name="webApiBaseUrl"><inheritdoc cref="WebApiBaseUrl" path="/summary" /></param>
/// <param name="blacklistUrl"><inheritdoc cref="BlacklistUrl" path="/summary" /></param>
/// <param name="verboseLogging"><inheritdoc cref="VerboseLogging" path="/summary" /></param>
/// <param name="rewriteMods"><inheritdoc cref="RewriteMods" path="/summary" /></param>
/// <param name="enableConfigMenu"><inheritdoc cref="EnableConfigMenu" path="/summary" /></param>
/// <param name="fixHarmony"><inheritdoc cref="FixHarmony" path="/summary" /></param>
/// <param name="useCaseInsensitivePaths"><inheritdoc cref="UseCaseInsensitivePaths" path="/summary" /></param>
/// <param name="logNetworkTraffic"><inheritdoc cref="LogNetworkTraffic" path="/summary" /></param>
/// <param name="logTechnicalDetailsForBrokenMods"><inheritdoc cref="LogTechnicalDetailsForBrokenMods" path="/summary" /></param>
/// <param name="consoleColors"><inheritdoc cref="ConsoleColors" path="/summary" /></param>
/// <param name="consoleColorScheme"><inheritdoc cref="ConsoleColorScheme" path="/summary" /></param>
/// <param name="consoleColorSchemes"><inheritdoc cref="ConsoleColorSchemes" path="/summary" /></param>
/// <param name="suppressHarmonyDebugMode"><inheritdoc cref="SuppressHarmonyDebugMode" path="/summary" /></param>
/// <param name="suppressUpdateChecks"><inheritdoc cref="SuppressUpdateChecks" path="/summary" /></param>
/// <param name="modsToLoadEarly"><inheritdoc cref="ModsToLoadEarly" path="/summary" /></param>
/// <param name="modsToLoadLate"><inheritdoc cref="ModsToLoadLate" path="/summary" /></param>
public SConfig(bool developerMode, bool? checkForUpdates, bool? checkForBlacklistUpdates, bool? checkContentIntegrity, bool? listenForConsoleInput, bool? paranoidWarnings, bool? useBetaChannel, string gitHubProjectName, string webApiBaseUrl, string[]? verboseLogging, bool? rewriteMods, bool? fixHarmony, bool? useCaseInsensitivePaths, bool? logNetworkTraffic, bool? logTechnicalDetailsForBrokenMods, ColorSchemeConfig consoleColors, bool? suppressHarmonyDebugMode, string[]? suppressUpdateChecks, string[]? modsToLoadEarly, string[]? modsToLoadLate)
public SConfig(
bool developerMode,
bool? checkForUpdates,
bool? checkForBlacklistUpdates,
bool? checkContentIntegrity,
bool? listenForConsoleInput,
bool? paranoidWarnings,
bool? useBetaChannel,
string gitHubProjectName,
string webApiBaseUrl,
string blacklistUrl,
string[]? verboseLogging,
bool? rewriteMods,
bool? enableConfigMenu,
bool? fixHarmony,
bool? useCaseInsensitivePaths,
bool? logNetworkTraffic,
bool? logTechnicalDetailsForBrokenMods,
MonitorColorScheme consoleColorScheme,
Dictionary<MonitorColorScheme, Dictionary<ConsoleLogLevel, ConsoleColor>>? consoleColorSchemes,
bool? suppressHarmonyDebugMode,
string[]? suppressUpdateChecks,
string[]? modsToLoadEarly,
string[]? modsToLoadLate
)
{
this.DeveloperMode = developerMode;
this.CheckForUpdates = checkForUpdates ?? (bool)SConfig.DefaultValues[nameof(this.CheckForUpdates)];
@@ -146,13 +180,16 @@ internal class SConfig
this.UseBetaChannel = useBetaChannel ?? (bool)SConfig.DefaultValues[nameof(this.UseBetaChannel)];
this.GitHubProjectName = gitHubProjectName;
this.WebApiBaseUrl = webApiBaseUrl;
this.BlacklistUrl = blacklistUrl;
this.VerboseLogging = new HashSet<string>(verboseLogging ?? [], StringComparer.OrdinalIgnoreCase);
this.RewriteMods = rewriteMods ?? (bool)SConfig.DefaultValues[nameof(this.RewriteMods)];
this.EnableConfigMenu = enableConfigMenu ?? (bool)SConfig.DefaultValues[nameof(this.EnableConfigMenu)];
this.FixHarmony = fixHarmony ?? (bool)SConfig.DefaultValues[nameof(this.FixHarmony)];
this.UseCaseInsensitivePaths = useCaseInsensitivePaths ?? (bool)SConfig.DefaultValues[nameof(this.UseCaseInsensitivePaths)];
this.LogNetworkTraffic = logNetworkTraffic ?? (bool)SConfig.DefaultValues[nameof(this.LogNetworkTraffic)];
this.LogTechnicalDetailsForBrokenMods = logTechnicalDetailsForBrokenMods ?? (bool)SConfig.DefaultValues[nameof(this.LogTechnicalDetailsForBrokenMods)];
this.ConsoleColors = consoleColors;
this.ConsoleColorScheme = consoleColorScheme;
this.ConsoleColorSchemes = consoleColorSchemes ?? [];
this.SuppressHarmonyDebugMode = suppressHarmonyDebugMode ?? (bool)SConfig.DefaultValues[nameof(this.SuppressHarmonyDebugMode)];
this.SuppressUpdateChecks = new HashSet<string>(suppressUpdateChecks ?? [], StringComparer.OrdinalIgnoreCase);
this.ModsToLoadEarly = new HashSet<string>(modsToLoadEarly ?? [], StringComparer.OrdinalIgnoreCase);
+11 -9
View File
@@ -13,9 +13,6 @@ internal class Monitor : IMonitor
/*********
** Fields
*********/
/// <summary>The mod ID, if applicable.</summary>
private readonly string ModId;
/// <summary>The name of the module which logs messages using this instance.</summary>
private readonly string Source;
@@ -41,6 +38,9 @@ internal class Monitor : IMonitor
/*********
** Accessors
*********/
/// <summary>The mod ID, if applicable.</summary>
public string ModId { get; }
/// <summary>Whether to log basic contextual info (like buttons pressed and menus opened) even if <see cref="IsVerbose"/> is disabled.</summary>
public static bool ForceLogContext { get; set; }
@@ -54,7 +54,11 @@ internal class Monitor : IMonitor
public static LogLevel ContextLogLevel => Monitor.ForceLogContext ? LogLevel.Info : LogLevel.Trace;
/// <inheritdoc />
public bool IsVerbose => field || Monitor.ForceVerboseLoggingForAll || Monitor.ForceVerboseLogging.Contains(this.ModId);
public bool IsVerbose
{
get => field || Monitor.ForceVerboseLoggingForAll || Monitor.ForceVerboseLogging.Contains(this.ModId);
set => field = value;
}
/// <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>
internal bool ShowFullStampInConsole { get; set; }
@@ -73,10 +77,9 @@ internal class Monitor : IMonitor
/// <param name="modId">The mod ID, if applicable.</param>
/// <param name="source">The name of the module which logs messages using this instance.</param>
/// <param name="logFile">The log file to which to write messages.</param>
/// <param name="colorConfig">The colors to use for text written to the SMAPI console.</param>
/// <param name="isVerbose">Whether verbose logging is enabled. This enables more detailed diagnostic messages than are normally needed.</param>
/// <param name="consoleWriter">Handles writing text to the console.</param>
/// <param name="getScreenIdForLog">Get the screen ID that should be logged to distinguish between players in split-screen mode, if any.</param>
public Monitor(string modId, string source, LogFileManager logFile, ColorSchemeConfig colorConfig, bool isVerbose, Func<int?> getScreenIdForLog)
public Monitor(string modId, string source, LogFileManager logFile, IConsoleWriter consoleWriter, Func<int?> getScreenIdForLog)
{
// validate
if (string.IsNullOrWhiteSpace(source))
@@ -86,8 +89,7 @@ internal class Monitor : IMonitor
this.ModId = modId;
this.Source = source;
this.LogFile = logFile ?? throw new ArgumentNullException(nameof(logFile), "The log file manager cannot be null.");
this.ConsoleWriter = new ColorfulConsoleWriter(Constants.Platform, colorConfig);
this.IsVerbose = isVerbose;
this.ConsoleWriter = consoleWriter ?? throw new ArgumentNullException(nameof(consoleWriter), "The console writer cannot be null.");
this.GetScreenIdForLog = getScreenIdForLog;
}
+70 -19
View File
@@ -37,6 +37,7 @@ using StardewModdingAPI.Framework.Rendering;
using StardewModdingAPI.Framework.Serialization;
using StardewModdingAPI.Framework.StateTracking.Snapshots;
using StardewModdingAPI.Framework.Utilities;
using StardewModdingAPI.Integrations.GenericModConfigMenu;
using StardewModdingAPI.Internal;
using StardewModdingAPI.Toolkit;
using StardewModdingAPI.Toolkit.Framework.Clients.WebApi;
@@ -85,12 +86,12 @@ internal class SCore : IDisposable
/// <summary>Encapsulates access to SMAPI core translations.</summary>
private readonly Translator Translator = new();
/// <summary>The SMAPI configuration settings.</summary>
private readonly SConfig Settings;
/// <summary>The mod toolkit used for generic mod interactions.</summary>
private readonly ModToolkit Toolkit = new();
/// <summary>The SMAPI configuration settings.</summary>
private SConfig Settings;
/****
** Higher-level components
****/
@@ -113,13 +114,15 @@ internal class SCore : IDisposable
/// <summary>Manages SMAPI events for mods.</summary>
private readonly EventManager EventManager;
/****
** State
****/
/// <summary>The path to search for mods.</summary>
private string ModsPath => Constants.ModsPath;
/// <summary>The override to apply for <see cref="SConfig.DeveloperMode"/>, or <c>null</c> to use the value from the settings file.</summary>
private readonly bool? OverrideDeveloperMode;
/// <summary>Whether the game is currently running.</summary>
private bool IsGameRunning;
@@ -178,8 +181,8 @@ internal class SCore : IDisposable
/// <summary>Construct an instance.</summary>
/// <param name="modsPath">The path to search for mods.</param>
/// <param name="writeToConsole">Whether to output log messages to the console.</param>
/// <param name="developerMode">Whether to enable development features, or <c>null</c> to use the value from the settings file.</param>
public SCore(string modsPath, bool writeToConsole, bool? developerMode)
/// <param name="overrideDeveloperMode">The override to apply for <see cref="SConfig.DeveloperMode"/>, or <c>null</c> to use the value from the settings file.</param>
public SCore(string modsPath, bool writeToConsole, bool? overrideDeveloperMode)
{
SCore.Instance = this;
@@ -193,20 +196,11 @@ internal class SCore : IDisposable
string logPath = this.GetLogPath();
// init settings
{
var deserializerSettings = new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore };
this.Settings = JsonConvert.DeserializeObject<SConfig>(File.ReadAllText(Constants.ApiConfigPath)) ?? throw new InvalidOperationException("The 'smapi-internal/config.json' file is missing or invalid. You can reinstall SMAPI to fix this.");
if (File.Exists(Constants.ApiUserConfigPath))
JsonConvert.PopulateObject(File.ReadAllText(Constants.ApiUserConfigPath), this.Settings, deserializerSettings);
if (File.Exists(Constants.ApiModGroupConfigPath))
JsonConvert.PopulateObject(File.ReadAllText(Constants.ApiModGroupConfigPath), this.Settings, deserializerSettings);
if (developerMode.HasValue)
this.Settings.OverrideDeveloperMode(developerMode.Value);
}
this.OverrideDeveloperMode = overrideDeveloperMode;
this.ReloadSettings();
// init basics
this.LogManager = new LogManager(logPath: logPath, colorConfig: this.Settings.ConsoleColors, writeToConsole: writeToConsole, verboseLogging: this.Settings.VerboseLogging, isDeveloperMode: this.Settings.DeveloperMode, getScreenIdForLog: this.GetScreenIdForLog);
this.LogManager = new LogManager(logPath: logPath, colorSchemeId: this.Settings.ConsoleColorScheme, colorConfig: this.Settings.ConsoleColorSchemes, writeToConsole: writeToConsole, verboseLogging: this.Settings.VerboseLogging, isDeveloperMode: this.Settings.DeveloperMode, getScreenIdForLog: this.GetScreenIdForLog);
this.CommandManager = new CommandManager(this.Monitor);
this.EventManager = new EventManager(this.ModRegistry);
SCore.DeprecationManager = new DeprecationManager(this.Monitor, this.ModRegistry);
@@ -444,6 +438,25 @@ internal class SCore : IDisposable
Constants.ApiBlacklistActualPath = Constants.ApiBlacklistPath;
}
// check for malicious loose files
this.Monitor.Log("Scanning for malicious files...");
{
bool foundMaliciousFiles = false;
foreach ((string filePath, LooseFileBlacklistEntryModel match) in modBlacklist.CheckLooseFiles(this.ModsPath))
{
foundMaliciousFiles = true;
this.Monitor.LogFatal("Malicious mod file detected.");
this.Monitor.Log($"File path: '{filePath}'", LogLevel.Error);
this.Monitor.Newline();
this.Monitor.Log(match.Message ?? "This file has been flagged as malicious. You should immediately delete the mod containing the file, and perform a full anti-malware scan of your computer to be safe.", LogLevel.Error);
}
if (foundMaliciousFiles)
this.LogManager.PressAnyKeyToExit();
}
// load mods
{
this.Monitor.Log("Loading mod metadata...", LogLevel.Debug);
@@ -502,6 +515,10 @@ internal class SCore : IDisposable
// check for updates
_ = this.CheckForUpdatesAsync(mods); // ignore task since the main thread doesn't need to wait for it
// register config menu with Generic Mod Config Menu
if (this.Settings.EnableConfigMenu)
new GenericModConfigMenuIntegration(this.Monitor, this.Translator, () => this.Settings, this.ReloadSettings).Register(this.ModRegistry);
}
// update window titles
@@ -743,7 +760,7 @@ internal class SCore : IDisposable
// conflict (e.g. collection changed during enumeration errors) and data may change
// unexpectedly from one mod instruction to the next.
//
// Therefore we can just run Game1.Update here without raising any SMAPI events. There's
// Therefore, we can just run Game1.Update here without raising any SMAPI events. There's
// a small chance that the task will finish after we defer but before the game checks,
// which means technically events should be raised, but the effects of missing one
// update tick are negligible and not worth the complications of bypassing Game1.Update.
@@ -1387,6 +1404,40 @@ internal class SCore : IDisposable
this.EventManager.AssetsInvalidated.Raise(new AssetsInvalidatedEventArgs(assetNames, assetNames.Select(p => p.GetBaseAssetName())));
}
/// <summary>Reload the SMAPI settings.</summary>
/// <exception cref="InvalidOperationException">The intern</exception>
[MemberNotNull(nameof(SCore.Settings))]
private void ReloadSettings()
{
JsonSerializerSettings deserializerSettings = new() { NullValueHandling = NullValueHandling.Ignore };
// load default settings
SConfig? settings = JsonConvert.DeserializeObject<SConfig>(File.ReadAllText(Constants.ApiConfigPath));
if (settings is null)
{
if (this.Settings is null)
throw new InvalidOperationException("The 'smapi-internal/config.json' file is missing or invalid. You can reinstall SMAPI to fix this.");
this.Monitor.Log("Could not load updated settings from the 'smapi-internal/config.json' file. Keeping the settings as-is.", LogLevel.Warn);
return;
}
// load user settings
if (File.Exists(Constants.ApiUserConfigPath))
JsonConvert.PopulateObject(File.ReadAllText(Constants.ApiUserConfigPath), settings, deserializerSettings);
if (File.Exists(Constants.ApiModGroupConfigPath))
JsonConvert.PopulateObject(File.ReadAllText(Constants.ApiModGroupConfigPath), settings, deserializerSettings);
if (this.OverrideDeveloperMode.HasValue)
settings.OverrideDeveloperMode(this.OverrideDeveloperMode.Value);
// apply
this.Settings = settings;
// update log manager if already initialized
// ReSharper disable once ConditionalAccessQualifierIsNonNullableAccordingToAPIContract
this.LogManager?.ApplySettings(settings.ConsoleColorScheme, settings.ConsoleColorSchemes, settings.VerboseLogging, this.OverrideDeveloperMode ?? settings.DeveloperMode);
}
/// <summary>Get the load/edit operations to apply to an asset by querying registered <see cref="IContentEvents.AssetRequested"/> event handlers.</summary>
/// <param name="asset">The asset info being requested.</param>
private AssetOperationGroup? RequestAssetOperations(IAssetInfo asset)
@@ -0,0 +1,343 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using StardewModdingAPI.Framework;
using StardewModdingAPI.Framework.Models;
using StardewModdingAPI.Framework.ModHelpers;
using StardewModdingAPI.Framework.ModLoading;
using StardewModdingAPI.Framework.Reflection;
using StardewModdingAPI.Internal.ConsoleWriting;
using StardewModdingAPI.Toolkit.Serialization.Models;
using StardewModdingAPI.Toolkit.Utilities;
using StardewValley.Extensions;
namespace StardewModdingAPI.Integrations.GenericModConfigMenu
{
/// <summary>Registers the SMAPI configuration with Generic Mod Config Menu.</summary>
internal class GenericModConfigMenuIntegration
{
/*********
** Fields
*********/
/// <summary>The unique mod ID for Generic Mod Config Menu.</summary>
private const string GenericModConfigMenuModId = "spacechase0.GenericModConfigMenu";
/// <summary>The minimum version of Generic Mod Config Menu supported by this integration.</summary>
private const string MinVersion = "1.9.6";
/// <summary>Encapsulates monitoring and logging.</summary>
private readonly IMonitor Monitor;
/// <summary>The core SMAPI translations.</summary>
private readonly Translator Translations;
/// <summary>Get the current SMAPI settings.</summary>
private readonly Func<SConfig> GetConfig;
/// <summary>Reload the SMAPI settings when they're changed through the config menu.</summary>
private readonly Action ReloadConfig;
/// <summary>The custom setting values which haven't been saved yet.</summary>
private readonly Dictionary<string, object> StagedSettings = [];
/*********
** Public methods
*********/
/// <summary>Construct an instance.</summary>
/// <param name="monitor"><inheritdoc cref="Monitor" path="/summary" /></param>
/// <param name="translations"><inheritdoc cref="Translations" path="/summary" /></param>
/// <param name="getConfig"><inheritdoc cref="GetConfig" path="/summary" /></param>
/// <param name="reloadConfig"><inheritdoc cref="ReloadConfig" path="/summary" /></param>
public GenericModConfigMenuIntegration(IMonitor monitor, Translator translations, Func<SConfig> getConfig, Action reloadConfig)
{
this.Monitor = monitor;
this.Translations = translations;
this.GetConfig = getConfig;
this.ReloadConfig = reloadConfig;
}
/// <summary>Register the mod config.</summary>
/// <param name="internalModRegistry">An API for fetching metadata about loaded mods.</param>
public void Register(ModRegistry internalModRegistry)
{
try
{
// init
ModMetadata smapiMod = this.CreateFakeMod();
IModRegistry modRegistry = this.CreateModRegistryHelper(internalModRegistry, this.Monitor, smapiMod);
IGenericModConfigMenuApi? api = this.GetGenericModConfigMenuApi(modRegistry, this.Monitor);
if (api is null)
return;
// register config
api.Register(smapiMod.Manifest, this.OnReset, this.Save);
var getConfig = this.GetConfig;
// add 'SMAPI features' section
api.AddSectionTitle(smapiMod.Manifest, () => this.Translations.Get("config.section.features"));
api.AddBoolOption(
mod: smapiMod.Manifest,
name: () => this.Translations.Get("config.check-for-updates.name"),
tooltip: () => this.Translations.Get("config.check-for-updates.desc"),
getValue: () => getConfig().CheckForUpdates,
setValue: value => this.StageOption(nameof(SConfig.CheckForUpdates), value)
);
api.AddBoolOption(
mod: smapiMod.Manifest,
name: () => this.Translations.Get("config.check-content-integrity.name"),
tooltip: () => this.Translations.Get("config.check-content-integrity.desc"),
getValue: () => getConfig().CheckContentIntegrity,
setValue: value => this.StageOption(nameof(SConfig.CheckContentIntegrity), value)
);
api.AddBoolOption(
mod: smapiMod.Manifest,
name: () => this.Translations.Get("config.read-console-input.name"),
tooltip: () => this.Translations.Get("config.read-console-input.desc"),
getValue: () => getConfig().ListenForConsoleInput,
setValue: value => this.StageOption(nameof(SConfig.ListenForConsoleInput), value)
);
// add 'console window' section
api.AddSectionTitle(smapiMod.Manifest, () => this.Translations.Get("config.section.console-window"));
api.AddBoolOption(
mod: smapiMod.Manifest,
name: () => this.Translations.Get("config.developer-mode.name"),
tooltip: () => this.Translations.Get("config.developer-mode.desc"),
getValue: () => getConfig().DeveloperMode,
setValue: value => this.StageOption(nameof(SConfig.DeveloperMode), value)
);
api.AddTextOption(
mod: smapiMod.Manifest,
name: () => this.Translations.Get("config.color-scheme.name"),
tooltip: () => this.Translations.Get("config.color-scheme.desc"),
getValue: () => getConfig().ConsoleColorScheme.ToString(),
setValue: value => this.StageOption(nameof(SConfig.ConsoleColorScheme), value),
allowedValues: Enum.GetValues<MonitorColorScheme>().Select(p => p.ToString()).ToArray(),
formatAllowedValue: value => this.Translations.Get(
value is nameof(MonitorColorScheme.AutoDetect) && Constants.Platform == Platform.Windows
? $"config.color-scheme.options.{value}.on-windows"
: $"config.color-scheme.options.{value}"
));
// add 'verbose logging' section
api.AddSectionTitle(smapiMod.Manifest, () => this.Translations.Get("config.section.verbose-logs"));
api.AddParagraph(smapiMod.Manifest, () => this.Translations.Get("config.section.verbose-logs.explanation"));
api.AddTextOption(
mod: smapiMod.Manifest,
name: () => this.Translations.Get("config.enable-for.name"),
tooltip: () => this.Translations.Get("config.enable-for.desc"),
getValue: () => getConfig().VerboseLogging.Contains("*").ToString(),
setValue: value => this.StageVerboseLoggingOption("*", value == bool.TrueString),
allowedValues: [bool.TrueString, bool.FalseString],
formatAllowedValue: value => this.Translations.Get($"config.enable-for.options.{(value == bool.TrueString ? "all" : "selected")}")
);
api.AddBoolOption(
mod: smapiMod.Manifest,
name: () => this.Translations.Get("config.enable-for-smapi.name"),
tooltip: () => this.Translations.Get("config.enable-for-smapi.desc"),
getValue: () => getConfig().VerboseLogging.Contains("SMAPI"),
setValue: value => this.StageVerboseLoggingOption("SMAPI", value)
);
foreach (IModInfo mod in modRegistry.GetAll().OrderBy(p => p.Manifest.Name, StringComparer.OrdinalIgnoreCase))
{
if (mod.IsContentPack)
continue;
api.AddBoolOption(
mod: smapiMod.Manifest,
name: () => this.Translations.Get("config.enable-for-mod.name", new { modName = mod.Manifest.Name }),
tooltip: () => this.Translations.Get("config.enable-for-mod.desc", new { modName = mod.Manifest.Name }),
getValue: () => getConfig().VerboseLogging.Contains(mod.Manifest.UniqueID),
setValue: value => this.StageVerboseLoggingOption(mod.Manifest.UniqueID, value)
);
}
}
catch (Exception ex)
{
this.Monitor.Log("Couldn't register the SMAPI settings with Generic Mod Config Menu. This has no effect on SMAPI, but the in-game config menu won't be available.", LogLevel.Warn);
this.Monitor.Log(ex.ToString());
}
}
/*********
** Private methods
*********/
/****
** Save events
****/
/// <summary>Reset the SMAPI configuration to the default values.</summary>
private void OnReset()
{
try
{
foreach (string customPath in new[] { Constants.ApiUserConfigPath, Constants.ApiModGroupConfigPath })
{
if (File.Exists(customPath))
File.Delete(customPath);
}
this.ReloadConfig();
}
catch (Exception ex)
{
this.Monitor.Log($"Couldn't reset SMAPI to the default settings from the Generic Mod Config Menu UI.\n\nTechnical details:\n{ex}", LogLevel.Error);
}
}
/// <summary>Save the mod configuration.</summary>
private void Save()
{
try
{
// load settings
JsonSettingsWrapper? defaultSettings = JsonSettingsWrapper.TryLoadFile(Constants.ApiConfigPath, this.Monitor);
JsonSettingsWrapper? userSettings = JsonSettingsWrapper.TryLoadFile(Constants.ApiUserConfigPath, this.Monitor);
JsonSettingsWrapper? modGroupSettings = JsonSettingsWrapper.TryLoadFile(Constants.ApiModGroupConfigPath, this.Monitor);
// skip if invalid
if (defaultSettings is null)
{
this.Monitor.Log("Couldn't save the SMAPI settings from the Generic Mod Config Menu UI. The default settings could not be loaded.", LogLevel.Error);
return;
}
// apply edited options
if (this.StagedSettings.Count > 0)
{
// create settings file if needed
if (userSettings is null && modGroupSettings is null)
userSettings = new JsonSettingsWrapper(Constants.ApiUserConfigPath);
// save simple options
foreach ((string fieldName, object newValue) in this.StagedSettings)
{
// replace existing property if possible
if (modGroupSettings?.SetUserOption(fieldName, newValue, defaultSettings, overwriteOnly: true) is true)
continue;
if (userSettings?.SetUserOption(fieldName, newValue, defaultSettings, overwriteOnly: true) is true)
continue;
// else add new property
(modGroupSettings ?? userSettings)?.SetUserOption(fieldName, newValue, defaultSettings, overwriteOnly: false);
}
}
// save to files
if (modGroupSettings?.Changed is true)
modGroupSettings.SaveOrDeleteFile();
if (userSettings?.Changed is true)
userSettings.SaveOrDeleteFile();
// reload settings
this.StagedSettings.Clear();
this.ReloadConfig();
}
catch (Exception ex)
{
this.Monitor.Log($"Couldn't save the SMAPI settings from the Generic Mod Config Menu UI.\n\nTechnical details:\n{ex}", LogLevel.Error);
}
}
/****
** Config option staging
****/
/// <summary>Store a value set by the player before it's saved to the config files.</summary>
/// <param name="fieldName">The name of the <see cref="SConfig"/> field to change.</param>
/// <param name="value">The value to stage.</param>
private void StageOption(string fieldName, object value)
{
this.StagedSettings[fieldName] = value;
}
/// <summary>Store a custom <see cref="SConfig.VerboseLogging"/> option before it's saved to the config files.</summary>
/// <param name="id">The mod ID, 'SMAPI', or '*' (all) for which to toggle verbose logging.</param>
/// <param name="enable">Whether verbose logging is enabled.</param>
private void StageVerboseLoggingOption(string id, bool enable)
{
const string fieldName = nameof(SConfig.VerboseLogging);
if (this.StagedSettings.TryGetValue(fieldName, out object? rawValue) && rawValue is HashSet<string> set)
set.Toggle(id, enable);
else
{
HashSet<string> newSet = new HashSet<string>(this.GetConfig().VerboseLogging, StringComparer.OrdinalIgnoreCase);
newSet.Toggle(id, enable);
this.StagedSettings[fieldName] = newSet;
}
}
/****
** Initialization
****/
/// <summary>Get the public API for Generic Mod Config Menu, if it's available.</summary>
/// <param name="modRegistry">The mod registry to query.</param>
/// <param name="monitor">The monitor with which to log issues loading the API.</param>
/// <returns>Returns the API if it's available, else <c>null</c>.</returns>
private IGenericModConfigMenuApi? GetGenericModConfigMenuApi(IModRegistry modRegistry, IMonitor monitor)
{
// check mod is installed
if (modRegistry.Get(GenericModConfigMenuIntegration.GenericModConfigMenuModId) is not { } configMod)
return null;
// check min version
if (configMod.Manifest.Version.IsOlderThan(GenericModConfigMenuIntegration.MinVersion))
{
monitor.Log("Can't register the SMAPI settings with Generic Mod Config Menu, because you need version 1.9.6 or later of that mod.", LogLevel.Debug);
return null;
}
// check API
if (modRegistry.GetApi<IGenericModConfigMenuApi>(GenericModConfigMenuIntegration.GenericModConfigMenuModId) is not { } api)
{
monitor.Log("Can't register the SMAPI settings with Generic Mod Config Menu, because it unexpectedly had no API.", LogLevel.Debug);
return null;
}
return api;
}
/// <summary>Create a fake mod entry to represent SMAPI itself.</summary>
private ModMetadata CreateFakeMod()
{
IManifest manifest = new Manifest(
uniqueId: "SMAPI",
name: "SMAPI",
author: "Pathoschild",
description: string.Empty,
version: Constants.ApiVersion,
minimumApiVersion: Constants.ApiVersion,
minimumGameVersion: Constants.MinimumGameVersion,
entryDll: "StardewModdingAPI.exe",
contentPackFor: null,
dependencies: [],
updateKeys: []
);
return new ModMetadata(
displayName: "SMAPI",
directoryPath: Constants.GamePath,
rootPath: Constants.ModsPath,
manifest: manifest,
dataRecord: null,
isIgnored: false
);
}
/// <summary>Create a mod registry helper.</summary>
/// <param name="internalRegistry">The internal mod registry.</param>
/// <param name="monitor">Encapsulates monitoring and logging.</param>
/// <param name="smapiMod">The fake mod entry representing SMAPI itself.</param>
private IModRegistry CreateModRegistryHelper(ModRegistry internalRegistry, IMonitor monitor, ModMetadata smapiMod)
{
return new ModRegistryHelper(
mod: smapiMod,
registry: internalRegistry,
proxyFactory: new InterfaceProxyFactory(),
monitor: monitor
);
}
}
}
@@ -0,0 +1,55 @@
using System;
namespace StardewModdingAPI.Integrations.GenericModConfigMenu;
/// <summary>The API provided by the Generic Mod Config Menu mod.</summary>
public interface IGenericModConfigMenuApi
{
/*********
** Methods
*********/
/****
** Must be called first
****/
/// <summary>Register a mod whose config can be edited through the UI.</summary>
/// <param name="mod">The mod's manifest.</param>
/// <param name="reset">Reset the mod's config to its default values.</param>
/// <param name="save">Save the mod's current config to the <c>config.json</c> file.</param>
/// <param name="titleScreenOnly">Whether the options can only be edited from the title screen.</param>
void Register(IManifest mod, Action reset, Action save, bool titleScreenOnly = false);
/****
** Basic options
****/
/// <summary>Add a section title at the current position in the form.</summary>
/// <param name="mod">The mod's manifest.</param>
/// <param name="text">The title text shown in the form.</param>
/// <param name="tooltip">The tooltip text shown when the cursor hovers on the title, or <c>null</c> to disable the tooltip.</param>
void AddSectionTitle(IManifest mod, Func<string> text, Func<string>? tooltip = null);
/// <summary>Add a paragraph of text at the current position in the form.</summary>
/// <param name="mod">The mod's manifest.</param>
/// <param name="text">The paragraph text to display.</param>
void AddParagraph(IManifest mod, Func<string> text);
/// <summary>Add a boolean option at the current position in the form.</summary>
/// <param name="mod">The mod's manifest.</param>
/// <param name="getValue">Get the current value from the mod config.</param>
/// <param name="setValue">Set a new value in the mod config.</param>
/// <param name="name">The label text to show in the form.</param>
/// <param name="tooltip">The tooltip text shown when the cursor hovers on the field, or <c>null</c> to disable the tooltip.</param>
/// <param name="fieldId">The unique field ID for use with <c>OnFieldChanged</c>, or <c>null</c> to auto-generate a randomized ID.</param>
void AddBoolOption(IManifest mod, Func<bool> getValue, Action<bool> setValue, Func<string> name, Func<string>? tooltip = null, string? fieldId = null);
/// <summary>Add a string option at the current position in the form.</summary>
/// <param name="mod">The mod's manifest.</param>
/// <param name="getValue">Get the current value from the mod config.</param>
/// <param name="setValue">Set a new value in the mod config.</param>
/// <param name="name">The label text to show in the form.</param>
/// <param name="tooltip">The tooltip text shown when the cursor hovers on the field, or <c>null</c> to disable the tooltip.</param>
/// <param name="allowedValues">The values that can be selected, or <c>null</c> to allow any.</param>
/// <param name="formatAllowedValue">Get the display text to show for a value from <paramref name="allowedValues"/>, or <c>null</c> to show the values as-is.</param>
/// <param name="fieldId">The unique field ID for use with <c>OnFieldChanged</c>, or <c>null</c> to auto-generate a randomized ID.</param>
void AddTextOption(IManifest mod, Func<string> getValue, Action<string> setValue, Func<string> name, Func<string>? tooltip = null, string[]? allowedValues = null, Func<string, string>? formatAllowedValue = null, string? fieldId = null);
}
@@ -0,0 +1,195 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using StardewModdingAPI.Framework.Models;
namespace StardewModdingAPI.Integrations.GenericModConfigMenu;
/// <summary>Wraps access to a JSON file which stores <see cref="SConfig"/> fields.</summary>
internal class JsonSettingsWrapper
{
/*********
** Accessors
*********/
/// <summary>The full path to the underlying file.</summary>
public string FilePath { get; }
/// <summary>The loaded option properties.</summary>
public Dictionary<string, JToken> Properties { get; }
/// <summary>Whether the saved values have changed.</summary>
public bool Changed { get; private set; }
/*********
** Public methods
*********/
/// <summary>Construct an instance.</summary>
/// <param name="filePath"><inheritdoc cref="FilePath" path="/summary" /></param>
public JsonSettingsWrapper(string filePath)
: this(filePath, new Dictionary<string, JToken>(StringComparer.OrdinalIgnoreCase)) { }
/// <summary>Set the value for a user settings field.</summary>
/// <param name="fieldName">The name of the field to set.</param>
/// <param name="newValue">The new value to set.</param>
/// <param name="defaultSettings">The default settings.</param>
/// <param name="overwriteOnly">Whether to only set the value if it's already set in this file.</param>
/// <returns>Returns whether the value changed.</returns>
public bool SetUserOption(string fieldName, object newValue, JsonSettingsWrapper defaultSettings, bool overwriteOnly)
{
return fieldName == nameof(SConfig.VerboseLogging)
? this.SetVerboseLoggingUserOption(fieldName, (HashSet<string>)newValue, defaultSettings, overwriteOnly)
: this.SetScalarUserOption(fieldName, newValue, defaultSettings, overwriteOnly);
}
/// <summary>Remove a user settings field.</summary>
/// <param name="fieldName">The name of the field to set.</param>
/// <returns>Returns whether the value was found and removed.</returns>
public bool RemoveUserOption(string fieldName)
{
bool changed = this.Properties.Remove(fieldName);
this.Changed = this.Changed || changed;
return changed;
}
/// <summary>Save the user settings to the file on disk, or delete the files if it's empty.</summary>
public void SaveOrDeleteFile()
{
if (this.Properties.Count == 0)
File.Delete(Constants.ApiUserConfigPath);
else
{
string json = JsonConvert.SerializeObject(this.Properties, Formatting.Indented);
File.WriteAllText(this.FilePath, json, Encoding.UTF8);
}
this.Changed = false;
}
/// <summary>Read a raw JSON settings file.</summary>
/// <param name="path">The full path for the file to read.</param>
/// <param name="monitor">The monitor with which to log errors.</param>
/// <returns>Returns the loaded JSON wrapper, or <c>null</c> if the file doesn't exist or couldn't be loaded.</returns>
public static JsonSettingsWrapper? TryLoadFile(string path, IMonitor monitor)
{
try
{
if (!File.Exists(path))
return null;
string rawJson = File.ReadAllText(path);
JObject? json = JsonConvert.DeserializeObject<JObject>(rawJson);
if (json is null)
return null;
Dictionary<string, JToken> properties = new(StringComparer.OrdinalIgnoreCase);
foreach (JProperty property in json.Properties())
properties[property.Name] = property.Value;
return new JsonSettingsWrapper(path, properties);
}
catch (Exception ex)
{
monitor.Log($"Can't read settings file at {path}.\n\nTechnical details:\n{ex}", LogLevel.Error);
return null;
}
}
/*********
** Private methods
*********/
/// <summary>Construct an instance.</summary>
/// <param name="filePath"><inheritdoc cref="FilePath" path="/summary" /></param>
/// <param name="properties"><inheritdoc cref="Properties" path="/summary" /></param>
private JsonSettingsWrapper(string filePath, Dictionary<string, JToken> properties)
{
this.FilePath = filePath;
this.Properties = properties;
}
/// <summary>Set the value for a scalar user settings field.</summary>
/// <param name="fieldName">The name of the field to set.</param>
/// <param name="newValue">The new value to set.</param>
/// <param name="defaultSettings">The default settings.</param>
/// <param name="overwriteOnly">Whether to only set the value if it's already set in this file.</param>
/// <returns>Returns whether the value changed.</returns>
public bool SetScalarUserOption(string fieldName, object newValue, JsonSettingsWrapper defaultSettings, bool overwriteOnly)
{
// remove default
if (defaultSettings.Properties.TryGetValue(fieldName, out JToken? rawDefaultValue) && this.TokenHasValue(rawDefaultValue, newValue))
return this.RemoveUserOption(fieldName);
// else overwrite value
if (this.Properties.TryGetValue(fieldName, out JToken? oldValue))
{
if (this.TokenHasValue(oldValue, newValue))
return false;
this.Properties[fieldName] = JToken.FromObject(newValue);
this.Changed = true;
return true;
}
// else add value
if (!overwriteOnly)
{
this.Properties[fieldName] = JToken.FromObject(newValue);
this.Changed = true;
return true;
}
return false;
}
/// <summary>Set the value for the verbose logging field.</summary>
/// <param name="fieldName">The name of the field to set.</param>
/// <param name="newValue">The new value to set.</param>
/// <param name="defaultSettings">The default settings.</param>
/// <param name="overwriteOnly">Whether to only set the value if it's already set in this file.</param>
/// <returns>Returns whether the value changed.</returns>
public bool SetVerboseLoggingUserOption(string fieldName, HashSet<string> newValue, JsonSettingsWrapper defaultSettings, bool overwriteOnly)
{
// remove default
if (newValue.Count == 0)
return this.RemoveUserOption(fieldName);
// else overwrite value
if (this.Properties.GetValueOrDefault(fieldName)?.ToObject<HashSet<string>>() is { } oldSet)
{
oldSet = new HashSet<string>(oldSet, StringComparer.OrdinalIgnoreCase);
bool changed = newValue.Count != oldSet.Count || !oldSet.All(newValue.Contains);
if (changed)
{
this.Properties[fieldName] = JToken.FromObject(newValue);
this.Changed = true;
}
return changed;
}
// else add value
if (!overwriteOnly)
{
this.Properties[fieldName] = JToken.FromObject(newValue);
this.Changed = true;
return true;
}
return false;
}
/// <summary>Get whether a token has the expected value.</summary>
/// <param name="token">The token whose value to check.</param>
/// <param name="expectedValue">The value to match.</param>
private bool TokenHasValue(JToken token, object expectedValue)
{
object? actualValue = token.Value<JValue>()?.Value;
return expectedValue.Equals(actualValue);
}
}
+35 -31
View File
@@ -16,7 +16,7 @@ in future SMAPI versions.
*/
{
/**
* The logs for which to enable verbose logging, which may show a lot more information to
* The mods for which to enable verbose logging, which may show a lot more information to
* simplify troubleshooting.
*
* The possible values are:
@@ -45,10 +45,11 @@ in future SMAPI versions.
"CheckContentIntegrity": true,
/**
* Whether to enable features intended for mod developers. Currently this only makes TRACE-level
* messages appear in the console.
* Whether to show much more info in the SMAPI consoler window, intended for mod developers.
*
* Not recommended for most players.
*/
"DeveloperMode": true,
"DeveloperMode": false,
/**
* Whether SMAPI should listen for console input. Disabling this will prevent you from using
@@ -57,8 +58,9 @@ in future SMAPI versions.
"ListenForConsoleInput": true,
/**
* Whether SMAPI should rewrite mods for compatibility. This may prevent older mods from
* loading, but bypasses a Visual Studio crash when debugging.
* Whether SMAPI should rewrite mods for compatibility.
*
* Disabling this may prevent older mods from loading.
*/
"RewriteMods": true,
@@ -121,42 +123,44 @@ in future SMAPI versions.
"LogTechnicalDetailsForBrokenMods": false,
/**
* The colors to use for text written to the SMAPI console.
* The color scheme to apply.
*
* The possible values for 'UseScheme' are:
* The possible values are:
* - AutoDetect: SMAPI will assume a light background on macOS, and detect the background color
* automatically on Linux or Windows.
* - LightBackground: use darker text colors that look better on a white or light background.
* - DarkBackground: use lighter text colors that look better on a black or dark background.
* - None: disables all colors, so everything is written in the default terminal color.
*/
"ConsoleColorScheme": "AutoDetect",
/**
* The colors to use for text written to the SMAPI console.
*
* For available color codes, see https://docs.microsoft.com/en-us/dotnet/api/system.consolecolor.
* Only built-scheme IDs can be set here. For available color codes, see
* https://docs.microsoft.com/en-us/dotnet/api/system.consolecolor.
*
* (These values are synched with ColorfulConsoleWriter.GetDefaultColorSchemeConfig in the
* SMAPI code.)
*/
"ConsoleColors": {
"UseScheme": "AutoDetect",
"Schemes": {
"DarkBackground": {
"Trace": "DarkGray",
"Debug": "DarkGray",
"Info": "White",
"Warn": "Yellow",
"Error": "Red",
"Alert": "Magenta",
"Success": "DarkGreen"
},
"LightBackground": {
"Trace": "DarkGray",
"Debug": "DarkGray",
"Info": "Black",
"Warn": "DarkYellow",
"Error": "Red",
"Alert": "DarkMagenta",
"Success": "DarkGreen"
}
"ConsoleColorSchemes": {
"DarkBackground": {
"Trace": "DarkGray",
"Debug": "DarkGray",
"Info": "White",
"Warn": "Yellow",
"Error": "Red",
"Alert": "Magenta",
"Success": "DarkGreen"
},
"LightBackground": {
"Trace": "DarkGray",
"Debug": "DarkGray",
"Info": "Black",
"Warn": "DarkYellow",
"Error": "Red",
"Alert": "DarkMagenta",
"Success": "DarkGreen"
}
},
+50 -1
View File
@@ -1,6 +1,55 @@
{
/*********
** Date formatting
*********/
// short date format for SDate
// tokens: {{day}} (like 15), {{season}} (like Spring), {{seasonLowercase}} (like spring), {{year}} (like 2)
"generic.date": "{{season}} {{day}}",
"generic.date-with-year": "{{season}} {{day}} im Jahr {{year}}"
"generic.date-with-year": "{{season}} {{day}} im Jahr {{year}}",
/*********
** Generic Mod Config Menu UI
*********/
// TODO
// 'SMAPI features' section
"config.section.features": "SMAPI features",
"config.check-for-updates.name": "Check for updates",
"config.check-for-updates.desc": "Whether to automatically check for newer versions of SMAPI and mods when you load the game. If new versions are available, an alert will be shown in the console. This doesn't affect the load time even if your connection is offline or slow, because it happens in the background.",
"config.check-content-integrity.name": "Check content integrity",
"config.check-content-integrity.desc": "Whether SMAPI should check whether the game's content files are present and unmodified. This should nearly always be enabled. If disabled, SMAPI will log a warning for visibility when someone helps you troubleshoot game issues.",
"config.read-console-input.name": "Read console input",
"config.read-console-input.desc": "Whether SMAPI should listen for console input. Disabling this will prevent you from using console commands. On some specific Linux systems, disabling this may reduce CPU usage.",
// 'Console window' section
"config.section.console-window": "Console window",
"config.developer-mode.name": "Developer mode",
"config.developer-mode.desc": "Whether to show much more info in the SMAPI consoler window, intended for mod developers. Not recommended for most players.",
"config.color-scheme.name": "Color scheme",
"config.color-scheme.desc": "The color scheme to apply to text in the SMAPI console window. This has no effect on the game, mods, or SMAPI log file.",
"config.color-scheme.options.AutoDetect": "Auto-detect",
"config.color-scheme.options.AutoDetect.on-windows": "Auto-detect (recommended)",
"config.color-scheme.options.DarkBackground": "Lighter text (for dark backgrounds)",
"config.color-scheme.options.LightBackground": "Darker text (for light backgrounds)",
"config.color-scheme.options.None": "No colors",
// 'Verbose logging' section
"config.section.verbose-logs": "Verbose logs",
"config.section.verbose-logs.explanation": "To help troubleshoot, SMAPI and many mods can send extra info to the SMAPI log (called 'verbose logging'). This may cause performance issues, and should usually be disabled.",
"config.enable-for.name": "Enable for",
"config.enable-for.desc": "Choose which mods have verbose logging enabled.",
"config.enable-for.options.all": "All (not recommended)",
"config.enable-for.options.selected": "Mods selected below",
"config.enable-for-smapi.name": "SMAPI",
"config.enable-for-smapi.desc": "Whether to enable verbose logs for SMAPI itself.",
"config.enable-for-mod.name": "{{modName}}",
"config.enable-for-mod.desc": "Whether to enable verbose logs for the '{{modName}}' mod."
}
+49 -1
View File
@@ -1,6 +1,54 @@
{
/*********
** Date formatting
*********/
// short date format for SDate
// tokens: {{day}} (like 15), {{season}} (like Spring), {{seasonLowercase}} (like spring), {{year}} (like 2)
"generic.date": "{{season}} {{day}}",
"generic.date-with-year": "{{season}} {{day}} in year {{year}}"
"generic.date-with-year": "{{season}} {{day}} in year {{year}}",
/*********
** Generic Mod Config Menu UI
*********/
// 'SMAPI features' section
"config.section.features": "SMAPI features",
"config.check-for-updates.name": "Check for updates",
"config.check-for-updates.desc": "Whether to automatically check for newer versions of SMAPI and mods when you load the game. If new versions are available, an alert will be shown in the console. This doesn't affect the load time even if your connection is offline or slow, because it happens in the background.",
"config.check-content-integrity.name": "Check content integrity",
"config.check-content-integrity.desc": "Whether SMAPI should check whether the game's content files are present and unmodified. This should nearly always be enabled. If disabled, SMAPI will log a warning for visibility when someone helps you troubleshoot game issues.",
"config.read-console-input.name": "Read console input",
"config.read-console-input.desc": "Whether SMAPI should listen for console input. Disabling this will prevent you from using console commands. On some specific Linux systems, disabling this may reduce CPU usage.",
// 'Console window' section
"config.section.console-window": "Console window",
"config.developer-mode.name": "Developer mode",
"config.developer-mode.desc": "Whether to show much more info in the SMAPI consoler window, intended for mod developers. Not recommended for most players.",
"config.color-scheme.name": "Color scheme",
"config.color-scheme.desc": "The color scheme to apply to text in the SMAPI console window. This has no effect on the game, mods, or SMAPI log file.",
"config.color-scheme.options.AutoDetect": "Auto-detect",
"config.color-scheme.options.AutoDetect.on-windows": "Auto-detect (recommended)",
"config.color-scheme.options.DarkBackground": "Lighter text (for dark backgrounds)",
"config.color-scheme.options.LightBackground": "Darker text (for light backgrounds)",
"config.color-scheme.options.None": "No colors",
// 'Verbose logging' section
"config.section.verbose-logs": "Verbose logs",
"config.section.verbose-logs.explanation": "To help troubleshoot, SMAPI and many mods can send extra info to the SMAPI log (called 'verbose logging'). This may cause performance issues, and should usually be disabled.",
"config.enable-for.name": "Enable for",
"config.enable-for.desc": "Choose which mods have verbose logging enabled.",
"config.enable-for.options.all": "All (not recommended)",
"config.enable-for.options.selected": "Mods selected below",
"config.enable-for-smapi.name": "SMAPI",
"config.enable-for-smapi.desc": "Whether to enable verbose logs for SMAPI itself.",
"config.enable-for-mod.name": "{{modName}}",
"config.enable-for-mod.desc": "Whether to enable verbose logs for the '{{modName}}' mod."
}
+50 -1
View File
@@ -1,6 +1,55 @@
{
/*********
** Date formatting
*********/
// short date format for SDate
// tokens: {{day}} (like 15), {{season}} (like Spring), {{seasonLowercase}} (like spring), {{year}} (like 2)
"generic.date": "{{seasonLowercase}} {{day}}",
"generic.date-with-year": "{{seasonLowercase}} {{day}} del año {{year}}"
"generic.date-with-year": "{{seasonLowercase}} {{day}} del año {{year}}",
/*********
** Generic Mod Config Menu UI
*********/
// TODO
// 'SMAPI features' section
"config.section.features": "SMAPI features",
"config.check-for-updates.name": "Check for updates",
"config.check-for-updates.desc": "Whether to automatically check for newer versions of SMAPI and mods when you load the game. If new versions are available, an alert will be shown in the console. This doesn't affect the load time even if your connection is offline or slow, because it happens in the background.",
"config.check-content-integrity.name": "Check content integrity",
"config.check-content-integrity.desc": "Whether SMAPI should check whether the game's content files are present and unmodified. This should nearly always be enabled. If disabled, SMAPI will log a warning for visibility when someone helps you troubleshoot game issues.",
"config.read-console-input.name": "Read console input",
"config.read-console-input.desc": "Whether SMAPI should listen for console input. Disabling this will prevent you from using console commands. On some specific Linux systems, disabling this may reduce CPU usage.",
// 'Console window' section
"config.section.console-window": "Console window",
"config.developer-mode.name": "Developer mode",
"config.developer-mode.desc": "Whether to show much more info in the SMAPI consoler window, intended for mod developers. Not recommended for most players.",
"config.color-scheme.name": "Color scheme",
"config.color-scheme.desc": "The color scheme to apply to text in the SMAPI console window. This has no effect on the game, mods, or SMAPI log file.",
"config.color-scheme.options.AutoDetect": "Auto-detect",
"config.color-scheme.options.AutoDetect.on-windows": "Auto-detect (recommended)",
"config.color-scheme.options.DarkBackground": "Lighter text (for dark backgrounds)",
"config.color-scheme.options.LightBackground": "Darker text (for light backgrounds)",
"config.color-scheme.options.None": "No colors",
// 'Verbose logging' section
"config.section.verbose-logs": "Verbose logs",
"config.section.verbose-logs.explanation": "To help troubleshoot, SMAPI and many mods can send extra info to the SMAPI log (called 'verbose logging'). This may cause performance issues, and should usually be disabled.",
"config.enable-for.name": "Enable for",
"config.enable-for.desc": "Choose which mods have verbose logging enabled.",
"config.enable-for.options.all": "All (not recommended)",
"config.enable-for.options.selected": "Mods selected below",
"config.enable-for-smapi.name": "SMAPI",
"config.enable-for-smapi.desc": "Whether to enable verbose logs for SMAPI itself.",
"config.enable-for-mod.name": "{{modName}}",
"config.enable-for-mod.desc": "Whether to enable verbose logs for the '{{modName}}' mod."
}
+50 -1
View File
@@ -1,6 +1,55 @@
{
/*********
** Date formatting
*********/
// short date format for SDate
// tokens: {{day}} (like 15), {{season}} (like Spring), {{seasonLowercase}} (like spring), {{year}} (like 2)
"generic.date": "{{day}} {{seasonLowercase}}",
"generic.date-with-year": "{{day}} {{seasonLowercase}} de l'année {{year}}"
"generic.date-with-year": "{{day}} {{seasonLowercase}} de l'année {{year}}",
/*********
** Generic Mod Config Menu UI
*********/
// TODO
// 'SMAPI features' section
"config.section.features": "SMAPI features",
"config.check-for-updates.name": "Check for updates",
"config.check-for-updates.desc": "Whether to automatically check for newer versions of SMAPI and mods when you load the game. If new versions are available, an alert will be shown in the console. This doesn't affect the load time even if your connection is offline or slow, because it happens in the background.",
"config.check-content-integrity.name": "Check content integrity",
"config.check-content-integrity.desc": "Whether SMAPI should check whether the game's content files are present and unmodified. This should nearly always be enabled. If disabled, SMAPI will log a warning for visibility when someone helps you troubleshoot game issues.",
"config.read-console-input.name": "Read console input",
"config.read-console-input.desc": "Whether SMAPI should listen for console input. Disabling this will prevent you from using console commands. On some specific Linux systems, disabling this may reduce CPU usage.",
// 'Console window' section
"config.section.console-window": "Console window",
"config.developer-mode.name": "Developer mode",
"config.developer-mode.desc": "Whether to show much more info in the SMAPI consoler window, intended for mod developers. Not recommended for most players.",
"config.color-scheme.name": "Color scheme",
"config.color-scheme.desc": "The color scheme to apply to text in the SMAPI console window. This has no effect on the game, mods, or SMAPI log file.",
"config.color-scheme.options.AutoDetect": "Auto-detect",
"config.color-scheme.options.AutoDetect.on-windows": "Auto-detect (recommended)",
"config.color-scheme.options.DarkBackground": "Lighter text (for dark backgrounds)",
"config.color-scheme.options.LightBackground": "Darker text (for light backgrounds)",
"config.color-scheme.options.None": "No colors",
// 'Verbose logging' section
"config.section.verbose-logs": "Verbose logs",
"config.section.verbose-logs.explanation": "To help troubleshoot, SMAPI and many mods can send extra info to the SMAPI log (called 'verbose logging'). This may cause performance issues, and should usually be disabled.",
"config.enable-for.name": "Enable for",
"config.enable-for.desc": "Choose which mods have verbose logging enabled.",
"config.enable-for.options.all": "All (not recommended)",
"config.enable-for.options.selected": "Mods selected below",
"config.enable-for-smapi.name": "SMAPI",
"config.enable-for-smapi.desc": "Whether to enable verbose logs for SMAPI itself.",
"config.enable-for-mod.name": "{{modName}}",
"config.enable-for-mod.desc": "Whether to enable verbose logs for the '{{modName}}' mod."
}
+50 -1
View File
@@ -1,6 +1,55 @@
{
/*********
** Date formatting
*********/
// short date format for SDate
// tokens: {{day}} (like 15), {{season}} (like Spring), {{seasonLowercase}} (like spring), {{year}} (like 2)
"generic.date": "{{season}} {{day}}",
"generic.date-with-year": "{{year}}. év {{season}} {{day}}"
"generic.date-with-year": "{{year}}. év {{season}} {{day}}",
/*********
** Generic Mod Config Menu UI
*********/
// TODO
// 'SMAPI features' section
"config.section.features": "SMAPI features",
"config.check-for-updates.name": "Check for updates",
"config.check-for-updates.desc": "Whether to automatically check for newer versions of SMAPI and mods when you load the game. If new versions are available, an alert will be shown in the console. This doesn't affect the load time even if your connection is offline or slow, because it happens in the background.",
"config.check-content-integrity.name": "Check content integrity",
"config.check-content-integrity.desc": "Whether SMAPI should check whether the game's content files are present and unmodified. This should nearly always be enabled. If disabled, SMAPI will log a warning for visibility when someone helps you troubleshoot game issues.",
"config.read-console-input.name": "Read console input",
"config.read-console-input.desc": "Whether SMAPI should listen for console input. Disabling this will prevent you from using console commands. On some specific Linux systems, disabling this may reduce CPU usage.",
// 'Console window' section
"config.section.console-window": "Console window",
"config.developer-mode.name": "Developer mode",
"config.developer-mode.desc": "Whether to show much more info in the SMAPI consoler window, intended for mod developers. Not recommended for most players.",
"config.color-scheme.name": "Color scheme",
"config.color-scheme.desc": "The color scheme to apply to text in the SMAPI console window. This has no effect on the game, mods, or SMAPI log file.",
"config.color-scheme.options.AutoDetect": "Auto-detect",
"config.color-scheme.options.AutoDetect.on-windows": "Auto-detect (recommended)",
"config.color-scheme.options.DarkBackground": "Lighter text (for dark backgrounds)",
"config.color-scheme.options.LightBackground": "Darker text (for light backgrounds)",
"config.color-scheme.options.None": "No colors",
// 'Verbose logging' section
"config.section.verbose-logs": "Verbose logs",
"config.section.verbose-logs.explanation": "To help troubleshoot, SMAPI and many mods can send extra info to the SMAPI log (called 'verbose logging'). This may cause performance issues, and should usually be disabled.",
"config.enable-for.name": "Enable for",
"config.enable-for.desc": "Choose which mods have verbose logging enabled.",
"config.enable-for.options.all": "All (not recommended)",
"config.enable-for.options.selected": "Mods selected below",
"config.enable-for-smapi.name": "SMAPI",
"config.enable-for-smapi.desc": "Whether to enable verbose logs for SMAPI itself.",
"config.enable-for-mod.name": "{{modName}}",
"config.enable-for-mod.desc": "Whether to enable verbose logs for the '{{modName}}' mod."
}
+50 -1
View File
@@ -1,6 +1,55 @@
{
/*********
** Date formatting
*********/
// short date format for SDate
// tokens: {{day}} (like 15), {{season}} (like Spring), {{seasonLowercase}} (like spring), {{year}} (like 2)
"generic.date": "Tanggal {{day}} di {{season}}",
"generic.date-with-year": "Tanggal {{day}} di {{season}} tahun ke-{{year}}"
"generic.date-with-year": "Tanggal {{day}} di {{season}} tahun ke-{{year}}",
/*********
** Generic Mod Config Menu UI
*********/
// TODO
// 'SMAPI features' section
"config.section.features": "SMAPI features",
"config.check-for-updates.name": "Check for updates",
"config.check-for-updates.desc": "Whether to automatically check for newer versions of SMAPI and mods when you load the game. If new versions are available, an alert will be shown in the console. This doesn't affect the load time even if your connection is offline or slow, because it happens in the background.",
"config.check-content-integrity.name": "Check content integrity",
"config.check-content-integrity.desc": "Whether SMAPI should check whether the game's content files are present and unmodified. This should nearly always be enabled. If disabled, SMAPI will log a warning for visibility when someone helps you troubleshoot game issues.",
"config.read-console-input.name": "Read console input",
"config.read-console-input.desc": "Whether SMAPI should listen for console input. Disabling this will prevent you from using console commands. On some specific Linux systems, disabling this may reduce CPU usage.",
// 'Console window' section
"config.section.console-window": "Console window",
"config.developer-mode.name": "Developer mode",
"config.developer-mode.desc": "Whether to show much more info in the SMAPI consoler window, intended for mod developers. Not recommended for most players.",
"config.color-scheme.name": "Color scheme",
"config.color-scheme.desc": "The color scheme to apply to text in the SMAPI console window. This has no effect on the game, mods, or SMAPI log file.",
"config.color-scheme.options.AutoDetect": "Auto-detect",
"config.color-scheme.options.AutoDetect.on-windows": "Auto-detect (recommended)",
"config.color-scheme.options.DarkBackground": "Lighter text (for dark backgrounds)",
"config.color-scheme.options.LightBackground": "Darker text (for light backgrounds)",
"config.color-scheme.options.None": "No colors",
// 'Verbose logging' section
"config.section.verbose-logs": "Verbose logs",
"config.section.verbose-logs.explanation": "To help troubleshoot, SMAPI and many mods can send extra info to the SMAPI log (called 'verbose logging'). This may cause performance issues, and should usually be disabled.",
"config.enable-for.name": "Enable for",
"config.enable-for.desc": "Choose which mods have verbose logging enabled.",
"config.enable-for.options.all": "All (not recommended)",
"config.enable-for.options.selected": "Mods selected below",
"config.enable-for-smapi.name": "SMAPI",
"config.enable-for-smapi.desc": "Whether to enable verbose logs for SMAPI itself.",
"config.enable-for-mod.name": "{{modName}}",
"config.enable-for-mod.desc": "Whether to enable verbose logs for the '{{modName}}' mod."
}
+50 -1
View File
@@ -1,6 +1,55 @@
{
/*********
** Date formatting
*********/
// short date format for SDate
// tokens: {{day}} (like 15), {{season}} (like Spring), {{seasonLowercase}} (like spring), {{year}} (like 2)
"generic.date": "{{day}} {{season}}",
"generic.date-with-year": "{{day}} {{season}} dell'anno {{year}}"
"generic.date-with-year": "{{day}} {{season}} dell'anno {{year}}",
/*********
** Generic Mod Config Menu UI
*********/
// TODO
// 'SMAPI features' section
"config.section.features": "SMAPI features",
"config.check-for-updates.name": "Check for updates",
"config.check-for-updates.desc": "Whether to automatically check for newer versions of SMAPI and mods when you load the game. If new versions are available, an alert will be shown in the console. This doesn't affect the load time even if your connection is offline or slow, because it happens in the background.",
"config.check-content-integrity.name": "Check content integrity",
"config.check-content-integrity.desc": "Whether SMAPI should check whether the game's content files are present and unmodified. This should nearly always be enabled. If disabled, SMAPI will log a warning for visibility when someone helps you troubleshoot game issues.",
"config.read-console-input.name": "Read console input",
"config.read-console-input.desc": "Whether SMAPI should listen for console input. Disabling this will prevent you from using console commands. On some specific Linux systems, disabling this may reduce CPU usage.",
// 'Console window' section
"config.section.console-window": "Console window",
"config.developer-mode.name": "Developer mode",
"config.developer-mode.desc": "Whether to show much more info in the SMAPI consoler window, intended for mod developers. Not recommended for most players.",
"config.color-scheme.name": "Color scheme",
"config.color-scheme.desc": "The color scheme to apply to text in the SMAPI console window. This has no effect on the game, mods, or SMAPI log file.",
"config.color-scheme.options.AutoDetect": "Auto-detect",
"config.color-scheme.options.AutoDetect.on-windows": "Auto-detect (recommended)",
"config.color-scheme.options.DarkBackground": "Lighter text (for dark backgrounds)",
"config.color-scheme.options.LightBackground": "Darker text (for light backgrounds)",
"config.color-scheme.options.None": "No colors",
// 'Verbose logging' section
"config.section.verbose-logs": "Verbose logs",
"config.section.verbose-logs.explanation": "To help troubleshoot, SMAPI and many mods can send extra info to the SMAPI log (called 'verbose logging'). This may cause performance issues, and should usually be disabled.",
"config.enable-for.name": "Enable for",
"config.enable-for.desc": "Choose which mods have verbose logging enabled.",
"config.enable-for.options.all": "All (not recommended)",
"config.enable-for.options.selected": "Mods selected below",
"config.enable-for-smapi.name": "SMAPI",
"config.enable-for-smapi.desc": "Whether to enable verbose logs for SMAPI itself.",
"config.enable-for-mod.name": "{{modName}}",
"config.enable-for-mod.desc": "Whether to enable verbose logs for the '{{modName}}' mod."
}
+50 -1
View File
@@ -1,6 +1,55 @@
{
/*********
** Date formatting
*********/
// short date format for SDate
// tokens: {{day}} (like 15), {{season}} (like Spring), {{seasonLowercase}} (like spring), {{year}} (like 2)
"generic.date": "{{season}} {{day}}日",
"generic.date-with-year": "{{year}}年目 {{season}} {{day}}日"
"generic.date-with-year": "{{year}}年目 {{season}} {{day}}日",
/*********
** Generic Mod Config Menu UI
*********/
// TODO
// 'SMAPI features' section
"config.section.features": "SMAPI features",
"config.check-for-updates.name": "Check for updates",
"config.check-for-updates.desc": "Whether to automatically check for newer versions of SMAPI and mods when you load the game. If new versions are available, an alert will be shown in the console. This doesn't affect the load time even if your connection is offline or slow, because it happens in the background.",
"config.check-content-integrity.name": "Check content integrity",
"config.check-content-integrity.desc": "Whether SMAPI should check whether the game's content files are present and unmodified. This should nearly always be enabled. If disabled, SMAPI will log a warning for visibility when someone helps you troubleshoot game issues.",
"config.read-console-input.name": "Read console input",
"config.read-console-input.desc": "Whether SMAPI should listen for console input. Disabling this will prevent you from using console commands. On some specific Linux systems, disabling this may reduce CPU usage.",
// 'Console window' section
"config.section.console-window": "Console window",
"config.developer-mode.name": "Developer mode",
"config.developer-mode.desc": "Whether to show much more info in the SMAPI consoler window, intended for mod developers. Not recommended for most players.",
"config.color-scheme.name": "Color scheme",
"config.color-scheme.desc": "The color scheme to apply to text in the SMAPI console window. This has no effect on the game, mods, or SMAPI log file.",
"config.color-scheme.options.AutoDetect": "Auto-detect",
"config.color-scheme.options.AutoDetect.on-windows": "Auto-detect (recommended)",
"config.color-scheme.options.DarkBackground": "Lighter text (for dark backgrounds)",
"config.color-scheme.options.LightBackground": "Darker text (for light backgrounds)",
"config.color-scheme.options.None": "No colors",
// 'Verbose logging' section
"config.section.verbose-logs": "Verbose logs",
"config.section.verbose-logs.explanation": "To help troubleshoot, SMAPI and many mods can send extra info to the SMAPI log (called 'verbose logging'). This may cause performance issues, and should usually be disabled.",
"config.enable-for.name": "Enable for",
"config.enable-for.desc": "Choose which mods have verbose logging enabled.",
"config.enable-for.options.all": "All (not recommended)",
"config.enable-for.options.selected": "Mods selected below",
"config.enable-for-smapi.name": "SMAPI",
"config.enable-for-smapi.desc": "Whether to enable verbose logs for SMAPI itself.",
"config.enable-for-mod.name": "{{modName}}",
"config.enable-for-mod.desc": "Whether to enable verbose logs for the '{{modName}}' mod."
}
+50 -1
View File
@@ -1,6 +1,55 @@
{
/*********
** Date formatting
*********/
// short date format for SDate
// tokens: {{day}} (like 15), {{season}} (like Spring), {{seasonLowercase}} (like spring), {{year}} (like 2)
"generic.date": "{{season}} {{day}}일",
"generic.date-with-year": "{{year}}년차 {{season}} {{day}}일"
"generic.date-with-year": "{{year}}년차 {{season}} {{day}}일",
/*********
** Generic Mod Config Menu UI
*********/
// TODO
// 'SMAPI features' section
"config.section.features": "SMAPI features",
"config.check-for-updates.name": "Check for updates",
"config.check-for-updates.desc": "Whether to automatically check for newer versions of SMAPI and mods when you load the game. If new versions are available, an alert will be shown in the console. This doesn't affect the load time even if your connection is offline or slow, because it happens in the background.",
"config.check-content-integrity.name": "Check content integrity",
"config.check-content-integrity.desc": "Whether SMAPI should check whether the game's content files are present and unmodified. This should nearly always be enabled. If disabled, SMAPI will log a warning for visibility when someone helps you troubleshoot game issues.",
"config.read-console-input.name": "Read console input",
"config.read-console-input.desc": "Whether SMAPI should listen for console input. Disabling this will prevent you from using console commands. On some specific Linux systems, disabling this may reduce CPU usage.",
// 'Console window' section
"config.section.console-window": "Console window",
"config.developer-mode.name": "Developer mode",
"config.developer-mode.desc": "Whether to show much more info in the SMAPI consoler window, intended for mod developers. Not recommended for most players.",
"config.color-scheme.name": "Color scheme",
"config.color-scheme.desc": "The color scheme to apply to text in the SMAPI console window. This has no effect on the game, mods, or SMAPI log file.",
"config.color-scheme.options.AutoDetect": "Auto-detect",
"config.color-scheme.options.AutoDetect.on-windows": "Auto-detect (recommended)",
"config.color-scheme.options.DarkBackground": "Lighter text (for dark backgrounds)",
"config.color-scheme.options.LightBackground": "Darker text (for light backgrounds)",
"config.color-scheme.options.None": "No colors",
// 'Verbose logging' section
"config.section.verbose-logs": "Verbose logs",
"config.section.verbose-logs.explanation": "To help troubleshoot, SMAPI and many mods can send extra info to the SMAPI log (called 'verbose logging'). This may cause performance issues, and should usually be disabled.",
"config.enable-for.name": "Enable for",
"config.enable-for.desc": "Choose which mods have verbose logging enabled.",
"config.enable-for.options.all": "All (not recommended)",
"config.enable-for.options.selected": "Mods selected below",
"config.enable-for-smapi.name": "SMAPI",
"config.enable-for-smapi.desc": "Whether to enable verbose logs for SMAPI itself.",
"config.enable-for-mod.name": "{{modName}}",
"config.enable-for-mod.desc": "Whether to enable verbose logs for the '{{modName}}' mod."
}
+50 -1
View File
@@ -1,6 +1,55 @@
{
/*********
** Date formatting
*********/
// short date format for SDate
// tokens: {{day}} (like 15), {{season}} (like Spring), {{seasonLowercase}} (like spring), {{year}} (like 2)
"generic.date": "{{day}} {{seasonLowercase}}",
"generic.date-with-year": "{{day}} {{seasonLowercase}} w roku {{year}}"
"generic.date-with-year": "{{day}} {{seasonLowercase}} w roku {{year}}",
/*********
** Generic Mod Config Menu UI
*********/
// TODO
// 'SMAPI features' section
"config.section.features": "SMAPI features",
"config.check-for-updates.name": "Check for updates",
"config.check-for-updates.desc": "Whether to automatically check for newer versions of SMAPI and mods when you load the game. If new versions are available, an alert will be shown in the console. This doesn't affect the load time even if your connection is offline or slow, because it happens in the background.",
"config.check-content-integrity.name": "Check content integrity",
"config.check-content-integrity.desc": "Whether SMAPI should check whether the game's content files are present and unmodified. This should nearly always be enabled. If disabled, SMAPI will log a warning for visibility when someone helps you troubleshoot game issues.",
"config.read-console-input.name": "Read console input",
"config.read-console-input.desc": "Whether SMAPI should listen for console input. Disabling this will prevent you from using console commands. On some specific Linux systems, disabling this may reduce CPU usage.",
// 'Console window' section
"config.section.console-window": "Console window",
"config.developer-mode.name": "Developer mode",
"config.developer-mode.desc": "Whether to show much more info in the SMAPI consoler window, intended for mod developers. Not recommended for most players.",
"config.color-scheme.name": "Color scheme",
"config.color-scheme.desc": "The color scheme to apply to text in the SMAPI console window. This has no effect on the game, mods, or SMAPI log file.",
"config.color-scheme.options.AutoDetect": "Auto-detect",
"config.color-scheme.options.AutoDetect.on-windows": "Auto-detect (recommended)",
"config.color-scheme.options.DarkBackground": "Lighter text (for dark backgrounds)",
"config.color-scheme.options.LightBackground": "Darker text (for light backgrounds)",
"config.color-scheme.options.None": "No colors",
// 'Verbose logging' section
"config.section.verbose-logs": "Verbose logs",
"config.section.verbose-logs.explanation": "To help troubleshoot, SMAPI and many mods can send extra info to the SMAPI log (called 'verbose logging'). This may cause performance issues, and should usually be disabled.",
"config.enable-for.name": "Enable for",
"config.enable-for.desc": "Choose which mods have verbose logging enabled.",
"config.enable-for.options.all": "All (not recommended)",
"config.enable-for.options.selected": "Mods selected below",
"config.enable-for-smapi.name": "SMAPI",
"config.enable-for-smapi.desc": "Whether to enable verbose logs for SMAPI itself.",
"config.enable-for-mod.name": "{{modName}}",
"config.enable-for-mod.desc": "Whether to enable verbose logs for the '{{modName}}' mod."
}
+50 -1
View File
@@ -1,6 +1,55 @@
{
/*********
** Date formatting
*********/
// short date format for SDate
// tokens: {{day}} (like 15), {{season}} (like Spring), {{seasonLowercase}} (like spring), {{year}} (like 2)
"generic.date": "{{season}} {{day}}",
"generic.date-with-year": "{{season}} {{day}} no ano {{year}}"
"generic.date-with-year": "{{season}} {{day}} no ano {{year}}",
/*********
** Generic Mod Config Menu UI
*********/
// TODO
// 'SMAPI features' section
"config.section.features": "SMAPI features",
"config.check-for-updates.name": "Check for updates",
"config.check-for-updates.desc": "Whether to automatically check for newer versions of SMAPI and mods when you load the game. If new versions are available, an alert will be shown in the console. This doesn't affect the load time even if your connection is offline or slow, because it happens in the background.",
"config.check-content-integrity.name": "Check content integrity",
"config.check-content-integrity.desc": "Whether SMAPI should check whether the game's content files are present and unmodified. This should nearly always be enabled. If disabled, SMAPI will log a warning for visibility when someone helps you troubleshoot game issues.",
"config.read-console-input.name": "Read console input",
"config.read-console-input.desc": "Whether SMAPI should listen for console input. Disabling this will prevent you from using console commands. On some specific Linux systems, disabling this may reduce CPU usage.",
// 'Console window' section
"config.section.console-window": "Console window",
"config.developer-mode.name": "Developer mode",
"config.developer-mode.desc": "Whether to show much more info in the SMAPI consoler window, intended for mod developers. Not recommended for most players.",
"config.color-scheme.name": "Color scheme",
"config.color-scheme.desc": "The color scheme to apply to text in the SMAPI console window. This has no effect on the game, mods, or SMAPI log file.",
"config.color-scheme.options.AutoDetect": "Auto-detect",
"config.color-scheme.options.AutoDetect.on-windows": "Auto-detect (recommended)",
"config.color-scheme.options.DarkBackground": "Lighter text (for dark backgrounds)",
"config.color-scheme.options.LightBackground": "Darker text (for light backgrounds)",
"config.color-scheme.options.None": "No colors",
// 'Verbose logging' section
"config.section.verbose-logs": "Verbose logs",
"config.section.verbose-logs.explanation": "To help troubleshoot, SMAPI and many mods can send extra info to the SMAPI log (called 'verbose logging'). This may cause performance issues, and should usually be disabled.",
"config.enable-for.name": "Enable for",
"config.enable-for.desc": "Choose which mods have verbose logging enabled.",
"config.enable-for.options.all": "All (not recommended)",
"config.enable-for.options.selected": "Mods selected below",
"config.enable-for-smapi.name": "SMAPI",
"config.enable-for-smapi.desc": "Whether to enable verbose logs for SMAPI itself.",
"config.enable-for-mod.name": "{{modName}}",
"config.enable-for-mod.desc": "Whether to enable verbose logs for the '{{modName}}' mod."
}
+50 -1
View File
@@ -1,6 +1,55 @@
{
/*********
** Date formatting
*********/
// short date format for SDate
// tokens: {{day}} (like 15), {{season}} (like Spring), {{seasonLowercase}} (like spring), {{year}} (like 2)
"generic.date": "{{season}}, {{day}}-е число",
"generic.date-with-year": "{{season}}, {{day}}-е число, {{year}}-й год"
"generic.date-with-year": "{{season}}, {{day}}-е число, {{year}}-й год",
/*********
** Generic Mod Config Menu UI
*********/
// TODO
// 'SMAPI features' section
"config.section.features": "SMAPI features",
"config.check-for-updates.name": "Check for updates",
"config.check-for-updates.desc": "Whether to automatically check for newer versions of SMAPI and mods when you load the game. If new versions are available, an alert will be shown in the console. This doesn't affect the load time even if your connection is offline or slow, because it happens in the background.",
"config.check-content-integrity.name": "Check content integrity",
"config.check-content-integrity.desc": "Whether SMAPI should check whether the game's content files are present and unmodified. This should nearly always be enabled. If disabled, SMAPI will log a warning for visibility when someone helps you troubleshoot game issues.",
"config.read-console-input.name": "Read console input",
"config.read-console-input.desc": "Whether SMAPI should listen for console input. Disabling this will prevent you from using console commands. On some specific Linux systems, disabling this may reduce CPU usage.",
// 'Console window' section
"config.section.console-window": "Console window",
"config.developer-mode.name": "Developer mode",
"config.developer-mode.desc": "Whether to show much more info in the SMAPI consoler window, intended for mod developers. Not recommended for most players.",
"config.color-scheme.name": "Color scheme",
"config.color-scheme.desc": "The color scheme to apply to text in the SMAPI console window. This has no effect on the game, mods, or SMAPI log file.",
"config.color-scheme.options.AutoDetect": "Auto-detect",
"config.color-scheme.options.AutoDetect.on-windows": "Auto-detect (recommended)",
"config.color-scheme.options.DarkBackground": "Lighter text (for dark backgrounds)",
"config.color-scheme.options.LightBackground": "Darker text (for light backgrounds)",
"config.color-scheme.options.None": "No colors",
// 'Verbose logging' section
"config.section.verbose-logs": "Verbose logs",
"config.section.verbose-logs.explanation": "To help troubleshoot, SMAPI and many mods can send extra info to the SMAPI log (called 'verbose logging'). This may cause performance issues, and should usually be disabled.",
"config.enable-for.name": "Enable for",
"config.enable-for.desc": "Choose which mods have verbose logging enabled.",
"config.enable-for.options.all": "All (not recommended)",
"config.enable-for.options.selected": "Mods selected below",
"config.enable-for-smapi.name": "SMAPI",
"config.enable-for-smapi.desc": "Whether to enable verbose logs for SMAPI itself.",
"config.enable-for-mod.name": "{{modName}}",
"config.enable-for-mod.desc": "Whether to enable verbose logs for the '{{modName}}' mod."
}
+50 -1
View File
@@ -1,6 +1,55 @@
{
/*********
** Date formatting
*********/
// short date format for SDate
// tokens: {{day}} (like 15), {{season}} (like Spring), {{seasonLowercase}} (like spring), {{year}} (like 2)
"generic.date": "วันที่ {{day}} {{season}}",
"generic.date-with-year": "วันที่ {{day}} {{season}} ปีที่ {{year}}"
"generic.date-with-year": "วันที่ {{day}} {{season}} ปีที่ {{year}}",
/*********
** Generic Mod Config Menu UI
*********/
// TODO
// 'SMAPI features' section
"config.section.features": "SMAPI features",
"config.check-for-updates.name": "Check for updates",
"config.check-for-updates.desc": "Whether to automatically check for newer versions of SMAPI and mods when you load the game. If new versions are available, an alert will be shown in the console. This doesn't affect the load time even if your connection is offline or slow, because it happens in the background.",
"config.check-content-integrity.name": "Check content integrity",
"config.check-content-integrity.desc": "Whether SMAPI should check whether the game's content files are present and unmodified. This should nearly always be enabled. If disabled, SMAPI will log a warning for visibility when someone helps you troubleshoot game issues.",
"config.read-console-input.name": "Read console input",
"config.read-console-input.desc": "Whether SMAPI should listen for console input. Disabling this will prevent you from using console commands. On some specific Linux systems, disabling this may reduce CPU usage.",
// 'Console window' section
"config.section.console-window": "Console window",
"config.developer-mode.name": "Developer mode",
"config.developer-mode.desc": "Whether to show much more info in the SMAPI consoler window, intended for mod developers. Not recommended for most players.",
"config.color-scheme.name": "Color scheme",
"config.color-scheme.desc": "The color scheme to apply to text in the SMAPI console window. This has no effect on the game, mods, or SMAPI log file.",
"config.color-scheme.options.AutoDetect": "Auto-detect",
"config.color-scheme.options.AutoDetect.on-windows": "Auto-detect (recommended)",
"config.color-scheme.options.DarkBackground": "Lighter text (for dark backgrounds)",
"config.color-scheme.options.LightBackground": "Darker text (for light backgrounds)",
"config.color-scheme.options.None": "No colors",
// 'Verbose logging' section
"config.section.verbose-logs": "Verbose logs",
"config.section.verbose-logs.explanation": "To help troubleshoot, SMAPI and many mods can send extra info to the SMAPI log (called 'verbose logging'). This may cause performance issues, and should usually be disabled.",
"config.enable-for.name": "Enable for",
"config.enable-for.desc": "Choose which mods have verbose logging enabled.",
"config.enable-for.options.all": "All (not recommended)",
"config.enable-for.options.selected": "Mods selected below",
"config.enable-for-smapi.name": "SMAPI",
"config.enable-for-smapi.desc": "Whether to enable verbose logs for SMAPI itself.",
"config.enable-for-mod.name": "{{modName}}",
"config.enable-for-mod.desc": "Whether to enable verbose logs for the '{{modName}}' mod."
}
+50 -1
View File
@@ -1,6 +1,55 @@
{
/*********
** Date formatting
*********/
// short date format for SDate
// tokens: {{day}} (like 15), {{season}} (like Spring), {{seasonLowercase}} (like spring), {{year}} (like 2)
"generic.date": "{{day}} {{season}}",
"generic.date-with-year": "{{day}} {{season}} Yıl {{year}}"
"generic.date-with-year": "{{day}} {{season}} Yıl {{year}}",
/*********
** Generic Mod Config Menu UI
*********/
// TODO
// 'SMAPI features' section
"config.section.features": "SMAPI features",
"config.check-for-updates.name": "Check for updates",
"config.check-for-updates.desc": "Whether to automatically check for newer versions of SMAPI and mods when you load the game. If new versions are available, an alert will be shown in the console. This doesn't affect the load time even if your connection is offline or slow, because it happens in the background.",
"config.check-content-integrity.name": "Check content integrity",
"config.check-content-integrity.desc": "Whether SMAPI should check whether the game's content files are present and unmodified. This should nearly always be enabled. If disabled, SMAPI will log a warning for visibility when someone helps you troubleshoot game issues.",
"config.read-console-input.name": "Read console input",
"config.read-console-input.desc": "Whether SMAPI should listen for console input. Disabling this will prevent you from using console commands. On some specific Linux systems, disabling this may reduce CPU usage.",
// 'Console window' section
"config.section.console-window": "Console window",
"config.developer-mode.name": "Developer mode",
"config.developer-mode.desc": "Whether to show much more info in the SMAPI consoler window, intended for mod developers. Not recommended for most players.",
"config.color-scheme.name": "Color scheme",
"config.color-scheme.desc": "The color scheme to apply to text in the SMAPI console window. This has no effect on the game, mods, or SMAPI log file.",
"config.color-scheme.options.AutoDetect": "Auto-detect",
"config.color-scheme.options.AutoDetect.on-windows": "Auto-detect (recommended)",
"config.color-scheme.options.DarkBackground": "Lighter text (for dark backgrounds)",
"config.color-scheme.options.LightBackground": "Darker text (for light backgrounds)",
"config.color-scheme.options.None": "No colors",
// 'Verbose logging' section
"config.section.verbose-logs": "Verbose logs",
"config.section.verbose-logs.explanation": "To help troubleshoot, SMAPI and many mods can send extra info to the SMAPI log (called 'verbose logging'). This may cause performance issues, and should usually be disabled.",
"config.enable-for.name": "Enable for",
"config.enable-for.desc": "Choose which mods have verbose logging enabled.",
"config.enable-for.options.all": "All (not recommended)",
"config.enable-for.options.selected": "Mods selected below",
"config.enable-for-smapi.name": "SMAPI",
"config.enable-for-smapi.desc": "Whether to enable verbose logs for SMAPI itself.",
"config.enable-for-mod.name": "{{modName}}",
"config.enable-for-mod.desc": "Whether to enable verbose logs for the '{{modName}}' mod."
}
+50 -1
View File
@@ -1,6 +1,55 @@
{
/*********
** Date formatting
*********/
// short date format for SDate
// tokens: {{day}} (like 15), {{season}} (like Spring), {{seasonLowercase}} (like spring), {{year}} (like 2)
"generic.date": "{{season}}, {{day}}-й день",
"generic.date-with-year": "{{season}}, {{day}}-й день, {{year}} рік"
"generic.date-with-year": "{{season}}, {{day}}-й день, {{year}} рік",
/*********
** Generic Mod Config Menu UI
*********/
// TODO
// 'SMAPI features' section
"config.section.features": "SMAPI features",
"config.check-for-updates.name": "Check for updates",
"config.check-for-updates.desc": "Whether to automatically check for newer versions of SMAPI and mods when you load the game. If new versions are available, an alert will be shown in the console. This doesn't affect the load time even if your connection is offline or slow, because it happens in the background.",
"config.check-content-integrity.name": "Check content integrity",
"config.check-content-integrity.desc": "Whether SMAPI should check whether the game's content files are present and unmodified. This should nearly always be enabled. If disabled, SMAPI will log a warning for visibility when someone helps you troubleshoot game issues.",
"config.read-console-input.name": "Read console input",
"config.read-console-input.desc": "Whether SMAPI should listen for console input. Disabling this will prevent you from using console commands. On some specific Linux systems, disabling this may reduce CPU usage.",
// 'Console window' section
"config.section.console-window": "Console window",
"config.developer-mode.name": "Developer mode",
"config.developer-mode.desc": "Whether to show much more info in the SMAPI consoler window, intended for mod developers. Not recommended for most players.",
"config.color-scheme.name": "Color scheme",
"config.color-scheme.desc": "The color scheme to apply to text in the SMAPI console window. This has no effect on the game, mods, or SMAPI log file.",
"config.color-scheme.options.AutoDetect": "Auto-detect",
"config.color-scheme.options.AutoDetect.on-windows": "Auto-detect (recommended)",
"config.color-scheme.options.DarkBackground": "Lighter text (for dark backgrounds)",
"config.color-scheme.options.LightBackground": "Darker text (for light backgrounds)",
"config.color-scheme.options.None": "No colors",
// 'Verbose logging' section
"config.section.verbose-logs": "Verbose logs",
"config.section.verbose-logs.explanation": "To help troubleshoot, SMAPI and many mods can send extra info to the SMAPI log (called 'verbose logging'). This may cause performance issues, and should usually be disabled.",
"config.enable-for.name": "Enable for",
"config.enable-for.desc": "Choose which mods have verbose logging enabled.",
"config.enable-for.options.all": "All (not recommended)",
"config.enable-for.options.selected": "Mods selected below",
"config.enable-for-smapi.name": "SMAPI",
"config.enable-for-smapi.desc": "Whether to enable verbose logs for SMAPI itself.",
"config.enable-for-mod.name": "{{modName}}",
"config.enable-for-mod.desc": "Whether to enable verbose logs for the '{{modName}}' mod."
}
+50 -1
View File
@@ -1,6 +1,55 @@
{
/*********
** Date formatting
*********/
// short date format for SDate
// tokens: {{day}} (like 15), {{season}} (like Spring), {{seasonLowercase}} (like spring), {{year}} (like 2)
"generic.date": "{{season}}{{day}}日",
"generic.date-with-year": "第{{year}}年{{season}}{{day}}日"
"generic.date-with-year": "第{{year}}年{{season}}{{day}}日",
/*********
** Generic Mod Config Menu UI
*********/
// TODO
// 'SMAPI features' section
"config.section.features": "SMAPI features",
"config.check-for-updates.name": "Check for updates",
"config.check-for-updates.desc": "Whether to automatically check for newer versions of SMAPI and mods when you load the game. If new versions are available, an alert will be shown in the console. This doesn't affect the load time even if your connection is offline or slow, because it happens in the background.",
"config.check-content-integrity.name": "Check content integrity",
"config.check-content-integrity.desc": "Whether SMAPI should check whether the game's content files are present and unmodified. This should nearly always be enabled. If disabled, SMAPI will log a warning for visibility when someone helps you troubleshoot game issues.",
"config.read-console-input.name": "Read console input",
"config.read-console-input.desc": "Whether SMAPI should listen for console input. Disabling this will prevent you from using console commands. On some specific Linux systems, disabling this may reduce CPU usage.",
// 'Console window' section
"config.section.console-window": "Console window",
"config.developer-mode.name": "Developer mode",
"config.developer-mode.desc": "Whether to show much more info in the SMAPI consoler window, intended for mod developers. Not recommended for most players.",
"config.color-scheme.name": "Color scheme",
"config.color-scheme.desc": "The color scheme to apply to text in the SMAPI console window. This has no effect on the game, mods, or SMAPI log file.",
"config.color-scheme.options.AutoDetect": "Auto-detect",
"config.color-scheme.options.AutoDetect.on-windows": "Auto-detect (recommended)",
"config.color-scheme.options.DarkBackground": "Lighter text (for dark backgrounds)",
"config.color-scheme.options.LightBackground": "Darker text (for light backgrounds)",
"config.color-scheme.options.None": "No colors",
// 'Verbose logging' section
"config.section.verbose-logs": "Verbose logs",
"config.section.verbose-logs.explanation": "To help troubleshoot, SMAPI and many mods can send extra info to the SMAPI log (called 'verbose logging'). This may cause performance issues, and should usually be disabled.",
"config.enable-for.name": "Enable for",
"config.enable-for.desc": "Choose which mods have verbose logging enabled.",
"config.enable-for.options.all": "All (not recommended)",
"config.enable-for.options.selected": "Mods selected below",
"config.enable-for-smapi.name": "SMAPI",
"config.enable-for-smapi.desc": "Whether to enable verbose logs for SMAPI itself.",
"config.enable-for-mod.name": "{{modName}}",
"config.enable-for-mod.desc": "Whether to enable verbose logs for the '{{modName}}' mod."
}