Update Android branch to SMAPI 3.1.0
This commit is contained in:
commit
326c7d7db1
|
@ -1,3 +1,3 @@
|
||||||
# normalise line endings
|
# normalize line endings
|
||||||
* text=auto
|
* text=auto
|
||||||
README.txt text=crlf
|
README.txt text=crlf
|
||||||
|
|
|
@ -1,19 +1,16 @@
|
||||||
Do you want to...
|
Do you want to...
|
||||||
|
|
||||||
* **Ask for help using SMAPI?**
|
* **Ask for help using SMAPI?**
|
||||||
Please post a message in the [SMAPI support thread](http://community.playstarbound.com/threads/108375)
|
Please ask in [the Stardew Valley Discord or mod forums](https://smapi.io/community), don't
|
||||||
or [ask on Discord](https://stardewvalleywiki.com/Modding:Community#Discord), don't create a
|
create a GitHub issue.
|
||||||
GitHub issue.
|
|
||||||
|
|
||||||
* **Report a bug?**
|
* **Report a bug?**
|
||||||
Please post a message in the [SMAPI support thread](http://community.playstarbound.com/threads/108375)
|
Please report it in [the Stardew Valley Discord or mod forums](https://smapi.io/community), don't
|
||||||
or [ask on Discord](https://stardewvalleywiki.com/Modding:Community#Discord) instead, unless
|
create a GitHub issue unless you're sure it's a bug in the SMAPI code.
|
||||||
you're sure it's a bug in SMAPI itself.
|
|
||||||
|
|
||||||
* **Submit a pull request?**
|
* **Submit a pull request?**
|
||||||
Pull requests are welcome! If you're submitting a new feature, it's best to discuss first to make
|
Pull requests are welcome! If you're submitting a new feature, it's best to discuss first to make
|
||||||
sure it'll be accepted. Feel free to come chat in [#modding on Discord](https://stardewvalleywiki.com/Modding:Community#Discord)
|
sure it'll be accepted. Feel free to come chat [on Discord or in the SMAPI discussion thread](https://smapi.io/community).
|
||||||
or post in the [SMAPI support thread](http://community.playstarbound.com/threads/108375).
|
|
||||||
|
|
||||||
Documenting your code and using the same formatting conventions is appreciated, but don't worry too
|
Documenting your code and using the same formatting conventions is appreciated, but don't worry too
|
||||||
much about it. We'll fix up the code after we accept the pull request if needed.
|
much about it. We'll fix up the code after we accept the pull request if needed.
|
||||||
|
|
|
@ -6,10 +6,8 @@ about: Report a problem with SMAPI.
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
|
|
||||||
Only report a bug here if you're sure it's a SMAPI bug! To request support instead, see:
|
Only report a bug here if you're sure it's a SMAPI bug!
|
||||||
- #modding on Discord: https://stardewvalleywiki.com/Modding:Community#Discord
|
To request support instead, see https://smapi.io/community.
|
||||||
- support forum thread: https://community.playstarbound.com/threads/108375
|
|
||||||
- Nexus mod page: https://www.nexusmods.com/stardewvalley/mods/2400
|
|
||||||
|
|
||||||
Replace the instructions below with the bug details.
|
Replace the instructions below with the bug details.
|
||||||
|
|
||||||
|
@ -26,7 +24,7 @@ Exact steps which reproduce the bug, if possible. For example:
|
||||||
4. Error occurs.
|
4. Error occurs.
|
||||||
|
|
||||||
**Log file**
|
**Log file**
|
||||||
Upload your SMAPI log to https://log.smapi.io and post a link here.
|
Upload your SMAPI log to https://smapi.io/log and post a link here.
|
||||||
|
|
||||||
**Screenshots**
|
**Screenshots**
|
||||||
If applicable, add screenshots to help explain your problem.
|
If applicable, add screenshots to help explain your problem.
|
||||||
|
|
|
@ -6,10 +6,7 @@ about: Suggest an idea for SMAPI.
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
|
|
||||||
GitHub issues are only used for development tasks. Please don't submit feature requests here! Instead, see...
|
GitHub issues are only used for development tasks. Please don't submit feature requests here!
|
||||||
|
Instead, see https://smapi.io/community to discuss SMAPI.
|
||||||
- #modding on Discord: https://stardewvalleywiki.com/Modding:Community#Discord
|
|
||||||
- support forum thread: https://community.playstarbound.com/threads/108375
|
|
||||||
- Nexus page: https://www.nexusmods.com/stardewvalley/mods/2400
|
|
||||||
|
|
||||||
-->
|
-->
|
||||||
|
|
|
@ -6,10 +6,7 @@ about: Create a ticket about something else.
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
|
|
||||||
GitHub issues are only used for development tasks. For support and questions, see...
|
GitHub issues are only used for development tasks.
|
||||||
|
For support and questions, see https://smapi.io/community instead.
|
||||||
- #modding on Discord: https://stardewvalleywiki.com/Modding:Community#Discord
|
|
||||||
- support forum thread: https://community.playstarbound.com/threads/108375
|
|
||||||
- Nexus page: https://www.nexusmods.com/stardewvalley/mods/2400
|
|
||||||
|
|
||||||
-->
|
-->
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
GitHub issues are only used for SMAPI development tasks.
|
GitHub issues are only used for SMAPI development tasks.
|
||||||
|
|
||||||
To get help with SMAPI problems, you can...
|
To get help with SMAPI problems, [ask on Discord or in the forums](https://smapi.io/community)
|
||||||
* [ask on Discord](https://stardewvalleywiki.com/Modding:Community#Discord);
|
instead.
|
||||||
* or post in the [SMAPI support thread](https://community.playstarbound.com/threads/108375).
|
|
||||||
|
|
|
@ -18,6 +18,9 @@ _ReSharper*/
|
||||||
*.[Rr]e[Ss]harper
|
*.[Rr]e[Ss]harper
|
||||||
*.DotSettings.user
|
*.DotSettings.user
|
||||||
|
|
||||||
|
# Rider
|
||||||
|
.idea/
|
||||||
|
|
||||||
# NuGet packages
|
# NuGet packages
|
||||||
*.nupkg
|
*.nupkg
|
||||||
**/packages/*
|
**/packages/*
|
||||||
|
@ -28,4 +31,7 @@ _ReSharper*/
|
||||||
appsettings.Development.json
|
appsettings.Development.json
|
||||||
|
|
||||||
# AWS generated files
|
# AWS generated files
|
||||||
src/SMAPI.Web/aws-beanstalk-tools-defaults.json
|
src/SMAPI.Web.LegacyRedirects/aws-beanstalk-tools-defaults.json
|
||||||
|
|
||||||
|
# Azure generated files
|
||||||
|
src/SMAPI.Web/Properties/PublishProfiles/*.pubxml
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
using System.Reflection;
|
|
||||||
|
|
||||||
[assembly: AssemblyProduct("SMAPI")]
|
|
||||||
[assembly: AssemblyVersion("2.11.3")]
|
|
||||||
[assembly: AssemblyFileVersion("2.11.3")]
|
|
|
@ -0,0 +1,70 @@
|
||||||
|
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
|
||||||
|
<Import Project="find-game-folder.targets" />
|
||||||
|
|
||||||
|
<!--set properties -->
|
||||||
|
<PropertyGroup>
|
||||||
|
<Version>3.1.0</Version>
|
||||||
|
<Product>SMAPI</Product>
|
||||||
|
|
||||||
|
<AssemblySearchPaths>$(AssemblySearchPaths);{GAC}</AssemblySearchPaths>
|
||||||
|
<DefineConstants Condition="$(OS) == 'Windows_NT'">$(DefineConstants);SMAPI_FOR_WINDOWS</DefineConstants>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<!-- if game path is invalid, show one user-friendly error instead of a slew of reference errors -->
|
||||||
|
<Target Name="ValidateInstallPath" AfterTargets="BeforeBuild">
|
||||||
|
<Error Condition="!Exists('$(GamePath)')" Text="Failed to find the game install path automatically. You can specify where to find it; see https://smapi.io/package/custom-game-path." />
|
||||||
|
</Target>
|
||||||
|
|
||||||
|
<!-- copy files into game directory and enable debugging -->
|
||||||
|
<Target Name="CopySmapiFiles" AfterTargets="AfterBuild">
|
||||||
|
<CallTarget Targets="CopySMAPI;CopyDefaultMods" />
|
||||||
|
</Target>
|
||||||
|
<Target Name="CopySMAPI" Condition="'$(MSBuildProjectName)' == 'SMAPI'">
|
||||||
|
<ItemGroup>
|
||||||
|
<TranslationFiles Include="$(TargetDir)\i18n\*.json" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<Copy SourceFiles="$(TargetDir)\$(TargetName).exe" DestinationFolder="$(GamePath)" />
|
||||||
|
<Copy SourceFiles="$(TargetDir)\$(TargetName).pdb" DestinationFolder="$(GamePath)" />
|
||||||
|
<Copy SourceFiles="$(TargetDir)\$(TargetName).xml" DestinationFolder="$(GamePath)" />
|
||||||
|
<Copy SourceFiles="$(TargetDir)\SMAPI.config.json" DestinationFiles="$(GamePath)\smapi-internal\config.json" />
|
||||||
|
<Copy SourceFiles="$(TargetDir)\SMAPI.metadata.json" DestinationFiles="$(GamePath)\smapi-internal\metadata.json" />
|
||||||
|
<Copy SourceFiles="$(TargetDir)\0Harmony.dll" DestinationFolder="$(GamePath)\smapi-internal" />
|
||||||
|
<Copy SourceFiles="$(TargetDir)\Newtonsoft.Json.dll" DestinationFolder="$(GamePath)\smapi-internal" />
|
||||||
|
<Copy SourceFiles="$(TargetDir)\Mono.Cecil.dll" DestinationFolder="$(GamePath)\smapi-internal" />
|
||||||
|
<Copy SourceFiles="@(TranslationFiles)" DestinationFolder="$(GamePath)\smapi-internal\i18n" />
|
||||||
|
</Target>
|
||||||
|
<Target Name="CopyDefaultMods" Condition="'$(MSBuildProjectName)' == 'SMAPI.Mods.ConsoleCommands' OR '$(MSBuildProjectName)' == 'SMAPI.Mods.SaveBackup'">
|
||||||
|
<Copy SourceFiles="$(TargetDir)\$(TargetName).dll" DestinationFolder="$(GamePath)\Mods\$(AssemblyName)" />
|
||||||
|
<Copy SourceFiles="$(TargetDir)\$(TargetName).pdb" DestinationFolder="$(GamePath)\Mods\$(AssemblyName)" Condition="Exists('$(TargetDir)\$(TargetName).pdb')" />
|
||||||
|
<Copy SourceFiles="$(TargetDir)\manifest.json" DestinationFolder="$(GamePath)\Mods\$(AssemblyName)" />
|
||||||
|
</Target>
|
||||||
|
<Target Name="CopyToolkit" Condition="'$(MSBuildProjectName)' == 'SMAPI.Toolkit' AND $(TargetFramework) == 'net4.5'" AfterTargets="PostBuildEvent">
|
||||||
|
<Copy SourceFiles="$(TargetDir)\$(TargetName).dll" DestinationFolder="$(GamePath)\smapi-internal" />
|
||||||
|
<Copy SourceFiles="$(TargetDir)\$(TargetName).pdb" DestinationFolder="$(GamePath)\smapi-internal" />
|
||||||
|
<Copy SourceFiles="$(TargetDir)\$(TargetName).xml" DestinationFolder="$(GamePath)\smapi-internal" />
|
||||||
|
</Target>
|
||||||
|
<Target Name="CopyToolkitCoreInterfaces" Condition="'$(MSBuildProjectName)' == 'SMAPI.Toolkit.CoreInterfaces' AND $(TargetFramework) == 'net4.5'" AfterTargets="PostBuildEvent">
|
||||||
|
<Copy SourceFiles="$(TargetDir)\$(TargetName).dll" DestinationFolder="$(GamePath)\smapi-internal" />
|
||||||
|
<Copy SourceFiles="$(TargetDir)\$(TargetName).pdb" DestinationFolder="$(GamePath)\smapi-internal" />
|
||||||
|
<Copy SourceFiles="$(TargetDir)\$(TargetName).xml" DestinationFolder="$(GamePath)\smapi-internal" />
|
||||||
|
</Target>
|
||||||
|
|
||||||
|
<!-- common build settings -->
|
||||||
|
<PropertyGroup>
|
||||||
|
<DebugType>pdbonly</DebugType>
|
||||||
|
<DebugSymbols>true</DebugSymbols>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<!-- launch SMAPI through Visual Studio -->
|
||||||
|
<PropertyGroup Condition="'$(MSBuildProjectName)' == 'SMAPI'">
|
||||||
|
<StartAction>Program</StartAction>
|
||||||
|
<StartProgram>$(GamePath)\StardewModdingAPI.exe</StartProgram>
|
||||||
|
<StartWorkingDirectory>$(GamePath)</StartWorkingDirectory>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<!-- Somehow this makes Visual Studio for Mac recognise the previous section. Nobody knows why. -->
|
||||||
|
<PropertyGroup Condition="'$(RunConfiguration)' == 'Default'" />
|
||||||
|
|
||||||
|
</Project>
|
|
@ -0,0 +1,47 @@
|
||||||
|
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<!-- import developer's custom path (if any) -->
|
||||||
|
<Import Condition="$(OS) != 'Windows_NT' AND Exists('$(HOME)\stardewvalley.targets')" Project="$(HOME)\stardewvalley.targets" />
|
||||||
|
<Import Condition="$(OS) == 'Windows_NT' AND Exists('$(USERPROFILE)\stardewvalley.targets')" Project="$(USERPROFILE)\stardewvalley.targets" />
|
||||||
|
|
||||||
|
<!-- find game path -->
|
||||||
|
<Choose>
|
||||||
|
<When Condition="$(OS) == 'Unix' OR $(OS) == 'OSX'">
|
||||||
|
<PropertyGroup>
|
||||||
|
<!-- Linux -->
|
||||||
|
<GamePath Condition="!Exists('$(GamePath)')">$(HOME)/GOG Games/Stardew Valley/game</GamePath>
|
||||||
|
<GamePath Condition="!Exists('$(GamePath)')">$(HOME)/.steam/steam/steamapps/common/Stardew Valley</GamePath>
|
||||||
|
<GamePath Condition="!Exists('$(GamePath)')">$(HOME)/.local/share/Steam/steamapps/common/Stardew Valley</GamePath>
|
||||||
|
|
||||||
|
<!-- Mac (may be 'Unix' or 'OSX') -->
|
||||||
|
<GamePath Condition="!Exists('$(GamePath)')">/Applications/Stardew Valley.app/Contents/MacOS</GamePath>
|
||||||
|
<GamePath Condition="!Exists('$(GamePath)')">$(HOME)/Library/Application Support/Steam/steamapps/common/Stardew Valley/Contents/MacOS</GamePath>
|
||||||
|
</PropertyGroup>
|
||||||
|
</When>
|
||||||
|
<When Condition="$(OS) == 'Windows_NT'">
|
||||||
|
<PropertyGroup>
|
||||||
|
<!-- default paths -->
|
||||||
|
<GamePath Condition="!Exists('$(GamePath)')">C:\Program Files\GalaxyClient\Games\Stardew Valley</GamePath>
|
||||||
|
<GamePath Condition="!Exists('$(GamePath)')">C:\Program Files\GOG Galaxy\Games\Stardew Valley</GamePath>
|
||||||
|
<GamePath Condition="!Exists('$(GamePath)')">C:\Program Files\Steam\steamapps\common\Stardew Valley</GamePath>
|
||||||
|
|
||||||
|
<GamePath Condition="!Exists('$(GamePath)')">C:\Program Files (x86)\GalaxyClient\Games\Stardew Valley</GamePath>
|
||||||
|
<GamePath Condition="!Exists('$(GamePath)')">C:\Program Files (x86)\GOG Galaxy\Games\Stardew Valley</GamePath>
|
||||||
|
<GamePath Condition="!Exists('$(GamePath)')">C:\Program Files (x86)\Steam\steamapps\common\Stardew Valley</GamePath>
|
||||||
|
|
||||||
|
<!-- registry paths -->
|
||||||
|
<GamePath Condition="!Exists('$(GamePath)')">$([MSBuild]::GetRegistryValueFromView('HKEY_LOCAL_MACHINE\SOFTWARE\GOG.com\Games\1453375253', 'PATH', null, RegistryView.Registry32))</GamePath>
|
||||||
|
<GamePath Condition="!Exists('$(GamePath)')">$([MSBuild]::GetRegistryValueFromView('HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Steam App 413150', 'InstallLocation', null, RegistryView.Registry64, RegistryView.Registry32))</GamePath>
|
||||||
|
|
||||||
|
<!-- derive from Steam library path -->
|
||||||
|
<_SteamLibraryPath>$([MSBuild]::GetRegistryValueFromView('HKEY_CURRENT_USER\SOFTWARE\Valve\Steam', 'SteamPath', null, RegistryView.Registry32))</_SteamLibraryPath>
|
||||||
|
<GamePath Condition="!Exists('$(GamePath)') AND '$(_SteamLibraryPath)' != ''">$(_SteamLibraryPath)\steamapps\common\Stardew Valley</GamePath>
|
||||||
|
</PropertyGroup>
|
||||||
|
</When>
|
||||||
|
</Choose>
|
||||||
|
|
||||||
|
<!-- set game metadata -->
|
||||||
|
<PropertyGroup>
|
||||||
|
<GameExecutableName>Stardew Valley</GameExecutableName>
|
||||||
|
<GameExecutableName Condition="$(OS) != 'Windows_NT'">StardewValley</GameExecutableName>
|
||||||
|
</PropertyGroup>
|
||||||
|
</Project>
|
|
@ -17,6 +17,9 @@
|
||||||
<PlatformName>windows</PlatformName>
|
<PlatformName>windows</PlatformName>
|
||||||
<PlatformName Condition="$(OS) != 'Windows_NT'">unix</PlatformName>
|
<PlatformName Condition="$(OS) != 'Windows_NT'">unix</PlatformName>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<TranslationFiles Include="$(CompiledSmapiPath)\i18n\*.json" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<!-- reset package directory -->
|
<!-- reset package directory -->
|
||||||
<RemoveDir Directories="$(PackagePath)" />
|
<RemoveDir Directories="$(PackagePath)" />
|
||||||
|
@ -38,14 +41,15 @@
|
||||||
<Copy SourceFiles="$(CompiledSmapiPath)\0Harmony.dll" DestinationFolder="$(PackagePath)\bundle\smapi-internal" />
|
<Copy SourceFiles="$(CompiledSmapiPath)\0Harmony.dll" DestinationFolder="$(PackagePath)\bundle\smapi-internal" />
|
||||||
<Copy SourceFiles="$(CompiledSmapiPath)\Mono.Cecil.dll" DestinationFolder="$(PackagePath)\bundle\smapi-internal" />
|
<Copy SourceFiles="$(CompiledSmapiPath)\Mono.Cecil.dll" DestinationFolder="$(PackagePath)\bundle\smapi-internal" />
|
||||||
<Copy SourceFiles="$(CompiledSmapiPath)\Newtonsoft.Json.dll" DestinationFolder="$(PackagePath)\bundle\smapi-internal" />
|
<Copy SourceFiles="$(CompiledSmapiPath)\Newtonsoft.Json.dll" DestinationFolder="$(PackagePath)\bundle\smapi-internal" />
|
||||||
<Copy SourceFiles="$(CompiledSmapiPath)\StardewModdingAPI.config.json" DestinationFolder="$(PackagePath)\bundle\smapi-internal" />
|
<Copy SourceFiles="$(CompiledSmapiPath)\SMAPI.config.json" DestinationFiles="$(PackagePath)\bundle\smapi-internal\config.json" />
|
||||||
<Copy SourceFiles="$(CompiledSmapiPath)\StardewModdingAPI.metadata.json" DestinationFolder="$(PackagePath)\bundle\smapi-internal" />
|
<Copy SourceFiles="$(CompiledSmapiPath)\SMAPI.metadata.json" DestinationFiles="$(PackagePath)\bundle\smapi-internal\metadata.json" />
|
||||||
<Copy SourceFiles="$(CompiledToolkitPath)\StardewModdingAPI.Toolkit.dll" DestinationFolder="$(PackagePath)\bundle\smapi-internal" />
|
<Copy SourceFiles="$(CompiledToolkitPath)\SMAPI.Toolkit.dll" DestinationFolder="$(PackagePath)\bundle\smapi-internal" />
|
||||||
<Copy SourceFiles="$(CompiledToolkitPath)\StardewModdingAPI.Toolkit.pdb" DestinationFolder="$(PackagePath)\bundle\smapi-internal" />
|
<Copy SourceFiles="$(CompiledToolkitPath)\SMAPI.Toolkit.pdb" DestinationFolder="$(PackagePath)\bundle\smapi-internal" />
|
||||||
<Copy SourceFiles="$(CompiledToolkitPath)\StardewModdingAPI.Toolkit.xml" DestinationFolder="$(PackagePath)\bundle\smapi-internal" />
|
<Copy SourceFiles="$(CompiledToolkitPath)\SMAPI.Toolkit.xml" DestinationFolder="$(PackagePath)\bundle\smapi-internal" />
|
||||||
<Copy SourceFiles="$(CompiledToolkitPath)\StardewModdingAPI.Toolkit.CoreInterfaces.dll" DestinationFolder="$(PackagePath)\bundle\smapi-internal" />
|
<Copy SourceFiles="$(CompiledToolkitPath)\SMAPI.Toolkit.CoreInterfaces.dll" DestinationFolder="$(PackagePath)\bundle\smapi-internal" />
|
||||||
<Copy SourceFiles="$(CompiledToolkitPath)\StardewModdingAPI.Toolkit.CoreInterfaces.pdb" DestinationFolder="$(PackagePath)\bundle\smapi-internal" />
|
<Copy SourceFiles="$(CompiledToolkitPath)\SMAPI.Toolkit.CoreInterfaces.pdb" DestinationFolder="$(PackagePath)\bundle\smapi-internal" />
|
||||||
<Copy SourceFiles="$(CompiledToolkitPath)\StardewModdingAPI.Toolkit.CoreInterfaces.xml" DestinationFolder="$(PackagePath)\bundle\smapi-internal" />
|
<Copy SourceFiles="$(CompiledToolkitPath)\SMAPI.Toolkit.CoreInterfaces.xml" DestinationFolder="$(PackagePath)\bundle\smapi-internal" />
|
||||||
|
<Copy SourceFiles="@(TranslationFiles)" DestinationFolder="$(PackagePath)\bundle\smapi-internal\i18n" />
|
||||||
<Copy Condition="$(OS) != 'Windows_NT'" SourceFiles="$(TargetDir)\unix-launcher.sh" DestinationFiles="$(PackagePath)\bundle\StardewModdingAPI" />
|
<Copy Condition="$(OS) != 'Windows_NT'" SourceFiles="$(TargetDir)\unix-launcher.sh" DestinationFiles="$(PackagePath)\bundle\StardewModdingAPI" />
|
||||||
<Copy Condition="$(OS) != 'Windows_NT'" SourceFiles="$(CompiledSmapiPath)\System.Numerics.dll" DestinationFolder="$(PackagePath)\bundle\smapi-internal" />
|
<Copy Condition="$(OS) != 'Windows_NT'" SourceFiles="$(CompiledSmapiPath)\System.Numerics.dll" DestinationFolder="$(PackagePath)\bundle\smapi-internal" />
|
||||||
<Copy Condition="$(OS) != 'Windows_NT'" SourceFiles="$(CompiledSmapiPath)\System.Runtime.Caching.dll" DestinationFolder="$(PackagePath)\bundle\smapi-internal" />
|
<Copy Condition="$(OS) != 'Windows_NT'" SourceFiles="$(CompiledSmapiPath)\System.Runtime.Caching.dll" DestinationFolder="$(PackagePath)\bundle\smapi-internal" />
|
||||||
|
@ -59,7 +63,7 @@
|
||||||
<Copy SourceFiles="$(CompiledModsPath)\SaveBackup\SaveBackup.pdb" DestinationFolder="$(PackagePath)\bundle\Mods\SaveBackup" />
|
<Copy SourceFiles="$(CompiledModsPath)\SaveBackup\SaveBackup.pdb" DestinationFolder="$(PackagePath)\bundle\Mods\SaveBackup" />
|
||||||
<Copy SourceFiles="$(CompiledModsPath)\SaveBackup\manifest.json" DestinationFolder="$(PackagePath)\bundle\Mods\SaveBackup" />
|
<Copy SourceFiles="$(CompiledModsPath)\SaveBackup\manifest.json" DestinationFolder="$(PackagePath)\bundle\Mods\SaveBackup" />
|
||||||
|
|
||||||
<!-- fix errors on Linux/Mac (sample: https://log.smapi.io/mMdFUpgB) -->
|
<!-- fix errors on Linux/Mac (sample: https://smapi.io/log/mMdFUpgB) -->
|
||||||
<Copy Condition="$(OS) != 'Windows_NT'" SourceFiles="$(RootPath)\build\lib\System.Numerics.dll" DestinationFolder="$(PackagePath)\bundle\smapi-internal" />
|
<Copy Condition="$(OS) != 'Windows_NT'" SourceFiles="$(RootPath)\build\lib\System.Numerics.dll" DestinationFolder="$(PackagePath)\bundle\smapi-internal" />
|
||||||
<Copy Condition="$(OS) != 'Windows_NT'" SourceFiles="$(RootPath)\build\lib\System.Runtime.Caching.dll" DestinationFolder="$(PackagePath)\bundle\smapi-internal" />
|
<Copy Condition="$(OS) != 'Windows_NT'" SourceFiles="$(RootPath)\build\lib\System.Runtime.Caching.dll" DestinationFolder="$(PackagePath)\bundle\smapi-internal" />
|
||||||
|
|
||||||
|
@ -76,7 +80,7 @@
|
||||||
<RemoveDir Directories="$(PackageDevPath)\bundle" />
|
<RemoveDir Directories="$(PackageDevPath)\bundle" />
|
||||||
|
|
||||||
<!-- finalise normal installer -->
|
<!-- finalise normal installer -->
|
||||||
<ReplaceFileText FilePath="$(PackagePath)\bundle\smapi-internal\StardewModdingAPI.config.json" Search=""DeveloperMode": true" Replace=""DeveloperMode": false" />
|
<ReplaceFileText FilePath="$(PackagePath)\bundle\smapi-internal\config.json" Search=""DeveloperMode": true" Replace=""DeveloperMode": false" />
|
||||||
<ZipDirectory FromDirPath="$(PackagePath)\bundle" ToFilePath="$(PackagePath)\internal\$(PlatformName)-install.dat" />
|
<ZipDirectory FromDirPath="$(PackagePath)\bundle" ToFilePath="$(PackagePath)\internal\$(PlatformName)-install.dat" />
|
||||||
<RemoveDir Directories="$(PackagePath)\bundle" />
|
<RemoveDir Directories="$(PackagePath)\bundle" />
|
||||||
</Target>
|
</Target>
|
||||||
|
|
|
@ -11,13 +11,13 @@
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<RemoveDir Directories="$(PackagePath)" />
|
<RemoveDir Directories="$(PackagePath)" />
|
||||||
<Copy SourceFiles="$(ProjectDir)/package.nuspec" DestinationFolder="$(PackagePath)" />
|
<Copy SourceFiles="$(ProjectDir)/package.nuspec" DestinationFolder="$(PackagePath)" />
|
||||||
|
<Copy SourceFiles="$(SolutionDir)/../build/find-game-folder.targets" DestinationFolder="$(PackagePath)/build" />
|
||||||
<Copy SourceFiles="$(ProjectDir)/build/smapi.targets" DestinationFiles="$(PackagePath)/build/Pathoschild.Stardew.ModBuildConfig.targets" />
|
<Copy SourceFiles="$(ProjectDir)/build/smapi.targets" DestinationFiles="$(PackagePath)/build/Pathoschild.Stardew.ModBuildConfig.targets" />
|
||||||
|
<Copy SourceFiles="$(TargetDir)/assets/nuget-icon.png" DestinationFiles="$(PackagePath)/images/icon.png" />
|
||||||
<Copy SourceFiles="$(TargetDir)/Newtonsoft.Json.dll" DestinationFolder="$(PackagePath)/build" />
|
<Copy SourceFiles="$(TargetDir)/Newtonsoft.Json.dll" DestinationFolder="$(PackagePath)/build" />
|
||||||
<Copy SourceFiles="$(TargetDir)/StardewModdingAPI.ModBuildConfig.dll" DestinationFolder="$(PackagePath)/build" />
|
<Copy SourceFiles="$(TargetDir)/SMAPI.ModBuildConfig.dll" DestinationFolder="$(PackagePath)/build" />
|
||||||
<Copy SourceFiles="$(TargetDir)/StardewModdingAPI.Toolkit.dll" DestinationFolder="$(PackagePath)/build" />
|
<Copy SourceFiles="$(TargetDir)/SMAPI.Toolkit.dll" DestinationFolder="$(PackagePath)/build" />
|
||||||
<Copy SourceFiles="$(TargetDir)/StardewModdingAPI.Toolkit.CoreInterfaces.dll" DestinationFolder="$(PackagePath)/build" />
|
<Copy SourceFiles="$(TargetDir)/SMAPI.Toolkit.CoreInterfaces.dll" DestinationFolder="$(PackagePath)/build" />
|
||||||
<Copy SourceFiles="$(SolutionDir)/SMAPI.ModBuildConfig.Analyzer/bin/netstandard1.3/StardewModdingAPI.ModBuildConfig.Analyzer.dll" DestinationFolder="$(PackagePath)/analyzers/dotnet/cs" />
|
<Copy SourceFiles="$(SolutionDir)/SMAPI.ModBuildConfig.Analyzer/bin/netstandard2.0/SMAPI.ModBuildConfig.Analyzer.dll" DestinationFolder="$(PackagePath)/analyzers/dotnet/cs" />
|
||||||
<Copy SourceFiles="$(SolutionDir)/SMAPI.ModBuildConfig.Analyzer/tools/install.ps1" DestinationFolder="$(PackagePath)/tools" />
|
|
||||||
<Copy SourceFiles="$(SolutionDir)/SMAPI.ModBuildConfig.Analyzer/tools/uninstall.ps1" DestinationFolder="$(PackagePath)/tools" />
|
|
||||||
</Target>
|
</Target>
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
@ -19,11 +19,14 @@ doesn't change any of your game files. It serves eight main purposes:
|
||||||
_SMAPI detects when a mod accesses part of the game that changed in a game update which affects
|
_SMAPI detects when a mod accesses part of the game that changed in a game update which affects
|
||||||
many mods, and rewrites the mod so it's compatible._
|
many mods, and rewrites the mod so it's compatible._
|
||||||
|
|
||||||
5. **Intercept errors.**
|
5. **Intercept errors and automatically fix saves.**
|
||||||
_SMAPI intercepts errors that happen in the game, displays the error details in the console
|
_SMAPI intercepts errors, shows the error info in the SMAPI console, and in most cases
|
||||||
window, and in most cases automatically recovers the game. This prevents mods from accidentally
|
automatically recovers the game. That prevents mods from crashing the game, and makes it
|
||||||
crashing the game, and makes it possible to troubleshoot errors in the game itself that would
|
possible to troubleshoot errors in the game itself that would otherwise show a generic 'program
|
||||||
otherwise show a generic 'program has stopped working' type of message._
|
has stopped working' type of message._
|
||||||
|
|
||||||
|
_SMAPI also automatically fixes save data in some cases when a load would crash, e.g. due to a
|
||||||
|
custom location or NPC mod that was removed._
|
||||||
|
|
||||||
6. **Provide update checks.**
|
6. **Provide update checks.**
|
||||||
_SMAPI automatically checks for new versions of your installed mods, and notifies you when any
|
_SMAPI automatically checks for new versions of your installed mods, and notifies you when any
|
||||||
|
@ -38,16 +41,36 @@ doesn't change any of your game files. It serves eight main purposes:
|
||||||
something goes wrong. (Via the bundled SaveBackup mod.)_
|
something goes wrong. (Via the bundled SaveBackup mod.)_
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
Have questions? Come [chat on Discord](https://discord.gg/KCJHWhX) with SMAPI developers and other
|
Have questions? Come [ask the community](https://smapi.io/community) to get help from SMAPI
|
||||||
modders!
|
developers and other modders!
|
||||||
|
|
||||||
### For players
|
### For players
|
||||||
* [Player guide](https://stardewvalleywiki.com/Modding:Player_Guide)
|
* [Player guide](https://stardewvalleywiki.com/Modding:Player_Guide)
|
||||||
|
|
||||||
### For modders
|
### For modders
|
||||||
* [Modding documentation](https://stardewvalleywiki.com/Modding:Index)
|
* [Modding documentation](https://smapi.io/docs)
|
||||||
* [Mod build configuration](mod-build-config.md)
|
* [Mod build configuration](technical/mod-package.md)
|
||||||
* [Release notes](release-notes.md)
|
* [Release notes](release-notes.md)
|
||||||
|
|
||||||
### For SMAPI developers
|
### For SMAPI developers
|
||||||
* [Technical docs](technical-docs.md)
|
* [Technical docs](technical/smapi.md)
|
||||||
|
|
||||||
|
## Translating SMAPI
|
||||||
|
SMAPI rarely shows text in-game, so it only has a few translations. Contributions are welcome! See
|
||||||
|
[Modding:Translations](https://stardewvalleywiki.com/Modding:Translations) on the wiki for help
|
||||||
|
contributing translations.
|
||||||
|
|
||||||
|
locale | status
|
||||||
|
---------- | :----------------
|
||||||
|
default | ✓ [fully translated](../src/SMAPI/i18n/default.json)
|
||||||
|
Chinese | ✓ [fully translated](../src/SMAPI/i18n/zh.json)
|
||||||
|
French | ❑ not translated
|
||||||
|
German | ✓ [fully translated](../src/SMAPI/i18n/de.json)
|
||||||
|
Hungarian | ❑ not translated
|
||||||
|
Italian | ❑ not translated
|
||||||
|
Japanese | ✓ [fully translated](../src/SMAPI/i18n/ja.json)
|
||||||
|
Korean | ❑ not translated
|
||||||
|
Portuguese | ✓ [fully translated](../src/SMAPI/i18n/pt.json)
|
||||||
|
Russian | ✓ [fully translated](../src/SMAPI/i18n/ru.json)
|
||||||
|
Spanish | ✓ [fully translated](../src/SMAPI/i18n/es.json)
|
||||||
|
Turkish | ✓ [fully translated](../src/SMAPI/i18n/tr.json)
|
||||||
|
|
|
@ -1,283 +1 @@
|
||||||
The **mod build package** is an open-source NuGet package which automates the MSBuild configuration
|
[Documentation moved](technical/mod-package.md).
|
||||||
for SMAPI mods.
|
|
||||||
|
|
||||||
The package...
|
|
||||||
|
|
||||||
* detects your game install path;
|
|
||||||
* adds the assembly references you need (with automatic support for Linux/Mac/Windows);
|
|
||||||
* packages the mod into your `Mods` folder when you rebuild the code (configurable);
|
|
||||||
* configures Visual Studio to enable debugging into the code when the game is running (_Windows only_);
|
|
||||||
* adds C# analyzers to warn for Stardew Valley-specific issues.
|
|
||||||
|
|
||||||
## Contents
|
|
||||||
* [Install](#install)
|
|
||||||
* [Configure](#configure)
|
|
||||||
* [Code analysis warnings](#code-analysis-warnings)
|
|
||||||
* [Troubleshoot](#troubleshoot)
|
|
||||||
* [Release notes](#release-notes)
|
|
||||||
|
|
||||||
## Install
|
|
||||||
**When creating a new mod:**
|
|
||||||
|
|
||||||
1. Create an empty library project.
|
|
||||||
2. Reference the [`Pathoschild.Stardew.ModBuildConfig` NuGet package](https://www.nuget.org/packages/Pathoschild.Stardew.ModBuildConfig).
|
|
||||||
3. [Write your code](https://stardewvalleywiki.com/Modding:Creating_a_SMAPI_mod).
|
|
||||||
4. Compile on any platform.
|
|
||||||
|
|
||||||
**When migrating an existing mod:**
|
|
||||||
|
|
||||||
1. Remove any project references to `Microsoft.Xna.*`, `MonoGame`, Stardew Valley,
|
|
||||||
`StardewModdingAPI`, and `xTile`.
|
|
||||||
2. Reference the [`Pathoschild.Stardew.ModBuildConfig` NuGet package](https://www.nuget.org/packages/Pathoschild.Stardew.ModBuildConfig).
|
|
||||||
3. Compile on any platform.
|
|
||||||
|
|
||||||
## Configure
|
|
||||||
### Deploy files into the `Mods` folder
|
|
||||||
By default, your mod will be copied into the game's `Mods` folder (with a subfolder matching your
|
|
||||||
project name) when you rebuild the code. The package will automatically include your
|
|
||||||
`manifest.json`, any `i18n` files, and the build output.
|
|
||||||
|
|
||||||
To add custom files to the mod folder, just [add them to the build output](https://stackoverflow.com/a/10828462/262123).
|
|
||||||
(If your project references another mod, make sure the reference is [_not_ marked 'copy local'](https://msdn.microsoft.com/en-us/library/t1zz5y8c(v=vs.100).aspx).)
|
|
||||||
|
|
||||||
You can change the mod's folder name by adding this above the first `</PropertyGroup>` in your
|
|
||||||
`.csproj`:
|
|
||||||
```xml
|
|
||||||
<ModFolderName>YourModName</ModFolderName>
|
|
||||||
```
|
|
||||||
|
|
||||||
If you don't want to deploy the mod automatically, you can add this:
|
|
||||||
```xml
|
|
||||||
<EnableModDeploy>False</EnableModDeploy>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Create release zip
|
|
||||||
By default, a zip file will be created in the build output when you rebuild the code. This zip file
|
|
||||||
contains all the files needed to share your mod in the recommended format for uploading to Nexus
|
|
||||||
Mods or other sites.
|
|
||||||
|
|
||||||
You can change the zipped folder name (and zip name) by adding this above the first
|
|
||||||
`</PropertyGroup>` in your `.csproj`:
|
|
||||||
```xml
|
|
||||||
<ModFolderName>YourModName</ModFolderName>
|
|
||||||
```
|
|
||||||
|
|
||||||
You can change the folder path where the zip is created like this:
|
|
||||||
```xml
|
|
||||||
<ModZipPath>$(SolutionDir)\_releases</ModZipPath>
|
|
||||||
```
|
|
||||||
|
|
||||||
Finally, you can disable the zip creation with this:
|
|
||||||
```xml
|
|
||||||
<EnableModZip>False</EnableModZip>
|
|
||||||
```
|
|
||||||
|
|
||||||
Or only create it in release builds with this:
|
|
||||||
```xml
|
|
||||||
<EnableModZip Condition="$(Configuration) != 'Release'">False</EnableModZip>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Game path
|
|
||||||
The package usually detects where your game is installed automatically. If it can't find your game
|
|
||||||
or you have multiple installs, you can specify the path yourself. There's two ways to do that:
|
|
||||||
|
|
||||||
* **Option 1: global game path (recommended).**
|
|
||||||
_This will apply to every project that uses the package._
|
|
||||||
|
|
||||||
1. Get the full folder path containing the Stardew Valley executable.
|
|
||||||
2. Create this file:
|
|
||||||
|
|
||||||
platform | path
|
|
||||||
--------- | ----
|
|
||||||
Linux/Mac | `~/stardewvalley.targets`
|
|
||||||
Windows | `%USERPROFILE%\stardewvalley.targets`
|
|
||||||
|
|
||||||
3. Save the file with this content:
|
|
||||||
|
|
||||||
```xml
|
|
||||||
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
|
||||||
<PropertyGroup>
|
|
||||||
<GamePath>PATH_HERE</GamePath>
|
|
||||||
</PropertyGroup>
|
|
||||||
</Project>
|
|
||||||
```
|
|
||||||
|
|
||||||
4. Replace `PATH_HERE` with your game path.
|
|
||||||
|
|
||||||
* **Option 2: path in the project file.**
|
|
||||||
_You'll need to do this for each project that uses the package._
|
|
||||||
|
|
||||||
1. Get the folder path containing the Stardew Valley `.exe` file.
|
|
||||||
2. Add this to your `.csproj` file under the `<Project` line:
|
|
||||||
|
|
||||||
```xml
|
|
||||||
<PropertyGroup>
|
|
||||||
<GamePath>PATH_HERE</GamePath>
|
|
||||||
</PropertyGroup>
|
|
||||||
```
|
|
||||||
|
|
||||||
3. Replace `PATH_HERE` with your custom game install path.
|
|
||||||
|
|
||||||
The configuration will check your custom path first, then fall back to the default paths (so it'll
|
|
||||||
still compile on a different computer).
|
|
||||||
|
|
||||||
### Ignore files
|
|
||||||
If you don't want to include a file in the mod folder or release zip:
|
|
||||||
* Make sure it's not copied to the build output. For a DLL, make sure the reference is [not marked 'copy local'](https://msdn.microsoft.com/en-us/library/t1zz5y8c(v=vs.100).aspx).
|
|
||||||
* Or add this to your `.csproj` file under the `<Project` line:
|
|
||||||
```xml
|
|
||||||
<IgnoreModFilePatterns>\.txt$, \.pdf$</IgnoreModFilePatterns>
|
|
||||||
```
|
|
||||||
This is a comma-delimited list of regular expression patterns. If any pattern matches a file's
|
|
||||||
relative path in your mod folder, that file won't be included.
|
|
||||||
|
|
||||||
### Non-mod projects
|
|
||||||
You can use the package in non-mod projects too (e.g. unit tests or framework DLLs). You'll need to
|
|
||||||
disable deploying the mod and creating a release zip:
|
|
||||||
|
|
||||||
```xml
|
|
||||||
<EnableModDeploy>False</EnableModDeploy>
|
|
||||||
<EnableModZip>False</EnableModZip>
|
|
||||||
```
|
|
||||||
|
|
||||||
If this is for unit tests, you may need to copy the referenced DLLs into your build output too:
|
|
||||||
```xml
|
|
||||||
<CopyModReferencesToBuildOutput>True</CopyModReferencesToBuildOutput>
|
|
||||||
```
|
|
||||||
|
|
||||||
## Code warnings
|
|
||||||
### Overview
|
|
||||||
The NuGet package adds code warnings in Visual Studio specific to Stardew Valley. For example:
|
|
||||||
![](screenshots/code-analyzer-example.png)
|
|
||||||
|
|
||||||
You can hide the warnings using the warning ID (shown under 'code' in the Error List). See...
|
|
||||||
* [for specific code](https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/preprocessor-directives/preprocessor-pragma-warning);
|
|
||||||
* for a method using this attribute:
|
|
||||||
```cs
|
|
||||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("SMAPI.CommonErrors", "AvoidNetField")]
|
|
||||||
```
|
|
||||||
* for an entire project:
|
|
||||||
1. Expand the _References_ node for the project in Visual Studio.
|
|
||||||
2. Right-click on _Analyzers_ and choose _Open Active Rule Set_.
|
|
||||||
4. Expand _StardewModdingAPI.ModBuildConfig.Analyzer_ and uncheck the warnings you want to hide.
|
|
||||||
|
|
||||||
See below for help with each specific warning.
|
|
||||||
|
|
||||||
### Avoid implicit net field cast
|
|
||||||
Warning text:
|
|
||||||
> This implicitly converts '{{expression}}' from {{net type}} to {{other type}}, but
|
|
||||||
> {{net type}} has unintuitive implicit conversion rules. Consider comparing against the actual
|
|
||||||
> value instead to avoid bugs.
|
|
||||||
|
|
||||||
Stardew Valley uses net types (like `NetBool` and `NetInt`) to handle multiplayer sync. These types
|
|
||||||
can implicitly convert to their equivalent normal values (like `bool x = new NetBool()`), but their
|
|
||||||
conversion rules are unintuitive and error-prone. For example,
|
|
||||||
`item?.category == null && item?.category != null` can both be true at once, and
|
|
||||||
`building.indoors != null` can be true for a null value.
|
|
||||||
|
|
||||||
Suggested fix:
|
|
||||||
* Some net fields have an equivalent non-net property like `monster.Health` (`int`) instead of
|
|
||||||
`monster.health` (`NetInt`). The package will add a separate [AvoidNetField](#avoid-net-field) warning for
|
|
||||||
these. Use the suggested property instead.
|
|
||||||
* For a reference type (i.e. one that can contain `null`), you can use the `.Value` property:
|
|
||||||
```c#
|
|
||||||
if (building.indoors.Value == null)
|
|
||||||
```
|
|
||||||
Or convert the value before comparison:
|
|
||||||
```c#
|
|
||||||
GameLocation indoors = building.indoors;
|
|
||||||
if(indoors == null)
|
|
||||||
// ...
|
|
||||||
```
|
|
||||||
* For a value type (i.e. one that can't contain `null`), check if the object is null (if applicable)
|
|
||||||
and compare with `.Value`:
|
|
||||||
```cs
|
|
||||||
if (item != null && item.category.Value == 0)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Avoid net field
|
|
||||||
Warning text:
|
|
||||||
> '{{expression}}' is a {{net type}} field; consider using the {{property name}} property instead.
|
|
||||||
|
|
||||||
Your code accesses a net field, which has some unusual behavior (see [AvoidImplicitNetFieldCast](#avoid-implicit-net-field-cast)).
|
|
||||||
This field has an equivalent non-net property that avoids those issues.
|
|
||||||
|
|
||||||
Suggested fix: access the suggested property name instead.
|
|
||||||
|
|
||||||
### Avoid obsolete field
|
|
||||||
Warning text:
|
|
||||||
> The '{{old field}}' field is obsolete and should be replaced with '{{new field}}'.
|
|
||||||
|
|
||||||
Your code accesses a field which is obsolete or no longer works. Use the suggested field instead.
|
|
||||||
|
|
||||||
## Troubleshoot
|
|
||||||
### "Failed to find the game install path"
|
|
||||||
That error means the package couldn't find your game. You can specify the game path yourself; see
|
|
||||||
_[Game path](#game-path)_ above.
|
|
||||||
|
|
||||||
## Release notes
|
|
||||||
### 2.2
|
|
||||||
* Added support for SMAPI 2.8+ (still compatible with earlier versions).
|
|
||||||
* Added default game paths for 32-bit Windows.
|
|
||||||
* Fixed valid manifests marked invalid in some cases.
|
|
||||||
|
|
||||||
### 2.1
|
|
||||||
* Added support for Stardew Valley 1.3.
|
|
||||||
* Added support for non-mod projects.
|
|
||||||
* Added C# analyzers to warn about implicit conversions of Netcode fields in Stardew Valley 1.3.
|
|
||||||
* Added option to ignore files by regex pattern.
|
|
||||||
* Added reference to new SMAPI DLL.
|
|
||||||
* Fixed some game paths not detected by NuGet package.
|
|
||||||
|
|
||||||
### 2.0.2
|
|
||||||
* Fixed compatibility issue on Linux.
|
|
||||||
|
|
||||||
### 2.0.1
|
|
||||||
* Fixed mod deploy failing to create subfolders if they don't already exist.
|
|
||||||
|
|
||||||
### 2.0
|
|
||||||
* Added: mods are now copied into the `Mods` folder automatically (configurable).
|
|
||||||
* Added: release zips are now created automatically in your build output folder (configurable).
|
|
||||||
* Added: mod deploy and release zips now exclude Json.NET automatically, since it's provided by SMAPI.
|
|
||||||
* Added mod's version to release zip filename.
|
|
||||||
* Improved errors to simplify troubleshooting.
|
|
||||||
* Fixed release zip not having a mod folder.
|
|
||||||
* Fixed release zip failing if mod name contains characters that aren't valid in a filename.
|
|
||||||
|
|
||||||
### 1.7.1
|
|
||||||
* Fixed issue where i18n folders were flattened.
|
|
||||||
* The manifest/i18n files in the project now take precedence over those in the build output if both
|
|
||||||
are present.
|
|
||||||
|
|
||||||
### 1.7
|
|
||||||
* Added option to create release zips on build.
|
|
||||||
* Added reference to XNA's XACT library for audio-related mods.
|
|
||||||
|
|
||||||
### 1.6
|
|
||||||
* Added support for deploying mod files into `Mods` automatically.
|
|
||||||
* Added a build error if a game folder is found, but doesn't contain Stardew Valley or SMAPI.
|
|
||||||
|
|
||||||
### 1.5
|
|
||||||
* Added support for setting a custom game path globally.
|
|
||||||
* Added default GOG path on Mac.
|
|
||||||
|
|
||||||
### 1.4
|
|
||||||
* Fixed detection of non-default game paths on 32-bit Windows.
|
|
||||||
* Removed support for SilVerPLuM (discontinued).
|
|
||||||
* Removed support for overriding the target platform (no longer needed since SMAPI crossplatforms
|
|
||||||
mods automatically).
|
|
||||||
|
|
||||||
### 1.3
|
|
||||||
* Added support for non-default game paths on Windows.
|
|
||||||
|
|
||||||
### 1.2
|
|
||||||
* Exclude game binaries from mod build output.
|
|
||||||
|
|
||||||
### 1.1
|
|
||||||
* Added support for overriding the target platform.
|
|
||||||
|
|
||||||
### 1.0
|
|
||||||
* Initial release.
|
|
||||||
* Added support for detecting the game path automatically.
|
|
||||||
* Added support for injecting XNA/MonoGame references automatically based on the OS.
|
|
||||||
* Added support for mod builders like SilVerPLuM.
|
|
||||||
|
|
|
@ -1,4 +1,216 @@
|
||||||
|
← [README](README.md)
|
||||||
|
|
||||||
# Release notes
|
# Release notes
|
||||||
|
## 3.1
|
||||||
|
Released 05 January 2019 for Stardew Valley 1.4 or later.
|
||||||
|
|
||||||
|
* For players:
|
||||||
|
* Added separate group in 'skipped mods' list for broken dependencies, so it's easier to see what to fix first.
|
||||||
|
* Added friendly log message for save file-not-found errors.
|
||||||
|
* Updated for gamepad modes in Stardew Valley 1.4.1.
|
||||||
|
* Improved performance in some cases.
|
||||||
|
* Fixed compatibility with Linux Mint 18 (thanks to techge!), Arch Linux, and Linux systems with libhybris-utils installed.
|
||||||
|
* Fixed memory leak when repeatedly loading a save and returning to title.
|
||||||
|
* Fixed memory leak when mods reload assets.
|
||||||
|
* Fixes for Console Commands mod:
|
||||||
|
* added new clothing items;
|
||||||
|
* fixed spawning new flooring and rings (thanks to Mizzion!);
|
||||||
|
* fixed spawning custom rings added by mods;
|
||||||
|
* Fixed errors when some item data is invalid.
|
||||||
|
* Updated translations. Thanks to L30Bola (added Portuguese), PlussRolf (added Spanish), and shirutan (added Japanese)!
|
||||||
|
|
||||||
|
* For the web UI:
|
||||||
|
* Added option to edit & reupload in the JSON validator.
|
||||||
|
* File uploads are now stored in Azure storage instead of Pastebin, due to ongoing Pastebin perfomance issues.
|
||||||
|
* File uploads now expire after one month.
|
||||||
|
* Updated the JSON validator for Content Patcher 1.10 and 1.11.
|
||||||
|
* Fixed JSON validator no longer letting you change format when viewing a file.
|
||||||
|
* Fixed JSON validator for Content Patcher not requiring `Default` if `AllowBlank` was omitted.
|
||||||
|
* Fixed log parser not correctly handling content packs with no author (thanks to danvolchek!).
|
||||||
|
* Fixed main sidebar link pointing to wiki instead of home page.
|
||||||
|
|
||||||
|
* For modders:
|
||||||
|
* Added `World.ChestInventoryChanged` event (thanks to collaboration with wartech0!).
|
||||||
|
* Added asset propagation for...
|
||||||
|
* grass textures;
|
||||||
|
* winter flooring textures;
|
||||||
|
* `Data\Bundles` changes (for added bundles only);
|
||||||
|
* `Characters\Farmer\farmer_girl_base_bald`.
|
||||||
|
* Added paranoid-mode warning for direct `Console` access.
|
||||||
|
* Improved error messages for `TargetParameterCountException` when using the reflection API.
|
||||||
|
* `helper.Read/WriteSaveData` can now be used while a save is being loaded (e.g. within a `Specialized.LoadStageChanged` event).
|
||||||
|
* Removed `DumpMetadata` option. It was only for specific debugging cases, but players would sometimes enable it incorrectly and then report crashes.
|
||||||
|
* Fixed private textures loaded from content packs not having their `Name` field set.
|
||||||
|
|
||||||
|
* For SMAPI developers:
|
||||||
|
* You can now run local environments without configuring Amazon, Azure, MongoDB, and Pastebin accounts.
|
||||||
|
|
||||||
|
## 3.0.1
|
||||||
|
Released 02 December 2019 for Stardew Valley 1.4 or later.
|
||||||
|
|
||||||
|
* For players:
|
||||||
|
* Updated for Stardew Valley 1.4.0.1.
|
||||||
|
* Improved compatibility with some Linux terminals (thanks to archification and DanielHeath!).
|
||||||
|
* Updated translations. Thanks to berkayylmao (added Turkish), feathershine (added Chinese), and Osiris901 (added Russian)!
|
||||||
|
|
||||||
|
* For the web UI:
|
||||||
|
* Rebuilt web infrastructure to handle higher traffic.
|
||||||
|
* If a log can't be uploaded to Pastebin (e.g. due to rate limits), it's now uploaded to Amazon S3 instead. Logs uploaded to S3 expire after one month.
|
||||||
|
* Fixed JSON validator not letting you drag & drop a file.
|
||||||
|
|
||||||
|
* For modders:
|
||||||
|
* `SemanticVersion` now supports [semver 2.0](https://semver.org/) build metadata.
|
||||||
|
|
||||||
|
## 3.0
|
||||||
|
Released 26 November 2019 for Stardew Valley 1.4.
|
||||||
|
|
||||||
|
### Release highlights
|
||||||
|
For players:
|
||||||
|
* **Updated for Stardew Valley 1.4.**
|
||||||
|
SMAPI 3.0 adds compatibility with the latest game version, and improves mod APIs for changes in
|
||||||
|
the game code.
|
||||||
|
|
||||||
|
* **Improved performance.**
|
||||||
|
SMAPI should have less impact on game performance and startup time for some players.
|
||||||
|
|
||||||
|
* **Automatic save fixing and more error recovery.**
|
||||||
|
SMAPI now detects and prevents more crashes due to game/mod bugs, and automatically fixes your
|
||||||
|
save if you remove some custom-content mods.
|
||||||
|
|
||||||
|
* **Improved mod scanning.**
|
||||||
|
SMAPI now supports some non-standard mod structures automatically, improves compatibility with
|
||||||
|
the Vortex mod manager, and improves various error/skip messages related to mod loading.
|
||||||
|
|
||||||
|
* **Overhauled update checks.**
|
||||||
|
SMAPI update checks are now handled entirely on the web server and support community-defined
|
||||||
|
version mappings. In particular, false update alerts due to author mistakes can now be solved by
|
||||||
|
the community for all players.
|
||||||
|
|
||||||
|
* **Fixed many bugs and edge cases.**
|
||||||
|
|
||||||
|
For modders:
|
||||||
|
* **New event system.**
|
||||||
|
SMAPI 3.0 removes the deprecated static events in favor of the new `helper.Events` API. The event
|
||||||
|
engine is rewritten to make events more efficient, add events that weren't possible before, make
|
||||||
|
existing events more useful, and make event usage and behavior more consistent. When a mod makes
|
||||||
|
changes in an event handler, those changes are now also reflected in the next event raise.
|
||||||
|
|
||||||
|
* **Improved mod build package.**
|
||||||
|
The [mod build package](https://www.nuget.org/packages/Pathoschild.Stardew.ModBuildConfig) now
|
||||||
|
includes the `assets` folder by default if present, supports the new `.csproj` project format,
|
||||||
|
enables mod `.pdb` files automatically (to provide line numbers in error messages), adds optional
|
||||||
|
Harmony support, and fixes some bugs and edge cases. This also adds compatibility with SMAPI 3.0
|
||||||
|
and Stardew Valley 1.4, and drops support for older versions.
|
||||||
|
|
||||||
|
* **Mods loaded earlier.**
|
||||||
|
SMAPI now loads mods much earlier, before the game is initialised. That lets mods do things that
|
||||||
|
were difficult before, like intercepting some core assets.
|
||||||
|
|
||||||
|
* **Improved Android support.**
|
||||||
|
SMAPI now automatically detects when it's running on Android, and updates `Constants.TargetPlatform`
|
||||||
|
so mods can adjust their logic if needed. The Save Backup mod is also now Android-compatible.
|
||||||
|
|
||||||
|
* **Improved asset propagation.**
|
||||||
|
SMAPI now automatically propagates asset changes for farm animal data, NPC default location data,
|
||||||
|
critter textures, and `DayTimeMoneyBox` buttons. Every loaded texture now also has a `Name` field
|
||||||
|
so mods can check which asset a texture was loaded for.
|
||||||
|
|
||||||
|
* **Breaking changes:**
|
||||||
|
See _[migrate to SMAPI 3.0](https://stardewvalleywiki.com/Modding:Migrate_to_SMAPI_3.0)_ and
|
||||||
|
_[migrate to Stardew Valley 1.4](https://stardewvalleywiki.com/Modding:Migrate_to_Stardew_Valley_1.4)_
|
||||||
|
for more info.
|
||||||
|
|
||||||
|
### For players
|
||||||
|
* Changes:
|
||||||
|
* Updated for Stardew Valley 1.4.
|
||||||
|
* Improved performance.
|
||||||
|
* Reworked update checks and added community-defined version mapping, to reduce false update alerts due to author mistakes.
|
||||||
|
* SMAPI now removes invalid locations/NPCs when loading a save to prevent crashes. A warning is shown in-game when this happens.
|
||||||
|
* Added update checks for CurseForge mods.
|
||||||
|
* Added support for editing console colors via `smapi-internal/config.json` (for players with unusual consoles).
|
||||||
|
* Added support for setting SMAPI CLI arguments as environment variables for Linux/macOS compatibility.
|
||||||
|
* Improved mod scanning:
|
||||||
|
* Now ignores metadata files/folders (like `__MACOSX` and `__folder_managed_by_vortex`) and content files (like `.txt` or `.png`), which avoids missing-manifest errors in some cases.
|
||||||
|
* Now detects XNB mods more accurately, and consolidates multi-folder XNB mods in logged messages.
|
||||||
|
* Improved launch script compatibility on Linux (thanks to kurumushi and toastal!).
|
||||||
|
* Made error messages more user-friendly in some cases.
|
||||||
|
* Save Backup now works in the background, to avoid affecting startup time for players with a large number of saves.
|
||||||
|
* The installer now recognises custom game paths stored in [`stardewvalley.targets`](http://smapi.io/package/custom-game-path).
|
||||||
|
* Duplicate-mod errors now show the mod version in each folder.
|
||||||
|
* Update checks are now faster in some cases.
|
||||||
|
* Updated mod compatibility list.
|
||||||
|
* Updated SMAPI/game version map.
|
||||||
|
* Updated translations. Thanks to eren-kemer (added German)!
|
||||||
|
* Fixes:
|
||||||
|
* Fixed some assets not updated when you switch language to English.
|
||||||
|
* Fixed lag in some cases due to incorrect asset caching when playing in non-English.
|
||||||
|
* Fixed lag when a mod invalidates many NPC portraits/sprites at once.
|
||||||
|
* Fixed Console Commands not including upgraded tools in item commands.
|
||||||
|
* Fixed Console Commands' item commands failing if a mod adds invalid item data.
|
||||||
|
* Fixed Save Backup not pruning old backups if they're uncompressed.
|
||||||
|
* Fixed issues when a farmhand reconnects before the game notices they're disconnected.
|
||||||
|
* Fixed 'received message' logs shown in non-developer mode.
|
||||||
|
* Fixed various error messages and inconsistent spelling.
|
||||||
|
* Fixed update-check error if a Nexus mod is marked as adult content.
|
||||||
|
* Fixed update-check error if the Chucklefish page for an update key doesn't exist.
|
||||||
|
|
||||||
|
### For the web UI
|
||||||
|
* Mod compatibility list:
|
||||||
|
* Added support for CurseForge mods.
|
||||||
|
* Added metadata links and dev notes (if any) to advanced info.
|
||||||
|
* Now loads faster (since data is fetched in a background service).
|
||||||
|
* Now continues working with cached data when the wiki is offline.
|
||||||
|
* Clicking a mod link now automatically adds it to the visible mods if the list is filtered.
|
||||||
|
|
||||||
|
* JSON validator:
|
||||||
|
* Added JSON validator at [smapi.io/json](https://smapi.io/json), which lets you validate a JSON file against predefined mod formats.
|
||||||
|
* Added support for the `manifest.json` format.
|
||||||
|
* Added support for the Content Patcher format (thanks to TehPers!).
|
||||||
|
* Added support for referencing a schema in a JSON Schema-compatible text editor.
|
||||||
|
|
||||||
|
* For the log parser:
|
||||||
|
* Added instructions for Android.
|
||||||
|
* The page now detects your OS and preselects the right instructions (thanks to danvolchek!).
|
||||||
|
|
||||||
|
### For modders
|
||||||
|
* Breaking changes:
|
||||||
|
* Mods are now loaded much earlier in the game launch. This lets mods intercept any content asset, but the game is not fully initialized when `Entry` is called; use the `GameLaunched` event if you need to run code when the game is initialized.
|
||||||
|
* Removed all deprecated APIs.
|
||||||
|
* Removed unused APIs: `Monitor.ExitGameImmediately`, `Translation.ModName`, and `Translation.Assert`.
|
||||||
|
* Fixed `ICursorPosition.AbsolutePixels` not adjusted for zoom.
|
||||||
|
* `SemanticVersion` no longer omits `.0` patch numbers when formatting versions, for better [semver](https://semver.org/) conformity (e.g. `3.0` is now formatted as `3.0.0`).
|
||||||
|
* Changes:
|
||||||
|
* Added support for content pack translations.
|
||||||
|
* Added `IContentPack.HasFile`, `Context.IsGameLaunched`, and `SemanticVersion.TryParse`.
|
||||||
|
* Added separate `LogNetworkTraffic` option to make verbose logging less overwhelmingly verbose.
|
||||||
|
* Added asset propagation for `Data\FarmAnimals`, critter textures, and `DayTimeMoneyBox` buttons.
|
||||||
|
* Added `Texture2D.Name` values set to the asset key.
|
||||||
|
* Added trace logs for skipped loose files in the `Mods` folder and custom SMAPI settings so it's easier to troubleshoot player logs.
|
||||||
|
* `Constants.TargetPlatform` now returns `Android` when playing on an Android device.
|
||||||
|
* Trace logs for a broken mod now list all detected issues (instead of the first one).
|
||||||
|
* Trace logs when loading mods are now more clear.
|
||||||
|
* Clarified update-check errors for mods with multiple update keys.
|
||||||
|
* Updated dependencies (including Json.NET 11.0.2 → 12.0.3 and Mono.Cecil 0.10.1 → 0.11.1).
|
||||||
|
* Fixes:
|
||||||
|
* Fixed map reloads resetting tilesheet seasons.
|
||||||
|
* Fixed map reloads not updating door warps.
|
||||||
|
* Fixed outdoor tilesheets being seasonalised when added to an indoor location.
|
||||||
|
* Fixed mods needing to load custom `Map` assets before the game accesses them. SMAPI now does so automatically.
|
||||||
|
* Fixed custom maps loaded from `.xnb` files not having their tilesheet paths automatically adjusted.
|
||||||
|
* Fixed custom maps loaded from the mod folder with tilesheets in a subfolder not working crossplatform. All tilesheet paths are now normalized for the OS automatically.
|
||||||
|
* Fixed issue where mod changes weren't tracked correctly for raising events in some cases. Events now reflect a frozen snapshot of the game state, and any mod changes are reflected in the next event tick.
|
||||||
|
* Fixed issue where, when a mod's `IAssetEditor` uses `asset.ReplaceWith` on a texture asset while playing in non-English, any changes from that point forth wouldn't affect subsequent cached asset loads.
|
||||||
|
* Fixed asset propagation for NPC portraits resetting any unique portraits (e.g. Maru's hospital portrait) to the default.
|
||||||
|
* Fixed changes to `Data\NPCDispositions` not always propagated correctly to existing NPCs.
|
||||||
|
* Fixed `Rendering`/`Rendered` events not raised during minigames.
|
||||||
|
* Fixed `LoadStageChanged` event not raising correct flags in some cases when creating a new save.
|
||||||
|
* Fixed `GetApi` without an interface not checking if all mods are loaded.
|
||||||
|
|
||||||
|
### For SMAPI maintainers
|
||||||
|
* Added support for core translation files.
|
||||||
|
* Migrated to new `.csproj` format.
|
||||||
|
* Internal refactoring.
|
||||||
|
|
||||||
## 2.11.3
|
## 2.11.3
|
||||||
Released 13 September 2019 for Stardew Valley 1.3.36.
|
Released 13 September 2019 for Stardew Valley 1.3.36.
|
||||||
|
|
||||||
|
@ -12,11 +224,14 @@ Released 13 September 2019 for Stardew Valley 1.3.36.
|
||||||
* For the web UI:
|
* For the web UI:
|
||||||
* When filtering the mod list, clicking a mod link now automatically adds it to the visible mods.
|
* When filtering the mod list, clicking a mod link now automatically adds it to the visible mods.
|
||||||
* Added log parser instructions for Android.
|
* Added log parser instructions for Android.
|
||||||
* Fixed log parser failing in some cases due to time format localisation.
|
* Fixed log parser failing in some cases due to time format localization.
|
||||||
|
|
||||||
* For modders:
|
* For modders:
|
||||||
* `this.Monitor.Log` now defaults to the `Trace` log level instead of `Debug`. The change will only take effect when you recompile the mod.
|
* `this.Monitor.Log` now defaults to the `Trace` log level instead of `Debug`. The change will only take effect when you recompile the mod.
|
||||||
* Fixed 'location list changed' verbose log not correctly listing changes.
|
* Fixed 'location list changed' verbose log not correctly listing changes.
|
||||||
|
* Fixed mods able to directly load (and in some cases edit) a different mod's local assets using internal asset key forwarding.
|
||||||
|
* Fixed changes to a map loaded by a mod being persisted across content managers.
|
||||||
|
* Fixed `SDate.AddDays` incorrectly changing year when the result is exactly winter 28.
|
||||||
|
|
||||||
## 2.11.2
|
## 2.11.2
|
||||||
Released 23 April 2019 for Stardew Valley 1.3.36.
|
Released 23 April 2019 for Stardew Valley 1.3.36.
|
||||||
|
@ -77,12 +292,12 @@ Released 09 January 2019 for Stardew Valley 1.3.32–33.
|
||||||
* Added locale to context trace logs.
|
* Added locale to context trace logs.
|
||||||
* Fixed error loading custom map tilesheets in some cases.
|
* Fixed error loading custom map tilesheets in some cases.
|
||||||
* Fixed error when swapping maps mid-session for a location with interior doors.
|
* Fixed error when swapping maps mid-session for a location with interior doors.
|
||||||
* Fixed `Constants.SaveFolderName` and `CurrentSavePath` not available during early load stages when using `Specialised.LoadStageChanged` event.
|
* Fixed `Constants.SaveFolderName` and `CurrentSavePath` not available during early load stages when using `Specialized.LoadStageChanged` event.
|
||||||
* Fixed `LoadStage.SaveParsed` raised before the parsed save data is available.
|
* Fixed `LoadStage.SaveParsed` raised before the parsed save data is available.
|
||||||
* Fixed 'unknown mod' deprecation warnings showing the wrong stack trace.
|
* Fixed 'unknown mod' deprecation warnings showing the wrong stack trace.
|
||||||
* Fixed `e.Cursor` in input events showing wrong grab tile when player using a controller moves without moving the viewpoint.
|
* Fixed `e.Cursor` in input events showing wrong grab tile when player using a controller moves without moving the viewpoint.
|
||||||
* Fixed incorrect 'bypassed safety checks' warning for mods using the new `Specialised.LoadStageChanged` event in 2.10.
|
* Fixed incorrect 'bypassed safety checks' warning for mods using the new `Specialized.LoadStageChanged` event in 2.10.
|
||||||
* Deprecated `EntryDll` values whose capitalisation don't match the actual file. (This works on Windows, but causes errors for Linux/Mac players.)
|
* Deprecated `EntryDll` values whose capitalization don't match the actual file. (This works on Windows, but causes errors for Linux/Mac players.)
|
||||||
|
|
||||||
## 2.10.1
|
## 2.10.1
|
||||||
Released 30 December 2018 for Stardew Valley 1.3.32–33.
|
Released 30 December 2018 for Stardew Valley 1.3.32–33.
|
||||||
|
@ -99,9 +314,9 @@ Released 29 December 2018 for Stardew Valley 1.3.32–33.
|
||||||
* Tweaked installer to reduce antivirus false positives.
|
* Tweaked installer to reduce antivirus false positives.
|
||||||
|
|
||||||
* For modders:
|
* For modders:
|
||||||
* Added [events](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Events): `GameLoop.OneSecondUpdateTicking`, `GameLoop.OneSecondUpdateTicked`, and `Specialised.LoadStageChanged`.
|
* Added [events](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Events): `GameLoop.OneSecondUpdateTicking`, `GameLoop.OneSecondUpdateTicked`, and `Specialized.LoadStageChanged`.
|
||||||
* Added `e.IsCurrentLocation` event arg to `World` events.
|
* Added `e.IsCurrentLocation` event arg to `World` events.
|
||||||
* You can now use `helper.Data.Read/WriteSaveData` as soon as the save is loaded (instead of once the world is initialised).
|
* You can now use `helper.Data.Read/WriteSaveData` as soon as the save is loaded (instead of once the world is initialized).
|
||||||
* Increased deprecation levels to _info_ for the upcoming SMAPI 3.0.
|
* Increased deprecation levels to _info_ for the upcoming SMAPI 3.0.
|
||||||
|
|
||||||
* For the web UI:
|
* For the web UI:
|
||||||
|
@ -211,7 +426,7 @@ Released 19 November 2018 for Stardew Valley 1.3.32.
|
||||||
* Updated compatibility list.
|
* Updated compatibility list.
|
||||||
|
|
||||||
* For the web UI:
|
* For the web UI:
|
||||||
* Added a [mod compatibility page](https://mods.smapi.io) and [privacy page](https://smapi.io/privacy).
|
* Added a [mod compatibility page](https://smapi.io/mods) and [privacy page](https://smapi.io/privacy).
|
||||||
* The log parser now has a separate filter for game messages.
|
* The log parser now has a separate filter for game messages.
|
||||||
* The log parser now shows content pack authors (thanks to danvolchek!).
|
* The log parser now shows content pack authors (thanks to danvolchek!).
|
||||||
* Tweaked log parser UI (thanks to danvolchek!).
|
* Tweaked log parser UI (thanks to danvolchek!).
|
||||||
|
@ -274,7 +489,7 @@ Released 14 August 2018 for Stardew Valley 1.3.28.
|
||||||
* dialogue;
|
* dialogue;
|
||||||
* map tilesheets.
|
* map tilesheets.
|
||||||
* Added `--mods-path` CLI command-line argument to switch between mod folders.
|
* Added `--mods-path` CLI command-line argument to switch between mod folders.
|
||||||
* All enums are now JSON-serialised by name instead of numeric value. (Previously only a few enums were serialised that way. JSON files which already have numeric enum values will still be parsed fine.)
|
* All enums are now JSON-serialized by name instead of numeric value. (Previously only a few enums were serialized that way. JSON files which already have numeric enum values will still be parsed fine.)
|
||||||
* Fixed false compatibility error when constructing multidimensional arrays.
|
* Fixed false compatibility error when constructing multidimensional arrays.
|
||||||
* Fixed `.ToSButton()` methods not being public.
|
* Fixed `.ToSButton()` methods not being public.
|
||||||
|
|
||||||
|
@ -301,7 +516,7 @@ Released 01 August 2018 for Stardew Valley 1.3.27.
|
||||||
* Improved the Console Commands mod:
|
* Improved the Console Commands mod:
|
||||||
* Added `player_add name`, which adds items to your inventory by name instead of ID.
|
* Added `player_add name`, which adds items to your inventory by name instead of ID.
|
||||||
* Fixed `world_setseason` not running season-change logic.
|
* Fixed `world_setseason` not running season-change logic.
|
||||||
* Fixed `world_setseason` not normalising the season value.
|
* Fixed `world_setseason` not normalizing the season value.
|
||||||
* Fixed `world_settime` sometimes breaking NPC schedules (e.g. so they stay in bed).
|
* Fixed `world_settime` sometimes breaking NPC schedules (e.g. so they stay in bed).
|
||||||
* Removed the `player_setlevel` and `player_setspeed` commands, which weren't implemented in a useful way. Use a mod like CJB Cheats Menu if you need those.
|
* Removed the `player_setlevel` and `player_setspeed` commands, which weren't implemented in a useful way. Use a mod like CJB Cheats Menu if you need those.
|
||||||
* Fixed `SEHException` errors for some players.
|
* Fixed `SEHException` errors for some players.
|
||||||
|
@ -392,10 +607,10 @@ Released 11 April 2018 for Stardew Valley 1.2.30–1.2.33.
|
||||||
* Fixed mod update alerts not shown if one mod has an invalid remote version.
|
* Fixed mod update alerts not shown if one mod has an invalid remote version.
|
||||||
* Fixed SMAPI update alerts linking to the GitHub repository instead of [smapi.io](https://smapi.io).
|
* Fixed SMAPI update alerts linking to the GitHub repository instead of [smapi.io](https://smapi.io).
|
||||||
* Fixed SMAPI update alerts for draft releases.
|
* Fixed SMAPI update alerts for draft releases.
|
||||||
* Fixed error when two content packs use different capitalisation for the same required mod ID.
|
* Fixed error when two content packs use different capitalization for the same required mod ID.
|
||||||
* Fixed rare crash if the game duplicates an item.
|
* Fixed rare crash if the game duplicates an item.
|
||||||
|
|
||||||
* For the [log parser](https://log.smapi.io):
|
* For the [log parser](https://smapi.io/log):
|
||||||
* Tweaked UI.
|
* Tweaked UI.
|
||||||
|
|
||||||
## 2.5.4
|
## 2.5.4
|
||||||
|
@ -407,7 +622,7 @@ Released 26 March 2018 for Stardew Valley 1.2.30–1.2.33.
|
||||||
* Fixed error when mods remove an asset editor/loader.
|
* Fixed error when mods remove an asset editor/loader.
|
||||||
* Fixed minimum game version incorrectly increased in SMAPI 2.5.3.
|
* Fixed minimum game version incorrectly increased in SMAPI 2.5.3.
|
||||||
|
|
||||||
* For the [log parser](https://log.smapi.io):
|
* For the [log parser](https://smapi.io/log):
|
||||||
* Fixed error when log text contains certain tokens.
|
* Fixed error when log text contains certain tokens.
|
||||||
|
|
||||||
* For modders:
|
* For modders:
|
||||||
|
@ -429,7 +644,7 @@ Released 13 March 2018 for Stardew Valley ~~1.2.30~~–1.2.33.
|
||||||
* Fixed Linux ["magic number is wrong" errors](https://github.com/mono/mono/issues/6752) by changing default terminal order.
|
* Fixed Linux ["magic number is wrong" errors](https://github.com/mono/mono/issues/6752) by changing default terminal order.
|
||||||
* Updated compatibility list and added update checks for more mods.
|
* Updated compatibility list and added update checks for more mods.
|
||||||
|
|
||||||
* For the [log parser](https://log.smapi.io):
|
* For the [log parser](https://smapi.io/log):
|
||||||
* Fixed incorrect filtering in some cases.
|
* Fixed incorrect filtering in some cases.
|
||||||
* Fixed error if mods have duplicate names.
|
* Fixed error if mods have duplicate names.
|
||||||
* Fixed parse bugs if a mod has no author name.
|
* Fixed parse bugs if a mod has no author name.
|
||||||
|
@ -443,7 +658,7 @@ Released 25 February 2018 for Stardew Valley 1.2.30–1.2.33.
|
||||||
* For modders:
|
* For modders:
|
||||||
* Fixed issue where replacing an asset through `asset.AsImage()` or `asset.AsDictionary()` didn't take effect.
|
* Fixed issue where replacing an asset through `asset.AsImage()` or `asset.AsDictionary()` didn't take effect.
|
||||||
|
|
||||||
* For the [log parser](https://log.smapi.io):
|
* For the [log parser](https://smapi.io/log):
|
||||||
* Fixed blank page after uploading a log in some cases.
|
* Fixed blank page after uploading a log in some cases.
|
||||||
|
|
||||||
## 2.5.1
|
## 2.5.1
|
||||||
|
@ -467,14 +682,14 @@ Released 24 February 2018 for Stardew Valley 1.2.30–1.2.33.
|
||||||
* For modders:
|
* For modders:
|
||||||
* Added support for content packs and new APIs to read them.
|
* Added support for content packs and new APIs to read them.
|
||||||
* Added support for `ISemanticVersion` in JSON models.
|
* Added support for `ISemanticVersion` in JSON models.
|
||||||
* Added `SpecialisedEvents.UnvalidatedUpdateTick` event for specialised use cases.
|
* Added `SpecializedEvents.UnvalidatedUpdateTick` event for specialized use cases.
|
||||||
* Added path normalising to `ReadJsonFile` and `WriteJsonFile` helpers (so no longer need `Path.Combine` with those).
|
* Added path normalizing to `ReadJsonFile` and `WriteJsonFile` helpers (so no longer need `Path.Combine` with those).
|
||||||
* Fixed deadlock in rare cases with asset loaders.
|
* Fixed deadlock in rare cases with asset loaders.
|
||||||
* Fixed unhelpful error when a mod exposes a non-public API.
|
* Fixed unhelpful error when a mod exposes a non-public API.
|
||||||
* Fixed unhelpful error when a translation file has duplicate keys due to case-insensitivity.
|
* Fixed unhelpful error when a translation file has duplicate keys due to case-insensitivity.
|
||||||
* Fixed some JSON field names being case-sensitive.
|
* Fixed some JSON field names being case-sensitive.
|
||||||
|
|
||||||
* For the [log parser](https://log.smapi.io):
|
* For the [log parser](https://smapi.io/log):
|
||||||
* Added support for SMAPI 2.5 content packs.
|
* Added support for SMAPI 2.5 content packs.
|
||||||
* Reduced download size when viewing a parsed log with repeated errors.
|
* Reduced download size when viewing a parsed log with repeated errors.
|
||||||
* Improved parse error handling.
|
* Improved parse error handling.
|
||||||
|
@ -495,7 +710,7 @@ Released 24 January 2018 for Stardew Valley 1.2.30–1.2.33.
|
||||||
* Fixed intermittent errors (e.g. 'collection has been modified') with some mods when loading a save.
|
* Fixed intermittent errors (e.g. 'collection has been modified') with some mods when loading a save.
|
||||||
* Fixed compatibility with Linux Terminator terminal.
|
* Fixed compatibility with Linux Terminator terminal.
|
||||||
|
|
||||||
* For the [log parser](https://log.smapi.io):
|
* For the [log parser](https://smapi.io/log):
|
||||||
* Fixed error parsing logs with zero installed mods.
|
* Fixed error parsing logs with zero installed mods.
|
||||||
|
|
||||||
* For modders:
|
* For modders:
|
||||||
|
@ -521,16 +736,16 @@ Released 26 December 2017 for Stardew Valley 1.2.30–1.2.33.
|
||||||
|
|
||||||
* For modders:
|
* For modders:
|
||||||
* **Added mod-provided APIs** to allow simple integrations between mods, even without direct assembly references.
|
* **Added mod-provided APIs** to allow simple integrations between mods, even without direct assembly references.
|
||||||
* Added `GameEvents.FirstUpdateTick` event (called once after all mods are initialised).
|
* Added `GameEvents.FirstUpdateTick` event (called once after all mods are initialized).
|
||||||
* Added `IsSuppressed` to input events so mods can optionally avoid handling keys another mod has already handled.
|
* Added `IsSuppressed` to input events so mods can optionally avoid handling keys another mod has already handled.
|
||||||
* Added trace message for mods with no update keys.
|
* Added trace message for mods with no update keys.
|
||||||
* Adjusted reflection API to match actual usage (e.g. renamed `GetPrivate*` to `Get*`), and deprecated previous methods.
|
* Adjusted reflection API to match actual usage (e.g. renamed `GetPrivate*` to `Get*`), and deprecated previous methods.
|
||||||
* Fixed `GraphicsEvents.OnPostRenderEvent` not being raised in some specialised cases.
|
* Fixed `GraphicsEvents.OnPostRenderEvent` not being raised in some specialized cases.
|
||||||
* Fixed reflection API error for properties missing a `get` and `set`.
|
* Fixed reflection API error for properties missing a `get` and `set`.
|
||||||
* Fixed issue where a mod could change the cursor position reported to other mods.
|
* Fixed issue where a mod could change the cursor position reported to other mods.
|
||||||
* Updated compatibility list.
|
* Updated compatibility list.
|
||||||
|
|
||||||
* For the [log parser](https://log.smapi.io):
|
* For the [log parser](https://smapi.io/log):
|
||||||
* Fixed broken favicon.
|
* Fixed broken favicon.
|
||||||
|
|
||||||
## 2.2
|
## 2.2
|
||||||
|
@ -544,13 +759,13 @@ Released 02 December 2017 for Stardew Valley 1.2.30–1.2.33.
|
||||||
* Improved error when a mod has an invalid `EntryDLL` filename format.
|
* Improved error when a mod has an invalid `EntryDLL` filename format.
|
||||||
* Updated compatibility list.
|
* Updated compatibility list.
|
||||||
|
|
||||||
* For the [log parser](https://log.smapi.io):
|
* For the [log parser](https://smapi.io/log):
|
||||||
* Logs no longer expire after a week.
|
* Logs no longer expire after a week.
|
||||||
* Fixed error when uploading very large logs.
|
* Fixed error when uploading very large logs.
|
||||||
* Slightly improved the UI.
|
* Slightly improved the UI.
|
||||||
|
|
||||||
* For modders:
|
* For modders:
|
||||||
* Added `helper.Content.NormaliseAssetName` method.
|
* Added `helper.Content.NormalizeAssetName` method.
|
||||||
* Added `SDate.DaysSinceStart` property.
|
* Added `SDate.DaysSinceStart` property.
|
||||||
* Fixed input events' `e.SuppressButton(button)` method ignoring specified button.
|
* Fixed input events' `e.SuppressButton(button)` method ignoring specified button.
|
||||||
* Fixed input events' `e.SuppressButton()` method not working with mouse buttons.
|
* Fixed input events' `e.SuppressButton()` method not working with mouse buttons.
|
||||||
|
@ -559,7 +774,7 @@ Released 02 December 2017 for Stardew Valley 1.2.30–1.2.33.
|
||||||
Released 01 November 2017 for Stardew Valley 1.2.30–1.2.33.
|
Released 01 November 2017 for Stardew Valley 1.2.30–1.2.33.
|
||||||
|
|
||||||
* For players:
|
* For players:
|
||||||
* Added a [log parser](https://log.smapi.io) site.
|
* Added a [log parser](https://smapi.io/log) site.
|
||||||
* Added better Steam instructions to the SMAPI installer.
|
* Added better Steam instructions to the SMAPI installer.
|
||||||
* Renamed the bundled _TrainerMod_ to _ConsoleCommands_ to make its purpose clearer.
|
* Renamed the bundled _TrainerMod_ to _ConsoleCommands_ to make its purpose clearer.
|
||||||
* Removed the game's test messages from the console log.
|
* Removed the game's test messages from the console log.
|
||||||
|
@ -634,7 +849,7 @@ Released 14 October 2017 for Stardew Valley 1.2.30–1.2.33.
|
||||||
|
|
||||||
* **Command-line install**
|
* **Command-line install**
|
||||||
For power users and mod managers, the SMAPI installer can now be scripted using command-line arguments
|
For power users and mod managers, the SMAPI installer can now be scripted using command-line arguments
|
||||||
(see [technical docs](technical-docs.md#command-line-arguments)).
|
(see [technical docs](technical/smapi.md#command-line-arguments)).
|
||||||
|
|
||||||
### Change log
|
### Change log
|
||||||
For players:
|
For players:
|
||||||
|
@ -705,7 +920,7 @@ For mod developers:
|
||||||
* Added content helper properties for the game's current language.
|
* Added content helper properties for the game's current language.
|
||||||
* Fixed `Context.IsPlayerFree` being false if the player is performing an action.
|
* Fixed `Context.IsPlayerFree` being false if the player is performing an action.
|
||||||
* Fixed `GraphicsEvents.Resize` being raised before the game updates its window data.
|
* Fixed `GraphicsEvents.Resize` being raised before the game updates its window data.
|
||||||
* Fixed `SemanticVersion` not being deserialisable through Json.NET.
|
* Fixed `SemanticVersion` not being deserializable through Json.NET.
|
||||||
* Fixed terminal not launching on Xfce Linux.
|
* Fixed terminal not launching on Xfce Linux.
|
||||||
|
|
||||||
For SMAPI developers:
|
For SMAPI developers:
|
||||||
|
@ -776,7 +991,7 @@ For modders:
|
||||||
* SMAPI now automatically fixes tilesheet references for maps loaded from the mod folder.
|
* SMAPI now automatically fixes tilesheet references for maps loaded from the mod folder.
|
||||||
<small>_When loading a map from the mod folder, SMAPI will automatically use tilesheets relative to the map file if they exists. Otherwise it will default to tilesheets in the game content._</small>
|
<small>_When loading a map from the mod folder, SMAPI will automatically use tilesheets relative to the map file if they exists. Otherwise it will default to tilesheets in the game content._</small>
|
||||||
* Added `Context.IsPlayerFree` for mods that need to check if the player can act (i.e. save is loaded, no menu is displayed, no cutscene is in progress, etc).
|
* Added `Context.IsPlayerFree` for mods that need to check if the player can act (i.e. save is loaded, no menu is displayed, no cutscene is in progress, etc).
|
||||||
* Added `Context.IsInDrawLoop` for specialised mods.
|
* Added `Context.IsInDrawLoop` for specialized mods.
|
||||||
* Fixed `smapi-crash.txt` being copied from the default log even if a different path is specified with `--log-path`.
|
* Fixed `smapi-crash.txt` being copied from the default log even if a different path is specified with `--log-path`.
|
||||||
* Fixed the content API not matching XNB filenames with two dots (like `a.b.xnb`) if you don't specify the `.xnb` extension.
|
* Fixed the content API not matching XNB filenames with two dots (like `a.b.xnb`) if you don't specify the `.xnb` extension.
|
||||||
* Fixed `debug` command output not printed to console.
|
* Fixed `debug` command output not printed to console.
|
||||||
|
@ -803,7 +1018,7 @@ For players:
|
||||||
|
|
||||||
For mod developers:
|
For mod developers:
|
||||||
* Added a `Context.IsWorldReady` flag for mods to use.
|
* Added a `Context.IsWorldReady` flag for mods to use.
|
||||||
<small>_This indicates whether a save is loaded and the world is finished initialising, which starts at the same point that `SaveEvents.AfterLoad` and `TimeEvents.AfterDayStarted` are raised. This is mainly useful for events which can be raised before the world is loaded (like update tick)._</small>
|
<small>_This indicates whether a save is loaded and the world is finished initializing, which starts at the same point that `SaveEvents.AfterLoad` and `TimeEvents.AfterDayStarted` are raised. This is mainly useful for events which can be raised before the world is loaded (like update tick)._</small>
|
||||||
* Added a `debug` console command which lets you run the game's debug commands (e.g. `debug warp FarmHouse 1 1` warps you to the farmhouse).
|
* Added a `debug` console command which lets you run the game's debug commands (e.g. `debug warp FarmHouse 1 1` warps you to the farmhouse).
|
||||||
* Added basic context info to logs to simplify troubleshooting.
|
* Added basic context info to logs to simplify troubleshooting.
|
||||||
* Added a `Mod.Dispose` method which can be overriden to clean up before exit. This method isn't guaranteed to be called on every exit.
|
* Added a `Mod.Dispose` method which can be overriden to clean up before exit. This method isn't guaranteed to be called on every exit.
|
||||||
|
@ -841,8 +1056,8 @@ For players:
|
||||||
For mod developers:
|
For mod developers:
|
||||||
* Added a content API which loads custom textures/maps/data from the mod's folder (`.xnb` or `.png` format) or game content.
|
* Added a content API which loads custom textures/maps/data from the mod's folder (`.xnb` or `.png` format) or game content.
|
||||||
* `Console.Out` messages are now written to the log file.
|
* `Console.Out` messages are now written to the log file.
|
||||||
* `Monitor.ExitGameImmediately` now aborts SMAPI initialisation and events more quickly.
|
* `Monitor.ExitGameImmediately` now aborts SMAPI initialization and events more quickly.
|
||||||
* Fixed value-changed events being raised when the player loads a save due to values being initialised.
|
* Fixed value-changed events being raised when the player loads a save due to values being initialized.
|
||||||
|
|
||||||
## 1.10
|
## 1.10
|
||||||
Released 24 April 2017 for Stardew Valley 1.2.26.
|
Released 24 April 2017 for Stardew Valley 1.2.26.
|
||||||
|
@ -858,7 +1073,7 @@ For players:
|
||||||
* Replaced `player_addmelee` with `player_addweapon` with support for non-melee weapons.
|
* Replaced `player_addmelee` with `player_addweapon` with support for non-melee weapons.
|
||||||
|
|
||||||
For mod developers:
|
For mod developers:
|
||||||
* Mods are now initialised after the `Initialize`/`LoadContent` phase, which means the `GameEvents.Initialize` and `GameEvents.LoadContent` events are deprecated. You can move any logic in those methods to your mod's `Entry` method.
|
* Mods are now initialized after the `Initialize`/`LoadContent` phase, which means the `GameEvents.Initialize` and `GameEvents.LoadContent` events are deprecated. You can move any logic in those methods to your mod's `Entry` method.
|
||||||
* Added `IsBetween` and string overloads to the `ISemanticVersion` methods.
|
* Added `IsBetween` and string overloads to the `ISemanticVersion` methods.
|
||||||
* Fixed mouse-changed event never updating prior mouse position.
|
* Fixed mouse-changed event never updating prior mouse position.
|
||||||
* Fixed `monitor.ExitGameImmediately` not working correctly.
|
* Fixed `monitor.ExitGameImmediately` not working correctly.
|
||||||
|
@ -897,7 +1112,7 @@ For mod developers:
|
||||||
* The SMAPI log now has a simpler filename.
|
* The SMAPI log now has a simpler filename.
|
||||||
* The SMAPI log now shows the OS caption (like "Windows 10") instead of its internal version when available.
|
* The SMAPI log now shows the OS caption (like "Windows 10") instead of its internal version when available.
|
||||||
* The SMAPI log now always uses `\r\n` line endings to simplify crossplatform viewing.
|
* The SMAPI log now always uses `\r\n` line endings to simplify crossplatform viewing.
|
||||||
* Fixed `SaveEvents.AfterLoad` being raised during the new-game intro before the player is initialised.
|
* Fixed `SaveEvents.AfterLoad` being raised during the new-game intro before the player is initialized.
|
||||||
* Fixed SMAPI not recognising `Mod` instances that don't subclass `Mod` directly.
|
* Fixed SMAPI not recognising `Mod` instances that don't subclass `Mod` directly.
|
||||||
* Several obsolete APIs have been removed (see [migration guides](https://stardewvalleywiki.com/Modding:Index#Migration_guides)),
|
* Several obsolete APIs have been removed (see [migration guides](https://stardewvalleywiki.com/Modding:Index#Migration_guides)),
|
||||||
and all _notice_-level deprecations have been increased to _info_.
|
and all _notice_-level deprecations have been increased to _info_.
|
||||||
|
@ -942,7 +1157,7 @@ For mod developers:
|
||||||
* Added a mod registry which provides metadata about loaded mods.
|
* Added a mod registry which provides metadata about loaded mods.
|
||||||
* The `Entry(…)` method is now deferred until all mods are loaded.
|
* The `Entry(…)` method is now deferred until all mods are loaded.
|
||||||
* Fixed `SaveEvents.BeforeSave` and `.AfterSave` not triggering on days when the player shipped something.
|
* Fixed `SaveEvents.BeforeSave` and `.AfterSave` not triggering on days when the player shipped something.
|
||||||
* Fixed `PlayerEvents.LoadedGame` and `SaveEvents.AfterLoad` being fired before the world finishes initialising.
|
* Fixed `PlayerEvents.LoadedGame` and `SaveEvents.AfterLoad` being fired before the world finishes initializing.
|
||||||
* Fixed some `LocationEvents`, `PlayerEvents`, and `TimeEvents` being fired during game startup.
|
* Fixed some `LocationEvents`, `PlayerEvents`, and `TimeEvents` being fired during game startup.
|
||||||
* Increased deprecation levels for `SObject`, `LogWriter` (not `Log`), and `Mod.Entry(ModHelper)` (not `Mod.Entry(IModHelper)`) to _pending removal_. Increased deprecation levels for `Mod.PerSaveConfigFolder`, `Mod.PerSaveConfigPath`, and `Version.VersionString` to _info_.
|
* Increased deprecation levels for `SObject`, `LogWriter` (not `Log`), and `Mod.Entry(ModHelper)` (not `Mod.Entry(IModHelper)`) to _pending removal_. Increased deprecation levels for `Mod.PerSaveConfigFolder`, `Mod.PerSaveConfigPath`, and `Version.VersionString` to _info_.
|
||||||
|
|
||||||
|
|
|
@ -1,232 +0,0 @@
|
||||||
← [README](README.md)
|
|
||||||
|
|
||||||
This file provides more technical documentation about SMAPI. If you only want to use or create
|
|
||||||
mods, this section isn't relevant to you; see the main README to use or create mods.
|
|
||||||
|
|
||||||
# Contents
|
|
||||||
* [SMAPI](#smapi)
|
|
||||||
* [Development](#development)
|
|
||||||
* [Compiling from source](#compiling-from-source)
|
|
||||||
* [Debugging a local build](#debugging-a-local-build)
|
|
||||||
* [Preparing a release](#preparing-a-release)
|
|
||||||
* [Customisation](#customisation)
|
|
||||||
* [Configuration file](#configuration-file)
|
|
||||||
* [Command-line arguments](#command-line-arguments)
|
|
||||||
* [Compile flags](#compile-flags)
|
|
||||||
* [SMAPI web services](#smapi-web-services)
|
|
||||||
* [Overview](#overview)
|
|
||||||
* [Log parser](#log-parser)
|
|
||||||
* [Web API](#web-api)
|
|
||||||
* [Development](#development-2)
|
|
||||||
* [Local development](#local-development)
|
|
||||||
* [Deploying to Amazon Beanstalk](#deploying-to-amazon-beanstalk)
|
|
||||||
* [Mod build config package](#mod-build-config-package)
|
|
||||||
|
|
||||||
# SMAPI
|
|
||||||
## Development
|
|
||||||
### Compiling from source
|
|
||||||
Using an official SMAPI release is recommended for most users.
|
|
||||||
|
|
||||||
SMAPI uses some C# 7 code, so you'll need at least
|
|
||||||
[Visual Studio 2017](https://www.visualstudio.com/vs/community/) on Windows,
|
|
||||||
[MonoDevelop 7.0](https://www.monodevelop.com/) on Linux,
|
|
||||||
[Visual Studio 2017 for Mac](https://www.visualstudio.com/vs/visual-studio-mac/), or an equivalent
|
|
||||||
IDE to compile it. It uses build configuration derived from the
|
|
||||||
[crossplatform mod config](https://github.com/Pathoschild/Stardew.ModBuildConfig#readme) to detect
|
|
||||||
your current OS automatically and load the correct references. Compile output will be placed in a
|
|
||||||
`bin` folder at the root of the git repository.
|
|
||||||
|
|
||||||
### Debugging a local build
|
|
||||||
Rebuilding the solution in debug mode will copy the SMAPI files into your game folder. Starting
|
|
||||||
the `StardewModdingAPI` project with debugging from Visual Studio (on Mac or Windows) will launch
|
|
||||||
SMAPI with the debugger attached, so you can intercept errors and step through the code being
|
|
||||||
executed. This doesn't work in MonoDevelop on Linux, unfortunately.
|
|
||||||
|
|
||||||
### Preparing a release
|
|
||||||
To prepare a crossplatform SMAPI release, you'll need to compile it on two platforms. See
|
|
||||||
[crossplatforming info](https://stardewvalleywiki.com/Modding:Modder_Guide/Test_and_Troubleshoot#Testing_on_all_platforms)
|
|
||||||
on the wiki for the first-time setup.
|
|
||||||
|
|
||||||
1. Update the version number in `GlobalAssemblyInfo.cs` and `Constants::Version`. Make sure you use a
|
|
||||||
[semantic version](https://semver.org). Recommended format:
|
|
||||||
|
|
||||||
build type | format | example
|
|
||||||
:--------- | :----------------------- | :------
|
|
||||||
dev build | `<version>-alpha.<date>` | `3.0-alpha.20171230`
|
|
||||||
prerelease | `<version>-beta.<count>` | `3.0-beta.2`
|
|
||||||
release | `<version>` | `3.0`
|
|
||||||
|
|
||||||
2. In Windows:
|
|
||||||
1. Rebuild the solution in Release mode.
|
|
||||||
2. Copy `windows-install.*` from `bin/SMAPI installer` and `bin/SMAPI installer for developers` to
|
|
||||||
Linux/Mac.
|
|
||||||
|
|
||||||
3. In Linux/Mac:
|
|
||||||
1. Rebuild the solution in Release mode.
|
|
||||||
2. Add the `windows-install.*` files to the `bin/SMAPI installer` and
|
|
||||||
`bin/SMAPI installer for developers` folders.
|
|
||||||
3. Rename the folders to `SMAPI <version> installer` and `SMAPI <version> installer for developers`.
|
|
||||||
4. Zip the two folders.
|
|
||||||
|
|
||||||
## Customisation
|
|
||||||
### Configuration file
|
|
||||||
You can customise the SMAPI behaviour by editing the `smapi-internal/StardewModdingAPI.config.json`
|
|
||||||
file in your game folder.
|
|
||||||
|
|
||||||
Basic fields:
|
|
||||||
|
|
||||||
field | purpose
|
|
||||||
----------------- | -------
|
|
||||||
`DeveloperMode` | Default `false` (except in _SMAPI for developers_ releases). Whether to enable features intended for mod developers (mainly more detailed console logging).
|
|
||||||
`CheckForUpdates` | Default `true`. Whether SMAPI should check for a newer version when you load the game. If a new version is available, a small message will appear in the console. This doesn't affect the load time even if your connection is offline or slow, because it happens in the background.
|
|
||||||
`VerboseLogging` | Default `false`. Whether SMAPI should log more information about the game context.
|
|
||||||
`ModData` | Internal metadata about SMAPI mods. Changing this isn't recommended and may destabilise your game. See documentation in the file.
|
|
||||||
|
|
||||||
### Command-line arguments
|
|
||||||
The SMAPI installer recognises three command-line arguments:
|
|
||||||
|
|
||||||
argument | purpose
|
|
||||||
-------- | -------
|
|
||||||
`--install` | Preselects the install action, skipping the prompt asking what the user wants to do.
|
|
||||||
`--uninstall` | Preselects the uninstall action, skipping the prompt asking what the user wants to do.
|
|
||||||
`--game-path "path"` | Specifies the full path to the folder containing the Stardew Valley executable, skipping automatic detection and any prompt to choose a path. If the path is not valid, the installer displays an error.
|
|
||||||
|
|
||||||
SMAPI itself recognises two arguments, but these are intended for internal use or testing and may
|
|
||||||
change without warning.
|
|
||||||
|
|
||||||
argument | purpose
|
|
||||||
-------- | -------
|
|
||||||
`--no-terminal` | SMAPI won't write anything to the console window. (Messages will still be written to the log file.)
|
|
||||||
`--mods-path` | The path to search for mods, if not the standard `Mods` folder. This can be a path relative to the game folder (like `--mods-path "Mods (test)"`) or an absolute path.
|
|
||||||
|
|
||||||
### Compile flags
|
|
||||||
SMAPI uses a small number of conditional compilation constants, which you can set by editing the
|
|
||||||
`<DefineConstants>` element in `StardewModdingAPI.csproj`. Supported constants:
|
|
||||||
|
|
||||||
flag | purpose
|
|
||||||
---- | -------
|
|
||||||
`SMAPI_FOR_WINDOWS` | Whether SMAPI is being compiled on Windows for players on Windows. Set automatically in `crossplatform.targets`.
|
|
||||||
`SMAPI_3_0_STRICT` | Whether to exclude all deprecated APIs from compilation. This is useful for testing mods for SMAPI 3.0 compatibility.
|
|
||||||
|
|
||||||
# SMAPI web services
|
|
||||||
## Overview
|
|
||||||
The `StardewModdingAPI.Web` project provides an API and web UI hosted at `*.smapi.io`.
|
|
||||||
|
|
||||||
### Log parser
|
|
||||||
The log parser provides a web UI for uploading, parsing, and sharing SMAPI logs. The logs are
|
|
||||||
persisted in a compressed form to Pastebin.
|
|
||||||
|
|
||||||
The log parser lives at https://log.smapi.io.
|
|
||||||
|
|
||||||
### Web API
|
|
||||||
SMAPI provides a web API at `api.smapi.io` for use by SMAPI and external tools. The URL includes a
|
|
||||||
`{version}` token, which is the SMAPI version for backwards compatibility. This API is publicly
|
|
||||||
accessible but not officially released; it may change at any time.
|
|
||||||
|
|
||||||
The API has one `/mods` endpoint. This provides mod info, including official versions and URLs
|
|
||||||
(from Chucklefish, GitHub, or Nexus), unofficial versions from the wiki, and optional mod metadata
|
|
||||||
from the wiki and SMAPI's internal data. This is used by SMAPI to perform update checks, and by
|
|
||||||
external tools to fetch mod data.
|
|
||||||
|
|
||||||
The API accepts a `POST` request with the mods to match, each of which **must** specify an ID and
|
|
||||||
may _optionally_ specify [update keys](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Manifest#Update_checks).
|
|
||||||
The API will automatically try to fetch known update keys from the wiki and internal data based on
|
|
||||||
the given ID.
|
|
||||||
|
|
||||||
```
|
|
||||||
POST https://api.smapi.io/v2.0/mods
|
|
||||||
{
|
|
||||||
"mods": [
|
|
||||||
{
|
|
||||||
"id": "Pathoschild.LookupAnything",
|
|
||||||
"updateKeys": [ "nexus:541", "chucklefish:4250" ]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"includeExtendedMetadata": true
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
The API will automatically aggregate versions and errors. Each mod will include...
|
|
||||||
* an `id` (matching what you passed in);
|
|
||||||
* up to three versions: `main` (e.g. 'latest version' field on Nexus), `optional` if newer (e.g.
|
|
||||||
optional files on Nexus), and `unofficial` if newer (from the wiki);
|
|
||||||
* `metadata` with mod info crossreferenced from the wiki and internal data (only if you specified
|
|
||||||
`includeExtendedMetadata: true`);
|
|
||||||
* and `errors` containing any error messages that occurred while fetching data.
|
|
||||||
|
|
||||||
For example:
|
|
||||||
```
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"id": "Pathoschild.LookupAnything",
|
|
||||||
"main": {
|
|
||||||
"version": "1.19",
|
|
||||||
"url": "https://www.nexusmods.com/stardewvalley/mods/541"
|
|
||||||
},
|
|
||||||
"metadata": {
|
|
||||||
"id": [
|
|
||||||
"Pathoschild.LookupAnything",
|
|
||||||
"LookupAnything"
|
|
||||||
],
|
|
||||||
"name": "Lookup Anything",
|
|
||||||
"nexusID": 541,
|
|
||||||
"gitHubRepo": "Pathoschild/StardewMods",
|
|
||||||
"compatibilityStatus": "Ok",
|
|
||||||
"compatibilitySummary": "✓ use latest version."
|
|
||||||
},
|
|
||||||
"errors": []
|
|
||||||
}
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
## Development
|
|
||||||
### Local development
|
|
||||||
`StardewModdingAPI.Web` is a regular ASP.NET MVC Core app, so you can just launch it from within
|
|
||||||
Visual Studio to run a local version.
|
|
||||||
|
|
||||||
There are two differences when it's run locally: all endpoints use HTTP instead of HTTPS, and the
|
|
||||||
subdomain portion becomes a route (e.g. `log.smapi.io` → `localhost:59482/log`).
|
|
||||||
|
|
||||||
Before running it locally, you need to enter your credentials in the `appsettings.Development.json`
|
|
||||||
file. See the next section for a description of each setting. This file is listed in `.gitignore`
|
|
||||||
to prevent accidentally committing credentials.
|
|
||||||
|
|
||||||
### Deploying to Amazon Beanstalk
|
|
||||||
The app can be deployed to a standard Amazon Beanstalk IIS environment. When creating the
|
|
||||||
environment, make sure to specify the following environment properties:
|
|
||||||
|
|
||||||
property name | description
|
|
||||||
------------------------------- | -----------------
|
|
||||||
`LogParser:PastebinDevKey` | The [Pastebin developer key](https://pastebin.com/api#1) used to authenticate with the Pastebin API.
|
|
||||||
`LogParser:PastebinUserKey` | The [Pastebin user key](https://pastebin.com/api#8) used to authenticate with the Pastebin API. Can be left blank to post anonymously.
|
|
||||||
`LogParser:SectionUrl` | The root URL of the log page, like `https://log.smapi.io/`.
|
|
||||||
`ModUpdateCheck:GitHubPassword` | The password with which to authenticate to GitHub when fetching release info.
|
|
||||||
`ModUpdateCheck:GitHubUsername` | The username with which to authenticate to GitHub when fetching release info.
|
|
||||||
|
|
||||||
## Mod build config package
|
|
||||||
### Overview
|
|
||||||
The mod build config package is a NuGet package that mods reference to automatically set up
|
|
||||||
references, configure the build, and add analyzers specific to Stardew Valley mods.
|
|
||||||
|
|
||||||
This involves three projects:
|
|
||||||
|
|
||||||
project | purpose
|
|
||||||
------------------------------------------------- | ----------------
|
|
||||||
`StardewModdingAPI.ModBuildConfig` | Configures the build (references, deploying the mod files, setting up debugging, etc).
|
|
||||||
`StardewModdingAPI.ModBuildConfig.Analyzer` | Adds C# analyzers which show code warnings in Visual Studio.
|
|
||||||
`StardewModdingAPI.ModBuildConfig.Analyzer.Tests` | Unit tests for the C# analyzers.
|
|
||||||
|
|
||||||
When the projects are built, the relevant files are copied into `bin/Pathoschild.Stardew.ModBuildConfig`.
|
|
||||||
|
|
||||||
### Preparing a build
|
|
||||||
To prepare a build of the NuGet package:
|
|
||||||
1. Install the [NuGet CLI](https://docs.microsoft.com/en-us/nuget/install-nuget-client-tools#nugetexe-cli).
|
|
||||||
1. Change the version and release notes in `package.nuspec`.
|
|
||||||
2. Rebuild the solution in _Release_ mode.
|
|
||||||
3. Open a terminal in the `bin/Pathoschild.Stardew.ModBuildConfig` package and run this command:
|
|
||||||
```bash
|
|
||||||
nuget.exe pack
|
|
||||||
```
|
|
||||||
|
|
||||||
That will create a `Pathoschild.Stardew.ModBuildConfig-<version>.nupkg` file in the same directory
|
|
||||||
which can be uploaded to NuGet or referenced directly.
|
|
|
@ -0,0 +1,366 @@
|
||||||
|
← [SMAPI](../README.md)
|
||||||
|
|
||||||
|
The **mod build package** is an open-source NuGet package which automates the MSBuild configuration
|
||||||
|
for SMAPI mods and related tools. The package is fully compatible with Linux, Mac, and Windows.
|
||||||
|
|
||||||
|
## Contents
|
||||||
|
* [Use](#use)
|
||||||
|
* [Features](#features)
|
||||||
|
* [Detect game path](#detect-game-path)
|
||||||
|
* [Add assembly references](#add-assembly-references)
|
||||||
|
* [Copy files into the `Mods` folder and create release zip](#copy-files-into-the-mods-folder-and-create-release-zip)
|
||||||
|
* [Launch or debug game](#launch-or-debug-game)
|
||||||
|
* [Preconfigure common settings](#preconfigure-common-settings)
|
||||||
|
* [Add code warnings](#add-code-warnings)
|
||||||
|
* [Code warnings](#code-warnings)
|
||||||
|
* [Special cases](#special-cases)
|
||||||
|
* [Custom game path](#custom-game-path)
|
||||||
|
* [Non-mod projects](#non-mod-projects)
|
||||||
|
* [For SMAPI developers](#for-smapi-developers)
|
||||||
|
* [Release notes](#release-notes)
|
||||||
|
|
||||||
|
## Use
|
||||||
|
1. Create an empty library project.
|
||||||
|
2. Reference the [`Pathoschild.Stardew.ModBuildConfig` NuGet package](https://www.nuget.org/packages/Pathoschild.Stardew.ModBuildConfig).
|
||||||
|
3. [Write your code](https://stardewvalleywiki.com/Modding:Creating_a_SMAPI_mod).
|
||||||
|
4. Compile on any platform.
|
||||||
|
5. Run the game to play with your mod.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
The package automatically makes the changes listed below. In some cases you can configure how it
|
||||||
|
works by editing your mod's `.csproj` file, and adding the given properties between the first
|
||||||
|
`<PropertyGroup>` and `</PropertyGroup>` tags.
|
||||||
|
|
||||||
|
### Detect game path
|
||||||
|
The package finds your game folder by scanning the default install paths and Windows registry. It
|
||||||
|
adds two MSBuild properties for use in your `.csproj` file if needed:
|
||||||
|
|
||||||
|
property | description
|
||||||
|
-------- | -----------
|
||||||
|
`$(GamePath)` | The absolute path to the detected game folder.
|
||||||
|
`$(GameExecutableName)` | The game's executable name for the current OS (`Stardew Valley` on Windows, or `StardewValley` on Linux/Mac).
|
||||||
|
|
||||||
|
If you get a build error saying it can't find your game, see [_custom game path_](#custom-game-path).
|
||||||
|
|
||||||
|
### Add assembly references
|
||||||
|
The package adds assembly references to SMAPI, Stardew Valley, xTile, and MonoGame (Linux/Mac) or XNA
|
||||||
|
Framework (Windows). It automatically adjusts depending on which OS you're compiling it on.
|
||||||
|
|
||||||
|
The assemblies aren't copied to the build output, since mods loaded by SMAPI won't need them. For
|
||||||
|
non-mod projects like unit tests, you can set this property:
|
||||||
|
```xml
|
||||||
|
<CopyModReferencesToBuildOutput>true</CopyModReferencesToBuildOutput>
|
||||||
|
```
|
||||||
|
|
||||||
|
If your mod uses [Harmony](https://github.com/pardeike/Harmony) (not recommended for most mods),
|
||||||
|
the package can add a reference to SMAPI's Harmony DLL for you:
|
||||||
|
```xml
|
||||||
|
<EnableHarmony>true</EnableHarmony>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Copy files into the `Mods` folder and create release zip
|
||||||
|
<dl>
|
||||||
|
<dt>Files considered part of your mod</dt>
|
||||||
|
<dd>
|
||||||
|
|
||||||
|
These files are selected by default: `manifest.json`,
|
||||||
|
[`i18n` files](https://stardewvalleywiki.com/Modding:Translations) (if any), the `assets` folder
|
||||||
|
(if any), and all files in the build output. You can select custom files by [adding them to the
|
||||||
|
build output](https://stackoverflow.com/a/10828462/262123). (If your project references another mod,
|
||||||
|
make sure the reference is [_not_ marked 'copy local'](https://msdn.microsoft.com/en-us/library/t1zz5y8c(v=vs.100).aspx).)
|
||||||
|
|
||||||
|
You can deselect a file by removing it from the build output. For a default file, you can set the
|
||||||
|
property below to a comma-delimited list of regex patterns to ignore. For crossplatform
|
||||||
|
compatibility, you should replace path delimiters with `[/\\]`.
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<IgnoreModFilePatterns>\.txt$, \.pdf$, assets[/\\]paths.png</IgnoreModFilePatterns>
|
||||||
|
```
|
||||||
|
|
||||||
|
</dd>
|
||||||
|
<dt>Copy files into the `Mods` folder</dt>
|
||||||
|
<dd>
|
||||||
|
|
||||||
|
The package copies the selected files into your game's `Mods` folder when you rebuild the code,
|
||||||
|
with a subfolder matching the mod's project name.
|
||||||
|
|
||||||
|
You can change the folder name:
|
||||||
|
```xml
|
||||||
|
<ModFolderName>YourModName</ModFolderName>
|
||||||
|
```
|
||||||
|
|
||||||
|
Or disable deploying the files:
|
||||||
|
```xml
|
||||||
|
<EnableModDeploy>false</EnableModDeploy>
|
||||||
|
```
|
||||||
|
|
||||||
|
</dd>
|
||||||
|
<dt>Create release zip</dt>
|
||||||
|
<dd>
|
||||||
|
|
||||||
|
The package adds a zip file in your project's `bin` folder when you rebuild the code, in the format
|
||||||
|
recommended for sites like Nexus Mods. The zip filename can be changed using `ModFolderName` above.
|
||||||
|
|
||||||
|
You can change the folder path where the zip is created:
|
||||||
|
```xml
|
||||||
|
<ModZipPath>$(SolutionDir)\_releases</ModZipPath>
|
||||||
|
```
|
||||||
|
|
||||||
|
Or disable zip creation:
|
||||||
|
```xml
|
||||||
|
<EnableModZip>false</EnableModZip>
|
||||||
|
```
|
||||||
|
|
||||||
|
</dd>
|
||||||
|
</dl>
|
||||||
|
|
||||||
|
### Launch or debug game
|
||||||
|
On Windows only, the package configures Visual Studio so you can launch the game and attach a
|
||||||
|
debugger using _Debug > Start Debugging_ or _Debug > Start Without Debugging_. This lets you [set
|
||||||
|
breakpoints](https://docs.microsoft.com/en-us/visualstudio/debugger/using-breakpoints?view=vs-2019)
|
||||||
|
in your code while the game is running, or [make simple changes to the mod code without needing to
|
||||||
|
restart the game](https://docs.microsoft.com/en-us/visualstudio/debugger/edit-and-continue?view=vs-2019).
|
||||||
|
|
||||||
|
This is disabled on Linux/Mac due to limitations with the Mono wrapper.
|
||||||
|
|
||||||
|
To disable game debugging (only needed for some non-mod projects):
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<EnableGameDebugging>false</EnableGameDebugging>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Preconfigure common settings
|
||||||
|
The package also automatically enables PDB files (so error logs show line numbers for simpler
|
||||||
|
debugging), and enables support for the simplified `.csproj` format.
|
||||||
|
|
||||||
|
### Add code warnings
|
||||||
|
The package runs code analysis on your mod and raises warnings for some common errors or pitfalls.
|
||||||
|
See [_code warnings_](#code-warnings) for more info.
|
||||||
|
|
||||||
|
## Code warnings
|
||||||
|
### Overview
|
||||||
|
The NuGet package adds code warnings in Visual Studio specific to Stardew Valley. For example:
|
||||||
|
![](screenshots/code-analyzer-example.png)
|
||||||
|
|
||||||
|
You can hide the warnings using the warning ID (shown under 'code' in the Error List). See...
|
||||||
|
* [for specific code](https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/preprocessor-directives/preprocessor-pragma-warning);
|
||||||
|
* for a method using this attribute:
|
||||||
|
```cs
|
||||||
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("SMAPI.CommonErrors", "AvoidNetField")]
|
||||||
|
```
|
||||||
|
* for an entire project:
|
||||||
|
1. Expand the _References_ node for the project in Visual Studio.
|
||||||
|
2. Right-click on _Analyzers_ and choose _Open Active Rule Set_.
|
||||||
|
4. Expand _StardewModdingAPI.ModBuildConfig.Analyzer_ and uncheck the warnings you want to hide.
|
||||||
|
|
||||||
|
See below for help with each specific warning.
|
||||||
|
|
||||||
|
### Avoid implicit net field cast
|
||||||
|
Warning text:
|
||||||
|
> This implicitly converts '{{expression}}' from {{net type}} to {{other type}}, but
|
||||||
|
> {{net type}} has unintuitive implicit conversion rules. Consider comparing against the actual
|
||||||
|
> value instead to avoid bugs.
|
||||||
|
|
||||||
|
Stardew Valley uses net types (like `NetBool` and `NetInt`) to handle multiplayer sync. These types
|
||||||
|
can implicitly convert to their equivalent normal values (like `bool x = new NetBool()`), but their
|
||||||
|
conversion rules are unintuitive and error-prone. For example,
|
||||||
|
`item?.category == null && item?.category != null` can both be true at once, and
|
||||||
|
`building.indoors != null` can be true for a null value.
|
||||||
|
|
||||||
|
Suggested fix:
|
||||||
|
* Some net fields have an equivalent non-net property like `monster.Health` (`int`) instead of
|
||||||
|
`monster.health` (`NetInt`). The package will add a separate [AvoidNetField](#avoid-net-field) warning for
|
||||||
|
these. Use the suggested property instead.
|
||||||
|
* For a reference type (i.e. one that can contain `null`), you can use the `.Value` property:
|
||||||
|
```c#
|
||||||
|
if (building.indoors.Value == null)
|
||||||
|
```
|
||||||
|
Or convert the value before comparison:
|
||||||
|
```c#
|
||||||
|
GameLocation indoors = building.indoors;
|
||||||
|
if(indoors == null)
|
||||||
|
// ...
|
||||||
|
```
|
||||||
|
* For a value type (i.e. one that can't contain `null`), check if the object is null (if applicable)
|
||||||
|
and compare with `.Value`:
|
||||||
|
```cs
|
||||||
|
if (item != null && item.category.Value == 0)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Avoid net field
|
||||||
|
Warning text:
|
||||||
|
> '{{expression}}' is a {{net type}} field; consider using the {{property name}} property instead.
|
||||||
|
|
||||||
|
Your code accesses a net field, which has some unusual behavior (see [AvoidImplicitNetFieldCast](#avoid-implicit-net-field-cast)).
|
||||||
|
This field has an equivalent non-net property that avoids those issues.
|
||||||
|
|
||||||
|
Suggested fix: access the suggested property name instead.
|
||||||
|
|
||||||
|
### Avoid obsolete field
|
||||||
|
Warning text:
|
||||||
|
> The '{{old field}}' field is obsolete and should be replaced with '{{new field}}'.
|
||||||
|
|
||||||
|
Your code accesses a field which is obsolete or no longer works. Use the suggested field instead.
|
||||||
|
|
||||||
|
## Special cases
|
||||||
|
### Custom game path
|
||||||
|
The package usually detects where your game is installed automatically. If it can't find your game
|
||||||
|
or you have multiple installs, you can specify the path yourself. There's two ways to do that:
|
||||||
|
|
||||||
|
* **Option 1: global game path (recommended).**
|
||||||
|
_This will apply to every project that uses the package._
|
||||||
|
|
||||||
|
1. Get the full folder path containing the Stardew Valley executable.
|
||||||
|
2. Create this file:
|
||||||
|
|
||||||
|
platform | path
|
||||||
|
--------- | ----
|
||||||
|
Linux/Mac | `~/stardewvalley.targets`
|
||||||
|
Windows | `%USERPROFILE%\stardewvalley.targets`
|
||||||
|
|
||||||
|
3. Save the file with this content:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<PropertyGroup>
|
||||||
|
<GamePath>PATH_HERE</GamePath>
|
||||||
|
</PropertyGroup>
|
||||||
|
</Project>
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Replace `PATH_HERE` with your game's folder path.
|
||||||
|
|
||||||
|
* **Option 2: path in the project file.**
|
||||||
|
_You'll need to do this for each project that uses the package._
|
||||||
|
|
||||||
|
1. Get the folder path containing the Stardew Valley `.exe` file.
|
||||||
|
2. Add this to your `.csproj` file under the `<Project` line:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<PropertyGroup>
|
||||||
|
<GamePath>PATH_HERE</GamePath>
|
||||||
|
</PropertyGroup>
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Replace `PATH_HERE` with your custom game install path.
|
||||||
|
|
||||||
|
The configuration will check your custom path first, then fall back to the default paths (so it'll
|
||||||
|
still compile on a different computer).
|
||||||
|
|
||||||
|
You access the game path via `$(GamePath)` in MSBuild properties, if you need to reference another
|
||||||
|
file in the game folder.
|
||||||
|
|
||||||
|
### Non-mod projects
|
||||||
|
You can use the package in non-mod projects too (e.g. unit tests or framework DLLs). Just disable
|
||||||
|
the mod-related package features:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<EnableGameDebugging>false</EnableGameDebugging>
|
||||||
|
<EnableModDeploy>false</EnableModDeploy>
|
||||||
|
<EnableModZip>false</EnableModZip>
|
||||||
|
```
|
||||||
|
|
||||||
|
If you need to copy the referenced DLLs into your build output, add this too:
|
||||||
|
```xml
|
||||||
|
<CopyModReferencesToBuildOutput>true</CopyModReferencesToBuildOutput>
|
||||||
|
```
|
||||||
|
|
||||||
|
## For SMAPI developers
|
||||||
|
The mod build package consists of three projects:
|
||||||
|
|
||||||
|
project | purpose
|
||||||
|
------------------------------------------------- | ----------------
|
||||||
|
`StardewModdingAPI.ModBuildConfig` | Configures the build (references, deploying the mod files, setting up debugging, etc).
|
||||||
|
`StardewModdingAPI.ModBuildConfig.Analyzer` | Adds C# analyzers which show code warnings in Visual Studio.
|
||||||
|
`StardewModdingAPI.ModBuildConfig.Analyzer.Tests` | Unit tests for the C# analyzers.
|
||||||
|
|
||||||
|
To prepare a build of the NuGet package:
|
||||||
|
1. Install the [NuGet CLI](https://docs.microsoft.com/en-us/nuget/install-nuget-client-tools#nugetexe-cli).
|
||||||
|
1. Change the version and release notes in `package.nuspec`.
|
||||||
|
2. Rebuild the solution in _Release_ mode.
|
||||||
|
3. Open a terminal in the `bin/Pathoschild.Stardew.ModBuildConfig` package and run this command:
|
||||||
|
```bash
|
||||||
|
nuget.exe pack
|
||||||
|
```
|
||||||
|
|
||||||
|
That will create a `Pathoschild.Stardew.ModBuildConfig-<version>.nupkg` file in the same directory
|
||||||
|
which can be uploaded to NuGet or referenced directly.
|
||||||
|
|
||||||
|
## Release notes
|
||||||
|
### Upcoming release
|
||||||
|
* Updated for SMAPI 3.0 and Stardew Valley 1.4.
|
||||||
|
* Added automatic support for `assets` folders.
|
||||||
|
* Added `$(GameExecutableName)` MSBuild variable.
|
||||||
|
* Added support for projects using the simplified `.csproj` format.
|
||||||
|
* Added option to disable game debugging config.
|
||||||
|
* Added `.pdb` files to builds by default (to enable line numbers in error stack traces).
|
||||||
|
* Added optional Harmony reference.
|
||||||
|
* Fixed `Newtonsoft.Json.pdb` included in release zips when Json.NET is referenced directly.
|
||||||
|
* Fixed `<IgnoreModFilePatterns>` not working for `i18n` files.
|
||||||
|
* Dropped support for older versions of SMAPI and Visual Studio.
|
||||||
|
|
||||||
|
### 2.2
|
||||||
|
* Added support for SMAPI 2.8+ (still compatible with earlier versions).
|
||||||
|
* Added default game paths for 32-bit Windows.
|
||||||
|
* Fixed valid manifests marked invalid in some cases.
|
||||||
|
|
||||||
|
### 2.1
|
||||||
|
* Added support for Stardew Valley 1.3.
|
||||||
|
* Added support for non-mod projects.
|
||||||
|
* Added C# analyzers to warn about implicit conversions of Netcode fields in Stardew Valley 1.3.
|
||||||
|
* Added option to ignore files by regex pattern.
|
||||||
|
* Added reference to new SMAPI DLL.
|
||||||
|
* Fixed some game paths not detected by NuGet package.
|
||||||
|
|
||||||
|
### 2.0.2
|
||||||
|
* Fixed compatibility issue on Linux.
|
||||||
|
|
||||||
|
### 2.0.1
|
||||||
|
* Fixed mod deploy failing to create subfolders if they don't already exist.
|
||||||
|
|
||||||
|
### 2.0
|
||||||
|
* Added: mods are now copied into the `Mods` folder automatically (configurable).
|
||||||
|
* Added: release zips are now created automatically in your build output folder (configurable).
|
||||||
|
* Added: mod deploy and release zips now exclude Json.NET automatically, since it's provided by SMAPI.
|
||||||
|
* Added mod's version to release zip filename.
|
||||||
|
* Improved errors to simplify troubleshooting.
|
||||||
|
* Fixed release zip not having a mod folder.
|
||||||
|
* Fixed release zip failing if mod name contains characters that aren't valid in a filename.
|
||||||
|
|
||||||
|
### 1.7.1
|
||||||
|
* Fixed issue where i18n folders were flattened.
|
||||||
|
* The manifest/i18n files in the project now take precedence over those in the build output if both
|
||||||
|
are present.
|
||||||
|
|
||||||
|
### 1.7
|
||||||
|
* Added option to create release zips on build.
|
||||||
|
* Added reference to XNA's XACT library for audio-related mods.
|
||||||
|
|
||||||
|
### 1.6
|
||||||
|
* Added support for deploying mod files into `Mods` automatically.
|
||||||
|
* Added a build error if a game folder is found, but doesn't contain Stardew Valley or SMAPI.
|
||||||
|
|
||||||
|
### 1.5
|
||||||
|
* Added support for setting a custom game path globally.
|
||||||
|
* Added default GOG path on Mac.
|
||||||
|
|
||||||
|
### 1.4
|
||||||
|
* Fixed detection of non-default game paths on 32-bit Windows.
|
||||||
|
* Removed support for SilVerPLuM (discontinued).
|
||||||
|
* Removed support for overriding the target platform (no longer needed since SMAPI crossplatforms
|
||||||
|
mods automatically).
|
||||||
|
|
||||||
|
### 1.3
|
||||||
|
* Added support for non-default game paths on Windows.
|
||||||
|
|
||||||
|
### 1.2
|
||||||
|
* Exclude game binaries from mod build output.
|
||||||
|
|
||||||
|
### 1.1
|
||||||
|
* Added support for overriding the target platform.
|
||||||
|
|
||||||
|
### 1.0
|
||||||
|
* Initial release.
|
||||||
|
* Added support for detecting the game path automatically.
|
||||||
|
* Added support for injecting XNA/MonoGame references automatically based on the OS.
|
||||||
|
* Added support for mod builders like SilVerPLuM.
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.4 KiB |
|
@ -0,0 +1,116 @@
|
||||||
|
← [README](../README.md)
|
||||||
|
|
||||||
|
This file provides more technical documentation about SMAPI. If you only want to use or create
|
||||||
|
mods, this section isn't relevant to you; see the main README to use or create mods.
|
||||||
|
|
||||||
|
This document is about SMAPI itself; see also [mod build package](mod-package.md) and
|
||||||
|
[web services](web.md).
|
||||||
|
|
||||||
|
# Contents
|
||||||
|
* [Customisation](#customisation)
|
||||||
|
* [Configuration file](#configuration-file)
|
||||||
|
* [Command-line arguments](#command-line-arguments)
|
||||||
|
* [Compile flags](#compile-flags)
|
||||||
|
* [For SMAPI developers](#for-smapi-developers)
|
||||||
|
* [Compiling from source](#compiling-from-source)
|
||||||
|
* [Debugging a local build](#debugging-a-local-build)
|
||||||
|
* [Preparing a release](#preparing-a-release)
|
||||||
|
* [Release notes](#release-notes)
|
||||||
|
|
||||||
|
## Customisation
|
||||||
|
### Configuration file
|
||||||
|
You can customise the SMAPI behaviour by editing the `smapi-internal/config.json` file in your game
|
||||||
|
folder.
|
||||||
|
|
||||||
|
Basic fields:
|
||||||
|
|
||||||
|
field | purpose
|
||||||
|
----------------- | -------
|
||||||
|
`DeveloperMode` | Default `false` (except in _SMAPI for developers_ releases). Whether to enable features intended for mod developers (mainly more detailed console logging).
|
||||||
|
`CheckForUpdates` | Default `true`. Whether SMAPI should check for a newer version when you load the game. If a new version is available, a small message will appear in the console. This doesn't affect the load time even if your connection is offline or slow, because it happens in the background.
|
||||||
|
`VerboseLogging` | Default `false`. Whether SMAPI should log more information about the game context.
|
||||||
|
`ModData` | Internal metadata about SMAPI mods. Changing this isn't recommended and may destabilise your game. See documentation in the file.
|
||||||
|
|
||||||
|
### Command-line arguments
|
||||||
|
The SMAPI installer recognises three command-line arguments:
|
||||||
|
|
||||||
|
argument | purpose
|
||||||
|
-------- | -------
|
||||||
|
`--install` | Preselects the install action, skipping the prompt asking what the user wants to do.
|
||||||
|
`--uninstall` | Preselects the uninstall action, skipping the prompt asking what the user wants to do.
|
||||||
|
`--game-path "path"` | Specifies the full path to the folder containing the Stardew Valley executable, skipping automatic detection and any prompt to choose a path. If the path is not valid, the installer displays an error.
|
||||||
|
|
||||||
|
SMAPI itself recognises two arguments **on Windows only**, but these are intended for internal use
|
||||||
|
or testing and may change without warning. On Linux/Mac, see _environment variables_ below.
|
||||||
|
|
||||||
|
argument | purpose
|
||||||
|
-------- | -------
|
||||||
|
`--no-terminal` | SMAPI won't write anything to the console window. (Messages will still be written to the log file.)
|
||||||
|
`--mods-path` | The path to search for mods, if not the standard `Mods` folder. This can be a path relative to the game folder (like `--mods-path "Mods (test)"`) or an absolute path.
|
||||||
|
|
||||||
|
### Environment variables
|
||||||
|
The above SMAPI arguments don't work on Linux/Mac due to the way the game launcher works. You can
|
||||||
|
set temporary environment variables instead. For example:
|
||||||
|
> SMAPI_MODS_PATH="Mods (multiplayer)" /path/to/StardewValley
|
||||||
|
|
||||||
|
environment variable | purpose
|
||||||
|
-------------------- | -------
|
||||||
|
`SMAPI_NO_TERMINAL` | Equivalent to `--no-terminal` above.
|
||||||
|
`SMAPI_MODS_PATH` | Equivalent to `--mods-path` above.
|
||||||
|
|
||||||
|
|
||||||
|
### Compile flags
|
||||||
|
SMAPI uses a small number of conditional compilation constants, which you can set by editing the
|
||||||
|
`<DefineConstants>` element in `SMAPI.csproj`. Supported constants:
|
||||||
|
|
||||||
|
flag | purpose
|
||||||
|
---- | -------
|
||||||
|
`SMAPI_FOR_WINDOWS` | Whether SMAPI is being compiled on Windows for players on Windows. Set automatically in `crossplatform.targets`.
|
||||||
|
|
||||||
|
## For SMAPI developers
|
||||||
|
### Compiling from source
|
||||||
|
Using an official SMAPI release is recommended for most users.
|
||||||
|
|
||||||
|
SMAPI often uses the latest C# syntax. You may need the latest version of
|
||||||
|
[Visual Studio](https://www.visualstudio.com/vs/community/) on Windows,
|
||||||
|
[MonoDevelop](https://www.monodevelop.com/) on Linux,
|
||||||
|
[Visual Studio for Mac](https://www.visualstudio.com/vs/visual-studio-mac/), or an equivalent IDE
|
||||||
|
to compile it. It uses build configuration derived from the
|
||||||
|
[crossplatform mod config](https://smapi.io/package/readme) to detect your current OS automatically
|
||||||
|
and load the correct references. Compile output will be placed in a `bin` folder at the root of the
|
||||||
|
git repository.
|
||||||
|
|
||||||
|
### Debugging a local build
|
||||||
|
Rebuilding the solution in debug mode will copy the SMAPI files into your game folder. Starting
|
||||||
|
the `SMAPI` project with debugging from Visual Studio (on Mac or Windows) will launch SMAPI with
|
||||||
|
the debugger attached, so you can intercept errors and step through the code being executed. This
|
||||||
|
doesn't work in MonoDevelop on Linux, unfortunately.
|
||||||
|
|
||||||
|
### Preparing a release
|
||||||
|
To prepare a crossplatform SMAPI release, you'll need to compile it on two platforms. See
|
||||||
|
[crossplatforming info](https://stardewvalleywiki.com/Modding:Modder_Guide/Test_and_Troubleshoot#Testing_on_all_platforms)
|
||||||
|
on the wiki for the first-time setup.
|
||||||
|
|
||||||
|
1. Update the version number in `.root/build/common.targets` and `Constants::Version`. Make sure
|
||||||
|
you use a [semantic version](https://semver.org). Recommended format:
|
||||||
|
|
||||||
|
build type | format | example
|
||||||
|
:--------- | :----------------------- | :------
|
||||||
|
dev build | `<version>-alpha.<date>` | `3.0-alpha.20171230`
|
||||||
|
prerelease | `<version>-beta.<count>` | `3.0-beta.2`
|
||||||
|
release | `<version>` | `3.0`
|
||||||
|
|
||||||
|
2. In Windows:
|
||||||
|
1. Rebuild the solution in Release mode.
|
||||||
|
2. Copy `windows-install.*` from `bin/SMAPI installer` and `bin/SMAPI installer for developers` to
|
||||||
|
Linux/Mac.
|
||||||
|
|
||||||
|
3. In Linux/Mac:
|
||||||
|
1. Rebuild the solution in Release mode.
|
||||||
|
2. Add the `windows-install.*` files to the `bin/SMAPI installer` and
|
||||||
|
`bin/SMAPI installer for developers` folders.
|
||||||
|
3. Rename the folders to `SMAPI <version> installer` and `SMAPI <version> installer for developers`.
|
||||||
|
4. Zip the two folders.
|
||||||
|
|
||||||
|
## Release notes
|
||||||
|
See [release notes](../release-notes.md).
|
|
@ -0,0 +1,383 @@
|
||||||
|
← [README](../README.md)
|
||||||
|
|
||||||
|
**SMAPI.Web** contains the code for the `smapi.io` website, including the mod compatibility list
|
||||||
|
and update check API.
|
||||||
|
|
||||||
|
## Contents
|
||||||
|
* [Log parser](#log-parser)
|
||||||
|
* [JSON validator](#json-validator)
|
||||||
|
* [Web API](#web-api)
|
||||||
|
* [Short URLs](#short-urls)
|
||||||
|
* [For SMAPI developers](#for-smapi-developers)
|
||||||
|
* [Local development](#local-development)
|
||||||
|
* [Production environment](#production-environment)
|
||||||
|
|
||||||
|
## Log parser
|
||||||
|
The log parser at https://smapi.io/log provides a web UI for uploading, parsing, and sharing SMAPI
|
||||||
|
logs.
|
||||||
|
|
||||||
|
The logs are saved in a compressed form to Amazon Blob storage for 30 days.
|
||||||
|
|
||||||
|
## JSON validator
|
||||||
|
### Overview
|
||||||
|
The JSON validator at https://smapi.io/json provides a web UI for uploading and sharing JSON files,
|
||||||
|
and validating them as plain JSON or against a predefined format like `manifest.json` or Content
|
||||||
|
Patcher's `content.json`.
|
||||||
|
|
||||||
|
The logs are saved in a compressed form to Amazon Blob storage for 30 days.
|
||||||
|
|
||||||
|
### Schema file format
|
||||||
|
Schema files are defined in `wwwroot/schemas` using the [JSON Schema](https://json-schema.org/)
|
||||||
|
format. The JSON validator UI recognises a superset of the standard fields to change output:
|
||||||
|
|
||||||
|
<dl>
|
||||||
|
<dt>Documentation URL</dt>
|
||||||
|
<dd>
|
||||||
|
|
||||||
|
The root schema may have a `@documentationURL` field, which is a web URL for the user
|
||||||
|
documentation:
|
||||||
|
```js
|
||||||
|
"@documentationUrl": "https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Manifest"
|
||||||
|
```
|
||||||
|
|
||||||
|
If present, this is shown in the JSON validator UI.
|
||||||
|
|
||||||
|
</dd>
|
||||||
|
<dt>Error messages</dt>
|
||||||
|
<dd>
|
||||||
|
|
||||||
|
Any part of the schema can define an `@errorMessages` field, which overrides matching schema
|
||||||
|
errors. You can override by error code (recommended), or by error type and a regex pattern matched
|
||||||
|
against the error message (more fragile):
|
||||||
|
|
||||||
|
```js
|
||||||
|
// by error type
|
||||||
|
"pattern": "^[a-zA-Z0-9_.-]+\\.dll$",
|
||||||
|
"@errorMessages": {
|
||||||
|
"pattern": "Invalid value; must be a filename ending with .dll."
|
||||||
|
}
|
||||||
|
```
|
||||||
|
```js
|
||||||
|
// by error type + message pattern
|
||||||
|
"@errorMessages": {
|
||||||
|
"oneOf:valid against no schemas": "Missing required field: EntryDll or ContentPackFor.",
|
||||||
|
"oneOf:valid against more than one schema": "Can't specify both EntryDll or ContentPackFor, they're mutually exclusive."
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Error messages may contain special tokens:
|
||||||
|
|
||||||
|
* The `@value` token is replaced with the error's value field. This is usually (but not always) the
|
||||||
|
original field value.
|
||||||
|
* When an error has child errors, by default they're flattened into one message:
|
||||||
|
```
|
||||||
|
line | field | error
|
||||||
|
---- | ---------- | -------------------------------------------------------------------------
|
||||||
|
4 | Changes[0] | JSON does not match schema from 'then'.
|
||||||
|
| | ==> Changes[0].ToArea.Y: Invalid type. Expected Integer but got String.
|
||||||
|
| | ==> Changes[0].ToArea: Missing required fields: Height.
|
||||||
|
```
|
||||||
|
|
||||||
|
If you set the message for an error to `$transparent`, the parent error is omitted entirely and
|
||||||
|
the child errors are shown instead:
|
||||||
|
```
|
||||||
|
line | field | error
|
||||||
|
---- | ------------------- | ----------------------------------------------
|
||||||
|
8 | Changes[0].ToArea.Y | Invalid type. Expected Integer but got String.
|
||||||
|
8 | Changes[0].ToArea | Missing required fields: Height.
|
||||||
|
```
|
||||||
|
|
||||||
|
The child errors themselves may be marked `$transparent`, etc. If an error has no child errors,
|
||||||
|
this override is ignored.
|
||||||
|
|
||||||
|
Validation errors for `then` blocks are transparent by default, unless overridden.
|
||||||
|
|
||||||
|
</dd>
|
||||||
|
</dl>
|
||||||
|
|
||||||
|
### Using a schema file directly
|
||||||
|
You can reference the validator schemas in your JSON file directly using the `$schema` field, for
|
||||||
|
text editors that support schema validation. For example:
|
||||||
|
```js
|
||||||
|
{
|
||||||
|
"$schema": "https://smapi.io/schemas/manifest.json",
|
||||||
|
"Name": "Some mod",
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Available schemas:
|
||||||
|
|
||||||
|
format | schema URL
|
||||||
|
------ | ----------
|
||||||
|
[SMAPI `manifest.json`](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Manifest) | https://smapi.io/schemas/manifest.json
|
||||||
|
[Content Patcher `content.json`](https://github.com/Pathoschild/StardewMods/tree/develop/ContentPatcher#readme) | https://smapi.io/schemas/content-patcher.json
|
||||||
|
|
||||||
|
## Web API
|
||||||
|
### Overview
|
||||||
|
SMAPI provides a web API at `smapi.io/api` for use by SMAPI and external tools. The URL includes a
|
||||||
|
`{version}` token, which is the SMAPI version for backwards compatibility. This API is publicly
|
||||||
|
accessible but not officially released; it may change at any time.
|
||||||
|
|
||||||
|
### `/mods` endpoint
|
||||||
|
The API has one `/mods` endpoint. This crossreferences the mod against a variety of sources (e.g.
|
||||||
|
the wiki, Chucklefish, CurseForge, ModDrop, and Nexus) to provide metadata mainly intended for
|
||||||
|
update checks.
|
||||||
|
|
||||||
|
The API accepts a `POST` request with these fields:
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<th>field</th>
|
||||||
|
<th>summary</th>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td><code>mods</code></td>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
The mods for which to fetch metadata. Included fields:
|
||||||
|
|
||||||
|
|
||||||
|
field | summary
|
||||||
|
----- | -------
|
||||||
|
`id` | The unique ID in the mod's `manifest.json`. This is used to crossreference with the wiki, and to index mods in the response. If it's unknown (e.g. you just have an update key), you can use a unique fake ID like `FAKE.Nexus.2400`.
|
||||||
|
`updateKeys` | _(optional)_ [Update keys](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Manifest#Update_checks) which specify the mod pages to check, in addition to any mod pages linked to the `ID`.
|
||||||
|
`installedVersion` | _(optional)_ The installed version of the mod. If not specified, the API won't recommend an update.
|
||||||
|
`isBroken` | _(optional)_ Whether SMAPI failed to load the installed version of the mod, e.g. due to incompatibility. If true, the web API will be more permissive when recommending updates (e.g. allowing a stable → prerelease update).
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td><code>apiVersion</code></td>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
_(optional)_ The installed version of SMAPI. If not specified, the API won't recommend an update.
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td><code>gameVersion</code></td>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
_(optional)_ The installed version of Stardew Valley. This may be used to select updates.
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td><code>platform</code></td>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
_(optional)_ The player's OS (`Android`, `Linux`, `Mac`, or `Windows`). This may be used to select updates.
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td><code>includeExtendedMetadata</code></td>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
_(optional)_ Whether to include extra metadata that's not needed for SMAPI update checks, but which
|
||||||
|
may be useful to external tools.
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
Example request:
|
||||||
|
```js
|
||||||
|
POST https://smapi.io/api/v3.0/mods
|
||||||
|
{
|
||||||
|
"mods": [
|
||||||
|
{
|
||||||
|
"id": "Pathoschild.ContentPatcher",
|
||||||
|
"updateKeys": [ "nexus:1915" ],
|
||||||
|
"installedVersion": "1.9.2",
|
||||||
|
"isBroken": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"apiVersion": "3.0.0",
|
||||||
|
"gameVersion": "1.4.0",
|
||||||
|
"platform": "Windows",
|
||||||
|
"includeExtendedMetadata": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Response fields:
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<th>field</th>
|
||||||
|
<th>summary</th>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td><code>id</code></td>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
The mod ID you specified in the request.
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td><code>suggestedUpdate</code></td>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
The update version recommended by the web API, if any. This is based on some internal rules (e.g.
|
||||||
|
it won't recommend a prerelease update if the player has a working stable version) and context
|
||||||
|
(e.g. whether the player is in the game beta channel). Choosing an update version yourself isn't
|
||||||
|
recommended, but you can set `includeExtendedMetadata: true` and check the `metadata` field if you
|
||||||
|
really want to do that.
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td><code>errors</code></td>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
Human-readable errors that occurred fetching the version info (e.g. if a mod page has an invalid
|
||||||
|
version).
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td><code>metadata</code></td>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
Extra metadata that's not needed for SMAPI update checks but which may be useful to external tools,
|
||||||
|
if you set `includeExtendedMetadata: true` in the request. Included fields:
|
||||||
|
|
||||||
|
field | summary
|
||||||
|
----- | -------
|
||||||
|
`id` | The known `manifest.json` unique IDs for this mod defined on the wiki, if any. That includes historical versions of the mod.
|
||||||
|
`name` | The normalised name for this mod based on the crossreferenced sites.
|
||||||
|
`nexusID` | The mod ID on [Nexus Mods](https://www.nexusmods.com/stardewvalley/), if any.
|
||||||
|
`chucklefishID` | The mod ID in the [Chucklefish mod repo](https://community.playstarbound.com/resources/categories/stardew-valley.22/), if any.
|
||||||
|
`curseForgeID` | The mod project ID on [CurseForge](https://www.curseforge.com/stardewvalley), if any.
|
||||||
|
`curseForgeKey` | The mod key on [CurseForge](https://www.curseforge.com/stardewvalley), if any. This is used in the mod page URL.
|
||||||
|
`modDropID` | The mod ID on [ModDrop](https://www.moddrop.com/stardew-valley), if any.
|
||||||
|
`gitHubRepo` | The GitHub repository containing the mod code, if any. Specified in the `Owner/Repo` form.
|
||||||
|
`customSourceUrl` | The custom URL to the mod code, if any. This is used for mods which aren't stored in a GitHub repo.
|
||||||
|
`customUrl` | The custom URL to the mod page, if any. This is used for mods which aren't stored on one of the standard mod sites covered by the ID fields.
|
||||||
|
`main` | The primary mod version, if any. This depends on the mod site, but it's typically either the version of the mod itself or of its latest non-optional download.
|
||||||
|
`optional` | The latest optional download version, if any.
|
||||||
|
`unofficial` | The version of the unofficial update defined on the wiki for this mod, if any.
|
||||||
|
`unofficialForBeta` | Equivalent to `unofficial`, but for beta versions of SMAPI or Stardew Valley.
|
||||||
|
`hasBetaInfo` | Whether there's an ongoing Stardew Valley or SMAPI beta which may affect update checks.
|
||||||
|
`compatibilityStatus` | The compatibility status for the mod for the stable version of the game, as defined on the wiki, if any. See [possible values](https://github.com/Pathoschild/SMAPI/blob/develop/src/SMAPI.Toolkit/Framework/Clients/Wiki/WikiCompatibilityStatus.cs).
|
||||||
|
`compatibilitySummary` | The human-readable summary of the mod's compatibility in HTML format, if any.
|
||||||
|
`brokeIn` | The SMAPI or Stardew Valley version that broke this mod, if any.
|
||||||
|
`betaCompatibilityStatus`<br />`betaCompatibilitySummary`<br />`betaBrokeIn` | Equivalent to the preceding fields, but for beta versions of SMAPI or Stardew Valley.
|
||||||
|
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
Example response with `includeExtendedMetadata: false`:
|
||||||
|
```js
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "Pathoschild.ContentPatcher",
|
||||||
|
"suggestedUpdate": {
|
||||||
|
"version": "1.10.0",
|
||||||
|
"url": "https://www.nexusmods.com/stardewvalley/mods/1915"
|
||||||
|
},
|
||||||
|
"errors": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
Example response with `includeExtendedMetadata: true`:
|
||||||
|
```js
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "Pathoschild.ContentPatcher",
|
||||||
|
"suggestedUpdate": {
|
||||||
|
"version": "1.10.0",
|
||||||
|
"url": "https://www.nexusmods.com/stardewvalley/mods/1915"
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"id": [ "Pathoschild.ContentPatcher" ],
|
||||||
|
"name": "Content Patcher",
|
||||||
|
"nexusID": 1915,
|
||||||
|
"curseForgeID": 309243,
|
||||||
|
"curseForgeKey": "content-patcher",
|
||||||
|
"modDropID": 470174,
|
||||||
|
"gitHubRepo": "Pathoschild/StardewMods",
|
||||||
|
"main": {
|
||||||
|
"version": "1.10",
|
||||||
|
"url": "https://www.nexusmods.com/stardewvalley/mods/1915"
|
||||||
|
},
|
||||||
|
"hasBetaInfo": true,
|
||||||
|
"compatibilityStatus": "Ok",
|
||||||
|
"compatibilitySummary": "✓ use latest version."
|
||||||
|
},
|
||||||
|
"errors": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Short URLs
|
||||||
|
The SMAPI web services provides a few short URLs for convenience:
|
||||||
|
|
||||||
|
short url | → | target page
|
||||||
|
:-------- | - | :----------
|
||||||
|
[smapi.io/3.0](https://smapi.io/3.0) | → | [stardewvalleywiki.com/Modding:Migrate_to_SMAPI_3.0](https://stardewvalleywiki.com/Modding:Migrate_to_SMAPI_3.0)
|
||||||
|
[smapi.io/community](https://smapi.io/community) | → | [stardewvalleywiki.com/Modding:Community](https://stardewvalleywiki.com/Modding:Community)
|
||||||
|
[smapi.io/docs](https://smapi.io/docs) | → | [stardewvalleywiki.com/Modding:Index](https://stardewvalleywiki.com/Modding:Index)
|
||||||
|
[smapi.io/package](https://smapi.io/package) | → | [github.com/Pathoschild/SMAPI/blob/develop/docs/technical/mod-package.md](https://github.com/Pathoschild/SMAPI/blob/develop/docs/technical/mod-package.md)
|
||||||
|
[smapi.io/troubleshoot](https://smapi.io/troubleshoot) | → | [stardewvalleywiki.com/Modding:Player_Guide/Troubleshooting](https://stardewvalleywiki.com/Modding:Player_Guide/Troubleshooting)
|
||||||
|
[smapi.io/xnb](https://smapi.io/xnb) | → | [stardewvalleywiki.com/Modding:Using_XNB_mods](https://stardewvalleywiki.com/Modding:Using_XNB_mods)
|
||||||
|
|
||||||
|
## For SMAPI developers
|
||||||
|
### Local environment
|
||||||
|
A local environment lets you run a complete copy of the web project (including cache database) on
|
||||||
|
your machine, with no external dependencies aside from the actual mod sites.
|
||||||
|
|
||||||
|
1. Enter the Nexus credentials in `appsettings.Development.json` . You can leave the other
|
||||||
|
credentials empty to default to fetching data anonymously, and storing data in-memory and
|
||||||
|
on disk.
|
||||||
|
2. Launch `SMAPI.Web` from Visual Studio to run a local version of the site.
|
||||||
|
|
||||||
|
### Production environment
|
||||||
|
A production environment includes the web servers and cache database hosted online for public
|
||||||
|
access.
|
||||||
|
|
||||||
|
This section assumes you're creating a new environment on Azure, but the app isn't tied to any
|
||||||
|
Azure services. If you want to host it on a different site, you'll need to adjust the instructions
|
||||||
|
accordingly.
|
||||||
|
|
||||||
|
Initial setup:
|
||||||
|
|
||||||
|
1. Launch an empty MongoDB server (e.g. using [MongoDB Atlas](https://www.mongodb.com/cloud/atlas))
|
||||||
|
for mod data.
|
||||||
|
2. Create an Azure Blob storage account for uploaded files.
|
||||||
|
3. Create an Azure App Services environment running the latest .NET Core on Linux or Windows.
|
||||||
|
4. Add these application settings in the new App Services environment:
|
||||||
|
|
||||||
|
property name | description
|
||||||
|
------------------------------- | -----------------
|
||||||
|
`ApiClients.AzureBlobConnectionString` | The connection string for the Azure Blob storage account created in step 2.
|
||||||
|
`ApiClients.GitHubUsername`<br />`ApiClients.GitHubPassword` | The login credentials for the GitHub account with which to fetch release info. If these are omitted, GitHub will impose much stricter rate limits.
|
||||||
|
`ApiClients:NexusApiKey` | The [Nexus API authentication key](https://github.com/Pathoschild/FluentNexus#init-a-client).
|
||||||
|
`MongoDB:ConnectionString` | The connection string for the MongoDB instance.
|
||||||
|
`MongoDB:Database` | The MongoDB database name (e.g. `smapi` in production or `smapi-edge` in testing environments).
|
||||||
|
|
||||||
|
Optional settings:
|
||||||
|
|
||||||
|
property name | description
|
||||||
|
------------------------------- | -----------------
|
||||||
|
`BackgroundServices:Enabled` | Set to `true` to enable background processes like fetching data from the wiki, or false to disable them.
|
||||||
|
`Site:BetaEnabled` | Set to `true` to show a separate download button if there's a beta version of SMAPI in its GitHub releases.
|
||||||
|
`Site:BetaBlurb` | If `Site:BetaEnabled` is true and there's a beta version of SMAPI in its GitHub releases, this is shown on the beta download button as explanatory subtext.
|
||||||
|
`Site:SupporterList` | A list of Patreon supports to credit on the download page.
|
||||||
|
|
||||||
|
To deploy updates:
|
||||||
|
1. [Deploy the web project from Visual Studio](https://docs.microsoft.com/en-us/visualstudio/deployment/quickstart-deploy-to-azure).
|
||||||
|
2. If the MongoDB schema changed, delete the MongoDB database. (It'll be recreated automatically.)
|
|
@ -59,7 +59,7 @@ namespace StardewModdingAPI.Installer.Framework
|
||||||
this.UnixLauncherPath = Path.Combine(gameDir.FullName, "StardewValley");
|
this.UnixLauncherPath = Path.Combine(gameDir.FullName, "StardewValley");
|
||||||
this.UnixSmapiLauncherPath = Path.Combine(gameDir.FullName, "StardewModdingAPI");
|
this.UnixSmapiLauncherPath = Path.Combine(gameDir.FullName, "StardewModdingAPI");
|
||||||
this.UnixBackupLauncherPath = Path.Combine(gameDir.FullName, "StardewValley-original");
|
this.UnixBackupLauncherPath = Path.Combine(gameDir.FullName, "StardewValley-original");
|
||||||
this.ApiConfigPath = Path.Combine(gameDir.FullName, "smapi-internal", "StardewModdingAPI.config.json");
|
this.ApiConfigPath = Path.Combine(gameDir.FullName, "smapi-internal", "config.json");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,6 @@ using System.Threading;
|
||||||
using Microsoft.Win32;
|
using Microsoft.Win32;
|
||||||
using StardewModdingApi.Installer.Enums;
|
using StardewModdingApi.Installer.Enums;
|
||||||
using StardewModdingAPI.Installer.Framework;
|
using StardewModdingAPI.Installer.Framework;
|
||||||
using StardewModdingAPI.Internal;
|
|
||||||
using StardewModdingAPI.Internal.ConsoleWriting;
|
using StardewModdingAPI.Internal.ConsoleWriting;
|
||||||
using StardewModdingAPI.Toolkit;
|
using StardewModdingAPI.Toolkit;
|
||||||
using StardewModdingAPI.Toolkit.Framework.ModScanning;
|
using StardewModdingAPI.Toolkit.Framework.ModScanning;
|
||||||
|
@ -37,64 +36,7 @@ namespace StardewModdingApi.Installer
|
||||||
"SMAPI.ConsoleCommands"
|
"SMAPI.ConsoleCommands"
|
||||||
};
|
};
|
||||||
|
|
||||||
/// <summary>The default file paths where Stardew Valley can be installed.</summary>
|
|
||||||
/// <param name="platform">The target platform.</param>
|
|
||||||
/// <remarks>Derived from the crossplatform mod config: https://github.com/Pathoschild/Stardew.ModBuildConfig. </remarks>
|
|
||||||
private IEnumerable<string> GetDefaultInstallPaths(Platform platform)
|
|
||||||
{
|
|
||||||
switch (platform)
|
|
||||||
{
|
|
||||||
case Platform.Linux:
|
|
||||||
case Platform.Mac:
|
|
||||||
{
|
|
||||||
string home = Environment.GetEnvironmentVariable("HOME");
|
|
||||||
|
|
||||||
// Linux
|
|
||||||
yield return $"{home}/GOG Games/Stardew Valley/game";
|
|
||||||
yield return Directory.Exists($"{home}/.steam/steam/steamapps/common/Stardew Valley")
|
|
||||||
? $"{home}/.steam/steam/steamapps/common/Stardew Valley"
|
|
||||||
: $"{home}/.local/share/Steam/steamapps/common/Stardew Valley";
|
|
||||||
|
|
||||||
// Mac
|
|
||||||
yield return "/Applications/Stardew Valley.app/Contents/MacOS";
|
|
||||||
yield return $"{home}/Library/Application Support/Steam/steamapps/common/Stardew Valley/Contents/MacOS";
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case Platform.Windows:
|
|
||||||
{
|
|
||||||
// Windows
|
|
||||||
foreach (string programFiles in new[] { @"C:\Program Files", @"C:\Program Files (x86)" })
|
|
||||||
{
|
|
||||||
yield return $@"{programFiles}\GalaxyClient\Games\Stardew Valley";
|
|
||||||
yield return $@"{programFiles}\GOG Galaxy\Games\Stardew Valley";
|
|
||||||
yield return $@"{programFiles}\Steam\steamapps\common\Stardew Valley";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Windows registry
|
|
||||||
IDictionary<string, string> registryKeys = new Dictionary<string, string>
|
|
||||||
{
|
|
||||||
[@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Steam App 413150"] = "InstallLocation", // Steam
|
|
||||||
[@"SOFTWARE\WOW6432Node\GOG.com\Games\1453375253"] = "PATH", // GOG on 64-bit Windows
|
|
||||||
};
|
|
||||||
foreach (var pair in registryKeys)
|
|
||||||
{
|
|
||||||
string path = this.GetLocalMachineRegistryValue(pair.Key, pair.Value);
|
|
||||||
if (!string.IsNullOrWhiteSpace(path))
|
|
||||||
yield return path;
|
|
||||||
}
|
|
||||||
|
|
||||||
// via Steam library path
|
|
||||||
string steampath = this.GetCurrentUserRegistryValue(@"Software\Valve\Steam", "SteamPath");
|
|
||||||
if (steampath != null)
|
|
||||||
yield return Path.Combine(steampath.Replace('/', '\\'), @"steamapps\common\Stardew Valley");
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
throw new InvalidOperationException($"Unknown platform '{platform}'.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Get the absolute file or folder paths to remove when uninstalling SMAPI.</summary>
|
/// <summary>Get the absolute file or folder paths to remove when uninstalling SMAPI.</summary>
|
||||||
/// <param name="installDir">The folder for Stardew Valley and SMAPI.</param>
|
/// <param name="installDir">The folder for Stardew Valley and SMAPI.</param>
|
||||||
|
@ -112,6 +54,7 @@ namespace StardewModdingApi.Installer
|
||||||
yield return GetInstallPath("StardewModdingAPI.pdb"); // Windows only
|
yield return GetInstallPath("StardewModdingAPI.pdb"); // Windows only
|
||||||
yield return GetInstallPath("StardewModdingAPI.xml");
|
yield return GetInstallPath("StardewModdingAPI.xml");
|
||||||
yield return GetInstallPath("smapi-internal");
|
yield return GetInstallPath("smapi-internal");
|
||||||
|
yield return GetInstallPath("steam_appid.txt");
|
||||||
|
|
||||||
// obsolete
|
// obsolete
|
||||||
yield return GetInstallPath(Path.Combine("Mods", ".cache")); // 1.3-1.4
|
yield return GetInstallPath(Path.Combine("Mods", ".cache")); // 1.3-1.4
|
||||||
|
@ -133,11 +76,9 @@ namespace StardewModdingApi.Installer
|
||||||
yield return GetInstallPath("StardewModdingAPI.Toolkit.CoreInterfaces.dll"); // moved in 2.8
|
yield return GetInstallPath("StardewModdingAPI.Toolkit.CoreInterfaces.dll"); // moved in 2.8
|
||||||
yield return GetInstallPath("StardewModdingAPI.Toolkit.CoreInterfaces.pdb"); // moved in 2.8
|
yield return GetInstallPath("StardewModdingAPI.Toolkit.CoreInterfaces.pdb"); // moved in 2.8
|
||||||
yield return GetInstallPath("StardewModdingAPI.Toolkit.CoreInterfaces.xml"); // moved in 2.8
|
yield return GetInstallPath("StardewModdingAPI.Toolkit.CoreInterfaces.xml"); // moved in 2.8
|
||||||
yield return GetInstallPath("StardewModdingAPI.xml"); // moved in 2.8
|
|
||||||
yield return GetInstallPath("System.Numerics.dll"); // moved in 2.8
|
yield return GetInstallPath("System.Numerics.dll"); // moved in 2.8
|
||||||
yield return GetInstallPath("System.Runtime.Caching.dll"); // moved in 2.8
|
yield return GetInstallPath("System.Runtime.Caching.dll"); // moved in 2.8
|
||||||
yield return GetInstallPath("System.ValueTuple.dll"); // moved in 2.8
|
yield return GetInstallPath("System.ValueTuple.dll"); // moved in 2.8
|
||||||
yield return GetInstallPath("steam_appid.txt"); // moved in 2.8
|
|
||||||
|
|
||||||
if (modsDir.Exists)
|
if (modsDir.Exists)
|
||||||
{
|
{
|
||||||
|
@ -159,13 +100,13 @@ namespace StardewModdingApi.Installer
|
||||||
public InteractiveInstaller(string bundlePath)
|
public InteractiveInstaller(string bundlePath)
|
||||||
{
|
{
|
||||||
this.BundlePath = bundlePath;
|
this.BundlePath = bundlePath;
|
||||||
this.ConsoleWriter = new ColorfulConsoleWriter(EnvironmentUtility.DetectPlatform(), MonitorColorScheme.AutoDetect);
|
this.ConsoleWriter = new ColorfulConsoleWriter(EnvironmentUtility.DetectPlatform());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Run the install or uninstall script.</summary>
|
/// <summary>Run the install or uninstall script.</summary>
|
||||||
/// <param name="args">The command line arguments.</param>
|
/// <param name="args">The command line arguments.</param>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// Initialisation flow:
|
/// Initialization flow:
|
||||||
/// 1. Collect information (mainly OS and install path) and validate it.
|
/// 1. Collect information (mainly OS and install path) and validate it.
|
||||||
/// 2. Ask the user whether to install or uninstall.
|
/// 2. Ask the user whether to install or uninstall.
|
||||||
///
|
///
|
||||||
|
@ -187,8 +128,9 @@ namespace StardewModdingApi.Installer
|
||||||
** Step 1: initial setup
|
** Step 1: initial setup
|
||||||
*********/
|
*********/
|
||||||
/****
|
/****
|
||||||
** Get platform & set window title
|
** Get basic info & set window title
|
||||||
****/
|
****/
|
||||||
|
ModToolkit toolkit = new ModToolkit();
|
||||||
Platform platform = EnvironmentUtility.DetectPlatform();
|
Platform platform = EnvironmentUtility.DetectPlatform();
|
||||||
Console.Title = $"SMAPI {this.GetDisplayVersion(this.GetType().Assembly.GetName().Version)} installer on {platform} {EnvironmentUtility.GetFriendlyPlatformName(platform)}";
|
Console.Title = $"SMAPI {this.GetDisplayVersion(this.GetType().Assembly.GetName().Version)} installer on {platform} {EnvironmentUtility.GetFriendlyPlatformName(platform)}";
|
||||||
Console.WriteLine();
|
Console.WriteLine();
|
||||||
|
@ -275,8 +217,8 @@ namespace StardewModdingApi.Installer
|
||||||
** show theme selector
|
** show theme selector
|
||||||
****/
|
****/
|
||||||
// get theme writers
|
// get theme writers
|
||||||
var lightBackgroundWriter = new ColorfulConsoleWriter(EnvironmentUtility.DetectPlatform(), MonitorColorScheme.LightBackground);
|
var lightBackgroundWriter = new ColorfulConsoleWriter(platform, ColorfulConsoleWriter.GetDefaultColorSchemeConfig(MonitorColorScheme.LightBackground));
|
||||||
var darkDarkgroundWriter = new ColorfulConsoleWriter(EnvironmentUtility.DetectPlatform(), MonitorColorScheme.DarkBackground);
|
var darkBackgroundWriter = new ColorfulConsoleWriter(platform, ColorfulConsoleWriter.GetDefaultColorSchemeConfig(MonitorColorScheme.DarkBackground));
|
||||||
|
|
||||||
// print question
|
// print question
|
||||||
this.PrintPlain("Which text looks more readable?");
|
this.PrintPlain("Which text looks more readable?");
|
||||||
|
@ -284,7 +226,7 @@ namespace StardewModdingApi.Installer
|
||||||
Console.Write(" [1] ");
|
Console.Write(" [1] ");
|
||||||
lightBackgroundWriter.WriteLine("Dark text on light background", ConsoleLogLevel.Info);
|
lightBackgroundWriter.WriteLine("Dark text on light background", ConsoleLogLevel.Info);
|
||||||
Console.Write(" [2] ");
|
Console.Write(" [2] ");
|
||||||
darkDarkgroundWriter.WriteLine("Light text on dark background", ConsoleLogLevel.Info);
|
darkBackgroundWriter.WriteLine("Light text on dark background", ConsoleLogLevel.Info);
|
||||||
Console.WriteLine();
|
Console.WriteLine();
|
||||||
|
|
||||||
// handle choice
|
// handle choice
|
||||||
|
@ -297,7 +239,7 @@ namespace StardewModdingApi.Installer
|
||||||
break;
|
break;
|
||||||
case "2":
|
case "2":
|
||||||
scheme = MonitorColorScheme.DarkBackground;
|
scheme = MonitorColorScheme.DarkBackground;
|
||||||
this.ConsoleWriter = darkDarkgroundWriter;
|
this.ConsoleWriter = darkBackgroundWriter;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new InvalidOperationException($"Unexpected action key '{choice}'.");
|
throw new InvalidOperationException($"Unexpected action key '{choice}'.");
|
||||||
|
@ -324,7 +266,7 @@ namespace StardewModdingApi.Installer
|
||||||
****/
|
****/
|
||||||
// get game path
|
// get game path
|
||||||
this.PrintInfo("Where is your game folder?");
|
this.PrintInfo("Where is your game folder?");
|
||||||
DirectoryInfo installDir = this.InteractivelyGetInstallPath(platform, gamePathArg);
|
DirectoryInfo installDir = this.InteractivelyGetInstallPath(platform, toolkit, gamePathArg);
|
||||||
if (installDir == null)
|
if (installDir == null)
|
||||||
{
|
{
|
||||||
this.PrintError("Failed finding your game path.");
|
this.PrintError("Failed finding your game path.");
|
||||||
|
@ -490,7 +432,6 @@ namespace StardewModdingApi.Installer
|
||||||
{
|
{
|
||||||
this.PrintDebug("Adding bundled mods...");
|
this.PrintDebug("Adding bundled mods...");
|
||||||
|
|
||||||
ModToolkit toolkit = new ModToolkit();
|
|
||||||
ModFolder[] targetMods = toolkit.GetModFolders(paths.ModsPath).ToArray();
|
ModFolder[] targetMods = toolkit.GetModFolders(paths.ModsPath).ToArray();
|
||||||
foreach (ModFolder sourceMod in toolkit.GetModFolders(bundledModsDir.FullName))
|
foreach (ModFolder sourceMod in toolkit.GetModFolders(bundledModsDir.FullName))
|
||||||
{
|
{
|
||||||
|
@ -529,7 +470,7 @@ namespace StardewModdingApi.Installer
|
||||||
{
|
{
|
||||||
string text = File
|
string text = File
|
||||||
.ReadAllText(paths.ApiConfigPath)
|
.ReadAllText(paths.ApiConfigPath)
|
||||||
.Replace(@"""ColorScheme"": ""AutoDetect""", $@"""ColorScheme"": ""{scheme}""");
|
.Replace(@"""UseScheme"": ""AutoDetect""", $@"""UseScheme"": ""{scheme}""");
|
||||||
File.WriteAllText(paths.ApiConfigPath, text);
|
File.WriteAllText(paths.ApiConfigPath, text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -598,32 +539,6 @@ namespace StardewModdingApi.Installer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Get the value of a key in the Windows HKLM registry.</summary>
|
|
||||||
/// <param name="key">The full path of the registry key relative to HKLM.</param>
|
|
||||||
/// <param name="name">The name of the value.</param>
|
|
||||||
private string GetLocalMachineRegistryValue(string key, string name)
|
|
||||||
{
|
|
||||||
RegistryKey localMachine = Environment.Is64BitOperatingSystem ? RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64) : Registry.LocalMachine;
|
|
||||||
RegistryKey openKey = localMachine.OpenSubKey(key);
|
|
||||||
if (openKey == null)
|
|
||||||
return null;
|
|
||||||
using (openKey)
|
|
||||||
return (string)openKey.GetValue(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Get the value of a key in the Windows HKCU registry.</summary>
|
|
||||||
/// <param name="key">The full path of the registry key relative to HKCU.</param>
|
|
||||||
/// <param name="name">The name of the value.</param>
|
|
||||||
private string GetCurrentUserRegistryValue(string key, string name)
|
|
||||||
{
|
|
||||||
RegistryKey currentuser = Environment.Is64BitOperatingSystem ? RegistryKey.OpenBaseKey(RegistryHive.CurrentUser, RegistryView.Registry64) : Registry.CurrentUser;
|
|
||||||
RegistryKey openKey = currentuser.OpenSubKey(key);
|
|
||||||
if (openKey == null)
|
|
||||||
return null;
|
|
||||||
using (openKey)
|
|
||||||
return (string)openKey.GetValue(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Print a message without formatting.</summary>
|
/// <summary>Print a message without formatting.</summary>
|
||||||
/// <param name="text">The text to print.</param>
|
/// <param name="text">The text to print.</param>
|
||||||
private void PrintPlain(string text) => Console.WriteLine(text);
|
private void PrintPlain(string text) => Console.WriteLine(text);
|
||||||
|
@ -731,7 +646,7 @@ namespace StardewModdingApi.Installer
|
||||||
|
|
||||||
/// <summary>Delete a file or folder regardless of file permissions, and block until deletion completes.</summary>
|
/// <summary>Delete a file or folder regardless of file permissions, and block until deletion completes.</summary>
|
||||||
/// <param name="entry">The file or folder to reset.</param>
|
/// <param name="entry">The file or folder to reset.</param>
|
||||||
/// <remarks>This method is mirred from <c>FileUtilities.ForceDelete</c> in the toolkit.</remarks>
|
/// <remarks>This method is mirrored from <c>FileUtilities.ForceDelete</c> in the toolkit.</remarks>
|
||||||
private void ForceDelete(FileSystemInfo entry)
|
private void ForceDelete(FileSystemInfo entry)
|
||||||
{
|
{
|
||||||
// ignore if already deleted
|
// ignore if already deleted
|
||||||
|
@ -789,8 +704,9 @@ namespace StardewModdingApi.Installer
|
||||||
|
|
||||||
/// <summary>Interactively locate the game install path to update.</summary>
|
/// <summary>Interactively locate the game install path to update.</summary>
|
||||||
/// <param name="platform">The current platform.</param>
|
/// <param name="platform">The current platform.</param>
|
||||||
|
/// <param name="toolkit">The mod toolkit.</param>
|
||||||
/// <param name="specifiedPath">The path specified as a command-line argument (if any), which should override automatic path detection.</param>
|
/// <param name="specifiedPath">The path specified as a command-line argument (if any), which should override automatic path detection.</param>
|
||||||
private DirectoryInfo InteractivelyGetInstallPath(Platform platform, string specifiedPath)
|
private DirectoryInfo InteractivelyGetInstallPath(Platform platform, ModToolkit toolkit, string specifiedPath)
|
||||||
{
|
{
|
||||||
// get executable name
|
// get executable name
|
||||||
string executableFilename = EnvironmentUtility.GetExecutableName(platform);
|
string executableFilename = EnvironmentUtility.GetExecutableName(platform);
|
||||||
|
@ -813,18 +729,7 @@ namespace StardewModdingApi.Installer
|
||||||
}
|
}
|
||||||
|
|
||||||
// get installed paths
|
// get installed paths
|
||||||
DirectoryInfo[] defaultPaths =
|
DirectoryInfo[] defaultPaths = toolkit.GetGameFolders().ToArray();
|
||||||
(
|
|
||||||
from path in this.GetDefaultInstallPaths(platform).Distinct(StringComparer.InvariantCultureIgnoreCase)
|
|
||||||
let dir = new DirectoryInfo(path)
|
|
||||||
where dir.Exists && dir.EnumerateFiles(executableFilename).Any()
|
|
||||||
select dir
|
|
||||||
)
|
|
||||||
.GroupBy(p => p.FullName, StringComparer.InvariantCultureIgnoreCase) // ignore duplicate paths
|
|
||||||
.Select(p => p.First())
|
|
||||||
.ToArray();
|
|
||||||
|
|
||||||
// choose where to install
|
|
||||||
if (defaultPaths.Any())
|
if (defaultPaths.Any())
|
||||||
{
|
{
|
||||||
// only one path
|
// only one path
|
||||||
|
@ -857,7 +762,7 @@ namespace StardewModdingApi.Installer
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// normalise path
|
// normalize path
|
||||||
if (platform == Platform.Windows)
|
if (platform == Platform.Windows)
|
||||||
path = path.Replace("\"", ""); // in Windows, quotes are used to escape spaces and aren't part of the file path
|
path = path.Replace("\"", ""); // in Windows, quotes are used to escape spaces and aren't part of the file path
|
||||||
if (platform == Platform.Linux || platform == Platform.Mac)
|
if (platform == Platform.Linux || platform == Platform.Mac)
|
||||||
|
|
|
@ -36,7 +36,7 @@ namespace StardewModdingApi.Installer
|
||||||
FileInfo zipFile = new FileInfo(Path.Combine(Program.InstallerPath, $"{(platform == PlatformID.Win32NT ? "windows" : "unix")}-install.dat"));
|
FileInfo zipFile = new FileInfo(Path.Combine(Program.InstallerPath, $"{(platform == PlatformID.Win32NT ? "windows" : "unix")}-install.dat"));
|
||||||
if (!zipFile.Exists)
|
if (!zipFile.Exists)
|
||||||
{
|
{
|
||||||
Console.WriteLine($"Oops! Some of the installer files are missing; try redownloading the installer. (Missing file: {zipFile.FullName})");
|
Console.WriteLine($"Oops! Some of the installer files are missing; try re-downloading the installer. (Missing file: {zipFile.FullName})");
|
||||||
Console.ReadLine();
|
Console.ReadLine();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
using System.Reflection;
|
|
||||||
|
|
||||||
[assembly: AssemblyTitle("SMAPI.Installer")]
|
|
||||||
[assembly: AssemblyDescription("The SMAPI installer for players.")]
|
|
|
@ -40,5 +40,5 @@ When installing on Linux or Mac:
|
||||||
- Make sure Mono is installed (normally the installer checks for you). While it's not required,
|
- Make sure Mono is installed (normally the installer checks for you). While it's not required,
|
||||||
many mods won't work correctly without it. (Specifically, mods which load PNG images may crash or
|
many mods won't work correctly without it. (Specifically, mods which load PNG images may crash or
|
||||||
freeze the game.)
|
freeze the game.)
|
||||||
- To configure the color scheme, edit the `smapi-internal/StardewModdingAPI.config.json` file and
|
- To configure the color scheme, edit the `smapi-internal/config.json` file and see instructions
|
||||||
see instructions there for the 'ColorScheme' setting.
|
there for the 'ColorScheme' setting.
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
<AssemblyName>SMAPI.Installer</AssemblyName>
|
||||||
<RootNamespace>StardewModdingAPI.Installer</RootNamespace>
|
<RootNamespace>StardewModdingAPI.Installer</RootNamespace>
|
||||||
<AssemblyName>StardewModdingAPI.Installer</AssemblyName>
|
<Description>The SMAPI installer for players.</Description>
|
||||||
<TargetFramework>net45</TargetFramework>
|
<TargetFramework>net45</TargetFramework>
|
||||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
|
||||||
<LangVersion>latest</LangVersion>
|
<LangVersion>latest</LangVersion>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<PlatformTarget>x86</PlatformTarget>
|
<PlatformTarget>x86</PlatformTarget>
|
||||||
|
@ -13,11 +13,7 @@
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="..\..\build\GlobalAssemblyInfo.cs" Link="Properties\GlobalAssemblyInfo.cs" />
|
<ProjectReference Include="..\SMAPI.Toolkit\SMAPI.Toolkit.csproj" />
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\SMAPI.Toolkit\StardewModdingAPI.Toolkit.csproj" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
|
@ -12,6 +12,9 @@ elif type type >/dev/null 2>&1; then
|
||||||
COMMAND="type"
|
COMMAND="type"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# if $TERM is not set to xterm, mono will bail out when attempting to write to the console.
|
||||||
|
export TERM=xterm
|
||||||
|
|
||||||
# validate Mono & run installer
|
# validate Mono & run installer
|
||||||
if $COMMAND mono >/dev/null 2>&1; then
|
if $COMMAND mono >/dev/null 2>&1; then
|
||||||
mono internal/unix-install.exe
|
mono internal/unix-install.exe
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
#!/bin/bash
|
#!/usr/bin/env bash
|
||||||
# MonoKickstart Shell Script
|
# MonoKickstart Shell Script
|
||||||
# Written by Ethan "flibitijibibo" Lee
|
# Written by Ethan "flibitijibibo" Lee
|
||||||
# Modified for StardewModdingAPI by Viz and Pathoschild
|
# Modified for SMAPI by various contributors
|
||||||
|
|
||||||
# Move to script's directory
|
# Move to script's directory
|
||||||
cd "`dirname "$0"`"
|
cd "$(dirname "$0")" || exit $?
|
||||||
|
|
||||||
# Get the system architecture
|
# Get the system architecture
|
||||||
UNAME=`uname`
|
UNAME=$(uname)
|
||||||
ARCH=`uname -m`
|
ARCH=$(uname -m)
|
||||||
|
|
||||||
# MonoKickstart picks the right libfolder, so just execute the right binary.
|
# MonoKickstart picks the right libfolder, so just execute the right binary.
|
||||||
if [ "$UNAME" == "Darwin" ]; then
|
if [ "$UNAME" == "Darwin" ]; then
|
||||||
|
@ -39,18 +39,18 @@ if [ "$UNAME" == "Darwin" ]; then
|
||||||
|
|
||||||
# launch SMAPI
|
# launch SMAPI
|
||||||
cp StardewValley.bin.osx StardewModdingAPI.bin.osx
|
cp StardewValley.bin.osx StardewModdingAPI.bin.osx
|
||||||
open -a Terminal ./StardewModdingAPI.bin.osx $@
|
open -a Terminal ./StardewModdingAPI.bin.osx "$@"
|
||||||
else
|
else
|
||||||
# choose launcher
|
# choose launcher
|
||||||
LAUNCHER=""
|
LAUNCHER=""
|
||||||
if [ "$ARCH" == "x86_64" ]; then
|
if [ "$ARCH" == "x86_64" ]; then
|
||||||
ln -sf mcs.bin.x86_64 mcs
|
ln -sf mcs.bin.x86_64 mcs
|
||||||
cp StardewValley.bin.x86_64 StardewModdingAPI.bin.x86_64
|
cp StardewValley.bin.x86_64 StardewModdingAPI.bin.x86_64
|
||||||
LAUNCHER="./StardewModdingAPI.bin.x86_64 $@"
|
LAUNCHER="./StardewModdingAPI.bin.x86_64 $*"
|
||||||
else
|
else
|
||||||
ln -sf mcs.bin.x86 mcs
|
ln -sf mcs.bin.x86 mcs
|
||||||
cp StardewValley.bin.x86 StardewModdingAPI.bin.x86
|
cp StardewValley.bin.x86 StardewModdingAPI.bin.x86
|
||||||
LAUNCHER="./StardewModdingAPI.bin.x86 $@"
|
LAUNCHER="./StardewModdingAPI.bin.x86 $*"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# get cross-distro version of POSIX command
|
# get cross-distro version of POSIX command
|
||||||
|
@ -61,37 +61,65 @@ else
|
||||||
COMMAND="type"
|
COMMAND="type"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# open SMAPI in terminal
|
# select terminal (prefer xterm for best compatibility, then known supported terminals)
|
||||||
if $COMMAND xterm 2>/dev/null; then
|
for terminal in xterm gnome-terminal kitty terminator xfce4-terminal konsole terminal termite alacritty mate-terminal x-terminal-emulator; do
|
||||||
xterm -e "$LAUNCHER"
|
if $COMMAND "$terminal" 2>/dev/null; then
|
||||||
elif $COMMAND x-terminal-emulator 2>/dev/null; then
|
# Find the true shell behind x-terminal-emulator
|
||||||
# Terminator converts -e to -x when used through x-terminal-emulator for some reason (per
|
if [ "$(basename "$(readlink -f $(which "$terminal"))")" != "x-terminal-emulator" ]; then
|
||||||
# `man terminator`), which causes an "unable to find shell" error. If x-terminal-emulator
|
export LAUNCHTERM=$terminal
|
||||||
# is mapped to Terminator, invoke it directly instead.
|
break;
|
||||||
if [[ "$(readlink -e $(which x-terminal-emulator))" == *"/terminator" ]]; then
|
else
|
||||||
terminator -e "sh -c 'TERM=xterm $LAUNCHER'"
|
export LAUNCHTERM="$(basename "$(readlink -f $(which x-terminal-emulator))")"
|
||||||
else
|
# Remember that we're using x-terminal-emulator just in case it points outside the $PATH
|
||||||
x-terminal-emulator -e "sh -c 'TERM=xterm $LAUNCHER'"
|
export XTE=1
|
||||||
|
break;
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
elif $COMMAND xfce4-terminal 2>/dev/null; then
|
done
|
||||||
xfce4-terminal -e "sh -c 'TERM=xterm $LAUNCHER'"
|
|
||||||
elif $COMMAND gnome-terminal 2>/dev/null; then
|
# if no terminal was found, run in current shell or with no output
|
||||||
gnome-terminal -e "sh -c 'TERM=xterm $LAUNCHER'"
|
if [ -z "$LAUNCHTERM" ]; then
|
||||||
elif $COMMAND konsole 2>/dev/null; then
|
|
||||||
konsole -p Environment=TERM=xterm -e "$LAUNCHER"
|
|
||||||
elif $COMMAND terminal 2>/dev/null; then
|
|
||||||
terminal -e "sh -c 'TERM=xterm $LAUNCHER'"
|
|
||||||
elif $COMMAND termite 2>/dev/null; then
|
|
||||||
termite -e "sh -c 'TERM=xterm $LAUNCHER'"
|
|
||||||
else
|
|
||||||
sh -c 'TERM=xterm $LAUNCHER'
|
sh -c 'TERM=xterm $LAUNCHER'
|
||||||
|
if [ $? -eq 127 ]; then
|
||||||
|
$LAUNCHER --no-terminal
|
||||||
|
fi
|
||||||
|
exit
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# some Linux users get error 127 (command not found) from the above block, even though
|
# run in selected terminal and account for quirks
|
||||||
# `command -v` indicates the command is valid. As a fallback, launch SMAPI without a terminal when
|
case $LAUNCHTERM in
|
||||||
# that happens and pass in an argument indicating SMAPI shouldn't try writing to the terminal
|
terminator)
|
||||||
# (which can be slow if there is none).
|
# Terminator converts -e to -x when used through x-terminal-emulator for some reason
|
||||||
if [ $? -eq 127 ]; then
|
if $XTE; then
|
||||||
$LAUNCHER --no-terminal
|
terminator -e "sh -c 'TERM=xterm $LAUNCHER'"
|
||||||
fi
|
else
|
||||||
|
terminator -x "sh -c 'TERM=xterm $LAUNCHER'"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
kitty)
|
||||||
|
# Kitty overrides the TERM varible unless you set it explicitly
|
||||||
|
kitty -o term=xterm $LAUNCHER
|
||||||
|
;;
|
||||||
|
alacritty)
|
||||||
|
# Alacritty doesn't like the double quotes or the variable
|
||||||
|
if [ "$ARCH" == "x86_64" ]; then
|
||||||
|
alacritty -e sh -c 'TERM=xterm ./StardewModdingAPI.bin.x86_64 $*'
|
||||||
|
else
|
||||||
|
alacritty -e sh -c 'TERM=xterm ./StardewModdingAPI.bin.x86 $*'
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
xterm|xfce4-terminal|gnome-terminal|terminal|termite|mate-terminal)
|
||||||
|
$LAUNCHTERM -e "sh -c 'TERM=xterm $LAUNCHER'"
|
||||||
|
;;
|
||||||
|
konsole)
|
||||||
|
konsole -p Environment=TERM=xterm -e "$LAUNCHER"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
# If we don't know the terminal, just try to run it in the current shell.
|
||||||
|
sh -c 'TERM=xterm $LAUNCHER'
|
||||||
|
# if THAT fails, launch with no output
|
||||||
|
if [ $? -eq 127 ]; then
|
||||||
|
$LAUNCHER --no-terminal
|
||||||
|
fi
|
||||||
|
esac
|
||||||
fi
|
fi
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace StardewModdingAPI.Internal.ConsoleWriting
|
||||||
|
{
|
||||||
|
/// <summary>The console color scheme options.</summary>
|
||||||
|
internal class ColorSchemeConfig
|
||||||
|
{
|
||||||
|
/// <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; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using StardewModdingAPI.Toolkit.Utilities;
|
||||||
|
|
||||||
namespace StardewModdingAPI.Internal.ConsoleWriting
|
namespace StardewModdingAPI.Internal.ConsoleWriting
|
||||||
{
|
{
|
||||||
|
@ -21,11 +22,16 @@ namespace StardewModdingAPI.Internal.ConsoleWriting
|
||||||
*********/
|
*********/
|
||||||
/// <summary>Construct an instance.</summary>
|
/// <summary>Construct an instance.</summary>
|
||||||
/// <param name="platform">The target platform.</param>
|
/// <param name="platform">The target platform.</param>
|
||||||
/// <param name="colorScheme">The console color scheme to use.</param>
|
public ColorfulConsoleWriter(Platform platform)
|
||||||
public ColorfulConsoleWriter(Platform platform, MonitorColorScheme colorScheme)
|
: this(platform, ColorfulConsoleWriter.GetDefaultColorSchemeConfig(MonitorColorScheme.AutoDetect)) { }
|
||||||
|
|
||||||
|
/// <summary>Construct an instance.</summary>
|
||||||
|
/// <param name="platform">The target platform.</param>
|
||||||
|
/// <param name="colorConfig">The colors to use for text written to the SMAPI console.</param>
|
||||||
|
public ColorfulConsoleWriter(Platform platform, ColorSchemeConfig colorConfig)
|
||||||
{
|
{
|
||||||
this.SupportsColor = this.TestColorSupport();
|
this.SupportsColor = this.TestColorSupport();
|
||||||
this.Colors = this.GetConsoleColorScheme(platform, colorScheme);
|
this.Colors = this.GetConsoleColorScheme(platform, colorConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Write a message line to the log.</summary>
|
/// <summary>Write a message line to the log.</summary>
|
||||||
|
@ -53,6 +59,40 @@ namespace StardewModdingAPI.Internal.ConsoleWriting
|
||||||
Console.WriteLine(message);
|
Console.WriteLine(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <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)
|
||||||
|
{
|
||||||
|
return new ColorSchemeConfig
|
||||||
|
{
|
||||||
|
UseScheme = useScheme,
|
||||||
|
Schemes = new Dictionary<MonitorColorScheme, IDictionary<ConsoleLogLevel, ConsoleColor>>
|
||||||
|
{
|
||||||
|
[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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*********
|
/*********
|
||||||
** Private methods
|
** Private methods
|
||||||
|
@ -73,47 +113,22 @@ namespace StardewModdingAPI.Internal.ConsoleWriting
|
||||||
|
|
||||||
/// <summary>Get the color scheme to use for the current console.</summary>
|
/// <summary>Get the color scheme to use for the current console.</summary>
|
||||||
/// <param name="platform">The target platform.</param>
|
/// <param name="platform">The target platform.</param>
|
||||||
/// <param name="colorScheme">The console color scheme to use.</param>
|
/// <param name="colorConfig">The colors to use for text written to the SMAPI console.</param>
|
||||||
private IDictionary<ConsoleLogLevel, ConsoleColor> GetConsoleColorScheme(Platform platform, MonitorColorScheme colorScheme)
|
private IDictionary<ConsoleLogLevel, ConsoleColor> GetConsoleColorScheme(Platform platform, ColorSchemeConfig colorConfig)
|
||||||
{
|
{
|
||||||
// auto detect color scheme
|
// get color scheme ID
|
||||||
if (colorScheme == MonitorColorScheme.AutoDetect)
|
MonitorColorScheme schemeID = colorConfig.UseScheme;
|
||||||
|
if (schemeID == MonitorColorScheme.AutoDetect)
|
||||||
{
|
{
|
||||||
colorScheme = platform == Platform.Mac
|
schemeID = platform == Platform.Mac
|
||||||
? MonitorColorScheme.LightBackground // MacOS doesn't provide console background color info, but it's usually white.
|
? MonitorColorScheme.LightBackground // MacOS doesn't provide console background color info, but it's usually white.
|
||||||
: ColorfulConsoleWriter.IsDark(Console.BackgroundColor) ? MonitorColorScheme.DarkBackground : MonitorColorScheme.LightBackground;
|
: ColorfulConsoleWriter.IsDark(Console.BackgroundColor) ? MonitorColorScheme.DarkBackground : MonitorColorScheme.LightBackground;
|
||||||
}
|
}
|
||||||
|
|
||||||
// get colors for scheme
|
// get colors for scheme
|
||||||
switch (colorScheme)
|
return colorConfig.Schemes.TryGetValue(schemeID, out IDictionary<ConsoleLogLevel, ConsoleColor> scheme)
|
||||||
{
|
? scheme
|
||||||
case MonitorColorScheme.DarkBackground:
|
: throw new NotSupportedException($"Unknown color scheme '{schemeID}'.");
|
||||||
return 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
|
|
||||||
};
|
|
||||||
|
|
||||||
case MonitorColorScheme.LightBackground:
|
|
||||||
return 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
|
|
||||||
};
|
|
||||||
|
|
||||||
default:
|
|
||||||
throw new NotSupportedException($"Unknown color scheme '{colorScheme}'.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Get whether a console color should be considered dark, which is subjectively defined as 'white looks better than black on this text'.</summary>
|
/// <summary>Get whether a console color should be considered dark, which is subjectively defined as 'white looks better than black on this text'.</summary>
|
||||||
|
@ -125,7 +140,7 @@ namespace StardewModdingAPI.Internal.ConsoleWriting
|
||||||
case ConsoleColor.Black:
|
case ConsoleColor.Black:
|
||||||
case ConsoleColor.Blue:
|
case ConsoleColor.Blue:
|
||||||
case ConsoleColor.DarkBlue:
|
case ConsoleColor.DarkBlue:
|
||||||
case ConsoleColor.DarkMagenta: // Powershell
|
case ConsoleColor.DarkMagenta: // PowerShell
|
||||||
case ConsoleColor.DarkRed:
|
case ConsoleColor.DarkRed:
|
||||||
case ConsoleColor.Red:
|
case ConsoleColor.Red:
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -10,9 +10,8 @@
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="$(MSBuildThisFileDirectory)ConsoleWriting\ColorfulConsoleWriter.cs" />
|
<Compile Include="$(MSBuildThisFileDirectory)ConsoleWriting\ColorfulConsoleWriter.cs" />
|
||||||
<Compile Include="$(MSBuildThisFileDirectory)EnvironmentUtility.cs" />
|
<Compile Include="$(MSBuildThisFileDirectory)ConsoleWriting\ColorSchemeConfig.cs" />
|
||||||
<Compile Include="$(MSBuildThisFileDirectory)ConsoleWriting\LogLevel.cs" />
|
<Compile Include="$(MSBuildThisFileDirectory)ConsoleWriting\ConsoleLogLevel.cs" />
|
||||||
<Compile Include="$(MSBuildThisFileDirectory)ConsoleWriting\MonitorColorScheme.cs" />
|
<Compile Include="$(MSBuildThisFileDirectory)ConsoleWriting\MonitorColorScheme.cs" />
|
||||||
<Compile Include="$(MSBuildThisFileDirectory)Platform.cs" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
|
@ -2,7 +2,7 @@
|
||||||
namespace Netcode
|
namespace Netcode
|
||||||
{
|
{
|
||||||
/// <summary>A simplified version of Stardew Valley's <c>Netcode.NetFieldBase</c> for unit testing.</summary>
|
/// <summary>A simplified version of Stardew Valley's <c>Netcode.NetFieldBase</c> for unit testing.</summary>
|
||||||
/// <typeparam name="T">The type of the synchronised value.</typeparam>
|
/// <typeparam name="T">The type of the synchronized value.</typeparam>
|
||||||
/// <typeparam name="TSelf">The type of the current instance.</typeparam>
|
/// <typeparam name="TSelf">The type of the current instance.</typeparam>
|
||||||
public class NetFieldBase<T, TSelf> where TSelf : NetFieldBase<T, TSelf>
|
public class NetFieldBase<T, TSelf> where TSelf : NetFieldBase<T, TSelf>
|
||||||
{
|
{
|
||||||
|
|
|
@ -96,7 +96,7 @@ namespace SMAPI.ModBuildConfig.Analyzer.Tests
|
||||||
DiagnosticResult expected = new DiagnosticResult
|
DiagnosticResult expected = new DiagnosticResult
|
||||||
{
|
{
|
||||||
Id = "AvoidImplicitNetFieldCast",
|
Id = "AvoidImplicitNetFieldCast",
|
||||||
Message = $"This implicitly converts '{expression}' from {fromType} to {toType}, but {fromType} has unintuitive implicit conversion rules. Consider comparing against the actual value instead to avoid bugs. See https://smapi.io/buildmsg/avoid-implicit-net-field-cast for details.",
|
Message = $"This implicitly converts '{expression}' from {fromType} to {toType}, but {fromType} has unintuitive implicit conversion rules. Consider comparing against the actual value instead to avoid bugs. See https://smapi.io/package/avoid-implicit-net-field-cast for details.",
|
||||||
Severity = DiagnosticSeverity.Warning,
|
Severity = DiagnosticSeverity.Warning,
|
||||||
Locations = new[] { new DiagnosticResultLocation("Test0.cs", NetFieldAnalyzerTests.SampleCodeLine, NetFieldAnalyzerTests.SampleCodeColumn + column) }
|
Locations = new[] { new DiagnosticResultLocation("Test0.cs", NetFieldAnalyzerTests.SampleCodeLine, NetFieldAnalyzerTests.SampleCodeColumn + column) }
|
||||||
};
|
};
|
||||||
|
@ -138,7 +138,7 @@ namespace SMAPI.ModBuildConfig.Analyzer.Tests
|
||||||
DiagnosticResult expected = new DiagnosticResult
|
DiagnosticResult expected = new DiagnosticResult
|
||||||
{
|
{
|
||||||
Id = "AvoidNetField",
|
Id = "AvoidNetField",
|
||||||
Message = $"'{expression}' is a {netType} field; consider using the {suggestedProperty} property instead. See https://smapi.io/buildmsg/avoid-net-field for details.",
|
Message = $"'{expression}' is a {netType} field; consider using the {suggestedProperty} property instead. See https://smapi.io/package/avoid-net-field for details.",
|
||||||
Severity = DiagnosticSeverity.Warning,
|
Severity = DiagnosticSeverity.Warning,
|
||||||
Locations = new[] { new DiagnosticResultLocation("Test0.cs", NetFieldAnalyzerTests.SampleCodeLine, NetFieldAnalyzerTests.SampleCodeColumn + column) }
|
Locations = new[] { new DiagnosticResultLocation("Test0.cs", NetFieldAnalyzerTests.SampleCodeLine, NetFieldAnalyzerTests.SampleCodeColumn + column) }
|
||||||
};
|
};
|
||||||
|
|
|
@ -67,7 +67,7 @@ namespace SMAPI.ModBuildConfig.Analyzer.Tests
|
||||||
DiagnosticResult expected = new DiagnosticResult
|
DiagnosticResult expected = new DiagnosticResult
|
||||||
{
|
{
|
||||||
Id = "AvoidObsoleteField",
|
Id = "AvoidObsoleteField",
|
||||||
Message = $"The '{oldName}' field is obsolete and should be replaced with '{newName}'. See https://smapi.io/buildmsg/avoid-obsolete-field for details.",
|
Message = $"The '{oldName}' field is obsolete and should be replaced with '{newName}'. See https://smapi.io/package/avoid-obsolete-field for details.",
|
||||||
Severity = DiagnosticSeverity.Warning,
|
Severity = DiagnosticSeverity.Warning,
|
||||||
Locations = new[] { new DiagnosticResultLocation("Test0.cs", ObsoleteFieldAnalyzerTests.SampleCodeLine, ObsoleteFieldAnalyzerTests.SampleCodeColumn + column) }
|
Locations = new[] { new DiagnosticResultLocation("Test0.cs", ObsoleteFieldAnalyzerTests.SampleCodeLine, ObsoleteFieldAnalyzerTests.SampleCodeColumn + column) }
|
||||||
};
|
};
|
||||||
|
|
|
@ -6,14 +6,16 @@
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="2.8.2" />
|
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="2.10.0" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.9.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" />
|
||||||
<PackageReference Include="NUnit" Version="3.11.0" />
|
<PackageReference Include="NUnit" Version="3.12.0" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="3.11.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="3.15.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\SMAPI.ModBuildConfig.Analyzer\StardewModdingAPI.ModBuildConfig.Analyzer.csproj" />
|
<ProjectReference Include="..\SMAPI.ModBuildConfig.Analyzer\SMAPI.ModBuildConfig.Analyzer.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<Import Project="..\..\build\common.targets" />
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
@ -135,22 +135,22 @@ namespace StardewModdingAPI.ModBuildConfig.Analyzer
|
||||||
private readonly DiagnosticDescriptor AvoidImplicitNetFieldCastRule = new DiagnosticDescriptor(
|
private readonly DiagnosticDescriptor AvoidImplicitNetFieldCastRule = new DiagnosticDescriptor(
|
||||||
id: "AvoidImplicitNetFieldCast",
|
id: "AvoidImplicitNetFieldCast",
|
||||||
title: "Netcode types shouldn't be implicitly converted",
|
title: "Netcode types shouldn't be implicitly converted",
|
||||||
messageFormat: "This implicitly converts '{0}' from {1} to {2}, but {1} has unintuitive implicit conversion rules. Consider comparing against the actual value instead to avoid bugs. See https://smapi.io/buildmsg/avoid-implicit-net-field-cast for details.",
|
messageFormat: "This implicitly converts '{0}' from {1} to {2}, but {1} has unintuitive implicit conversion rules. Consider comparing against the actual value instead to avoid bugs. See https://smapi.io/package/avoid-implicit-net-field-cast for details.",
|
||||||
category: "SMAPI.CommonErrors",
|
category: "SMAPI.CommonErrors",
|
||||||
defaultSeverity: DiagnosticSeverity.Warning,
|
defaultSeverity: DiagnosticSeverity.Warning,
|
||||||
isEnabledByDefault: true,
|
isEnabledByDefault: true,
|
||||||
helpLinkUri: "https://smapi.io/buildmsg/avoid-implicit-net-field-cast"
|
helpLinkUri: "https://smapi.io/package/avoid-implicit-net-field-cast"
|
||||||
);
|
);
|
||||||
|
|
||||||
/// <summary>The diagnostic info for an avoidable net field access.</summary>
|
/// <summary>The diagnostic info for an avoidable net field access.</summary>
|
||||||
private readonly DiagnosticDescriptor AvoidNetFieldRule = new DiagnosticDescriptor(
|
private readonly DiagnosticDescriptor AvoidNetFieldRule = new DiagnosticDescriptor(
|
||||||
id: "AvoidNetField",
|
id: "AvoidNetField",
|
||||||
title: "Avoid Netcode types when possible",
|
title: "Avoid Netcode types when possible",
|
||||||
messageFormat: "'{0}' is a {1} field; consider using the {2} property instead. See https://smapi.io/buildmsg/avoid-net-field for details.",
|
messageFormat: "'{0}' is a {1} field; consider using the {2} property instead. See https://smapi.io/package/avoid-net-field for details.",
|
||||||
category: "SMAPI.CommonErrors",
|
category: "SMAPI.CommonErrors",
|
||||||
defaultSeverity: DiagnosticSeverity.Warning,
|
defaultSeverity: DiagnosticSeverity.Warning,
|
||||||
isEnabledByDefault: true,
|
isEnabledByDefault: true,
|
||||||
helpLinkUri: "https://smapi.io/buildmsg/avoid-net-field"
|
helpLinkUri: "https://smapi.io/package/avoid-net-field"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
|
@ -199,7 +199,7 @@ namespace StardewModdingAPI.ModBuildConfig.Analyzer
|
||||||
/*********
|
/*********
|
||||||
** Private methods
|
** Private methods
|
||||||
*********/
|
*********/
|
||||||
/// <summary>Analyse a member access syntax node and add a diagnostic message if applicable.</summary>
|
/// <summary>Analyze a member access syntax node and add a diagnostic message if applicable.</summary>
|
||||||
/// <param name="context">The analysis context.</param>
|
/// <param name="context">The analysis context.</param>
|
||||||
/// <returns>Returns whether any warnings were added.</returns>
|
/// <returns>Returns whether any warnings were added.</returns>
|
||||||
private void AnalyzeMemberAccess(SyntaxNodeAnalysisContext context)
|
private void AnalyzeMemberAccess(SyntaxNodeAnalysisContext context)
|
||||||
|
@ -231,7 +231,7 @@ namespace StardewModdingAPI.ModBuildConfig.Analyzer
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Analyse an explicit cast or 'x as y' node and add a diagnostic message if applicable.</summary>
|
/// <summary>Analyze an explicit cast or 'x as y' node and add a diagnostic message if applicable.</summary>
|
||||||
/// <param name="context">The analysis context.</param>
|
/// <param name="context">The analysis context.</param>
|
||||||
/// <returns>Returns whether any warnings were added.</returns>
|
/// <returns>Returns whether any warnings were added.</returns>
|
||||||
private void AnalyzeCast(SyntaxNodeAnalysisContext context)
|
private void AnalyzeCast(SyntaxNodeAnalysisContext context)
|
||||||
|
@ -248,7 +248,7 @@ namespace StardewModdingAPI.ModBuildConfig.Analyzer
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Analyse a binary comparison syntax node and add a diagnostic message if applicable.</summary>
|
/// <summary>Analyze a binary comparison syntax node and add a diagnostic message if applicable.</summary>
|
||||||
/// <param name="context">The analysis context.</param>
|
/// <param name="context">The analysis context.</param>
|
||||||
/// <returns>Returns whether any warnings were added.</returns>
|
/// <returns>Returns whether any warnings were added.</returns>
|
||||||
private void AnalyzeBinaryComparison(SyntaxNodeAnalysisContext context)
|
private void AnalyzeBinaryComparison(SyntaxNodeAnalysisContext context)
|
||||||
|
@ -288,7 +288,7 @@ namespace StardewModdingAPI.ModBuildConfig.Analyzer
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Handle exceptions raised while analyzing a node.</summary>
|
/// <summary>Handle exceptions raised while analyzing a node.</summary>
|
||||||
/// <param name="node">The node being analysed.</param>
|
/// <param name="node">The node being analyzed.</param>
|
||||||
/// <param name="action">The callback to invoke.</param>
|
/// <param name="action">The callback to invoke.</param>
|
||||||
private void HandleErrors(SyntaxNode node, Action action)
|
private void HandleErrors(SyntaxNode node, Action action)
|
||||||
{
|
{
|
||||||
|
|
|
@ -27,11 +27,11 @@ namespace StardewModdingAPI.ModBuildConfig.Analyzer
|
||||||
["AvoidObsoleteField"] = new DiagnosticDescriptor(
|
["AvoidObsoleteField"] = new DiagnosticDescriptor(
|
||||||
id: "AvoidObsoleteField",
|
id: "AvoidObsoleteField",
|
||||||
title: "Reference to obsolete field",
|
title: "Reference to obsolete field",
|
||||||
messageFormat: "The '{0}' field is obsolete and should be replaced with '{1}'. See https://smapi.io/buildmsg/avoid-obsolete-field for details.",
|
messageFormat: "The '{0}' field is obsolete and should be replaced with '{1}'. See https://smapi.io/package/avoid-obsolete-field for details.",
|
||||||
category: "SMAPI.CommonErrors",
|
category: "SMAPI.CommonErrors",
|
||||||
defaultSeverity: DiagnosticSeverity.Warning,
|
defaultSeverity: DiagnosticSeverity.Warning,
|
||||||
isEnabledByDefault: true,
|
isEnabledByDefault: true,
|
||||||
helpLinkUri: "https://smapi.io/buildmsg/avoid-obsolete-field"
|
helpLinkUri: "https://smapi.io/package/avoid-obsolete-field"
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -67,7 +67,7 @@ namespace StardewModdingAPI.ModBuildConfig.Analyzer
|
||||||
/*********
|
/*********
|
||||||
** Private methods
|
** Private methods
|
||||||
*********/
|
*********/
|
||||||
/// <summary>Analyse a syntax node and add a diagnostic message if it references an obsolete field.</summary>
|
/// <summary>Analyze a syntax node and add a diagnostic message if it references an obsolete field.</summary>
|
||||||
/// <param name="context">The analysis context.</param>
|
/// <param name="context">The analysis context.</param>
|
||||||
private void AnalyzeObsoleteFields(SyntaxNodeAnalysisContext context)
|
private void AnalyzeObsoleteFields(SyntaxNodeAnalysisContext context)
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
using System.Reflection;
|
|
||||||
|
|
||||||
[assembly: AssemblyTitle("SMAPI.ModBuildConfig.Analyzer")]
|
|
||||||
[assembly: AssemblyDescription("")]
|
|
|
@ -1,19 +1,18 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netstandard1.3</TargetFramework>
|
<AssemblyName>SMAPI.ModBuildConfig.Analyzer</AssemblyName>
|
||||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
<RootNamespace>StardewModdingAPI.ModBuildConfig.Analyzer</RootNamespace>
|
||||||
|
<Version>3.0.0</Version>
|
||||||
|
<TargetFramework>netstandard2.0</TargetFramework>
|
||||||
|
<LangVersion>latest</LangVersion>
|
||||||
<IncludeBuildOutput>false</IncludeBuildOutput>
|
<IncludeBuildOutput>false</IncludeBuildOutput>
|
||||||
<OutputPath>bin</OutputPath>
|
<OutputPath>bin</OutputPath>
|
||||||
<LangVersion>latest</LangVersion>
|
<LangVersion>latest</LangVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="..\..\build\GlobalAssemblyInfo.cs" Link="Properties\GlobalAssemblyInfo.cs" />
|
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="2.10.0" PrivateAssets="all" />
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="2.8.2" PrivateAssets="all" />
|
|
||||||
<PackageReference Update="NETStandard.Library" PrivateAssets="all" />
|
<PackageReference Update="NETStandard.Library" PrivateAssets="all" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
|
@ -1,58 +0,0 @@
|
||||||
param($installPath, $toolsPath, $package, $project)
|
|
||||||
|
|
||||||
if($project.Object.SupportsPackageDependencyResolution)
|
|
||||||
{
|
|
||||||
if($project.Object.SupportsPackageDependencyResolution())
|
|
||||||
{
|
|
||||||
# Do not install analyzers via install.ps1, instead let the project system handle it.
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$analyzersPaths = Join-Path (Join-Path (Split-Path -Path $toolsPath -Parent) "analyzers") * -Resolve
|
|
||||||
|
|
||||||
foreach($analyzersPath in $analyzersPaths)
|
|
||||||
{
|
|
||||||
if (Test-Path $analyzersPath)
|
|
||||||
{
|
|
||||||
# Install the language agnostic analyzers.
|
|
||||||
foreach ($analyzerFilePath in Get-ChildItem -Path "$analyzersPath\*.dll" -Exclude *.resources.dll)
|
|
||||||
{
|
|
||||||
if($project.Object.AnalyzerReferences)
|
|
||||||
{
|
|
||||||
$project.Object.AnalyzerReferences.Add($analyzerFilePath.FullName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# $project.Type gives the language name like (C# or VB.NET)
|
|
||||||
$languageFolder = ""
|
|
||||||
if($project.Type -eq "C#")
|
|
||||||
{
|
|
||||||
$languageFolder = "cs"
|
|
||||||
}
|
|
||||||
if($project.Type -eq "VB.NET")
|
|
||||||
{
|
|
||||||
$languageFolder = "vb"
|
|
||||||
}
|
|
||||||
if($languageFolder -eq "")
|
|
||||||
{
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach($analyzersPath in $analyzersPaths)
|
|
||||||
{
|
|
||||||
# Install language specific analyzers.
|
|
||||||
$languageAnalyzersPath = join-path $analyzersPath $languageFolder
|
|
||||||
if (Test-Path $languageAnalyzersPath)
|
|
||||||
{
|
|
||||||
foreach ($analyzerFilePath in Get-ChildItem -Path "$languageAnalyzersPath\*.dll" -Exclude *.resources.dll)
|
|
||||||
{
|
|
||||||
if($project.Object.AnalyzerReferences)
|
|
||||||
{
|
|
||||||
$project.Object.AnalyzerReferences.Add($analyzerFilePath.FullName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,65 +0,0 @@
|
||||||
param($installPath, $toolsPath, $package, $project)
|
|
||||||
|
|
||||||
if($project.Object.SupportsPackageDependencyResolution)
|
|
||||||
{
|
|
||||||
if($project.Object.SupportsPackageDependencyResolution())
|
|
||||||
{
|
|
||||||
# Do not uninstall analyzers via uninstall.ps1, instead let the project system handle it.
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$analyzersPaths = Join-Path (Join-Path (Split-Path -Path $toolsPath -Parent) "analyzers") * -Resolve
|
|
||||||
|
|
||||||
foreach($analyzersPath in $analyzersPaths)
|
|
||||||
{
|
|
||||||
# Uninstall the language agnostic analyzers.
|
|
||||||
if (Test-Path $analyzersPath)
|
|
||||||
{
|
|
||||||
foreach ($analyzerFilePath in Get-ChildItem -Path "$analyzersPath\*.dll" -Exclude *.resources.dll)
|
|
||||||
{
|
|
||||||
if($project.Object.AnalyzerReferences)
|
|
||||||
{
|
|
||||||
$project.Object.AnalyzerReferences.Remove($analyzerFilePath.FullName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# $project.Type gives the language name like (C# or VB.NET)
|
|
||||||
$languageFolder = ""
|
|
||||||
if($project.Type -eq "C#")
|
|
||||||
{
|
|
||||||
$languageFolder = "cs"
|
|
||||||
}
|
|
||||||
if($project.Type -eq "VB.NET")
|
|
||||||
{
|
|
||||||
$languageFolder = "vb"
|
|
||||||
}
|
|
||||||
if($languageFolder -eq "")
|
|
||||||
{
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach($analyzersPath in $analyzersPaths)
|
|
||||||
{
|
|
||||||
# Uninstall language specific analyzers.
|
|
||||||
$languageAnalyzersPath = join-path $analyzersPath $languageFolder
|
|
||||||
if (Test-Path $languageAnalyzersPath)
|
|
||||||
{
|
|
||||||
foreach ($analyzerFilePath in Get-ChildItem -Path "$languageAnalyzersPath\*.dll" -Exclude *.resources.dll)
|
|
||||||
{
|
|
||||||
if($project.Object.AnalyzerReferences)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
$project.Object.AnalyzerReferences.Remove($analyzerFilePath.FullName)
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -3,8 +3,9 @@ using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using StardewModdingAPI.Toolkit.Serialisation;
|
using StardewModdingAPI.Toolkit.Serialization;
|
||||||
using StardewModdingAPI.Toolkit.Serialisation.Models;
|
using StardewModdingAPI.Toolkit.Serialization.Models;
|
||||||
|
using StardewModdingAPI.Toolkit.Utilities;
|
||||||
|
|
||||||
namespace StardewModdingAPI.ModBuildConfig.Framework
|
namespace StardewModdingAPI.ModBuildConfig.Framework
|
||||||
{
|
{
|
||||||
|
@ -40,47 +41,14 @@ namespace StardewModdingAPI.ModBuildConfig.Framework
|
||||||
if (!Directory.Exists(targetDir))
|
if (!Directory.Exists(targetDir))
|
||||||
throw new UserErrorException("Could not create mod package because no build output was found.");
|
throw new UserErrorException("Could not create mod package because no build output was found.");
|
||||||
|
|
||||||
// project manifest
|
// collect files
|
||||||
bool hasProjectManifest = false;
|
foreach (Tuple<string, FileInfo> entry in this.GetPossibleFiles(projectDir, targetDir))
|
||||||
{
|
{
|
||||||
FileInfo manifest = new FileInfo(Path.Combine(projectDir, "manifest.json"));
|
string relativePath = entry.Item1;
|
||||||
if (manifest.Exists)
|
FileInfo file = entry.Item2;
|
||||||
{
|
|
||||||
this.Files[this.ManifestFileName] = manifest;
|
|
||||||
hasProjectManifest = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// project i18n files
|
if (!this.ShouldIgnore(file, relativePath, ignoreFilePatterns))
|
||||||
bool hasProjectTranslations = false;
|
this.Files[relativePath] = file;
|
||||||
DirectoryInfo translationsFolder = new DirectoryInfo(Path.Combine(projectDir, "i18n"));
|
|
||||||
if (translationsFolder.Exists)
|
|
||||||
{
|
|
||||||
foreach (FileInfo file in translationsFolder.EnumerateFiles())
|
|
||||||
this.Files[Path.Combine("i18n", file.Name)] = file;
|
|
||||||
hasProjectTranslations = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// build output
|
|
||||||
DirectoryInfo buildFolder = new DirectoryInfo(targetDir);
|
|
||||||
foreach (FileInfo file in buildFolder.EnumerateFiles("*", SearchOption.AllDirectories))
|
|
||||||
{
|
|
||||||
// get relative paths
|
|
||||||
string relativePath = file.FullName.Replace(buildFolder.FullName, "");
|
|
||||||
string relativeDirPath = file.Directory.FullName.Replace(buildFolder.FullName, "");
|
|
||||||
|
|
||||||
// prefer project manifest/i18n files
|
|
||||||
if (hasProjectManifest && this.EqualsInvariant(relativePath, this.ManifestFileName))
|
|
||||||
continue;
|
|
||||||
if (hasProjectTranslations && this.EqualsInvariant(relativeDirPath, "i18n"))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// handle ignored files
|
|
||||||
if (this.ShouldIgnore(file, relativePath, ignoreFilePatterns))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// add file
|
|
||||||
this.Files[relativePath] = file;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// check for required files
|
// check for required files
|
||||||
|
@ -117,6 +85,67 @@ namespace StardewModdingAPI.ModBuildConfig.Framework
|
||||||
/*********
|
/*********
|
||||||
** Private methods
|
** Private methods
|
||||||
*********/
|
*********/
|
||||||
|
/// <summary>Get all files to include in the mod folder, not accounting for ignore patterns.</summary>
|
||||||
|
/// <param name="projectDir">The folder containing the project files.</param>
|
||||||
|
/// <param name="targetDir">The folder containing the build output.</param>
|
||||||
|
/// <returns>Returns tuples containing the relative path within the mod folder, and the file to copy to it.</returns>
|
||||||
|
private IEnumerable<Tuple<string, FileInfo>> GetPossibleFiles(string projectDir, string targetDir)
|
||||||
|
{
|
||||||
|
// project manifest
|
||||||
|
bool hasProjectManifest = false;
|
||||||
|
{
|
||||||
|
FileInfo manifest = new FileInfo(Path.Combine(projectDir, this.ManifestFileName));
|
||||||
|
if (manifest.Exists)
|
||||||
|
{
|
||||||
|
yield return Tuple.Create(this.ManifestFileName, manifest);
|
||||||
|
hasProjectManifest = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// project i18n files
|
||||||
|
bool hasProjectTranslations = false;
|
||||||
|
DirectoryInfo translationsFolder = new DirectoryInfo(Path.Combine(projectDir, "i18n"));
|
||||||
|
if (translationsFolder.Exists)
|
||||||
|
{
|
||||||
|
foreach (FileInfo file in translationsFolder.EnumerateFiles())
|
||||||
|
yield return Tuple.Create(Path.Combine("i18n", file.Name), file);
|
||||||
|
hasProjectTranslations = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// project assets folder
|
||||||
|
bool hasAssetsFolder = false;
|
||||||
|
DirectoryInfo assetsFolder = new DirectoryInfo(Path.Combine(projectDir, "assets"));
|
||||||
|
if (assetsFolder.Exists)
|
||||||
|
{
|
||||||
|
foreach (FileInfo file in assetsFolder.EnumerateFiles("*", SearchOption.AllDirectories))
|
||||||
|
{
|
||||||
|
string relativePath = PathUtilities.GetRelativePath(projectDir, file.FullName);
|
||||||
|
yield return Tuple.Create(relativePath, file);
|
||||||
|
}
|
||||||
|
hasAssetsFolder = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// build output
|
||||||
|
DirectoryInfo buildFolder = new DirectoryInfo(targetDir);
|
||||||
|
foreach (FileInfo file in buildFolder.EnumerateFiles("*", SearchOption.AllDirectories))
|
||||||
|
{
|
||||||
|
// get path info
|
||||||
|
string relativePath = PathUtilities.GetRelativePath(buildFolder.FullName, file.FullName);
|
||||||
|
string[] segments = PathUtilities.GetSegments(relativePath);
|
||||||
|
|
||||||
|
// prefer project manifest/i18n/assets files
|
||||||
|
if (hasProjectManifest && this.EqualsInvariant(relativePath, this.ManifestFileName))
|
||||||
|
continue;
|
||||||
|
if (hasProjectTranslations && this.EqualsInvariant(segments[0], "i18n"))
|
||||||
|
continue;
|
||||||
|
if (hasAssetsFolder && this.EqualsInvariant(segments[0], "assets"))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// add file
|
||||||
|
yield return Tuple.Create(relativePath, file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>Get whether a build output file should be ignored.</summary>
|
/// <summary>Get whether a build output file should be ignored.</summary>
|
||||||
/// <param name="file">The file to check.</param>
|
/// <param name="file">The file to check.</param>
|
||||||
/// <param name="relativePath">The file's relative path in the package.</param>
|
/// <param name="relativePath">The file's relative path in the package.</param>
|
||||||
|
@ -129,6 +158,7 @@ namespace StardewModdingAPI.ModBuildConfig.Framework
|
||||||
|
|
||||||
// Json.NET (bundled into SMAPI)
|
// Json.NET (bundled into SMAPI)
|
||||||
|| this.EqualsInvariant(file.Name, "Newtonsoft.Json.dll")
|
|| this.EqualsInvariant(file.Name, "Newtonsoft.Json.dll")
|
||||||
|
|| this.EqualsInvariant(file.Name, "Newtonsoft.Json.pdb")
|
||||||
|| this.EqualsInvariant(file.Name, "Newtonsoft.Json.xml")
|
|| this.EqualsInvariant(file.Name, "Newtonsoft.Json.xml")
|
||||||
|
|
||||||
// code analysis files
|
// code analysis files
|
||||||
|
@ -148,6 +178,8 @@ namespace StardewModdingAPI.ModBuildConfig.Framework
|
||||||
/// <param name="other">The string to compare with.</param>
|
/// <param name="other">The string to compare with.</param>
|
||||||
private bool EqualsInvariant(string str, string other)
|
private bool EqualsInvariant(string str, string other)
|
||||||
{
|
{
|
||||||
|
if (str == null)
|
||||||
|
return other == null;
|
||||||
return str.Equals(other, StringComparison.InvariantCultureIgnoreCase);
|
return str.Equals(other, StringComparison.InvariantCultureIgnoreCase);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
using System.Reflection;
|
|
||||||
|
|
||||||
[assembly: AssemblyTitle("SMAPI.ModBuildConfig")]
|
|
||||||
[assembly: AssemblyDescription("")]
|
|
||||||
[assembly: AssemblyVersion("2.2.0")]
|
|
||||||
[assembly: AssemblyFileVersion("2.2.0")]
|
|
|
@ -1,23 +1,22 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
<AssemblyName>SMAPI.ModBuildConfig</AssemblyName>
|
||||||
<RootNamespace>StardewModdingAPI.ModBuildConfig</RootNamespace>
|
<RootNamespace>StardewModdingAPI.ModBuildConfig</RootNamespace>
|
||||||
<AssemblyName>StardewModdingAPI.ModBuildConfig</AssemblyName>
|
<Version>3.0.0</Version>
|
||||||
<TargetFramework>net45</TargetFramework>
|
<TargetFramework>net45</TargetFramework>
|
||||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
|
||||||
<LangVersion>latest</LangVersion>
|
<LangVersion>latest</LangVersion>
|
||||||
<PlatformTarget>x86</PlatformTarget>
|
<PlatformTarget>x86</PlatformTarget>
|
||||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\SMAPI.Toolkit\StardewModdingAPI.Toolkit.csproj" />
|
<ProjectReference Include="..\SMAPI.Toolkit\SMAPI.Toolkit.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Include="..\..\docs\mod-build-config.md">
|
<None Include="..\..\build\find-game-folder.targets" Link="build\find-game-folder.targets" />
|
||||||
<Link>mod-build-config.md</Link>
|
<None Include="..\..\docs\technical\mod-package.md" Link="mod-build-config.md" />
|
||||||
</None>
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -28,6 +27,20 @@
|
||||||
<Reference Include="System.Web.Extensions" />
|
<Reference Include="System.Web.Extensions" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Include="..\..\docs\technical\mod-package.md">
|
||||||
|
<Link>mod-package.md</Link>
|
||||||
|
</None>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Update="assets\nuget-icon.png">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<Import Project="..\SMAPI.Internal\SMAPI.Internal.projitems" Label="Shared" />
|
<Import Project="..\SMAPI.Internal\SMAPI.Internal.projitems" Label="Shared" />
|
||||||
|
<Import Project="..\..\build\common.targets" />
|
||||||
|
<Import Project="..\..\build\prepare-nuget-package.targets" />
|
||||||
|
|
||||||
</Project>
|
</Project>
|
|
@ -1,175 +1,113 @@
|
||||||
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
<!--*********************************************
|
|
||||||
** Import build tasks
|
<Import Project="find-game-folder.targets" />
|
||||||
**********************************************-->
|
<UsingTask TaskName="DeployModTask" AssemblyFile="SMAPI.ModBuildConfig.dll" />
|
||||||
<UsingTask TaskName="DeployModTask" AssemblyFile="StardewModdingAPI.ModBuildConfig.dll" />
|
|
||||||
|
|
||||||
<!--*********************************************
|
<!--*********************************************
|
||||||
** Find the basic mod metadata
|
** Set build options
|
||||||
**********************************************-->
|
**********************************************-->
|
||||||
<!-- import developer's custom settings (if any) -->
|
|
||||||
<Import Condition="$(OS) != 'Windows_NT' AND Exists('$(HOME)\stardewvalley.targets')" Project="$(HOME)\stardewvalley.targets" />
|
|
||||||
<Import Condition="$(OS) == 'Windows_NT' AND Exists('$(USERPROFILE)\stardewvalley.targets')" Project="$(USERPROFILE)\stardewvalley.targets" />
|
|
||||||
|
|
||||||
<!-- set setting defaults -->
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<!-- map legacy settings -->
|
<!-- include PDB file by default to enable line numbers in stack traces -->
|
||||||
<ModFolderName Condition="'$(ModFolderName)' == '' AND '$(DeployModFolderName)' != ''">$(DeployModFolderName)</ModFolderName>
|
<DebugType>pdbonly</DebugType>
|
||||||
<ModZipPath Condition="'$(ModZipPath)' == '' AND '$(DeployModZipTo)' != ''">$(DeployModZipTo)</ModZipPath>
|
<DebugSymbols>true</DebugSymbols>
|
||||||
|
|
||||||
<!-- set default settings -->
|
<!-- recognise XNA Framework DLLs in the GAC (only affects mods using new csproj format) -->
|
||||||
|
<AssemblySearchPaths>$(AssemblySearchPaths);{GAC}</AssemblySearchPaths>
|
||||||
|
|
||||||
|
<!-- set default package options -->
|
||||||
<ModFolderName Condition="'$(ModFolderName)' == ''">$(MSBuildProjectName)</ModFolderName>
|
<ModFolderName Condition="'$(ModFolderName)' == ''">$(MSBuildProjectName)</ModFolderName>
|
||||||
<ModZipPath Condition="'$(ModZipPath)' == ''">$(TargetDir)</ModZipPath>
|
<ModZipPath Condition="'$(ModZipPath)' == ''">$(TargetDir)</ModZipPath>
|
||||||
<EnableModDeploy Condition="'$(EnableModDeploy)' == ''">True</EnableModDeploy>
|
<EnableModDeploy Condition="'$(EnableModDeploy)' == ''">true</EnableModDeploy>
|
||||||
<EnableModZip Condition="'$(EnableModZip)' == ''">True</EnableModZip>
|
<EnableModZip Condition="'$(EnableModZip)' == ''">true</EnableModZip>
|
||||||
<CopyModReferencesToBuildOutput Condition="'$(CopyModReferencesToBuildOutput)' == ''">False</CopyModReferencesToBuildOutput>
|
<EnableHarmony Condition="'$(EnableModZip)' == ''">false</EnableHarmony>
|
||||||
|
<EnableGameDebugging Condition="$(EnableGameDebugging) == ''">true</EnableGameDebugging>
|
||||||
|
<CopyModReferencesToBuildOutput Condition="'$(CopyModReferencesToBuildOutput)' == '' OR ('$(CopyModReferencesToBuildOutput)' != 'true' AND '$(CopyModReferencesToBuildOutput)' != 'false')">false</CopyModReferencesToBuildOutput>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<!-- find platform + game path -->
|
<PropertyGroup Condition="$(OS) == 'Windows_NT' AND $(EnableGameDebugging) == 'true'">
|
||||||
<Choose>
|
<!-- enable game debugging -->
|
||||||
<When Condition="$(OS) == 'Unix' OR $(OS) == 'OSX'">
|
<StartAction>Program</StartAction>
|
||||||
<PropertyGroup>
|
<StartProgram>$(GamePath)\StardewModdingAPI.exe</StartProgram>
|
||||||
<!-- Linux -->
|
<StartWorkingDirectory>$(GamePath)</StartWorkingDirectory>
|
||||||
<GamePath Condition="!Exists('$(GamePath)')">$(HOME)/GOG Games/Stardew Valley/game</GamePath>
|
</PropertyGroup>
|
||||||
<GamePath Condition="!Exists('$(GamePath)')">$(HOME)/.steam/steam/steamapps/common/Stardew Valley</GamePath>
|
|
||||||
<GamePath Condition="!Exists('$(GamePath)')">$(HOME)/.local/share/Steam/steamapps/common/Stardew Valley</GamePath>
|
|
||||||
|
|
||||||
<!-- Mac (may be 'Unix' or 'OSX') -->
|
|
||||||
<GamePath Condition="!Exists('$(GamePath)')">/Applications/Stardew Valley.app/Contents/MacOS</GamePath>
|
|
||||||
<GamePath Condition="!Exists('$(GamePath)')">$(HOME)/Library/Application Support/Steam/steamapps/common/Stardew Valley/Contents/MacOS</GamePath>
|
|
||||||
</PropertyGroup>
|
|
||||||
</When>
|
|
||||||
<When Condition="$(OS) == 'Windows_NT'">
|
|
||||||
<PropertyGroup>
|
|
||||||
<!-- default paths -->
|
|
||||||
<GamePath Condition="!Exists('$(GamePath)')">C:\Program Files\GalaxyClient\Games\Stardew Valley</GamePath>
|
|
||||||
<GamePath Condition="!Exists('$(GamePath)')">C:\Program Files\GOG Galaxy\Games\Stardew Valley</GamePath>
|
|
||||||
<GamePath Condition="!Exists('$(GamePath)')">C:\Program Files\Steam\steamapps\common\Stardew Valley</GamePath>
|
|
||||||
|
|
||||||
<GamePath Condition="!Exists('$(GamePath)')">C:\Program Files (x86)\GalaxyClient\Games\Stardew Valley</GamePath>
|
|
||||||
<GamePath Condition="!Exists('$(GamePath)')">C:\Program Files (x86)\GOG Galaxy\Games\Stardew Valley</GamePath>
|
|
||||||
<GamePath Condition="!Exists('$(GamePath)')">C:\Program Files (x86)\Steam\steamapps\common\Stardew Valley</GamePath>
|
|
||||||
|
|
||||||
<!-- registry paths -->
|
|
||||||
<GamePath Condition="!Exists('$(GamePath)')">$([MSBuild]::GetRegistryValueFromView('HKEY_LOCAL_MACHINE\SOFTWARE\GOG.com\Games\1453375253', 'PATH', null, RegistryView.Registry32))</GamePath>
|
|
||||||
<GamePath Condition="!Exists('$(GamePath)')">$([MSBuild]::GetRegistryValueFromView('HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Steam App 413150', 'InstallLocation', null, RegistryView.Registry64, RegistryView.Registry32))</GamePath>
|
|
||||||
|
|
||||||
<!-- derive from Steam library path -->
|
|
||||||
<_SteamLibraryPath>$([MSBuild]::GetRegistryValueFromView('HKEY_CURRENT_USER\SOFTWARE\Valve\Steam', 'SteamPath', null, RegistryView.Registry32))</_SteamLibraryPath>
|
|
||||||
<GamePath Condition="!Exists('$(GamePath)') AND '$(_SteamLibraryPath)' != ''">$(_SteamLibraryPath)\steamapps\common\Stardew Valley</GamePath>
|
|
||||||
</PropertyGroup>
|
|
||||||
</When>
|
|
||||||
</Choose>
|
|
||||||
|
|
||||||
|
|
||||||
<!--*********************************************
|
<!--*********************************************
|
||||||
** Inject the assembly references and debugging configuration
|
** Add assembly references
|
||||||
**********************************************-->
|
**********************************************-->
|
||||||
<Choose>
|
<!-- common -->
|
||||||
<When Condition="$(OS) == 'Windows_NT'">
|
<ItemGroup>
|
||||||
<!-- references -->
|
<Reference Include="$(GameExecutableName)">
|
||||||
<ItemGroup>
|
<HintPath>$(GamePath)\$(GameExecutableName).exe</HintPath>
|
||||||
<Reference Include="Microsoft.Xna.Framework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=x86">
|
<Private>$(CopyModReferencesToBuildOutput)</Private>
|
||||||
<Private>false</Private>
|
</Reference>
|
||||||
<Private Condition="$(CopyModReferencesToBuildOutput)">true</Private>
|
<Reference Include="StardewValley.GameData">
|
||||||
</Reference>
|
<HintPath>$(GamePath)\StardewValley.GameData.dll</HintPath>
|
||||||
<Reference Include="Microsoft.Xna.Framework.Game, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=x86">
|
<Private>$(CopyModReferencesToBuildOutput)</Private>
|
||||||
<Private>false</Private>
|
</Reference>
|
||||||
<Private Condition="$(CopyModReferencesToBuildOutput)">true</Private>
|
<Reference Include="StardewModdingAPI">
|
||||||
</Reference>
|
<HintPath>$(GamePath)\StardewModdingAPI.exe</HintPath>
|
||||||
<Reference Include="Microsoft.Xna.Framework.Graphics, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=x86">
|
<Private>$(CopyModReferencesToBuildOutput)</Private>
|
||||||
<Private>false</Private>
|
</Reference>
|
||||||
<Private Condition="$(CopyModReferencesToBuildOutput)">true</Private>
|
<Reference Include="SMAPI.Toolkit.CoreInterfaces">
|
||||||
</Reference>
|
<HintPath>$(GamePath)\smapi-internal\SMAPI.Toolkit.CoreInterfaces.dll</HintPath>
|
||||||
<Reference Include="Microsoft.Xna.Framework.Xact, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=x86">
|
<Private>$(CopyModReferencesToBuildOutput)</Private>
|
||||||
<Private>false</Private>
|
</Reference>
|
||||||
<Private Condition="$(CopyModReferencesToBuildOutput)">true</Private>
|
<Reference Include="xTile">
|
||||||
</Reference>
|
<HintPath>$(GamePath)\xTile.dll</HintPath>
|
||||||
<Reference Include="Netcode">
|
<Private>$(CopyModReferencesToBuildOutput)</Private>
|
||||||
<HintPath>$(GamePath)\Netcode.dll</HintPath>
|
</Reference>
|
||||||
<Private>False</Private>
|
<Reference Include="0Harmony" Condition="'$(EnableHarmony)' == 'true'">
|
||||||
<Private Condition="$(CopyModReferencesToBuildOutput)">true</Private>
|
<HintPath>$(GamePath)\smapi-internal\0Harmony.dll</HintPath>
|
||||||
</Reference>
|
<Private>$(CopyModReferencesToBuildOutput)</Private>
|
||||||
<Reference Include="Stardew Valley">
|
</Reference>
|
||||||
<HintPath>$(GamePath)\Stardew Valley.exe</HintPath>
|
</ItemGroup>
|
||||||
<Private>false</Private>
|
|
||||||
<Private Condition="$(CopyModReferencesToBuildOutput)">true</Private>
|
|
||||||
</Reference>
|
|
||||||
<Reference Include="StardewModdingAPI">
|
|
||||||
<HintPath>$(GamePath)\StardewModdingAPI.exe</HintPath>
|
|
||||||
<Private>false</Private>
|
|
||||||
<Private Condition="$(CopyModReferencesToBuildOutput)">true</Private>
|
|
||||||
</Reference>
|
|
||||||
<Reference Include="StardewModdingAPI.Toolkit.CoreInterfaces">
|
|
||||||
<HintPath>$(GamePath)\smapi-internal\StardewModdingAPI.Toolkit.CoreInterfaces.dll</HintPath>
|
|
||||||
<HintPath Condition="!Exists('$(GamePath)\smapi-internal')">$(GamePath)\StardewModdingAPI.Toolkit.CoreInterfaces.dll</HintPath>
|
|
||||||
<Private>false</Private>
|
|
||||||
<Private Condition="$(CopyModReferencesToBuildOutput)">true</Private>
|
|
||||||
</Reference>
|
|
||||||
<Reference Include="xTile, Version=2.0.4.0, Culture=neutral, processorArchitecture=x86">
|
|
||||||
<HintPath>$(GamePath)\xTile.dll</HintPath>
|
|
||||||
<Private>false</Private>
|
|
||||||
<SpecificVersion>False</SpecificVersion>
|
|
||||||
<Private Condition="$(CopyModReferencesToBuildOutput)">true</Private>
|
|
||||||
</Reference>
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<!-- launch game for debugging -->
|
<!-- Windows -->
|
||||||
<PropertyGroup>
|
<ItemGroup Condition="$(OS) == 'Windows_NT'">
|
||||||
<StartAction>Program</StartAction>
|
<Reference Include="Microsoft.Xna.Framework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=x86">
|
||||||
<StartProgram>$(GamePath)\StardewModdingAPI.exe</StartProgram>
|
<Private>$(CopyModReferencesToBuildOutput)</Private>
|
||||||
<StartWorkingDirectory>$(GamePath)</StartWorkingDirectory>
|
</Reference>
|
||||||
</PropertyGroup>
|
<Reference Include="Microsoft.Xna.Framework.Game, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=x86">
|
||||||
</When>
|
<Private>$(CopyModReferencesToBuildOutput)</Private>
|
||||||
<Otherwise>
|
</Reference>
|
||||||
<!-- references -->
|
<Reference Include="Microsoft.Xna.Framework.Graphics, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=x86">
|
||||||
<ItemGroup>
|
<Private>$(CopyModReferencesToBuildOutput)</Private>
|
||||||
<Reference Include="MonoGame.Framework">
|
</Reference>
|
||||||
<HintPath>$(GamePath)\MonoGame.Framework.dll</HintPath>
|
<Reference Include="Microsoft.Xna.Framework.Xact, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=x86">
|
||||||
<Private>false</Private>
|
<Private>$(CopyModReferencesToBuildOutput)</Private>
|
||||||
<SpecificVersion>False</SpecificVersion>
|
</Reference>
|
||||||
<Private Condition="$(CopyModReferencesToBuildOutput)">true</Private>
|
<Reference Include="Netcode">
|
||||||
</Reference>
|
<HintPath>$(GamePath)\Netcode.dll</HintPath>
|
||||||
<Reference Include="StardewValley">
|
<Private>$(CopyModReferencesToBuildOutput)</Private>
|
||||||
<HintPath>$(GamePath)\StardewValley.exe</HintPath>
|
</Reference>
|
||||||
<Private>false</Private>
|
</ItemGroup>
|
||||||
<Private Condition="$(CopyModReferencesToBuildOutput)">true</Private>
|
|
||||||
</Reference>
|
<!-- Linux/Mac -->
|
||||||
<Reference Include="StardewModdingAPI">
|
<ItemGroup Condition="$(OS) != 'Windows_NT'">
|
||||||
<HintPath>$(GamePath)\StardewModdingAPI.exe</HintPath>
|
<Reference Include="MonoGame.Framework">
|
||||||
<Private>false</Private>
|
<HintPath>$(GamePath)\MonoGame.Framework.dll</HintPath>
|
||||||
<Private Condition="$(CopyModReferencesToBuildOutput)">true</Private>
|
<Private>$(CopyModReferencesToBuildOutput)</Private>
|
||||||
</Reference>
|
</Reference>
|
||||||
<Reference Include="StardewModdingAPI.Toolkit.CoreInterfaces">
|
</ItemGroup>
|
||||||
<HintPath>$(GamePath)\smapi-internal\StardewModdingAPI.Toolkit.CoreInterfaces.dll</HintPath>
|
|
||||||
<HintPath Condition="!Exists('$(GamePath)\smapi-internal')">$(GamePath)\StardewModdingAPI.Toolkit.CoreInterfaces.dll</HintPath>
|
|
||||||
<Private>false</Private>
|
|
||||||
<Private Condition="$(CopyModReferencesToBuildOutput)">true</Private>
|
|
||||||
</Reference>
|
|
||||||
<Reference Include="xTile">
|
|
||||||
<HintPath>$(GamePath)\xTile.dll</HintPath>
|
|
||||||
<Private>false</Private>
|
|
||||||
<Private Condition="$(CopyModReferencesToBuildOutput)">true</Private>
|
|
||||||
</Reference>
|
|
||||||
</ItemGroup>
|
|
||||||
</Otherwise>
|
|
||||||
</Choose>
|
|
||||||
|
|
||||||
|
|
||||||
<!--*********************************************
|
<!--*********************************************
|
||||||
** Deploy mod files & create release zip after build
|
** Show friendly error for invalid OS or game path
|
||||||
**********************************************-->
|
**********************************************-->
|
||||||
<!-- if game path or OS is invalid, show one user-friendly error instead of a slew of reference errors -->
|
|
||||||
<Target Name="BeforeBuild">
|
<Target Name="BeforeBuild">
|
||||||
<Error Condition="'$(OS)' != 'OSX' AND '$(OS)' != 'Unix' AND '$(OS)' != 'Windows_NT'" Text="The mod build package doesn't recognise OS type '$(OS)'." />
|
<Error Condition="'$(OS)' != 'OSX' AND '$(OS)' != 'Unix' AND '$(OS)' != 'Windows_NT'" Text="The mod build package doesn't recognise OS type '$(OS)'." />
|
||||||
|
|
||||||
<Error Condition="!Exists('$(GamePath)')" Text="The mod build package can't find your game folder. You can specify where to find it; see https://smapi.io/buildmsg/game-path." />
|
<Error Condition="!Exists('$(GamePath)')" Text="The mod build package can't find your game folder. You can specify where to find it; see https://smapi.io/package/custom-game-path." />
|
||||||
<Error Condition="'$(OS)' == 'Windows_NT' AND !Exists('$(GamePath)\Stardew Valley.exe')" Text="The mod build package found a game folder at $(GamePath), but it doesn't contain the Stardew Valley.exe file. If this folder is invalid, delete it and the package will autodetect another game install path." />
|
<Error Condition="!Exists('$(GamePath)\$(GameExecutableName).exe')" Text="The mod build package found a game folder at $(GamePath), but it doesn't contain the $(GameExecutableName) file. If this folder is invalid, delete it and the package will autodetect another game install path." />
|
||||||
<Error Condition="'$(OS)' != 'Windows_NT' AND !Exists('$(GamePath)\StardewValley.exe')" Text="The mod build package found a game folder at $(GamePath), but it doesn't contain the StardewValley.exe file. If this folder is invalid, delete it and the package will autodetect another game install path." />
|
|
||||||
<Error Condition="!Exists('$(GamePath)\StardewModdingAPI.exe')" Text="The mod build package found a game folder at $(GamePath), but it doesn't contain SMAPI. You need to install SMAPI before building the mod." />
|
<Error Condition="!Exists('$(GamePath)\StardewModdingAPI.exe')" Text="The mod build package found a game folder at $(GamePath), but it doesn't contain SMAPI. You need to install SMAPI before building the mod." />
|
||||||
</Target>
|
</Target>
|
||||||
|
|
||||||
<!-- deploy mod files & create release zip -->
|
|
||||||
|
<!--*********************************************
|
||||||
|
** Deploy mod files & create release zip
|
||||||
|
**********************************************-->
|
||||||
<Target Name="AfterBuild">
|
<Target Name="AfterBuild">
|
||||||
<DeployModTask
|
<DeployModTask
|
||||||
ModFolderName="$(ModFolderName)"
|
ModFolderName="$(ModFolderName)"
|
||||||
|
|
|
@ -2,20 +2,35 @@
|
||||||
<package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">
|
<package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">
|
||||||
<metadata>
|
<metadata>
|
||||||
<id>Pathoschild.Stardew.ModBuildConfig</id>
|
<id>Pathoschild.Stardew.ModBuildConfig</id>
|
||||||
<version>2.2</version>
|
<version>3.0.0</version>
|
||||||
<title>Build package for SMAPI mods</title>
|
<title>Build package for SMAPI mods</title>
|
||||||
<authors>Pathoschild</authors>
|
<authors>Pathoschild</authors>
|
||||||
<owners>Pathoschild</owners>
|
<owners>Pathoschild</owners>
|
||||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||||
<licenseUrl>https://github.com/Pathoschild/SMAPI/blob/develop/LICENSE.txt</licenseUrl>
|
<license type="expression">MIT</license>
|
||||||
<projectUrl>https://github.com/Pathoschild/SMAPI/blob/develop/docs/mod-build-config.md#readme</projectUrl>
|
<repository type="git" url="https://github.com/Pathoschild/SMAPI" />
|
||||||
|
<projectUrl>https://smapi.io/package/readme</projectUrl>
|
||||||
|
<icon>images\icon.png</icon>
|
||||||
<iconUrl>https://raw.githubusercontent.com/Pathoschild/SMAPI/develop/src/SMAPI.ModBuildConfig/assets/nuget-icon.png</iconUrl>
|
<iconUrl>https://raw.githubusercontent.com/Pathoschild/SMAPI/develop/src/SMAPI.ModBuildConfig/assets/nuget-icon.png</iconUrl>
|
||||||
<description>Automates the build configuration for crossplatform Stardew Valley SMAPI mods. For Stardew Valley 1.3 or later.</description>
|
<description>Automates the build configuration for crossplatform Stardew Valley SMAPI mods. For SMAPI 3.0 or later.</description>
|
||||||
<releaseNotes>
|
<releaseNotes>
|
||||||
2.2:
|
3.0.0:
|
||||||
- Added support for SMAPI 2.8+ (still compatible with earlier versions).
|
- Updated for SMAPI 3.0 and Stardew Valley 1.4.
|
||||||
- Added default game paths for 32-bit Windows.
|
- Added automatic support for 'assets' folders.
|
||||||
- Fixed valid manifests marked invalid in some cases.
|
- Added $(GameExecutableName) MSBuild variable.
|
||||||
|
- Added support for projects using the simplified .csproj format.
|
||||||
|
- Added option to disable game debugging config.
|
||||||
|
- Added .pdb files to builds by default (to enable line numbers in error stack traces).
|
||||||
|
- Added optional Harmony reference.
|
||||||
|
- Fixed Newtonsoft.Json.pdb included in release zips when Json.NET is referenced directly.
|
||||||
|
- Fixed <IgnoreModFilePatterns> not working for i18n files.
|
||||||
|
- Dropped support for older versions of SMAPI and Visual Studio.
|
||||||
|
- Migrated package icon to NuGet's new format.
|
||||||
</releaseNotes>
|
</releaseNotes>
|
||||||
</metadata>
|
</metadata>
|
||||||
|
<files>
|
||||||
|
<file src="analyzers\**" target="analyzers" />
|
||||||
|
<file src="build\**" target="build" />
|
||||||
|
<file src="images\**" target="images" />
|
||||||
|
</files>
|
||||||
</package>
|
</package>
|
||||||
|
|
|
@ -15,7 +15,7 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Player
|
||||||
/// <summary>Provides methods for searching and constructing items.</summary>
|
/// <summary>Provides methods for searching and constructing items.</summary>
|
||||||
private readonly ItemRepository Items = new ItemRepository();
|
private readonly ItemRepository Items = new ItemRepository();
|
||||||
|
|
||||||
/// <summary>The type names recognised by this command.</summary>
|
/// <summary>The type names recognized by this command.</summary>
|
||||||
private readonly string[] ValidTypes = Enum.GetNames(typeof(ItemType)).Concat(new[] { "Name" }).ToArray();
|
private readonly string[] ValidTypes = Enum.GetNames(typeof(ItemType)).Concat(new[] { "Name" }).ToArray();
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using StardewModdingAPI.Mods.ConsoleCommands.Framework.ItemData;
|
using StardewModdingAPI.Mods.ConsoleCommands.Framework.ItemData;
|
||||||
|
@ -58,7 +58,7 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Player
|
||||||
/// <param name="searchWords">The search string to find.</param>
|
/// <param name="searchWords">The search string to find.</param>
|
||||||
private IEnumerable<SearchableItem> GetItems(string[] searchWords)
|
private IEnumerable<SearchableItem> GetItems(string[] searchWords)
|
||||||
{
|
{
|
||||||
// normalise search term
|
// normalize search term
|
||||||
searchWords = searchWords?.Where(word => !string.IsNullOrWhiteSpace(word)).ToArray();
|
searchWords = searchWords?.Where(word => !string.IsNullOrWhiteSpace(word)).ToArray();
|
||||||
if (searchWords?.Any() == false)
|
if (searchWords?.Any() == false)
|
||||||
searchWords = null;
|
searchWords = null;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using StardewValley;
|
using StardewValley;
|
||||||
|
|
||||||
namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Player
|
namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Player
|
||||||
|
@ -65,7 +65,7 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Player
|
||||||
public override void Update(IMonitor monitor)
|
public override void Update(IMonitor monitor)
|
||||||
{
|
{
|
||||||
if (this.InfiniteMoney)
|
if (this.InfiniteMoney)
|
||||||
Game1.player.money = 999999;
|
Game1.player.Money = 999999;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,7 +60,7 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.World
|
||||||
{
|
{
|
||||||
for (int i = 0; i > intervals; i--)
|
for (int i = 0; i > intervals; i--)
|
||||||
{
|
{
|
||||||
Game1.timeOfDay = FromTimeSpan(ToTimeSpan(Game1.timeOfDay).Subtract(TimeSpan.FromMinutes(20))); // offset 20 mins so game updates to next interval
|
Game1.timeOfDay = FromTimeSpan(ToTimeSpan(Game1.timeOfDay).Subtract(TimeSpan.FromMinutes(20))); // offset 20 minutes so game updates to next interval
|
||||||
Game1.performTenMinuteClockUpdate();
|
Game1.performTenMinuteClockUpdate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,28 +6,31 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.ItemData
|
||||||
/// <summary>A big craftable object in <see cref="StardewValley.Game1.bigCraftablesInformation"/></summary>
|
/// <summary>A big craftable object in <see cref="StardewValley.Game1.bigCraftablesInformation"/></summary>
|
||||||
BigCraftable,
|
BigCraftable,
|
||||||
|
|
||||||
/// <summary>A <see cref="Boots"/> item.</summary>
|
/// <summary>A <see cref="StardewValley.Objects.Boots"/> item.</summary>
|
||||||
Boots,
|
Boots,
|
||||||
|
|
||||||
/// <summary>A <see cref="Wallpaper"/> flooring item.</summary>
|
/// <summary>A <see cref="StardewValley.Objects.Clothing"/> item.</summary>
|
||||||
|
Clothing,
|
||||||
|
|
||||||
|
/// <summary>A <see cref="StardewValley.Objects.Wallpaper"/> flooring item.</summary>
|
||||||
Flooring,
|
Flooring,
|
||||||
|
|
||||||
/// <summary>A <see cref="Furniture"/> item.</summary>
|
/// <summary>A <see cref="StardewValley.Objects.Furniture"/> item.</summary>
|
||||||
Furniture,
|
Furniture,
|
||||||
|
|
||||||
/// <summary>A <see cref="Hat"/> item.</summary>
|
/// <summary>A <see cref="StardewValley.Objects.Hat"/> item.</summary>
|
||||||
Hat,
|
Hat,
|
||||||
|
|
||||||
/// <summary>Any object in <see cref="StardewValley.Game1.objectInformation"/> (except rings).</summary>
|
/// <summary>Any object in <see cref="StardewValley.Game1.objectInformation"/> (except rings).</summary>
|
||||||
Object,
|
Object,
|
||||||
|
|
||||||
/// <summary>A <see cref="Ring"/> item.</summary>
|
/// <summary>A <see cref="StardewValley.Objects.Ring"/> item.</summary>
|
||||||
Ring,
|
Ring,
|
||||||
|
|
||||||
/// <summary>A <see cref="Tool"/> tool.</summary>
|
/// <summary>A <see cref="StardewValley.Tool"/> tool.</summary>
|
||||||
Tool,
|
Tool,
|
||||||
|
|
||||||
/// <summary>A <see cref="Wallpaper"/> wall item.</summary>
|
/// <summary>A <see cref="StardewValley.Objects.Wallpaper"/> wall item.</summary>
|
||||||
Wallpaper,
|
Wallpaper,
|
||||||
|
|
||||||
/// <summary>A <see cref="StardewValley.Tools.MeleeWeapon"/> or <see cref="StardewValley.Tools.Slingshot"/> item.</summary>
|
/// <summary>A <see cref="StardewValley.Tools.MeleeWeapon"/> or <see cref="StardewValley.Tools.Slingshot"/> item.</summary>
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Linq;
|
||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
using StardewModdingAPI.Mods.ConsoleCommands.Framework.ItemData;
|
using StardewModdingAPI.Mods.ConsoleCommands.Framework.ItemData;
|
||||||
using StardewValley;
|
using StardewValley;
|
||||||
|
using StardewValley.Menus;
|
||||||
using StardewValley.Objects;
|
using StardewValley.Objects;
|
||||||
using StardewValley.Tools;
|
using StardewValley.Tools;
|
||||||
using SObject = StardewValley.Object;
|
using SObject = StardewValley.Object;
|
||||||
|
@ -22,172 +26,227 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework
|
||||||
** Public methods
|
** Public methods
|
||||||
*********/
|
*********/
|
||||||
/// <summary>Get all spawnable items.</summary>
|
/// <summary>Get all spawnable items.</summary>
|
||||||
|
[SuppressMessage("ReSharper", "AccessToModifiedClosure", Justification = "TryCreate invokes the lambda immediately.")]
|
||||||
public IEnumerable<SearchableItem> GetAll()
|
public IEnumerable<SearchableItem> GetAll()
|
||||||
{
|
{
|
||||||
// get tools
|
IEnumerable<SearchableItem> GetAllRaw()
|
||||||
yield return new SearchableItem(ItemType.Tool, ToolFactory.axe, ToolFactory.getToolFromDescription(ToolFactory.axe, 0));
|
|
||||||
yield return new SearchableItem(ItemType.Tool, ToolFactory.hoe, ToolFactory.getToolFromDescription(ToolFactory.hoe, 0));
|
|
||||||
yield return new SearchableItem(ItemType.Tool, ToolFactory.pickAxe, ToolFactory.getToolFromDescription(ToolFactory.pickAxe, 0));
|
|
||||||
yield return new SearchableItem(ItemType.Tool, ToolFactory.wateringCan, ToolFactory.getToolFromDescription(ToolFactory.wateringCan, 0));
|
|
||||||
yield return new SearchableItem(ItemType.Tool, ToolFactory.fishingRod, ToolFactory.getToolFromDescription(ToolFactory.fishingRod, 0));
|
|
||||||
yield return new SearchableItem(ItemType.Tool, this.CustomIDOffset, new MilkPail()); // these don't have any sort of ID, so we'll just assign some arbitrary ones
|
|
||||||
yield return new SearchableItem(ItemType.Tool, this.CustomIDOffset + 1, new Shears());
|
|
||||||
yield return new SearchableItem(ItemType.Tool, this.CustomIDOffset + 2, new Pan());
|
|
||||||
yield return new SearchableItem(ItemType.Tool, this.CustomIDOffset + 3, new Wand());
|
|
||||||
|
|
||||||
// wallpapers
|
|
||||||
for (int id = 0; id < 112; id++)
|
|
||||||
yield return new SearchableItem(ItemType.Wallpaper, id, new Wallpaper(id) { Category = SObject.furnitureCategory });
|
|
||||||
|
|
||||||
// flooring
|
|
||||||
for (int id = 0; id < 40; id++)
|
|
||||||
yield return new SearchableItem(ItemType.Flooring, id, new Wallpaper(id, isFloor: true) { Category = SObject.furnitureCategory });
|
|
||||||
|
|
||||||
// equipment
|
|
||||||
foreach (int id in Game1.content.Load<Dictionary<int, string>>("Data\\Boots").Keys)
|
|
||||||
yield return new SearchableItem(ItemType.Boots, id, new Boots(id));
|
|
||||||
foreach (int id in Game1.content.Load<Dictionary<int, string>>("Data\\hats").Keys)
|
|
||||||
yield return new SearchableItem(ItemType.Hat, id, new Hat(id));
|
|
||||||
foreach (int id in Game1.objectInformation.Keys)
|
|
||||||
{
|
{
|
||||||
if (id >= Ring.ringLowerIndexRange && id <= Ring.ringUpperIndexRange)
|
// get tools
|
||||||
yield return new SearchableItem(ItemType.Ring, id, new Ring(id));
|
for (int quality = Tool.stone; quality <= Tool.iridium; quality++)
|
||||||
}
|
|
||||||
|
|
||||||
// weapons
|
|
||||||
foreach (int id in Game1.content.Load<Dictionary<int, string>>("Data\\weapons").Keys)
|
|
||||||
{
|
|
||||||
Item weapon = (id >= 32 && id <= 34)
|
|
||||||
? (Item)new Slingshot(id)
|
|
||||||
: new MeleeWeapon(id);
|
|
||||||
yield return new SearchableItem(ItemType.Weapon, id, weapon);
|
|
||||||
}
|
|
||||||
|
|
||||||
// furniture
|
|
||||||
foreach (int id in Game1.content.Load<Dictionary<int, string>>("Data\\Furniture").Keys)
|
|
||||||
{
|
|
||||||
if (id == 1466 || id == 1468)
|
|
||||||
yield return new SearchableItem(ItemType.Furniture, id, new TV(id, Vector2.Zero));
|
|
||||||
else
|
|
||||||
yield return new SearchableItem(ItemType.Furniture, id, new Furniture(id, Vector2.Zero));
|
|
||||||
}
|
|
||||||
|
|
||||||
// craftables
|
|
||||||
foreach (int id in Game1.bigCraftablesInformation.Keys)
|
|
||||||
yield return new SearchableItem(ItemType.BigCraftable, id, new SObject(Vector2.Zero, id));
|
|
||||||
|
|
||||||
// secret notes
|
|
||||||
foreach (int id in Game1.content.Load<Dictionary<int, string>>("Data\\SecretNotes").Keys)
|
|
||||||
{
|
|
||||||
SObject note = new SObject(79, 1);
|
|
||||||
note.name = $"{note.name} #{id}";
|
|
||||||
yield return new SearchableItem(ItemType.Object, this.CustomIDOffset + id, note);
|
|
||||||
}
|
|
||||||
|
|
||||||
// objects
|
|
||||||
foreach (int id in Game1.objectInformation.Keys)
|
|
||||||
{
|
|
||||||
if (id == 79)
|
|
||||||
continue; // secret note handled above
|
|
||||||
if (id >= Ring.ringLowerIndexRange && id <= Ring.ringUpperIndexRange)
|
|
||||||
continue; // handled separated
|
|
||||||
|
|
||||||
SObject item = new SObject(id, 1);
|
|
||||||
yield return new SearchableItem(ItemType.Object, id, item);
|
|
||||||
|
|
||||||
// fruit products
|
|
||||||
if (item.Category == SObject.FruitsCategory)
|
|
||||||
{
|
{
|
||||||
// wine
|
yield return this.TryCreate(ItemType.Tool, ToolFactory.axe, () => ToolFactory.getToolFromDescription(ToolFactory.axe, quality));
|
||||||
SObject wine = new SObject(348, 1)
|
yield return this.TryCreate(ItemType.Tool, ToolFactory.hoe, () => ToolFactory.getToolFromDescription(ToolFactory.hoe, quality));
|
||||||
{
|
yield return this.TryCreate(ItemType.Tool, ToolFactory.pickAxe, () => ToolFactory.getToolFromDescription(ToolFactory.pickAxe, quality));
|
||||||
Name = $"{item.Name} Wine",
|
yield return this.TryCreate(ItemType.Tool, ToolFactory.wateringCan, () => ToolFactory.getToolFromDescription(ToolFactory.wateringCan, quality));
|
||||||
Price = item.Price * 3
|
if (quality != Tool.iridium)
|
||||||
};
|
yield return this.TryCreate(ItemType.Tool, ToolFactory.fishingRod, () => ToolFactory.getToolFromDescription(ToolFactory.fishingRod, quality));
|
||||||
wine.preserve.Value = SObject.PreserveType.Wine;
|
}
|
||||||
wine.preservedParentSheetIndex.Value = item.ParentSheetIndex;
|
yield return this.TryCreate(ItemType.Tool, this.CustomIDOffset, () => new MilkPail()); // these don't have any sort of ID, so we'll just assign some arbitrary ones
|
||||||
yield return new SearchableItem(ItemType.Object, this.CustomIDOffset * 2 + id, wine);
|
yield return this.TryCreate(ItemType.Tool, this.CustomIDOffset + 1, () => new Shears());
|
||||||
|
yield return this.TryCreate(ItemType.Tool, this.CustomIDOffset + 2, () => new Pan());
|
||||||
|
yield return this.TryCreate(ItemType.Tool, this.CustomIDOffset + 3, () => new Wand());
|
||||||
|
|
||||||
// jelly
|
// clothing
|
||||||
SObject jelly = new SObject(344, 1)
|
foreach (int id in Game1.clothingInformation.Keys)
|
||||||
{
|
yield return this.TryCreate(ItemType.Clothing, id, () => new Clothing(id));
|
||||||
Name = $"{item.Name} Jelly",
|
|
||||||
Price = 50 + item.Price * 2
|
// wallpapers
|
||||||
};
|
for (int id = 0; id < 112; id++)
|
||||||
jelly.preserve.Value = SObject.PreserveType.Jelly;
|
yield return this.TryCreate(ItemType.Wallpaper, id, () => new Wallpaper(id) { Category = SObject.furnitureCategory });
|
||||||
jelly.preservedParentSheetIndex.Value = item.ParentSheetIndex;
|
|
||||||
yield return new SearchableItem(ItemType.Object, this.CustomIDOffset * 3 + id, jelly);
|
// flooring
|
||||||
|
for (int id = 0; id < 56; id++)
|
||||||
|
yield return this.TryCreate(ItemType.Flooring, id, () => new Wallpaper(id, isFloor: true) { Category = SObject.furnitureCategory });
|
||||||
|
|
||||||
|
// equipment
|
||||||
|
foreach (int id in Game1.content.Load<Dictionary<int, string>>("Data\\Boots").Keys)
|
||||||
|
yield return this.TryCreate(ItemType.Boots, id, () => new Boots(id));
|
||||||
|
foreach (int id in Game1.content.Load<Dictionary<int, string>>("Data\\hats").Keys)
|
||||||
|
yield return this.TryCreate(ItemType.Hat, id, () => new Hat(id));
|
||||||
|
|
||||||
|
// weapons
|
||||||
|
foreach (int id in Game1.content.Load<Dictionary<int, string>>("Data\\weapons").Keys)
|
||||||
|
{
|
||||||
|
yield return this.TryCreate(ItemType.Weapon, id, () => (id >= 32 && id <= 34)
|
||||||
|
? (Item)new Slingshot(id)
|
||||||
|
: new MeleeWeapon(id)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// vegetable products
|
// furniture
|
||||||
else if (item.Category == SObject.VegetableCategory)
|
foreach (int id in Game1.content.Load<Dictionary<int, string>>("Data\\Furniture").Keys)
|
||||||
{
|
{
|
||||||
// juice
|
if (id == 1466 || id == 1468)
|
||||||
SObject juice = new SObject(350, 1)
|
yield return this.TryCreate(ItemType.Furniture, id, () => new TV(id, Vector2.Zero));
|
||||||
{
|
else
|
||||||
Name = $"{item.Name} Juice",
|
yield return this.TryCreate(ItemType.Furniture, id, () => new Furniture(id, Vector2.Zero));
|
||||||
Price = (int)(item.Price * 2.25d)
|
|
||||||
};
|
|
||||||
juice.preserve.Value = SObject.PreserveType.Juice;
|
|
||||||
juice.preservedParentSheetIndex.Value = item.ParentSheetIndex;
|
|
||||||
yield return new SearchableItem(ItemType.Object, this.CustomIDOffset * 4 + id, juice);
|
|
||||||
|
|
||||||
// pickled
|
|
||||||
SObject pickled = new SObject(342, 1)
|
|
||||||
{
|
|
||||||
Name = $"Pickled {item.Name}",
|
|
||||||
Price = 50 + item.Price * 2
|
|
||||||
};
|
|
||||||
pickled.preserve.Value = SObject.PreserveType.Pickle;
|
|
||||||
pickled.preservedParentSheetIndex.Value = item.ParentSheetIndex;
|
|
||||||
yield return new SearchableItem(ItemType.Object, this.CustomIDOffset * 5 + id, pickled);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// flower honey
|
// craftables
|
||||||
else if (item.Category == SObject.flowersCategory)
|
foreach (int id in Game1.bigCraftablesInformation.Keys)
|
||||||
|
yield return this.TryCreate(ItemType.BigCraftable, id, () => new SObject(Vector2.Zero, id));
|
||||||
|
|
||||||
|
// objects
|
||||||
|
foreach (int id in Game1.objectInformation.Keys)
|
||||||
{
|
{
|
||||||
// get honey type
|
string[] fields = Game1.objectInformation[id]?.Split('/');
|
||||||
SObject.HoneyType? type = null;
|
|
||||||
switch (item.ParentSheetIndex)
|
|
||||||
{
|
|
||||||
case 376:
|
|
||||||
type = SObject.HoneyType.Poppy;
|
|
||||||
break;
|
|
||||||
case 591:
|
|
||||||
type = SObject.HoneyType.Tulip;
|
|
||||||
break;
|
|
||||||
case 593:
|
|
||||||
type = SObject.HoneyType.SummerSpangle;
|
|
||||||
break;
|
|
||||||
case 595:
|
|
||||||
type = SObject.HoneyType.FairyRose;
|
|
||||||
break;
|
|
||||||
case 597:
|
|
||||||
type = SObject.HoneyType.BlueJazz;
|
|
||||||
break;
|
|
||||||
case 421: // sunflower standing in for all other flowers
|
|
||||||
type = SObject.HoneyType.Wild;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// yield honey
|
// secret notes
|
||||||
if (type != null)
|
if (id == 79)
|
||||||
{
|
{
|
||||||
SObject honey = new SObject(Vector2.Zero, 340, item.Name + " Honey", false, true, false, false)
|
foreach (int secretNoteId in Game1.content.Load<Dictionary<int, string>>("Data\\SecretNotes").Keys)
|
||||||
{
|
{
|
||||||
Name = "Wild Honey"
|
yield return this.TryCreate(ItemType.Object, this.CustomIDOffset + secretNoteId, () =>
|
||||||
};
|
{
|
||||||
honey.honeyType.Value = type;
|
SObject note = new SObject(79, 1);
|
||||||
|
note.name = $"{note.name} #{secretNoteId}";
|
||||||
if (type != SObject.HoneyType.Wild)
|
return note;
|
||||||
{
|
});
|
||||||
honey.Name = $"{item.Name} Honey";
|
}
|
||||||
honey.Price += item.Price * 2;
|
}
|
||||||
|
|
||||||
|
// ring
|
||||||
|
else if (id != 801 && fields?.Length >= 4 && fields[3] == "Ring") // 801 = wedding ring, which isn't an equippable ring
|
||||||
|
yield return this.TryCreate(ItemType.Ring, id, () => new Ring(id));
|
||||||
|
|
||||||
|
// item
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// spawn main item
|
||||||
|
SObject item = null;
|
||||||
|
yield return this.TryCreate(ItemType.Object, id, () =>
|
||||||
|
{
|
||||||
|
return item = (id == 812 // roe
|
||||||
|
? new ColoredObject(id, 1, Color.White)
|
||||||
|
: new SObject(id, 1)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
if (item == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// flavored items
|
||||||
|
switch (item.Category)
|
||||||
|
{
|
||||||
|
// fruit products
|
||||||
|
case SObject.FruitsCategory:
|
||||||
|
// wine
|
||||||
|
yield return this.TryCreate(ItemType.Object, this.CustomIDOffset * 2 + id, () => new SObject(348, 1)
|
||||||
|
{
|
||||||
|
Name = $"{item.Name} Wine",
|
||||||
|
Price = item.Price * 3,
|
||||||
|
preserve = { SObject.PreserveType.Wine },
|
||||||
|
preservedParentSheetIndex = { item.ParentSheetIndex }
|
||||||
|
});
|
||||||
|
|
||||||
|
// jelly
|
||||||
|
yield return this.TryCreate(ItemType.Object, this.CustomIDOffset * 3 + id, () => new SObject(344, 1)
|
||||||
|
{
|
||||||
|
Name = $"{item.Name} Jelly",
|
||||||
|
Price = 50 + item.Price * 2,
|
||||||
|
preserve = { SObject.PreserveType.Jelly },
|
||||||
|
preservedParentSheetIndex = { item.ParentSheetIndex }
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
|
||||||
|
// vegetable products
|
||||||
|
case SObject.VegetableCategory:
|
||||||
|
// juice
|
||||||
|
yield return this.TryCreate(ItemType.Object, this.CustomIDOffset * 4 + id, () => new SObject(350, 1)
|
||||||
|
{
|
||||||
|
Name = $"{item.Name} Juice",
|
||||||
|
Price = (int)(item.Price * 2.25d),
|
||||||
|
preserve = { SObject.PreserveType.Juice },
|
||||||
|
preservedParentSheetIndex = { item.ParentSheetIndex }
|
||||||
|
});
|
||||||
|
|
||||||
|
// pickled
|
||||||
|
yield return this.TryCreate(ItemType.Object, this.CustomIDOffset * 5 + id, () => new SObject(342, 1)
|
||||||
|
{
|
||||||
|
Name = $"Pickled {item.Name}",
|
||||||
|
Price = 50 + item.Price * 2,
|
||||||
|
preserve = { SObject.PreserveType.Pickle },
|
||||||
|
preservedParentSheetIndex = { item.ParentSheetIndex }
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
|
||||||
|
// flower honey
|
||||||
|
case SObject.flowersCategory:
|
||||||
|
yield return this.TryCreate(ItemType.Object, this.CustomIDOffset * 5 + id, () =>
|
||||||
|
{
|
||||||
|
SObject honey = new SObject(Vector2.Zero, 340, $"{item.Name} Honey", false, true, false, false)
|
||||||
|
{
|
||||||
|
Name = $"{item.Name} Honey",
|
||||||
|
preservedParentSheetIndex = { item.ParentSheetIndex }
|
||||||
|
};
|
||||||
|
honey.Price += item.Price * 2;
|
||||||
|
return honey;
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
|
||||||
|
// roe and aged roe (derived from FishPond.GetFishProduce)
|
||||||
|
case SObject.sellAtFishShopCategory when id == 812:
|
||||||
|
foreach (var pair in Game1.objectInformation)
|
||||||
|
{
|
||||||
|
// get input
|
||||||
|
SObject input = this.TryCreate(ItemType.Object, -1, () => new SObject(pair.Key, 1))?.Item as SObject;
|
||||||
|
if (input == null || input.Category != SObject.FishCategory)
|
||||||
|
continue;
|
||||||
|
Color color = TailoringMenu.GetDyeColor(input) ?? Color.Orange;
|
||||||
|
|
||||||
|
// yield roe
|
||||||
|
SObject roe = null;
|
||||||
|
yield return this.TryCreate(ItemType.Object, this.CustomIDOffset * 7 + id, () =>
|
||||||
|
{
|
||||||
|
roe = new ColoredObject(812, 1, color)
|
||||||
|
{
|
||||||
|
name = $"{input.Name} Roe",
|
||||||
|
preserve = { Value = SObject.PreserveType.Roe },
|
||||||
|
preservedParentSheetIndex = { Value = input.ParentSheetIndex }
|
||||||
|
};
|
||||||
|
roe.Price += input.Price / 2;
|
||||||
|
return roe;
|
||||||
|
});
|
||||||
|
|
||||||
|
// aged roe
|
||||||
|
if (roe != null && pair.Key != 698) // aged sturgeon roe is caviar, which is a separate item
|
||||||
|
{
|
||||||
|
yield return this.TryCreate(ItemType.Object, this.CustomIDOffset * 7 + id, () => new ColoredObject(447, 1, color)
|
||||||
|
{
|
||||||
|
name = $"Aged {input.Name} Roe",
|
||||||
|
Category = -27,
|
||||||
|
preserve = { Value = SObject.PreserveType.AgedRoe },
|
||||||
|
preservedParentSheetIndex = { Value = input.ParentSheetIndex },
|
||||||
|
Price = roe.Price * 2
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
yield return new SearchableItem(ItemType.Object, this.CustomIDOffset * 5 + id, honey);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return GetAllRaw().Where(p => p != null);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*********
|
||||||
|
** Private methods
|
||||||
|
*********/
|
||||||
|
/// <summary>Create a searchable item if valid.</summary>
|
||||||
|
/// <param name="type">The item type.</param>
|
||||||
|
/// <param name="id">The unique ID (if different from the item's parent sheet index).</param>
|
||||||
|
/// <param name="createItem">Create an item instance.</param>
|
||||||
|
private SearchableItem TryCreate(ItemType type, int id, Func<Item> createItem)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return new SearchableItem(type, id, createItem());
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return null; // if some item data is invalid, just don't include it
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
using System.Reflection;
|
|
||||||
|
|
||||||
[assembly: AssemblyTitle("SMAPI.Mods.ConsoleCommands")]
|
|
||||||
[assembly: AssemblyDescription("")]
|
|
|
@ -0,0 +1,73 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<AssemblyName>ConsoleCommands</AssemblyName>
|
||||||
|
<RootNamespace>StardewModdingAPI.Mods.ConsoleCommands</RootNamespace>
|
||||||
|
<TargetFramework>net45</TargetFramework>
|
||||||
|
<LangVersion>latest</LangVersion>
|
||||||
|
<OutputPath>$(SolutionDir)\..\bin\$(Configuration)\Mods\ConsoleCommands</OutputPath>
|
||||||
|
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||||
|
<PlatformTarget>x86</PlatformTarget>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\SMAPI\SMAPI.csproj">
|
||||||
|
<Private>False</Private>
|
||||||
|
</ProjectReference>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Reference Include="$(GameExecutableName)">
|
||||||
|
<HintPath>$(GamePath)\$(GameExecutableName).exe</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="StardewValley.GameData">
|
||||||
|
<HintPath>$(GamePath)\StardewValley.GameData.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<Choose>
|
||||||
|
<!-- Windows -->
|
||||||
|
<When Condition="$(OS) == 'Windows_NT'">
|
||||||
|
<ItemGroup>
|
||||||
|
<Reference Include="Netcode">
|
||||||
|
<HintPath>$(GamePath)\Netcode.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="Microsoft.Xna.Framework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=x86">
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="Microsoft.Xna.Framework.Game, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=x86">
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="Microsoft.Xna.Framework.Graphics, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=x86">
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="Microsoft.Xna.Framework.Xact, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=x86">
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
</ItemGroup>
|
||||||
|
</When>
|
||||||
|
|
||||||
|
<!-- Linux/Mac -->
|
||||||
|
<Otherwise>
|
||||||
|
<ItemGroup>
|
||||||
|
<Reference Include="MonoGame.Framework">
|
||||||
|
<HintPath>$(GamePath)\MonoGame.Framework.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
</ItemGroup>
|
||||||
|
</Otherwise>
|
||||||
|
</Choose>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Update="manifest.json">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<Import Project="..\SMAPI.Internal\SMAPI.Internal.projitems" Label="Shared" />
|
||||||
|
<Import Project="..\..\build\common.targets" />
|
||||||
|
|
||||||
|
</Project>
|
|
@ -1,44 +0,0 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<RootNamespace>StardewModdingAPI.Mods.ConsoleCommands</RootNamespace>
|
|
||||||
<AssemblyName>ConsoleCommands</AssemblyName>
|
|
||||||
<TargetFramework>net45</TargetFramework>
|
|
||||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
|
||||||
<LangVersion>latest</LangVersion>
|
|
||||||
<OutputPath>$(SolutionDir)\..\bin\$(Configuration)\Mods\ConsoleCommands</OutputPath>
|
|
||||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
|
||||||
<PlatformTarget>x86</PlatformTarget>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<Compile Include="..\..\build\GlobalAssemblyInfo.cs">
|
|
||||||
<Link>Properties\GlobalAssemblyInfo.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\SMAPI.Toolkit.CoreInterfaces\StardewModdingAPI.Toolkit.CoreInterfaces.csproj" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<Reference Include="MonoGame.Framework">
|
|
||||||
<HintPath>..\..\..\..\..\Downloads\com.chucklefish.stardewvalley_1.322\assemblies\MonoGame.Framework.dll</HintPath>
|
|
||||||
</Reference>
|
|
||||||
<Reference Include="StardewModdingAPI">
|
|
||||||
<HintPath>..\SMAPI\bin\Debug\StardewModdingAPI.dll</HintPath>
|
|
||||||
</Reference>
|
|
||||||
<Reference Include="StardewValley">
|
|
||||||
<HintPath>..\..\..\..\..\Downloads\com.chucklefish.stardewvalley_1.322\assemblies\StardewValley.dll</HintPath>
|
|
||||||
</Reference>
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<None Update="manifest.json">
|
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
|
||||||
</None>
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<Import Project="..\SMAPI.Internal\SMAPI.Internal.projitems" Label="Shared" />
|
|
||||||
|
|
||||||
</Project>
|
|
|
@ -1,9 +1,9 @@
|
||||||
{
|
{
|
||||||
"Name": "Console Commands",
|
"Name": "Console Commands",
|
||||||
"Author": "SMAPI",
|
"Author": "SMAPI",
|
||||||
"Version": "2.11.3",
|
"Version": "3.1.0",
|
||||||
"Description": "Adds SMAPI console commands that let you manipulate the game.",
|
"Description": "Adds SMAPI console commands that let you manipulate the game.",
|
||||||
"UniqueID": "SMAPI.ConsoleCommands",
|
"UniqueID": "SMAPI.ConsoleCommands",
|
||||||
"EntryDll": "ConsoleCommands.dll",
|
"EntryDll": "ConsoleCommands.dll",
|
||||||
"MinimumApiVersion": "2.11.3"
|
"MinimumApiVersion": "3.1.0"
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ using System.IO;
|
||||||
using System.IO.Compression;
|
using System.IO.Compression;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using StardewValley;
|
using StardewValley;
|
||||||
|
|
||||||
namespace StardewModdingAPI.Mods.SaveBackup
|
namespace StardewModdingAPI.Mods.SaveBackup
|
||||||
|
@ -40,9 +41,10 @@ namespace StardewModdingAPI.Mods.SaveBackup
|
||||||
DirectoryInfo backupFolder = new DirectoryInfo(this.BackupFolder);
|
DirectoryInfo backupFolder = new DirectoryInfo(this.BackupFolder);
|
||||||
backupFolder.Create();
|
backupFolder.Create();
|
||||||
|
|
||||||
// back up saves
|
// back up & prune saves
|
||||||
this.CreateBackup(backupFolder);
|
Task
|
||||||
this.PruneBackups(backupFolder, this.BackupsToKeep);
|
.Run(() => this.CreateBackup(backupFolder))
|
||||||
|
.ContinueWith(backupTask => this.PruneBackups(backupFolder, this.BackupsToKeep));
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
@ -66,49 +68,23 @@ namespace StardewModdingAPI.Mods.SaveBackup
|
||||||
if (targetFile.Exists || fallbackDir.Exists)
|
if (targetFile.Exists || fallbackDir.Exists)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// create zip
|
// back up saves
|
||||||
// due to limitations with the bundled Mono on Mac, we can't reference System.IO.Compression.
|
this.Monitor.Log($"Backing up saves to {targetFile.FullName}...", LogLevel.Trace);
|
||||||
this.Monitor.Log($"Adding {targetFile.Name}...", LogLevel.Trace);
|
if (!this.TryCompress(Constants.SavesPath, targetFile, out Exception compressError))
|
||||||
switch (Constants.TargetPlatform)
|
|
||||||
{
|
{
|
||||||
case GamePlatform.Android:
|
// log error (expected on Android due to missing compression DLLs)
|
||||||
case GamePlatform.Linux:
|
if (Constants.TargetPlatform == GamePlatform.Android)
|
||||||
case GamePlatform.Windows:
|
this.Monitor.VerboseLog($"Compression isn't supported on Android:\n{compressError}");
|
||||||
{
|
else
|
||||||
try
|
{
|
||||||
{
|
this.Monitor.Log("Couldn't zip the save backup, creating uncompressed backup instead.", LogLevel.Debug);
|
||||||
// create compressed backup
|
this.Monitor.Log(compressError.ToString(), LogLevel.Trace);
|
||||||
Assembly coreAssembly = Assembly.Load("System.IO.Compression, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089") ?? throw new InvalidOperationException("Can't load System.IO.Compression assembly.");
|
}
|
||||||
Assembly fsAssembly = Assembly.Load("System.IO.Compression.FileSystem, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089") ?? throw new InvalidOperationException("Can't load System.IO.Compression assembly.");
|
|
||||||
Type compressionLevelType = coreAssembly.GetType("System.IO.Compression.CompressionLevel") ?? throw new InvalidOperationException("Can't load CompressionLevel type.");
|
|
||||||
Type zipFileType = fsAssembly.GetType("System.IO.Compression.ZipFile") ?? throw new InvalidOperationException("Can't load ZipFile type.");
|
|
||||||
MethodInfo createMethod = zipFileType.GetMethod("CreateFromDirectory", new[] { typeof(string), typeof(string), compressionLevelType, typeof(bool) }) ?? throw new InvalidOperationException("Can't load ZipFile.CreateFromDirectory method.");
|
|
||||||
createMethod.Invoke(null, new object[] { Constants.SavesPath, targetFile.FullName, CompressionLevel.Fastest, false });
|
|
||||||
}
|
|
||||||
catch (Exception ex) when (ex is TypeLoadException || ex.InnerException is TypeLoadException)
|
|
||||||
{
|
|
||||||
// create uncompressed backup if compression fails
|
|
||||||
this.Monitor.Log("Couldn't zip the save backup, creating uncompressed backup instead.", LogLevel.Debug);
|
|
||||||
this.Monitor.Log(ex.ToString(), LogLevel.Trace);
|
|
||||||
this.RecursiveCopy(new DirectoryInfo(Constants.SavesPath), fallbackDir, copyRoot: false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case GamePlatform.Mac:
|
// fallback to uncompressed
|
||||||
{
|
this.RecursiveCopy(new DirectoryInfo(Constants.SavesPath), fallbackDir, copyRoot: false);
|
||||||
DirectoryInfo saveFolder = new DirectoryInfo(Constants.SavesPath);
|
|
||||||
ProcessStartInfo startInfo = new ProcessStartInfo
|
|
||||||
{
|
|
||||||
FileName = "zip",
|
|
||||||
Arguments = $"-rq \"{targetFile.FullName}\" \"{saveFolder.Name}\" -x \"*.DS_Store\" -x \"__MACOSX\"",
|
|
||||||
WorkingDirectory = $"{Constants.SavesPath}/../",
|
|
||||||
CreateNoWindow = true
|
|
||||||
};
|
|
||||||
new Process { StartInfo = startInfo }.Start();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
this.Monitor.Log("Backup done!", LogLevel.Trace);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
@ -125,20 +101,23 @@ namespace StardewModdingAPI.Mods.SaveBackup
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var oldBackups = backupFolder
|
var oldBackups = backupFolder
|
||||||
.GetFiles()
|
.GetFileSystemInfos()
|
||||||
.OrderByDescending(p => p.CreationTimeUtc)
|
.OrderByDescending(p => p.CreationTimeUtc)
|
||||||
.Skip(backupsToKeep);
|
.Skip(backupsToKeep);
|
||||||
|
|
||||||
foreach (FileInfo file in oldBackups)
|
foreach (FileSystemInfo entry in oldBackups)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
this.Monitor.Log($"Deleting {file.Name}...", LogLevel.Trace);
|
this.Monitor.Log($"Deleting {entry.Name}...", LogLevel.Trace);
|
||||||
file.Delete();
|
if (entry is DirectoryInfo folder)
|
||||||
|
folder.Delete(recursive: true);
|
||||||
|
else
|
||||||
|
entry.Delete();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
this.Monitor.Log($"Error deleting old save backup '{file.Name}': {ex}", LogLevel.Error);
|
this.Monitor.Log($"Error deleting old save backup '{entry.Name}': {ex}", LogLevel.Error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -149,6 +128,72 @@ namespace StardewModdingAPI.Mods.SaveBackup
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Create a zip using the best available method.</summary>
|
||||||
|
/// <param name="sourcePath">The file or directory path to zip.</param>
|
||||||
|
/// <param name="destination">The destination file to create.</param>
|
||||||
|
/// <param name="error">The error which occurred trying to compress, if applicable. This is <see cref="NotSupportedException"/> if compression isn't supported on this platform.</param>
|
||||||
|
/// <returns>Returns whether compression succeeded.</returns>
|
||||||
|
private bool TryCompress(string sourcePath, FileInfo destination, out Exception error)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (Constants.TargetPlatform == GamePlatform.Mac)
|
||||||
|
this.CompressUsingMacProcess(sourcePath, destination); // due to limitations with the bundled Mono on Mac, we can't reference System.IO.Compression
|
||||||
|
else
|
||||||
|
this.CompressUsingNetFramework(sourcePath, destination);
|
||||||
|
|
||||||
|
error = null;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
error = ex;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Create a zip using the .NET compression library.</summary>
|
||||||
|
/// <param name="sourcePath">The file or directory path to zip.</param>
|
||||||
|
/// <param name="destination">The destination file to create.</param>
|
||||||
|
/// <exception cref="NotSupportedException">The compression libraries aren't available on this system.</exception>
|
||||||
|
private void CompressUsingNetFramework(string sourcePath, FileInfo destination)
|
||||||
|
{
|
||||||
|
// get compress method
|
||||||
|
MethodInfo createFromDirectory;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// create compressed backup
|
||||||
|
Assembly coreAssembly = Assembly.Load("System.IO.Compression, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089") ?? throw new InvalidOperationException("Can't load System.IO.Compression assembly.");
|
||||||
|
Assembly fsAssembly = Assembly.Load("System.IO.Compression.FileSystem, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089") ?? throw new InvalidOperationException("Can't load System.IO.Compression assembly.");
|
||||||
|
Type compressionLevelType = coreAssembly.GetType("System.IO.Compression.CompressionLevel") ?? throw new InvalidOperationException("Can't load CompressionLevel type.");
|
||||||
|
Type zipFileType = fsAssembly.GetType("System.IO.Compression.ZipFile") ?? throw new InvalidOperationException("Can't load ZipFile type.");
|
||||||
|
createFromDirectory = zipFileType.GetMethod("CreateFromDirectory", new[] { typeof(string), typeof(string), compressionLevelType, typeof(bool) }) ?? throw new InvalidOperationException("Can't load ZipFile.CreateFromDirectory method.");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException("Couldn't load the .NET compression libraries on this system.", ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
// compress file
|
||||||
|
createFromDirectory.Invoke(null, new object[] { sourcePath, destination.FullName, CompressionLevel.Fastest, false });
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Create a zip using a process command on MacOS.</summary>
|
||||||
|
/// <param name="sourcePath">The file or directory path to zip.</param>
|
||||||
|
/// <param name="destination">The destination file to create.</param>
|
||||||
|
private void CompressUsingMacProcess(string sourcePath, FileInfo destination)
|
||||||
|
{
|
||||||
|
DirectoryInfo saveFolder = new DirectoryInfo(sourcePath);
|
||||||
|
ProcessStartInfo startInfo = new ProcessStartInfo
|
||||||
|
{
|
||||||
|
FileName = "zip",
|
||||||
|
Arguments = $"-rq \"{destination.FullName}\" \"{saveFolder.Name}\" -x \"*.DS_Store\" -x \"__MACOSX\"",
|
||||||
|
WorkingDirectory = $"{saveFolder.FullName}/../",
|
||||||
|
CreateNoWindow = true
|
||||||
|
};
|
||||||
|
new Process { StartInfo = startInfo }.Start();
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>Recursively copy a directory or file.</summary>
|
/// <summary>Recursively copy a directory or file.</summary>
|
||||||
/// <param name="source">The file or folder to copy.</param>
|
/// <param name="source">The file or folder to copy.</param>
|
||||||
/// <param name="targetFolder">The folder to copy into.</param>
|
/// <param name="targetFolder">The folder to copy into.</param>
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
using System.Reflection;
|
|
||||||
|
|
||||||
[assembly: AssemblyTitle("StardewModdingAPI.Mods.SaveBackup")]
|
|
||||||
[assembly: AssemblyDescription("")]
|
|
|
@ -1,10 +1,9 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<RootNamespace>StardewModdingAPI.Mods.SaveBackup</RootNamespace>
|
|
||||||
<AssemblyName>SaveBackup</AssemblyName>
|
<AssemblyName>SaveBackup</AssemblyName>
|
||||||
|
<RootNamespace>StardewModdingAPI.Mods.SaveBackup</RootNamespace>
|
||||||
<TargetFramework>net45</TargetFramework>
|
<TargetFramework>net45</TargetFramework>
|
||||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
|
||||||
<LangVersion>latest</LangVersion>
|
<LangVersion>latest</LangVersion>
|
||||||
<OutputPath>C:\Users\Chris\source\repos\SMAPI\bin\Debug\Mods\SaveBackup\</OutputPath>
|
<OutputPath>C:\Users\Chris\source\repos\SMAPI\bin\Debug\Mods\SaveBackup\</OutputPath>
|
||||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||||
|
@ -12,21 +11,15 @@
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="..\..\build\GlobalAssemblyInfo.cs">
|
<ProjectReference Include="..\SMAPI\SMAPI.csproj">
|
||||||
<Link>Properties\GlobalAssemblyInfo.cs</Link>
|
<Private>False</Private>
|
||||||
</Compile>
|
</ProjectReference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\SMAPI.Toolkit.CoreInterfaces\StardewModdingAPI.Toolkit.CoreInterfaces.csproj" />
|
<Reference Include="$(GameExecutableName)">
|
||||||
</ItemGroup>
|
<HintPath>$(GamePath)\$(GameExecutableName).exe</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
<ItemGroup>
|
|
||||||
<Reference Include="StardewModdingAPI">
|
|
||||||
<HintPath>..\SMAPI\bin\Debug\StardewModdingAPI.dll</HintPath>
|
|
||||||
</Reference>
|
|
||||||
<Reference Include="StardewValley">
|
|
||||||
<HintPath>..\..\..\..\..\Downloads\com.chucklefish.stardewvalley_1.322\assemblies\StardewValley.dll</HintPath>
|
|
||||||
</Reference>
|
</Reference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
{
|
{
|
||||||
"Name": "Save Backup",
|
"Name": "Save Backup",
|
||||||
"Author": "SMAPI",
|
"Author": "SMAPI",
|
||||||
"Version": "2.11.3",
|
"Version": "3.1.0",
|
||||||
"Description": "Automatically backs up all your saves once per day into its folder.",
|
"Description": "Automatically backs up all your saves once per day into its folder.",
|
||||||
"UniqueID": "SMAPI.SaveBackup",
|
"UniqueID": "SMAPI.SaveBackup",
|
||||||
"EntryDll": "SaveBackup.dll",
|
"EntryDll": "SaveBackup.dll",
|
||||||
"MinimumApiVersion": "2.11.3"
|
"MinimumApiVersion": "3.1.0"
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,13 +5,15 @@ using System.Linq;
|
||||||
using Moq;
|
using Moq;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using StardewModdingAPI;
|
||||||
using StardewModdingAPI.Framework;
|
using StardewModdingAPI.Framework;
|
||||||
using StardewModdingAPI.Framework.ModLoading;
|
using StardewModdingAPI.Framework.ModLoading;
|
||||||
using StardewModdingAPI.Toolkit;
|
using StardewModdingAPI.Toolkit;
|
||||||
using StardewModdingAPI.Toolkit.Framework.ModData;
|
using StardewModdingAPI.Toolkit.Framework.ModData;
|
||||||
using StardewModdingAPI.Toolkit.Serialisation.Models;
|
using StardewModdingAPI.Toolkit.Serialization.Models;
|
||||||
|
using SemanticVersion = StardewModdingAPI.SemanticVersion;
|
||||||
|
|
||||||
namespace StardewModdingAPI.Tests.Core
|
namespace SMAPI.Tests.Core
|
||||||
{
|
{
|
||||||
/// <summary>Unit tests for <see cref="ModResolver"/>.</summary>
|
/// <summary>Unit tests for <see cref="ModResolver"/>.</summary>
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
|
@ -27,7 +29,7 @@ namespace StardewModdingAPI.Tests.Core
|
||||||
public void ReadBasicManifest_NoMods_ReturnsEmptyList()
|
public void ReadBasicManifest_NoMods_ReturnsEmptyList()
|
||||||
{
|
{
|
||||||
// arrange
|
// arrange
|
||||||
string rootFolder = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N"));
|
string rootFolder = this.GetTempFolderPath();
|
||||||
Directory.CreateDirectory(rootFolder);
|
Directory.CreateDirectory(rootFolder);
|
||||||
|
|
||||||
// act
|
// act
|
||||||
|
@ -41,7 +43,7 @@ namespace StardewModdingAPI.Tests.Core
|
||||||
public void ReadBasicManifest_EmptyModFolder_ReturnsFailedManifest()
|
public void ReadBasicManifest_EmptyModFolder_ReturnsFailedManifest()
|
||||||
{
|
{
|
||||||
// arrange
|
// arrange
|
||||||
string rootFolder = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N"));
|
string rootFolder = this.GetTempFolderPath();
|
||||||
string modFolder = Path.Combine(rootFolder, Guid.NewGuid().ToString("N"));
|
string modFolder = Path.Combine(rootFolder, Guid.NewGuid().ToString("N"));
|
||||||
Directory.CreateDirectory(modFolder);
|
Directory.CreateDirectory(modFolder);
|
||||||
|
|
||||||
|
@ -55,7 +57,7 @@ namespace StardewModdingAPI.Tests.Core
|
||||||
Assert.IsNotNull(mod.Error, "The mod metadata did not have an error message set.");
|
Assert.IsNotNull(mod.Error, "The mod metadata did not have an error message set.");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test(Description = "Assert that the resolver correctly reads manifest data from a randomised file.")]
|
[Test(Description = "Assert that the resolver correctly reads manifest data from a randomized file.")]
|
||||||
public void ReadBasicManifest_CanReadFile()
|
public void ReadBasicManifest_CanReadFile()
|
||||||
{
|
{
|
||||||
// create manifest data
|
// create manifest data
|
||||||
|
@ -78,7 +80,7 @@ namespace StardewModdingAPI.Tests.Core
|
||||||
};
|
};
|
||||||
|
|
||||||
// write to filesystem
|
// write to filesystem
|
||||||
string rootFolder = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N"));
|
string rootFolder = this.GetTempFolderPath();
|
||||||
string modFolder = Path.Combine(rootFolder, Guid.NewGuid().ToString("N"));
|
string modFolder = Path.Combine(rootFolder, Guid.NewGuid().ToString("N"));
|
||||||
string filename = Path.Combine(modFolder, "manifest.json");
|
string filename = Path.Combine(modFolder, "manifest.json");
|
||||||
Directory.CreateDirectory(modFolder);
|
Directory.CreateDirectory(modFolder);
|
||||||
|
@ -209,7 +211,7 @@ namespace StardewModdingAPI.Tests.Core
|
||||||
IManifest manifest = this.GetManifest();
|
IManifest manifest = this.GetManifest();
|
||||||
|
|
||||||
// create DLL
|
// create DLL
|
||||||
string modFolder = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N"));
|
string modFolder = Path.Combine(this.GetTempFolderPath(), Guid.NewGuid().ToString("N"));
|
||||||
Directory.CreateDirectory(modFolder);
|
Directory.CreateDirectory(modFolder);
|
||||||
File.WriteAllText(Path.Combine(modFolder, manifest.EntryDll), "");
|
File.WriteAllText(Path.Combine(modFolder, manifest.EntryDll), "");
|
||||||
|
|
||||||
|
@ -462,7 +464,13 @@ namespace StardewModdingAPI.Tests.Core
|
||||||
/*********
|
/*********
|
||||||
** Private methods
|
** Private methods
|
||||||
*********/
|
*********/
|
||||||
/// <summary>Get a randomised basic manifest.</summary>
|
/// <summary>Get a generated folder path in the temp folder. This folder isn't created automatically.</summary>
|
||||||
|
private string GetTempFolderPath()
|
||||||
|
{
|
||||||
|
return Path.Combine(Path.GetTempPath(), "smapi-unit-tests", Guid.NewGuid().ToString("N"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Get a randomized basic manifest.</summary>
|
||||||
/// <param name="id">The <see cref="IManifest.UniqueID"/> value, or <c>null</c> for a generated value.</param>
|
/// <param name="id">The <see cref="IManifest.UniqueID"/> value, or <c>null</c> for a generated value.</param>
|
||||||
/// <param name="name">The <see cref="IManifest.Name"/> value, or <c>null</c> for a generated value.</param>
|
/// <param name="name">The <see cref="IManifest.Name"/> value, or <c>null</c> for a generated value.</param>
|
||||||
/// <param name="version">The <see cref="IManifest.Version"/> value, or <c>null</c> for a generated value.</param>
|
/// <param name="version">The <see cref="IManifest.Version"/> value, or <c>null</c> for a generated value.</param>
|
||||||
|
@ -486,14 +494,14 @@ namespace StardewModdingAPI.Tests.Core
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Get a randomised basic manifest.</summary>
|
/// <summary>Get a randomized basic manifest.</summary>
|
||||||
/// <param name="uniqueID">The mod's name and unique ID.</param>
|
/// <param name="uniqueID">The mod's name and unique ID.</param>
|
||||||
private Mock<IModMetadata> GetMetadata(string uniqueID)
|
private Mock<IModMetadata> GetMetadata(string uniqueID)
|
||||||
{
|
{
|
||||||
return this.GetMetadata(this.GetManifest(uniqueID, "1.0"));
|
return this.GetMetadata(this.GetManifest(uniqueID, "1.0"));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Get a randomised basic manifest.</summary>
|
/// <summary>Get a randomized basic manifest.</summary>
|
||||||
/// <param name="uniqueID">The mod's name and unique ID.</param>
|
/// <param name="uniqueID">The mod's name and unique ID.</param>
|
||||||
/// <param name="dependencies">The dependencies this mod requires.</param>
|
/// <param name="dependencies">The dependencies this mod requires.</param>
|
||||||
/// <param name="allowStatusChange">Whether the code being tested is allowed to change the mod status.</param>
|
/// <param name="allowStatusChange">Whether the code being tested is allowed to change the mod status.</param>
|
||||||
|
@ -503,7 +511,7 @@ namespace StardewModdingAPI.Tests.Core
|
||||||
return this.GetMetadata(manifest, allowStatusChange);
|
return this.GetMetadata(manifest, allowStatusChange);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Get a randomised basic manifest.</summary>
|
/// <summary>Get a randomized basic manifest.</summary>
|
||||||
/// <param name="manifest">The mod manifest.</param>
|
/// <param name="manifest">The mod manifest.</param>
|
||||||
/// <param name="allowStatusChange">Whether the code being tested is allowed to change the mod status.</param>
|
/// <param name="allowStatusChange">Whether the code being tested is allowed to change the mod status.</param>
|
||||||
private Mock<IModMetadata> GetMetadata(IManifest manifest, bool allowStatusChange = false)
|
private Mock<IModMetadata> GetMetadata(IManifest manifest, bool allowStatusChange = false)
|
||||||
|
|
|
@ -2,10 +2,11 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using StardewModdingAPI;
|
||||||
using StardewModdingAPI.Framework.ModHelpers;
|
using StardewModdingAPI.Framework.ModHelpers;
|
||||||
using StardewValley;
|
using StardewValley;
|
||||||
|
|
||||||
namespace StardewModdingAPI.Tests.Core
|
namespace SMAPI.Tests.Core
|
||||||
{
|
{
|
||||||
/// <summary>Unit tests for <see cref="TranslationHelper"/> and <see cref="Translation"/>.</summary>
|
/// <summary>Unit tests for <see cref="TranslationHelper"/> and <see cref="Translation"/>.</summary>
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
|
@ -31,7 +32,7 @@ namespace StardewModdingAPI.Tests.Core
|
||||||
var data = new Dictionary<string, IDictionary<string, string>>();
|
var data = new Dictionary<string, IDictionary<string, string>>();
|
||||||
|
|
||||||
// act
|
// act
|
||||||
ITranslationHelper helper = new TranslationHelper("ModID", "ModName", "en", LocalizedContentManager.LanguageCode.en).SetTranslations(data);
|
ITranslationHelper helper = new TranslationHelper("ModID", "en", LocalizedContentManager.LanguageCode.en).SetTranslations(data);
|
||||||
Translation translation = helper.Get("key");
|
Translation translation = helper.Get("key");
|
||||||
Translation[] translationList = helper.GetTranslations()?.ToArray();
|
Translation[] translationList = helper.GetTranslations()?.ToArray();
|
||||||
|
|
||||||
|
@ -54,7 +55,7 @@ namespace StardewModdingAPI.Tests.Core
|
||||||
|
|
||||||
// act
|
// act
|
||||||
var actual = new Dictionary<string, Translation[]>();
|
var actual = new Dictionary<string, Translation[]>();
|
||||||
TranslationHelper helper = new TranslationHelper("ModID", "ModName", "en", LocalizedContentManager.LanguageCode.en).SetTranslations(data);
|
TranslationHelper helper = new TranslationHelper("ModID", "en", LocalizedContentManager.LanguageCode.en).SetTranslations(data);
|
||||||
foreach (string locale in expected.Keys)
|
foreach (string locale in expected.Keys)
|
||||||
{
|
{
|
||||||
this.AssertSetLocale(helper, locale, LocalizedContentManager.LanguageCode.en);
|
this.AssertSetLocale(helper, locale, LocalizedContentManager.LanguageCode.en);
|
||||||
|
@ -78,7 +79,7 @@ namespace StardewModdingAPI.Tests.Core
|
||||||
|
|
||||||
// act
|
// act
|
||||||
var actual = new Dictionary<string, Translation[]>();
|
var actual = new Dictionary<string, Translation[]>();
|
||||||
TranslationHelper helper = new TranslationHelper("ModID", "ModName", "en", LocalizedContentManager.LanguageCode.en).SetTranslations(data);
|
TranslationHelper helper = new TranslationHelper("ModID", "en", LocalizedContentManager.LanguageCode.en).SetTranslations(data);
|
||||||
foreach (string locale in expected.Keys)
|
foreach (string locale in expected.Keys)
|
||||||
{
|
{
|
||||||
this.AssertSetLocale(helper, locale, LocalizedContentManager.LanguageCode.en);
|
this.AssertSetLocale(helper, locale, LocalizedContentManager.LanguageCode.en);
|
||||||
|
@ -108,14 +109,14 @@ namespace StardewModdingAPI.Tests.Core
|
||||||
[TestCase(" boop ", ExpectedResult = true)]
|
[TestCase(" boop ", ExpectedResult = true)]
|
||||||
public bool Translation_HasValue(string text)
|
public bool Translation_HasValue(string text)
|
||||||
{
|
{
|
||||||
return new Translation("ModName", "pt-BR", "key", text).HasValue();
|
return new Translation("pt-BR", "key", text).HasValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test(Description = "Assert that the translation's ToString method returns the expected text for various inputs.")]
|
[Test(Description = "Assert that the translation's ToString method returns the expected text for various inputs.")]
|
||||||
public void Translation_ToString([ValueSource(nameof(TranslationTests.Samples))] string text)
|
public void Translation_ToString([ValueSource(nameof(TranslationTests.Samples))] string text)
|
||||||
{
|
{
|
||||||
// act
|
// act
|
||||||
Translation translation = new Translation("ModName", "pt-BR", "key", text);
|
Translation translation = new Translation("pt-BR", "key", text);
|
||||||
|
|
||||||
// assert
|
// assert
|
||||||
if (translation.HasValue())
|
if (translation.HasValue())
|
||||||
|
@ -128,7 +129,7 @@ namespace StardewModdingAPI.Tests.Core
|
||||||
public void Translation_ImplicitStringConversion([ValueSource(nameof(TranslationTests.Samples))] string text)
|
public void Translation_ImplicitStringConversion([ValueSource(nameof(TranslationTests.Samples))] string text)
|
||||||
{
|
{
|
||||||
// act
|
// act
|
||||||
Translation translation = new Translation("ModName", "pt-BR", "key", text);
|
Translation translation = new Translation("pt-BR", "key", text);
|
||||||
|
|
||||||
// assert
|
// assert
|
||||||
if (translation.HasValue())
|
if (translation.HasValue())
|
||||||
|
@ -141,7 +142,7 @@ namespace StardewModdingAPI.Tests.Core
|
||||||
public void Translation_UsePlaceholder([Values(true, false)] bool value, [ValueSource(nameof(TranslationTests.Samples))] string text)
|
public void Translation_UsePlaceholder([Values(true, false)] bool value, [ValueSource(nameof(TranslationTests.Samples))] string text)
|
||||||
{
|
{
|
||||||
// act
|
// act
|
||||||
Translation translation = new Translation("ModName", "pt-BR", "key", text).UsePlaceholder(value);
|
Translation translation = new Translation("pt-BR", "key", text).UsePlaceholder(value);
|
||||||
|
|
||||||
// assert
|
// assert
|
||||||
if (translation.HasValue())
|
if (translation.HasValue())
|
||||||
|
@ -152,24 +153,11 @@ namespace StardewModdingAPI.Tests.Core
|
||||||
Assert.AreEqual(this.GetPlaceholderText("key"), translation.ToString(), "The translation returned an unexpected value given a null or empty input with the placeholder enabled.");
|
Assert.AreEqual(this.GetPlaceholderText("key"), translation.ToString(), "The translation returned an unexpected value given a null or empty input with the placeholder enabled.");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test(Description = "Assert that the translation's Assert method throws the expected exception.")]
|
|
||||||
public void Translation_Assert([ValueSource(nameof(TranslationTests.Samples))] string text)
|
|
||||||
{
|
|
||||||
// act
|
|
||||||
Translation translation = new Translation("ModName", "pt-BR", "key", text);
|
|
||||||
|
|
||||||
// assert
|
|
||||||
if (translation.HasValue())
|
|
||||||
Assert.That(() => translation.Assert(), Throws.Nothing, "The assert unexpected threw an exception for a valid input.");
|
|
||||||
else
|
|
||||||
Assert.That(() => translation.Assert(), Throws.Exception.TypeOf<KeyNotFoundException>(), "The assert didn't throw an exception for invalid input.");
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test(Description = "Assert that the translation returns the expected text after setting the default.")]
|
[Test(Description = "Assert that the translation returns the expected text after setting the default.")]
|
||||||
public void Translation_Default([ValueSource(nameof(TranslationTests.Samples))] string text, [ValueSource(nameof(TranslationTests.Samples))] string @default)
|
public void Translation_Default([ValueSource(nameof(TranslationTests.Samples))] string text, [ValueSource(nameof(TranslationTests.Samples))] string @default)
|
||||||
{
|
{
|
||||||
// act
|
// act
|
||||||
Translation translation = new Translation("ModName", "pt-BR", "key", text).Default(@default);
|
Translation translation = new Translation("pt-BR", "key", text).Default(@default);
|
||||||
|
|
||||||
// assert
|
// assert
|
||||||
if (!string.IsNullOrEmpty(text))
|
if (!string.IsNullOrEmpty(text))
|
||||||
|
@ -194,7 +182,7 @@ namespace StardewModdingAPI.Tests.Core
|
||||||
string expected = $"{start} tokens are properly replaced (including {middle} {middle}) {end}";
|
string expected = $"{start} tokens are properly replaced (including {middle} {middle}) {end}";
|
||||||
|
|
||||||
// act
|
// act
|
||||||
Translation translation = new Translation("ModName", "pt-BR", "key", input);
|
Translation translation = new Translation("pt-BR", "key", input);
|
||||||
switch (structure)
|
switch (structure)
|
||||||
{
|
{
|
||||||
case "anonymous object":
|
case "anonymous object":
|
||||||
|
@ -235,7 +223,7 @@ namespace StardewModdingAPI.Tests.Core
|
||||||
string value = Guid.NewGuid().ToString("N");
|
string value = Guid.NewGuid().ToString("N");
|
||||||
|
|
||||||
// act
|
// act
|
||||||
Translation translation = new Translation("ModName", "pt-BR", "key", text).Tokens(new Dictionary<string, object> { [key] = value });
|
Translation translation = new Translation("pt-BR", "key", text).Tokens(new Dictionary<string, object> { [key] = value });
|
||||||
|
|
||||||
// assert
|
// assert
|
||||||
Assert.AreEqual(value, translation.ToString(), "The translation returned an unexpected value given a valid base text.");
|
Assert.AreEqual(value, translation.ToString(), "The translation returned an unexpected value given a valid base text.");
|
||||||
|
@ -245,13 +233,13 @@ namespace StardewModdingAPI.Tests.Core
|
||||||
[TestCase("{{value}}", "value")]
|
[TestCase("{{value}}", "value")]
|
||||||
[TestCase("{{VaLuE}}", "vAlUe")]
|
[TestCase("{{VaLuE}}", "vAlUe")]
|
||||||
[TestCase("{{VaLuE }}", " vAlUe")]
|
[TestCase("{{VaLuE }}", " vAlUe")]
|
||||||
public void Translation_Tokens_KeysAreNormalised(string text, string key)
|
public void Translation_Tokens_KeysAreNormalized(string text, string key)
|
||||||
{
|
{
|
||||||
// arrange
|
// arrange
|
||||||
string value = Guid.NewGuid().ToString("N");
|
string value = Guid.NewGuid().ToString("N");
|
||||||
|
|
||||||
// act
|
// act
|
||||||
Translation translation = new Translation("ModName", "pt-BR", "key", text).Tokens(new Dictionary<string, object> { [key] = value });
|
Translation translation = new Translation("pt-BR", "key", text).Tokens(new Dictionary<string, object> { [key] = value });
|
||||||
|
|
||||||
// assert
|
// assert
|
||||||
Assert.AreEqual(value, translation.ToString(), "The translation returned an unexpected value given a valid base text.");
|
Assert.AreEqual(value, translation.ToString(), "The translation returned an unexpected value given a valid base text.");
|
||||||
|
@ -302,19 +290,19 @@ namespace StardewModdingAPI.Tests.Core
|
||||||
{
|
{
|
||||||
["default"] = new[]
|
["default"] = new[]
|
||||||
{
|
{
|
||||||
new Translation(string.Empty, "default", "key A", "default A"),
|
new Translation("default", "key A", "default A"),
|
||||||
new Translation(string.Empty, "default", "key C", "default C")
|
new Translation("default", "key C", "default C")
|
||||||
},
|
},
|
||||||
["en"] = new[]
|
["en"] = new[]
|
||||||
{
|
{
|
||||||
new Translation(string.Empty, "en", "key A", "en A"),
|
new Translation("en", "key A", "en A"),
|
||||||
new Translation(string.Empty, "en", "key B", "en B"),
|
new Translation("en", "key B", "en B"),
|
||||||
new Translation(string.Empty, "en", "key C", "default C")
|
new Translation("en", "key C", "default C")
|
||||||
},
|
},
|
||||||
["zzz"] = new[]
|
["zzz"] = new[]
|
||||||
{
|
{
|
||||||
new Translation(string.Empty, "zzz", "key A", "zzz A"),
|
new Translation("zzz", "key A", "zzz A"),
|
||||||
new Translation(string.Empty, "zzz", "key C", "default C")
|
new Translation("zzz", "key C", "default C")
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
expected["en-us"] = expected["en"].ToArray();
|
expected["en-us"] = expected["en"].ToArray();
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
using System.Reflection;
|
|
||||||
|
|
||||||
[assembly: AssemblyTitle("SMAPI.Tests")]
|
|
||||||
[assembly: AssemblyDescription("")]
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<AssemblyName>SMAPI.Tests</AssemblyName>
|
||||||
|
<RootNamespace>SMAPI.Tests</RootNamespace>
|
||||||
|
<TargetFramework>net45</TargetFramework>
|
||||||
|
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||||
|
<LangVersion>latest</LangVersion>
|
||||||
|
<PlatformTarget>x86</PlatformTarget>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\SMAPI.Toolkit.CoreInterfaces\SMAPI.Toolkit.CoreInterfaces.csproj" />
|
||||||
|
<ProjectReference Include="..\SMAPI.Toolkit\SMAPI.Toolkit.csproj" />
|
||||||
|
<ProjectReference Include="..\SMAPI\SMAPI.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Moq" Version="4.13.1" />
|
||||||
|
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||||
|
<PackageReference Include="NUnit" Version="3.12.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Reference Include="$(GameExecutableName)">
|
||||||
|
<HintPath>$(GamePath)\$(GameExecutableName).exe</HintPath>
|
||||||
|
<Private>True</Private>
|
||||||
|
</Reference>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<Import Project="..\..\build\common.targets" />
|
||||||
|
|
||||||
|
</Project>
|
|
@ -1,6 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace StardewModdingAPI.Tests
|
namespace SMAPI.Tests
|
||||||
{
|
{
|
||||||
/// <summary>Provides sample values for unit testing.</summary>
|
/// <summary>Provides sample values for unit testing.</summary>
|
||||||
internal static class Sample
|
internal static class Sample
|
||||||
|
|
|
@ -1,37 +0,0 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<RootNamespace>StardewModdingAPI.Tests</RootNamespace>
|
|
||||||
<AssemblyName>StardewModdingAPI.Tests</AssemblyName>
|
|
||||||
<TargetFramework>net45</TargetFramework>
|
|
||||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
|
||||||
<LangVersion>latest</LangVersion>
|
|
||||||
<PlatformTarget>x86</PlatformTarget>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\SMAPI.Toolkit.CoreInterfaces\StardewModdingAPI.Toolkit.CoreInterfaces.csproj" />
|
|
||||||
<ProjectReference Include="..\SMAPI.Toolkit\StardewModdingAPI.Toolkit.csproj" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="Castle.Core" Version="4.3.1" />
|
|
||||||
<PackageReference Include="Moq" Version="4.10.0" />
|
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
|
|
||||||
<PackageReference Include="NUnit" Version="3.11.0" />
|
|
||||||
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="4.5.2" />
|
|
||||||
<PackageReference Include="System.Threading.Tasks.Extensions" Version="4.5.1" />
|
|
||||||
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<Compile Include="..\..\build\GlobalAssemblyInfo.cs">
|
|
||||||
<Link>Properties\GlobalAssemblyInfo.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
|
@ -1,7 +1,7 @@
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using StardewModdingAPI.Toolkit.Utilities;
|
using StardewModdingAPI.Toolkit.Utilities;
|
||||||
|
|
||||||
namespace StardewModdingAPI.Tests.Toolkit
|
namespace SMAPI.Tests.Toolkit
|
||||||
{
|
{
|
||||||
/// <summary>Unit tests for <see cref="PathUtilities"/>.</summary>
|
/// <summary>Unit tests for <see cref="PathUtilities"/>.</summary>
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
|
@ -25,7 +25,7 @@ namespace StardewModdingAPI.Tests.Toolkit
|
||||||
return string.Join("|", PathUtilities.GetSegments(path));
|
return string.Join("|", PathUtilities.GetSegments(path));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test(Description = "Assert that NormalisePathSeparators returns the expected values.")]
|
[Test(Description = "Assert that NormalizePathSeparators returns the expected values.")]
|
||||||
#if SMAPI_FOR_WINDOWS
|
#if SMAPI_FOR_WINDOWS
|
||||||
[TestCase("", ExpectedResult = "")]
|
[TestCase("", ExpectedResult = "")]
|
||||||
[TestCase("/", ExpectedResult = "")]
|
[TestCase("/", ExpectedResult = "")]
|
||||||
|
@ -47,9 +47,9 @@ namespace StardewModdingAPI.Tests.Toolkit
|
||||||
[TestCase("C:/boop", ExpectedResult = "C:/boop")]
|
[TestCase("C:/boop", ExpectedResult = "C:/boop")]
|
||||||
[TestCase(@"C:\usr\bin//.././boop.exe", ExpectedResult = "C:/usr/bin/.././boop.exe")]
|
[TestCase(@"C:\usr\bin//.././boop.exe", ExpectedResult = "C:/usr/bin/.././boop.exe")]
|
||||||
#endif
|
#endif
|
||||||
public string NormalisePathSeparators(string path)
|
public string NormalizePathSeparators(string path)
|
||||||
{
|
{
|
||||||
return PathUtilities.NormalisePathSeparators(path);
|
return PathUtilities.NormalizePathSeparators(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test(Description = "Assert that GetRelativePath returns the expected values.")]
|
[Test(Description = "Assert that GetRelativePath returns the expected values.")]
|
||||||
|
|
|
@ -6,7 +6,7 @@ using System.Text.RegularExpressions;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using StardewModdingAPI.Utilities;
|
using StardewModdingAPI.Utilities;
|
||||||
|
|
||||||
namespace StardewModdingAPI.Tests.Utilities
|
namespace SMAPI.Tests.Utilities
|
||||||
{
|
{
|
||||||
/// <summary>Unit tests for <see cref="SDate"/>.</summary>
|
/// <summary>Unit tests for <see cref="SDate"/>.</summary>
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
|
@ -159,7 +159,7 @@ namespace StardewModdingAPI.Tests.Utilities
|
||||||
[TestCase("15 summer Y1", -28, ExpectedResult = "15 spring Y1")] // negative season transition
|
[TestCase("15 summer Y1", -28, ExpectedResult = "15 spring Y1")] // negative season transition
|
||||||
[TestCase("15 summer Y2", -28 * 4, ExpectedResult = "15 summer Y1")] // negative year transition
|
[TestCase("15 summer Y2", -28 * 4, ExpectedResult = "15 summer Y1")] // negative year transition
|
||||||
[TestCase("01 spring Y3", -(28 * 7 + 17), ExpectedResult = "12 spring Y1")] // negative year transition
|
[TestCase("01 spring Y3", -(28 * 7 + 17), ExpectedResult = "12 spring Y1")] // negative year transition
|
||||||
[TestCase("06 fall Y2", 50, ExpectedResult = "28 winter Y3")] // test for zero-index errors
|
[TestCase("06 fall Y2", 50, ExpectedResult = "28 winter Y2")] // test for zero-index errors
|
||||||
[TestCase("06 fall Y2", 51, ExpectedResult = "01 spring Y3")] // test for zero-index errors
|
[TestCase("06 fall Y2", 51, ExpectedResult = "01 spring Y3")] // test for zero-index errors
|
||||||
public string AddDays(string dateStr, int addDays)
|
public string AddDays(string dateStr, int addDays)
|
||||||
{
|
{
|
||||||
|
|
|
@ -2,9 +2,10 @@ using System;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using StardewModdingAPI;
|
||||||
using StardewModdingAPI.Framework;
|
using StardewModdingAPI.Framework;
|
||||||
|
|
||||||
namespace StardewModdingAPI.Tests.Utilities
|
namespace SMAPI.Tests.Utilities
|
||||||
{
|
{
|
||||||
/// <summary>Unit tests for <see cref="SemanticVersion"/>.</summary>
|
/// <summary>Unit tests for <see cref="SemanticVersion"/>.</summary>
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
|
@ -17,55 +18,61 @@ namespace StardewModdingAPI.Tests.Utilities
|
||||||
** Constructor
|
** Constructor
|
||||||
****/
|
****/
|
||||||
[Test(Description = "Assert that the constructor sets the expected values for all valid versions when constructed from a string.")]
|
[Test(Description = "Assert that the constructor sets the expected values for all valid versions when constructed from a string.")]
|
||||||
[TestCase("1.0", ExpectedResult = "1.0")]
|
[TestCase("1.0", ExpectedResult = "1.0.0")]
|
||||||
[TestCase("1.0.0", ExpectedResult = "1.0")]
|
[TestCase("1.0.0", ExpectedResult = "1.0.0")]
|
||||||
[TestCase("3000.4000.5000", ExpectedResult = "3000.4000.5000")]
|
[TestCase("3000.4000.5000", ExpectedResult = "3000.4000.5000")]
|
||||||
[TestCase("1.2-some-tag.4", ExpectedResult = "1.2-some-tag.4")]
|
[TestCase("1.2-some-tag.4", ExpectedResult = "1.2.0-some-tag.4")]
|
||||||
[TestCase("1.2.3-some-tag.4", ExpectedResult = "1.2.3-some-tag.4")]
|
[TestCase("1.2.3-some-tag.4", ExpectedResult = "1.2.3-some-tag.4")]
|
||||||
[TestCase("1.2.3-SoME-tAg.4", ExpectedResult = "1.2.3-SoME-tAg.4")]
|
[TestCase("1.2.3-SoME-tAg.4", ExpectedResult = "1.2.3-SoME-tAg.4")]
|
||||||
[TestCase("1.2.3-some-tag.4 ", ExpectedResult = "1.2.3-some-tag.4")]
|
[TestCase("1.2.3-some-tag.4 ", ExpectedResult = "1.2.3-some-tag.4")]
|
||||||
|
[TestCase("1.2.3-some-tag.4+build.004", ExpectedResult = "1.2.3-some-tag.4+build.004")]
|
||||||
|
[TestCase("1.2+3.4.5-build.004", ExpectedResult = "1.2.0+3.4.5-build.004")]
|
||||||
public string Constructor_FromString(string input)
|
public string Constructor_FromString(string input)
|
||||||
{
|
{
|
||||||
return new SemanticVersion(input).ToString();
|
return new SemanticVersion(input).ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test(Description = "Assert that the constructor sets the expected values for all valid versions when constructed from the individual numbers.")]
|
[Test(Description = "Assert that the constructor sets the expected values for all valid versions when constructed from the individual numbers.")]
|
||||||
[TestCase(1, 0, 0, null, ExpectedResult = "1.0")]
|
[TestCase(1, 0, 0, null, null, ExpectedResult = "1.0.0")]
|
||||||
[TestCase(3000, 4000, 5000, null, ExpectedResult = "3000.4000.5000")]
|
[TestCase(3000, 4000, 5000, null, null, ExpectedResult = "3000.4000.5000")]
|
||||||
[TestCase(1, 2, 3, "", ExpectedResult = "1.2.3")]
|
[TestCase(1, 2, 3, "", null, ExpectedResult = "1.2.3")]
|
||||||
[TestCase(1, 2, 3, " ", ExpectedResult = "1.2.3")]
|
[TestCase(1, 2, 3, " ", null, ExpectedResult = "1.2.3")]
|
||||||
[TestCase(1, 2, 3, "0", ExpectedResult = "1.2.3-0")]
|
[TestCase(1, 2, 3, "0", null, ExpectedResult = "1.2.3-0")]
|
||||||
[TestCase(1, 2, 3, "some-tag.4", ExpectedResult = "1.2.3-some-tag.4")]
|
[TestCase(1, 2, 3, "some-tag.4", null, ExpectedResult = "1.2.3-some-tag.4")]
|
||||||
[TestCase(1, 2, 3, "sOMe-TaG.4", ExpectedResult = "1.2.3-sOMe-TaG.4")]
|
[TestCase(1, 2, 3, "sOMe-TaG.4", null, ExpectedResult = "1.2.3-sOMe-TaG.4")]
|
||||||
[TestCase(1, 2, 3, "some-tag.4 ", ExpectedResult = "1.2.3-some-tag.4")]
|
[TestCase(1, 2, 3, "some-tag.4 ", null, ExpectedResult = "1.2.3-some-tag.4")]
|
||||||
public string Constructor_FromParts(int major, int minor, int patch, string tag)
|
[TestCase(1, 2, 3, "some-tag.4 ", "build.004", ExpectedResult = "1.2.3-some-tag.4+build.004")]
|
||||||
|
[TestCase(1, 2, 0, null, "3.4.5-build.004", ExpectedResult = "1.2.0+3.4.5-build.004")]
|
||||||
|
public string Constructor_FromParts(int major, int minor, int patch, string prerelease, string build)
|
||||||
{
|
{
|
||||||
// act
|
// act
|
||||||
ISemanticVersion version = new SemanticVersion(major, minor, patch, tag);
|
ISemanticVersion version = new SemanticVersion(major, minor, patch, prerelease, build);
|
||||||
|
|
||||||
// assert
|
// assert
|
||||||
Assert.AreEqual(major, version.MajorVersion, "The major version doesn't match the given value.");
|
Assert.AreEqual(major, version.MajorVersion, "The major version doesn't match the given value.");
|
||||||
Assert.AreEqual(minor, version.MinorVersion, "The minor version doesn't match the given value.");
|
Assert.AreEqual(minor, version.MinorVersion, "The minor version doesn't match the given value.");
|
||||||
Assert.AreEqual(patch, version.PatchVersion, "The patch version doesn't match the given value.");
|
Assert.AreEqual(patch, version.PatchVersion, "The patch version doesn't match the given value.");
|
||||||
Assert.AreEqual(string.IsNullOrWhiteSpace(tag) ? null : tag.Trim(), version.PrereleaseTag, "The tag doesn't match the given value.");
|
Assert.AreEqual(string.IsNullOrWhiteSpace(prerelease) ? null : prerelease.Trim(), version.PrereleaseTag, "The prerelease tag doesn't match the given value.");
|
||||||
|
Assert.AreEqual(string.IsNullOrWhiteSpace(build) ? null : build.Trim(), version.BuildMetadata, "The build metadata doesn't match the given value.");
|
||||||
return version.ToString();
|
return version.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test(Description = "Assert that the constructor throws the expected exception for invalid versions when constructed from the individual numbers.")]
|
[Test(Description = "Assert that the constructor throws the expected exception for invalid versions when constructed from the individual numbers.")]
|
||||||
[TestCase(0, 0, 0, null)]
|
[TestCase(0, 0, 0, null, null)]
|
||||||
[TestCase(-1, 0, 0, null)]
|
[TestCase(-1, 0, 0, null, null)]
|
||||||
[TestCase(0, -1, 0, null)]
|
[TestCase(0, -1, 0, null, null)]
|
||||||
[TestCase(0, 0, -1, null)]
|
[TestCase(0, 0, -1, null, null)]
|
||||||
[TestCase(1, 0, 0, "-tag")]
|
[TestCase(1, 0, 0, "-tag", null)]
|
||||||
[TestCase(1, 0, 0, "tag spaces")]
|
[TestCase(1, 0, 0, "tag spaces", null)]
|
||||||
[TestCase(1, 0, 0, "tag~")]
|
[TestCase(1, 0, 0, "tag~", null)]
|
||||||
public void Constructor_FromParts_WithInvalidValues(int major, int minor, int patch, string tag)
|
[TestCase(1, 0, 0, null, "build~")]
|
||||||
|
public void Constructor_FromParts_WithInvalidValues(int major, int minor, int patch, string prerelease, string build)
|
||||||
{
|
{
|
||||||
this.AssertAndLogException<FormatException>(() => new SemanticVersion(major, minor, patch, tag));
|
this.AssertAndLogException<FormatException>(() => new SemanticVersion(major, minor, patch, prerelease, build));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test(Description = "Assert that the constructor sets the expected values for all valid versions when constructed from an assembly version.")]
|
[Test(Description = "Assert that the constructor sets the expected values for all valid versions when constructed from an assembly version.")]
|
||||||
[TestCase(1, 0, 0, ExpectedResult = "1.0")]
|
[TestCase(1, 0, 0, ExpectedResult = "1.0.0")]
|
||||||
[TestCase(1, 2, 3, ExpectedResult = "1.2.3")]
|
[TestCase(1, 2, 3, ExpectedResult = "1.2.3")]
|
||||||
[TestCase(3000, 4000, 5000, ExpectedResult = "3000.4000.5000")]
|
[TestCase(3000, 4000, 5000, ExpectedResult = "3000.4000.5000")]
|
||||||
public string Constructor_FromAssemblyVersion(int major, int minor, int patch)
|
public string Constructor_FromAssemblyVersion(int major, int minor, int patch)
|
||||||
|
@ -97,6 +104,7 @@ namespace StardewModdingAPI.Tests.Utilities
|
||||||
[TestCase("1.2.3--some-tag")]
|
[TestCase("1.2.3--some-tag")]
|
||||||
[TestCase("1.2.3-some-tag...")]
|
[TestCase("1.2.3-some-tag...")]
|
||||||
[TestCase("1.2.3-some-tag...4")]
|
[TestCase("1.2.3-some-tag...4")]
|
||||||
|
[TestCase("1.2.3-some-tag.4+build...4")]
|
||||||
[TestCase("apple")]
|
[TestCase("apple")]
|
||||||
[TestCase("-apple")]
|
[TestCase("-apple")]
|
||||||
[TestCase("-5")]
|
[TestCase("-5")]
|
||||||
|
@ -118,6 +126,8 @@ namespace StardewModdingAPI.Tests.Utilities
|
||||||
[TestCase("1.0-beta", "1.0-beta", ExpectedResult = 0)]
|
[TestCase("1.0-beta", "1.0-beta", ExpectedResult = 0)]
|
||||||
[TestCase("1.0-beta.10", "1.0-beta.10", ExpectedResult = 0)]
|
[TestCase("1.0-beta.10", "1.0-beta.10", ExpectedResult = 0)]
|
||||||
[TestCase("1.0-beta", "1.0-beta ", ExpectedResult = 0)]
|
[TestCase("1.0-beta", "1.0-beta ", ExpectedResult = 0)]
|
||||||
|
[TestCase("1.0-beta+build.001", "1.0-beta+build.001", ExpectedResult = 0)]
|
||||||
|
[TestCase("1.0-beta+build.001", "1.0-beta+build.006", ExpectedResult = 0)] // build metadata must not affect precedence
|
||||||
|
|
||||||
// less than
|
// less than
|
||||||
[TestCase("0.5.7", "0.5.8", ExpectedResult = -1)]
|
[TestCase("0.5.7", "0.5.8", ExpectedResult = -1)]
|
||||||
|
@ -155,6 +165,8 @@ namespace StardewModdingAPI.Tests.Utilities
|
||||||
[TestCase("1.0-beta", "1.0-beta", ExpectedResult = false)]
|
[TestCase("1.0-beta", "1.0-beta", ExpectedResult = false)]
|
||||||
[TestCase("1.0-beta.10", "1.0-beta.10", ExpectedResult = false)]
|
[TestCase("1.0-beta.10", "1.0-beta.10", ExpectedResult = false)]
|
||||||
[TestCase("1.0-beta", "1.0-beta ", ExpectedResult = false)]
|
[TestCase("1.0-beta", "1.0-beta ", ExpectedResult = false)]
|
||||||
|
[TestCase("1.0-beta+build.001", "1.0-beta+build.001", ExpectedResult = false)] // build metadata must not affect precedence
|
||||||
|
[TestCase("1.0-beta+build.001", "1.0-beta+build.006", ExpectedResult = false)] // build metadata must not affect precedence
|
||||||
|
|
||||||
// less than
|
// less than
|
||||||
[TestCase("0.5.7", "0.5.8", ExpectedResult = true)]
|
[TestCase("0.5.7", "0.5.8", ExpectedResult = true)]
|
||||||
|
@ -191,6 +203,8 @@ namespace StardewModdingAPI.Tests.Utilities
|
||||||
[TestCase("1.0-beta", "1.0-beta", ExpectedResult = false)]
|
[TestCase("1.0-beta", "1.0-beta", ExpectedResult = false)]
|
||||||
[TestCase("1.0-beta.10", "1.0-beta.10", ExpectedResult = false)]
|
[TestCase("1.0-beta.10", "1.0-beta.10", ExpectedResult = false)]
|
||||||
[TestCase("1.0-beta", "1.0-beta ", ExpectedResult = false)]
|
[TestCase("1.0-beta", "1.0-beta ", ExpectedResult = false)]
|
||||||
|
[TestCase("1.0-beta+build.001", "1.0-beta+build.001", ExpectedResult = false)] // build metadata must not affect precedence
|
||||||
|
[TestCase("1.0-beta+build.001", "1.0-beta+build.006", ExpectedResult = false)] // build metadata must not affect precedence
|
||||||
|
|
||||||
// less than
|
// less than
|
||||||
[TestCase("0.5.7", "0.5.8", ExpectedResult = false)]
|
[TestCase("0.5.7", "0.5.8", ExpectedResult = false)]
|
||||||
|
@ -243,19 +257,19 @@ namespace StardewModdingAPI.Tests.Utilities
|
||||||
}
|
}
|
||||||
|
|
||||||
/****
|
/****
|
||||||
** Serialisable
|
** Serializable
|
||||||
****/
|
****/
|
||||||
[Test(Description = "Assert that SemanticVersion can be round-tripped through JSON with no special configuration.")]
|
[Test(Description = "Assert that SemanticVersion can be round-tripped through JSON with no special configuration.")]
|
||||||
[TestCase("1.0")]
|
[TestCase("1.0.0")]
|
||||||
public void Serialisable(string versionStr)
|
public void Serializable(string versionStr)
|
||||||
{
|
{
|
||||||
// act
|
// act
|
||||||
string json = JsonConvert.SerializeObject(new SemanticVersion(versionStr));
|
string json = JsonConvert.SerializeObject(new SemanticVersion(versionStr));
|
||||||
SemanticVersion after = JsonConvert.DeserializeObject<SemanticVersion>(json);
|
SemanticVersion after = JsonConvert.DeserializeObject<SemanticVersion>(json);
|
||||||
|
|
||||||
// assert
|
// assert
|
||||||
Assert.IsNotNull(after, "The semantic version after deserialisation is unexpectedly null.");
|
Assert.IsNotNull(after, "The semantic version after deserialization is unexpectedly null.");
|
||||||
Assert.AreEqual(versionStr, after.ToString(), "The semantic version after deserialisation doesn't match the input version.");
|
Assert.AreEqual(versionStr, after.ToString(), "The semantic version after deserialization doesn't match the input version.");
|
||||||
}
|
}
|
||||||
|
|
||||||
/****
|
/****
|
||||||
|
@ -278,6 +292,8 @@ namespace StardewModdingAPI.Tests.Utilities
|
||||||
[TestCase("1.11")]
|
[TestCase("1.11")]
|
||||||
[TestCase("1.2")]
|
[TestCase("1.2")]
|
||||||
[TestCase("1.2.15")]
|
[TestCase("1.2.15")]
|
||||||
|
[TestCase("1.4.0.1")]
|
||||||
|
[TestCase("1.4.0.6")]
|
||||||
public void GameVersion(string versionStr)
|
public void GameVersion(string versionStr)
|
||||||
{
|
{
|
||||||
// act
|
// act
|
||||||
|
@ -285,7 +301,6 @@ namespace StardewModdingAPI.Tests.Utilities
|
||||||
|
|
||||||
// assert
|
// assert
|
||||||
Assert.AreEqual(versionStr, version.ToString(), "The game version did not round-trip to the same value.");
|
Assert.AreEqual(versionStr, version.ToString(), "The game version did not round-trip to the same value.");
|
||||||
Assert.IsTrue(version.IsOlderThan(new SemanticVersion("1.2.30")), "The game version should be considered older than the later semantic versions.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -17,20 +17,17 @@ namespace StardewModdingAPI
|
||||||
/// <summary>The patch version for backwards-compatible bug fixes.</summary>
|
/// <summary>The patch version for backwards-compatible bug fixes.</summary>
|
||||||
int PatchVersion { get; }
|
int PatchVersion { get; }
|
||||||
|
|
||||||
#if !SMAPI_3_0_STRICT
|
|
||||||
/// <summary>An optional build tag.</summary>
|
|
||||||
[Obsolete("Use " + nameof(ISemanticVersion.PrereleaseTag) + " instead")]
|
|
||||||
string Build { get; }
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/// <summary>An optional prerelease tag.</summary>
|
/// <summary>An optional prerelease tag.</summary>
|
||||||
string PrereleaseTag { get; }
|
string PrereleaseTag { get; }
|
||||||
|
|
||||||
|
/// <summary>Optional build metadata. This is ignored when determining version precedence.</summary>
|
||||||
|
string BuildMetadata { get; }
|
||||||
|
|
||||||
|
|
||||||
/*********
|
/*********
|
||||||
** Accessors
|
** Accessors
|
||||||
*********/
|
*********/
|
||||||
/// <summary>Whether this is a pre-release version.</summary>
|
/// <summary>Whether this is a prerelease version.</summary>
|
||||||
bool IsPrerelease();
|
bool IsPrerelease();
|
||||||
|
|
||||||
/// <summary>Get whether this version is older than the specified version.</summary>
|
/// <summary>Get whether this version is older than the specified version.</summary>
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
using System.Reflection;
|
|
||||||
|
|
||||||
[assembly: AssemblyTitle("SMAPI.Toolkit.CoreInterfaces")]
|
|
||||||
[assembly: AssemblyDescription("Provides toolkit interfaces which are available to SMAPI mods.")]
|
|
|
@ -1,16 +1,16 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFrameworks>net4.5;netstandard2.0</TargetFrameworks>
|
<AssemblyName>SMAPI.Toolkit.CoreInterfaces</AssemblyName>
|
||||||
<RootNamespace>StardewModdingAPI</RootNamespace>
|
<RootNamespace>StardewModdingAPI</RootNamespace>
|
||||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
<Description>Provides toolkit interfaces which are available to SMAPI mods.</Description>
|
||||||
|
<TargetFrameworks>net4.5;netstandard2.0</TargetFrameworks>
|
||||||
<OutputPath>..\..\bin\$(Configuration)\SMAPI.Toolkit.CoreInterfaces</OutputPath>
|
<OutputPath>..\..\bin\$(Configuration)\SMAPI.Toolkit.CoreInterfaces</OutputPath>
|
||||||
<DocumentationFile>..\..\bin\$(Configuration)\SMAPI.Toolkit.CoreInterfaces\$(TargetFramework)\StardewModdingAPI.Toolkit.CoreInterfaces.xml</DocumentationFile>
|
<DocumentationFile>..\..\bin\$(Configuration)\SMAPI.Toolkit.CoreInterfaces\$(TargetFramework)\SMAPI.Toolkit.CoreInterfaces.xml</DocumentationFile>
|
||||||
<LangVersion>latest</LangVersion>
|
<LangVersion>latest</LangVersion>
|
||||||
|
<PlatformTarget Condition="'$(TargetFramework)' == 'net4.5'">x86</PlatformTarget>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<Import Project="..\..\build\common.targets" />
|
||||||
<Compile Include="..\..\build\GlobalAssemblyInfo.cs" Link="Properties\GlobalAssemblyInfo.cs" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
</Project>
|
|
@ -1,3 +1,5 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
namespace StardewModdingAPI.Toolkit.Framework.Clients.WebApi
|
namespace StardewModdingAPI.Toolkit.Framework.Clients.WebApi
|
||||||
{
|
{
|
||||||
/// <summary>Metadata about a mod.</summary>
|
/// <summary>Metadata about a mod.</summary>
|
||||||
|
@ -9,23 +11,31 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.WebApi
|
||||||
/// <summary>The mod's unique ID (if known).</summary>
|
/// <summary>The mod's unique ID (if known).</summary>
|
||||||
public string ID { get; set; }
|
public string ID { get; set; }
|
||||||
|
|
||||||
/// <summary>The main version.</summary>
|
/// <summary>The update version recommended by the web API based on its version update and mapping rules.</summary>
|
||||||
public ModEntryVersionModel Main { get; set; }
|
public ModEntryVersionModel SuggestedUpdate { get; set; }
|
||||||
|
|
||||||
/// <summary>The latest optional version, if newer than <see cref="Main"/>.</summary>
|
|
||||||
public ModEntryVersionModel Optional { get; set; }
|
|
||||||
|
|
||||||
/// <summary>The latest unofficial version, if newer than <see cref="Main"/> and <see cref="Optional"/>.</summary>
|
|
||||||
public ModEntryVersionModel Unofficial { get; set; }
|
|
||||||
|
|
||||||
/// <summary>The latest unofficial version for the current Stardew Valley or SMAPI beta, if any (see <see cref="HasBetaInfo"/>).</summary>
|
|
||||||
public ModEntryVersionModel UnofficialForBeta { get; set; }
|
|
||||||
|
|
||||||
/// <summary>Optional extended data which isn't needed for update checks.</summary>
|
/// <summary>Optional extended data which isn't needed for update checks.</summary>
|
||||||
public ModExtendedMetadataModel Metadata { get; set; }
|
public ModExtendedMetadataModel Metadata { get; set; }
|
||||||
|
|
||||||
|
/// <summary>The main version.</summary>
|
||||||
|
[Obsolete]
|
||||||
|
public ModEntryVersionModel Main { get; set; }
|
||||||
|
|
||||||
|
/// <summary>The latest optional version, if newer than <see cref="Main"/>.</summary>
|
||||||
|
[Obsolete]
|
||||||
|
public ModEntryVersionModel Optional { get; set; }
|
||||||
|
|
||||||
|
/// <summary>The latest unofficial version, if newer than <see cref="Main"/> and <see cref="Optional"/>.</summary>
|
||||||
|
[Obsolete]
|
||||||
|
public ModEntryVersionModel Unofficial { get; set; }
|
||||||
|
|
||||||
|
/// <summary>The latest unofficial version for the current Stardew Valley or SMAPI beta, if any (see <see cref="HasBetaInfo"/>).</summary>
|
||||||
|
[Obsolete]
|
||||||
|
public ModEntryVersionModel UnofficialForBeta { get; set; }
|
||||||
|
|
||||||
/// <summary>Whether a Stardew Valley or SMAPI beta which affects mod compatibility is in progress. If this is true, <see cref="UnofficialForBeta"/> should be used for beta versions of SMAPI instead of <see cref="Unofficial"/>.</summary>
|
/// <summary>Whether a Stardew Valley or SMAPI beta which affects mod compatibility is in progress. If this is true, <see cref="UnofficialForBeta"/> should be used for beta versions of SMAPI instead of <see cref="Unofficial"/>.</summary>
|
||||||
public bool HasBetaInfo { get; set; }
|
[Obsolete]
|
||||||
|
public bool? HasBetaInfo { get; set; }
|
||||||
|
|
||||||
/// <summary>The errors that occurred while fetching update data.</summary>
|
/// <summary>The errors that occurred while fetching update data.</summary>
|
||||||
public string[] Errors { get; set; } = new string[0];
|
public string[] Errors { get; set; } = new string[0];
|
||||||
|
|
|
@ -28,6 +28,12 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.WebApi
|
||||||
/// <summary>The mod ID in the Chucklefish mod repo.</summary>
|
/// <summary>The mod ID in the Chucklefish mod repo.</summary>
|
||||||
public int? ChucklefishID { get; set; }
|
public int? ChucklefishID { get; set; }
|
||||||
|
|
||||||
|
/// <summary>The mod ID in the CurseForge mod repo.</summary>
|
||||||
|
public int? CurseForgeID { get; set; }
|
||||||
|
|
||||||
|
/// <summary>The mod key in the CurseForge mod repo (used in mod page URLs).</summary>
|
||||||
|
public string CurseForgeKey { get; set; }
|
||||||
|
|
||||||
/// <summary>The mod ID in the ModDrop mod repo.</summary>
|
/// <summary>The mod ID in the ModDrop mod repo.</summary>
|
||||||
public int? ModDropID { get; set; }
|
public int? ModDropID { get; set; }
|
||||||
|
|
||||||
|
@ -40,6 +46,17 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.WebApi
|
||||||
/// <summary>The custom mod page URL (if applicable).</summary>
|
/// <summary>The custom mod page URL (if applicable).</summary>
|
||||||
public string CustomUrl { get; set; }
|
public string CustomUrl { get; set; }
|
||||||
|
|
||||||
|
/// <summary>The main version.</summary>
|
||||||
|
public ModEntryVersionModel Main { get; set; }
|
||||||
|
|
||||||
|
/// <summary>The latest optional version, if newer than <see cref="Main"/>.</summary>
|
||||||
|
public ModEntryVersionModel Optional { get; set; }
|
||||||
|
|
||||||
|
/// <summary>The latest unofficial version, if newer than <see cref="Main"/> and <see cref="Optional"/>.</summary>
|
||||||
|
public ModEntryVersionModel Unofficial { get; set; }
|
||||||
|
|
||||||
|
/// <summary>The latest unofficial version for the current Stardew Valley or SMAPI beta, if any (see <see cref="HasBetaInfo"/>).</summary>
|
||||||
|
public ModEntryVersionModel UnofficialForBeta { get; set; }
|
||||||
|
|
||||||
/****
|
/****
|
||||||
** Stable compatibility
|
** Stable compatibility
|
||||||
|
@ -48,13 +65,12 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.WebApi
|
||||||
[JsonConverter(typeof(StringEnumConverter))]
|
[JsonConverter(typeof(StringEnumConverter))]
|
||||||
public WikiCompatibilityStatus? CompatibilityStatus { get; set; }
|
public WikiCompatibilityStatus? CompatibilityStatus { get; set; }
|
||||||
|
|
||||||
/// <summary>The human-readable summary of the compatibility status or workaround, without HTML formatitng.</summary>
|
/// <summary>The human-readable summary of the compatibility status or workaround, without HTML formatting.</summary>
|
||||||
public string CompatibilitySummary { get; set; }
|
public string CompatibilitySummary { get; set; }
|
||||||
|
|
||||||
/// <summary>The game or SMAPI version which broke this mod, if applicable.</summary>
|
/// <summary>The game or SMAPI version which broke this mod, if applicable.</summary>
|
||||||
public string BrokeIn { get; set; }
|
public string BrokeIn { get; set; }
|
||||||
|
|
||||||
|
|
||||||
/****
|
/****
|
||||||
** Beta compatibility
|
** Beta compatibility
|
||||||
****/
|
****/
|
||||||
|
@ -62,7 +78,7 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.WebApi
|
||||||
[JsonConverter(typeof(StringEnumConverter))]
|
[JsonConverter(typeof(StringEnumConverter))]
|
||||||
public WikiCompatibilityStatus? BetaCompatibilityStatus { get; set; }
|
public WikiCompatibilityStatus? BetaCompatibilityStatus { get; set; }
|
||||||
|
|
||||||
/// <summary>The human-readable summary of the compatibility status or workaround for the Stardew Valley beta (if any), without HTML formatitng.</summary>
|
/// <summary>The human-readable summary of the compatibility status or workaround for the Stardew Valley beta (if any), without HTML formatting.</summary>
|
||||||
public string BetaCompatibilitySummary { get; set; }
|
public string BetaCompatibilitySummary { get; set; }
|
||||||
|
|
||||||
/// <summary>The beta game or SMAPI version which broke this mod, if applicable.</summary>
|
/// <summary>The beta game or SMAPI version which broke this mod, if applicable.</summary>
|
||||||
|
@ -78,8 +94,18 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.WebApi
|
||||||
/// <summary>Construct an instance.</summary>
|
/// <summary>Construct an instance.</summary>
|
||||||
/// <param name="wiki">The mod metadata from the wiki (if available).</param>
|
/// <param name="wiki">The mod metadata from the wiki (if available).</param>
|
||||||
/// <param name="db">The mod metadata from SMAPI's internal DB (if available).</param>
|
/// <param name="db">The mod metadata from SMAPI's internal DB (if available).</param>
|
||||||
public ModExtendedMetadataModel(WikiModEntry wiki, ModDataRecord db)
|
/// <param name="main">The main version.</param>
|
||||||
|
/// <param name="optional">The latest optional version, if newer than <paramref name="main"/>.</param>
|
||||||
|
/// <param name="unofficial">The latest unofficial version, if newer than <paramref name="main"/> and <paramref name="optional"/>.</param>
|
||||||
|
/// <param name="unofficialForBeta">The latest unofficial version for the current Stardew Valley or SMAPI beta, if any.</param>
|
||||||
|
public ModExtendedMetadataModel(WikiModEntry wiki, ModDataRecord db, ModEntryVersionModel main, ModEntryVersionModel optional, ModEntryVersionModel unofficial, ModEntryVersionModel unofficialForBeta)
|
||||||
{
|
{
|
||||||
|
// versions
|
||||||
|
this.Main = main;
|
||||||
|
this.Optional = optional;
|
||||||
|
this.Unofficial = unofficial;
|
||||||
|
this.UnofficialForBeta = unofficialForBeta;
|
||||||
|
|
||||||
// wiki data
|
// wiki data
|
||||||
if (wiki != null)
|
if (wiki != null)
|
||||||
{
|
{
|
||||||
|
@ -87,6 +113,8 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.WebApi
|
||||||
this.Name = wiki.Name.FirstOrDefault();
|
this.Name = wiki.Name.FirstOrDefault();
|
||||||
this.NexusID = wiki.NexusID;
|
this.NexusID = wiki.NexusID;
|
||||||
this.ChucklefishID = wiki.ChucklefishID;
|
this.ChucklefishID = wiki.ChucklefishID;
|
||||||
|
this.CurseForgeID = wiki.CurseForgeID;
|
||||||
|
this.CurseForgeKey = wiki.CurseForgeKey;
|
||||||
this.ModDropID = wiki.ModDropID;
|
this.ModDropID = wiki.ModDropID;
|
||||||
this.GitHubRepo = wiki.GitHubRepo;
|
this.GitHubRepo = wiki.GitHubRepo;
|
||||||
this.CustomSourceUrl = wiki.CustomSourceUrl;
|
this.CustomSourceUrl = wiki.CustomSourceUrl;
|
||||||
|
|
|
@ -1,36 +0,0 @@
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace StardewModdingAPI.Toolkit.Framework.Clients.WebApi
|
|
||||||
{
|
|
||||||
/// <summary>Specifies mods whose update-check info to fetch.</summary>
|
|
||||||
public class ModSearchModel
|
|
||||||
{
|
|
||||||
/*********
|
|
||||||
** Accessors
|
|
||||||
*********/
|
|
||||||
/// <summary>The mods for which to find data.</summary>
|
|
||||||
public ModSearchEntryModel[] Mods { get; set; }
|
|
||||||
|
|
||||||
/// <summary>Whether to include extended metadata for each mod.</summary>
|
|
||||||
public bool IncludeExtendedMetadata { get; set; }
|
|
||||||
|
|
||||||
|
|
||||||
/*********
|
|
||||||
** Public methods
|
|
||||||
*********/
|
|
||||||
/// <summary>Construct an empty instance.</summary>
|
|
||||||
public ModSearchModel()
|
|
||||||
{
|
|
||||||
// needed for JSON deserialising
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Construct an instance.</summary>
|
|
||||||
/// <param name="mods">The mods to search.</param>
|
|
||||||
/// <param name="includeExtendedMetadata">Whether to include extended metadata for each mod.</param>
|
|
||||||
public ModSearchModel(ModSearchEntryModel[] mods, bool includeExtendedMetadata)
|
|
||||||
{
|
|
||||||
this.Mods = mods.ToArray();
|
|
||||||
this.IncludeExtendedMetadata = includeExtendedMetadata;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -12,6 +12,12 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.WebApi
|
||||||
/// <summary>The namespaced mod update keys (if available).</summary>
|
/// <summary>The namespaced mod update keys (if available).</summary>
|
||||||
public string[] UpdateKeys { get; set; }
|
public string[] UpdateKeys { get; set; }
|
||||||
|
|
||||||
|
/// <summary>The mod version installed by the local player. This is used for version mapping in some cases.</summary>
|
||||||
|
public ISemanticVersion InstalledVersion { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Whether the installed version is broken or could not be loaded.</summary>
|
||||||
|
public bool IsBroken { get; set; }
|
||||||
|
|
||||||
|
|
||||||
/*********
|
/*********
|
||||||
** Public methods
|
** Public methods
|
||||||
|
@ -19,15 +25,18 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.WebApi
|
||||||
/// <summary>Construct an empty instance.</summary>
|
/// <summary>Construct an empty instance.</summary>
|
||||||
public ModSearchEntryModel()
|
public ModSearchEntryModel()
|
||||||
{
|
{
|
||||||
// needed for JSON deserialising
|
// needed for JSON deserializing
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Construct an instance.</summary>
|
/// <summary>Construct an instance.</summary>
|
||||||
/// <param name="id">The unique mod ID.</param>
|
/// <param name="id">The unique mod ID.</param>
|
||||||
|
/// <param name="installedVersion">The version installed by the local player. This is used for version mapping in some cases.</param>
|
||||||
/// <param name="updateKeys">The namespaced mod update keys (if available).</param>
|
/// <param name="updateKeys">The namespaced mod update keys (if available).</param>
|
||||||
public ModSearchEntryModel(string id, string[] updateKeys)
|
/// <param name="isBroken">Whether the installed version is broken or could not be loaded.</param>
|
||||||
|
public ModSearchEntryModel(string id, ISemanticVersion installedVersion, string[] updateKeys, bool isBroken = false)
|
||||||
{
|
{
|
||||||
this.ID = id;
|
this.ID = id;
|
||||||
|
this.InstalledVersion = installedVersion;
|
||||||
this.UpdateKeys = updateKeys ?? new string[0];
|
this.UpdateKeys = updateKeys ?? new string[0];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
using System.Linq;
|
||||||
|
using StardewModdingAPI.Toolkit.Utilities;
|
||||||
|
|
||||||
|
namespace StardewModdingAPI.Toolkit.Framework.Clients.WebApi
|
||||||
|
{
|
||||||
|
/// <summary>Specifies mods whose update-check info to fetch.</summary>
|
||||||
|
public class ModSearchModel
|
||||||
|
{
|
||||||
|
/*********
|
||||||
|
** Accessors
|
||||||
|
*********/
|
||||||
|
/// <summary>The mods for which to find data.</summary>
|
||||||
|
public ModSearchEntryModel[] Mods { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Whether to include extended metadata for each mod.</summary>
|
||||||
|
public bool IncludeExtendedMetadata { get; set; }
|
||||||
|
|
||||||
|
/// <summary>The SMAPI version installed by the player. This is used for version mapping in some cases.</summary>
|
||||||
|
public ISemanticVersion ApiVersion { get; set; }
|
||||||
|
|
||||||
|
/// <summary>The Stardew Valley version installed by the player.</summary>
|
||||||
|
public ISemanticVersion GameVersion { get; set; }
|
||||||
|
|
||||||
|
/// <summary>The OS on which the player plays.</summary>
|
||||||
|
public Platform? Platform { get; set; }
|
||||||
|
|
||||||
|
|
||||||
|
/*********
|
||||||
|
** Public methods
|
||||||
|
*********/
|
||||||
|
/// <summary>Construct an empty instance.</summary>
|
||||||
|
public ModSearchModel()
|
||||||
|
{
|
||||||
|
// needed for JSON deserializing
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Construct an instance.</summary>
|
||||||
|
/// <param name="mods">The mods to search.</param>
|
||||||
|
/// <param name="apiVersion">The SMAPI version installed by the player. If this is null, the API won't provide a recommended update.</param>
|
||||||
|
/// <param name="gameVersion">The Stardew Valley version installed by the player.</param>
|
||||||
|
/// <param name="platform">The OS on which the player plays.</param>
|
||||||
|
/// <param name="includeExtendedMetadata">Whether to include extended metadata for each mod.</param>
|
||||||
|
public ModSearchModel(ModSearchEntryModel[] mods, ISemanticVersion apiVersion, ISemanticVersion gameVersion, Platform platform, bool includeExtendedMetadata)
|
||||||
|
{
|
||||||
|
this.Mods = mods.ToArray();
|
||||||
|
this.ApiVersion = apiVersion;
|
||||||
|
this.GameVersion = gameVersion;
|
||||||
|
this.Platform = platform;
|
||||||
|
this.IncludeExtendedMetadata = includeExtendedMetadata;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,7 +3,8 @@ using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using StardewModdingAPI.Toolkit.Serialisation;
|
using StardewModdingAPI.Toolkit.Serialization;
|
||||||
|
using StardewModdingAPI.Toolkit.Utilities;
|
||||||
|
|
||||||
namespace StardewModdingAPI.Toolkit.Framework.Clients.WebApi
|
namespace StardewModdingAPI.Toolkit.Framework.Clients.WebApi
|
||||||
{
|
{
|
||||||
|
@ -37,12 +38,15 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.WebApi
|
||||||
|
|
||||||
/// <summary>Get metadata about a set of mods from the web API.</summary>
|
/// <summary>Get metadata about a set of mods from the web API.</summary>
|
||||||
/// <param name="mods">The mod keys for which to fetch the latest version.</param>
|
/// <param name="mods">The mod keys for which to fetch the latest version.</param>
|
||||||
|
/// <param name="apiVersion">The SMAPI version installed by the player. If this is null, the API won't provide a recommended update.</param>
|
||||||
|
/// <param name="gameVersion">The Stardew Valley version installed by the player.</param>
|
||||||
|
/// <param name="platform">The OS on which the player plays.</param>
|
||||||
/// <param name="includeExtendedMetadata">Whether to include extended metadata for each mod.</param>
|
/// <param name="includeExtendedMetadata">Whether to include extended metadata for each mod.</param>
|
||||||
public IDictionary<string, ModEntryModel> GetModInfo(ModSearchEntryModel[] mods, bool includeExtendedMetadata = false)
|
public IDictionary<string, ModEntryModel> GetModInfo(ModSearchEntryModel[] mods, ISemanticVersion apiVersion, ISemanticVersion gameVersion, Platform platform, bool includeExtendedMetadata = false)
|
||||||
{
|
{
|
||||||
return this.Post<ModSearchModel, ModEntryModel[]>(
|
return this.Post<ModSearchModel, ModEntryModel[]>(
|
||||||
$"v{this.Version}/mods",
|
$"v{this.Version}/mods",
|
||||||
new ModSearchModel(mods, includeExtendedMetadata)
|
new ModSearchModel(mods, apiVersion, gameVersion, platform, includeExtendedMetadata)
|
||||||
).ToDictionary(p => p.ID);
|
).ToDictionary(p => p.ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -93,12 +93,17 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.Wiki
|
||||||
string[] warnings = this.GetAttributeAsCsv(node, "data-warnings");
|
string[] warnings = this.GetAttributeAsCsv(node, "data-warnings");
|
||||||
int? nexusID = this.GetAttributeAsNullableInt(node, "data-nexus-id");
|
int? nexusID = this.GetAttributeAsNullableInt(node, "data-nexus-id");
|
||||||
int? chucklefishID = this.GetAttributeAsNullableInt(node, "data-cf-id");
|
int? chucklefishID = this.GetAttributeAsNullableInt(node, "data-cf-id");
|
||||||
|
int? curseForgeID = this.GetAttributeAsNullableInt(node, "data-curseforge-id");
|
||||||
|
string curseForgeKey = this.GetAttribute(node, "data-curseforge-key");
|
||||||
int? modDropID = this.GetAttributeAsNullableInt(node, "data-moddrop-id");
|
int? modDropID = this.GetAttributeAsNullableInt(node, "data-moddrop-id");
|
||||||
string githubRepo = this.GetAttribute(node, "data-github");
|
string githubRepo = this.GetAttribute(node, "data-github");
|
||||||
string customSourceUrl = this.GetAttribute(node, "data-custom-source");
|
string customSourceUrl = this.GetAttribute(node, "data-custom-source");
|
||||||
string customUrl = this.GetAttribute(node, "data-url");
|
string customUrl = this.GetAttribute(node, "data-url");
|
||||||
string anchor = this.GetAttribute(node, "id");
|
string anchor = this.GetAttribute(node, "id");
|
||||||
string contentPackFor = this.GetAttribute(node, "data-content-pack-for");
|
string contentPackFor = this.GetAttribute(node, "data-content-pack-for");
|
||||||
|
string devNote = this.GetAttribute(node, "data-dev-note");
|
||||||
|
IDictionary<string, string> mapLocalVersions = this.GetAttributeAsVersionMapping(node, "data-map-local-versions");
|
||||||
|
IDictionary<string, string> mapRemoteVersions = this.GetAttributeAsVersionMapping(node, "data-map-remote-versions");
|
||||||
|
|
||||||
// parse stable compatibility
|
// parse stable compatibility
|
||||||
WikiCompatibilityInfo compatibility = new WikiCompatibilityInfo
|
WikiCompatibilityInfo compatibility = new WikiCompatibilityInfo
|
||||||
|
@ -127,6 +132,15 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.Wiki
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// parse links
|
||||||
|
List<Tuple<Uri, string>> metadataLinks = new List<Tuple<Uri, string>>();
|
||||||
|
foreach (HtmlNode linkElement in node.Descendants("td").Last().Descendants("a").Skip(1)) // skip anchor link
|
||||||
|
{
|
||||||
|
string text = linkElement.InnerText.Trim();
|
||||||
|
Uri url = new Uri(linkElement.GetAttributeValue("href", ""));
|
||||||
|
metadataLinks.Add(Tuple.Create(url, text));
|
||||||
|
}
|
||||||
|
|
||||||
// yield model
|
// yield model
|
||||||
yield return new WikiModEntry
|
yield return new WikiModEntry
|
||||||
{
|
{
|
||||||
|
@ -135,6 +149,8 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.Wiki
|
||||||
Author = authors,
|
Author = authors,
|
||||||
NexusID = nexusID,
|
NexusID = nexusID,
|
||||||
ChucklefishID = chucklefishID,
|
ChucklefishID = chucklefishID,
|
||||||
|
CurseForgeID = curseForgeID,
|
||||||
|
CurseForgeKey = curseForgeKey,
|
||||||
ModDropID = modDropID,
|
ModDropID = modDropID,
|
||||||
GitHubRepo = githubRepo,
|
GitHubRepo = githubRepo,
|
||||||
CustomSourceUrl = customSourceUrl,
|
CustomSourceUrl = customSourceUrl,
|
||||||
|
@ -143,6 +159,10 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.Wiki
|
||||||
Compatibility = compatibility,
|
Compatibility = compatibility,
|
||||||
BetaCompatibility = betaCompatibility,
|
BetaCompatibility = betaCompatibility,
|
||||||
Warnings = warnings,
|
Warnings = warnings,
|
||||||
|
MetadataLinks = metadataLinks.ToArray(),
|
||||||
|
DevNote = devNote,
|
||||||
|
MapLocalVersions = mapLocalVersions,
|
||||||
|
MapRemoteVersions = mapRemoteVersions,
|
||||||
Anchor = anchor
|
Anchor = anchor
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -207,6 +227,28 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.Wiki
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Get an attribute value and parse it as a version mapping.</summary>
|
||||||
|
/// <param name="element">The element whose attributes to read.</param>
|
||||||
|
/// <param name="name">The attribute name.</param>
|
||||||
|
private IDictionary<string, string> GetAttributeAsVersionMapping(HtmlNode element, string name)
|
||||||
|
{
|
||||||
|
// get raw value
|
||||||
|
string raw = this.GetAttribute(element, name);
|
||||||
|
if (raw?.Contains("→") != true)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
// parse
|
||||||
|
// Specified on the wiki in the form "remote version → mapped version; another remote version → mapped version"
|
||||||
|
IDictionary<string, string> map = new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase);
|
||||||
|
foreach (string pair in raw.Split(';'))
|
||||||
|
{
|
||||||
|
string[] versions = pair.Split('→');
|
||||||
|
if (versions.Length == 2 && !string.IsNullOrWhiteSpace(versions[0]) && !string.IsNullOrWhiteSpace(versions[1]))
|
||||||
|
map[versions[0].Trim()] = versions[1].Trim();
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>Get the text of an element with the given class name.</summary>
|
/// <summary>Get the text of an element with the given class name.</summary>
|
||||||
/// <param name="container">The metadata container.</param>
|
/// <param name="container">The metadata container.</param>
|
||||||
/// <param name="className">The field name.</param>
|
/// <param name="className">The field name.</param>
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace StardewModdingAPI.Toolkit.Framework.Clients.Wiki
|
namespace StardewModdingAPI.Toolkit.Framework.Clients.Wiki
|
||||||
{
|
{
|
||||||
/// <summary>A mod entry in the wiki list.</summary>
|
/// <summary>A mod entry in the wiki list.</summary>
|
||||||
|
@ -21,6 +24,12 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.Wiki
|
||||||
/// <summary>The mod ID in the Chucklefish mod repo.</summary>
|
/// <summary>The mod ID in the Chucklefish mod repo.</summary>
|
||||||
public int? ChucklefishID { get; set; }
|
public int? ChucklefishID { get; set; }
|
||||||
|
|
||||||
|
/// <summary>The mod ID in the CurseForge mod repo.</summary>
|
||||||
|
public int? CurseForgeID { get; set; }
|
||||||
|
|
||||||
|
/// <summary>The mod key in the CurseForge mod repo (used in mod page URLs).</summary>
|
||||||
|
public string CurseForgeKey { get; set; }
|
||||||
|
|
||||||
/// <summary>The mod ID in the ModDrop mod repo.</summary>
|
/// <summary>The mod ID in the ModDrop mod repo.</summary>
|
||||||
public int? ModDropID { get; set; }
|
public int? ModDropID { get; set; }
|
||||||
|
|
||||||
|
@ -48,6 +57,18 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.Wiki
|
||||||
/// <summary>The human-readable warnings for players about this mod.</summary>
|
/// <summary>The human-readable warnings for players about this mod.</summary>
|
||||||
public string[] Warnings { get; set; }
|
public string[] Warnings { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Extra metadata links (usually for open pull requests).</summary>
|
||||||
|
public Tuple<Uri, string>[] MetadataLinks { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Special notes intended for developers who maintain unofficial updates or submit pull requests. </summary>
|
||||||
|
public string DevNote { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Maps local versions to a semantic version for update checks.</summary>
|
||||||
|
public IDictionary<string, string> MapLocalVersions { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Maps remote versions to a semantic version for update checks.</summary>
|
||||||
|
public IDictionary<string, string> MapRemoteVersions { get; set; }
|
||||||
|
|
||||||
/// <summary>The link anchor for the mod entry in the wiki compatibility list.</summary>
|
/// <summary>The link anchor for the mod entry in the wiki compatibility list.</summary>
|
||||||
public string Anchor { get; set; }
|
public string Anchor { get; set; }
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,169 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Xml.Linq;
|
||||||
|
using System.Xml.XPath;
|
||||||
|
using StardewModdingAPI.Toolkit.Utilities;
|
||||||
|
#if SMAPI_FOR_WINDOWS
|
||||||
|
using Microsoft.Win32;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace StardewModdingAPI.Toolkit.Framework.GameScanning
|
||||||
|
{
|
||||||
|
/// <summary>Finds installed game folders.</summary>
|
||||||
|
public class GameScanner
|
||||||
|
{
|
||||||
|
/*********
|
||||||
|
** Public methods
|
||||||
|
*********/
|
||||||
|
/// <summary>Find all valid Stardew Valley install folders.</summary>
|
||||||
|
/// <remarks>This checks default game locations, and on Windows checks the Windows registry for GOG/Steam install data. A folder is considered 'valid' if it contains the Stardew Valley executable for the current OS.</remarks>
|
||||||
|
public IEnumerable<DirectoryInfo> Scan()
|
||||||
|
{
|
||||||
|
// get OS info
|
||||||
|
Platform platform = EnvironmentUtility.DetectPlatform();
|
||||||
|
string executableFilename = EnvironmentUtility.GetExecutableName(platform);
|
||||||
|
|
||||||
|
// get install paths
|
||||||
|
IEnumerable<string> paths = this
|
||||||
|
.GetCustomInstallPaths(platform)
|
||||||
|
.Concat(this.GetDefaultInstallPaths(platform))
|
||||||
|
.Select(PathUtilities.NormalizePathSeparators)
|
||||||
|
.Distinct(StringComparer.InvariantCultureIgnoreCase);
|
||||||
|
|
||||||
|
// yield valid folders
|
||||||
|
foreach (string path in paths)
|
||||||
|
{
|
||||||
|
DirectoryInfo folder = new DirectoryInfo(path);
|
||||||
|
if (folder.Exists && folder.EnumerateFiles(executableFilename).Any())
|
||||||
|
yield return folder;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*********
|
||||||
|
** Private methods
|
||||||
|
*********/
|
||||||
|
/// <summary>The default file paths where Stardew Valley can be installed.</summary>
|
||||||
|
/// <param name="platform">The target platform.</param>
|
||||||
|
/// <remarks>Derived from the crossplatform mod config: https://github.com/Pathoschild/Stardew.ModBuildConfig. </remarks>
|
||||||
|
private IEnumerable<string> GetDefaultInstallPaths(Platform platform)
|
||||||
|
{
|
||||||
|
switch (platform)
|
||||||
|
{
|
||||||
|
case Platform.Linux:
|
||||||
|
case Platform.Mac:
|
||||||
|
{
|
||||||
|
string home = Environment.GetEnvironmentVariable("HOME");
|
||||||
|
|
||||||
|
// Linux
|
||||||
|
yield return $"{home}/GOG Games/Stardew Valley/game";
|
||||||
|
yield return Directory.Exists($"{home}/.steam/steam/steamapps/common/Stardew Valley")
|
||||||
|
? $"{home}/.steam/steam/steamapps/common/Stardew Valley"
|
||||||
|
: $"{home}/.local/share/Steam/steamapps/common/Stardew Valley";
|
||||||
|
|
||||||
|
// Mac
|
||||||
|
yield return "/Applications/Stardew Valley.app/Contents/MacOS";
|
||||||
|
yield return $"{home}/Library/Application Support/Steam/steamapps/common/Stardew Valley/Contents/MacOS";
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Platform.Windows:
|
||||||
|
{
|
||||||
|
// Windows
|
||||||
|
foreach (string programFiles in new[] { @"C:\Program Files", @"C:\Program Files (x86)" })
|
||||||
|
{
|
||||||
|
yield return $@"{programFiles}\GalaxyClient\Games\Stardew Valley";
|
||||||
|
yield return $@"{programFiles}\GOG Galaxy\Games\Stardew Valley";
|
||||||
|
yield return $@"{programFiles}\Steam\steamapps\common\Stardew Valley";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Windows registry
|
||||||
|
#if SMAPI_FOR_WINDOWS
|
||||||
|
IDictionary<string, string> registryKeys = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
[@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Steam App 413150"] = "InstallLocation", // Steam
|
||||||
|
[@"SOFTWARE\WOW6432Node\GOG.com\Games\1453375253"] = "PATH", // GOG on 64-bit Windows
|
||||||
|
};
|
||||||
|
foreach (var pair in registryKeys)
|
||||||
|
{
|
||||||
|
string path = this.GetLocalMachineRegistryValue(pair.Key, pair.Value);
|
||||||
|
if (!string.IsNullOrWhiteSpace(path))
|
||||||
|
yield return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
// via Steam library path
|
||||||
|
string steampath = this.GetCurrentUserRegistryValue(@"Software\Valve\Steam", "SteamPath");
|
||||||
|
if (steampath != null)
|
||||||
|
yield return Path.Combine(steampath.Replace('/', '\\'), @"steamapps\common\Stardew Valley");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new InvalidOperationException($"Unknown platform '{platform}'.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Get the custom install path from the <c>stardewvalley.targets</c> file in the home directory, if any.</summary>
|
||||||
|
/// <param name="platform">The target platform.</param>
|
||||||
|
private IEnumerable<string> GetCustomInstallPaths(Platform platform)
|
||||||
|
{
|
||||||
|
// get home path
|
||||||
|
string homePath = Environment.GetEnvironmentVariable(platform == Platform.Windows ? "USERPROFILE" : "HOME");
|
||||||
|
if (string.IsNullOrWhiteSpace(homePath))
|
||||||
|
yield break;
|
||||||
|
|
||||||
|
// get targets file
|
||||||
|
FileInfo file = new FileInfo(Path.Combine(homePath, "stardewvalley.targets"));
|
||||||
|
if (!file.Exists)
|
||||||
|
yield break;
|
||||||
|
|
||||||
|
// parse file
|
||||||
|
XElement root;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (FileStream stream = file.OpenRead())
|
||||||
|
root = XElement.Load(stream);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
yield break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// get install path
|
||||||
|
XElement element = root.XPathSelectElement("//*[local-name() = 'GamePath']"); // can't use '//GamePath' due to the default namespace
|
||||||
|
if (!string.IsNullOrWhiteSpace(element?.Value))
|
||||||
|
yield return element.Value.Trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
#if SMAPI_FOR_WINDOWS
|
||||||
|
/// <summary>Get the value of a key in the Windows HKLM registry.</summary>
|
||||||
|
/// <param name="key">The full path of the registry key relative to HKLM.</param>
|
||||||
|
/// <param name="name">The name of the value.</param>
|
||||||
|
private string GetLocalMachineRegistryValue(string key, string name)
|
||||||
|
{
|
||||||
|
RegistryKey localMachine = Environment.Is64BitOperatingSystem ? RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64) : Registry.LocalMachine;
|
||||||
|
RegistryKey openKey = localMachine.OpenSubKey(key);
|
||||||
|
if (openKey == null)
|
||||||
|
return null;
|
||||||
|
using (openKey)
|
||||||
|
return (string)openKey.GetValue(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Get the value of a key in the Windows HKCU registry.</summary>
|
||||||
|
/// <param name="key">The full path of the registry key relative to HKCU.</param>
|
||||||
|
/// <param name="name">The name of the value.</param>
|
||||||
|
private string GetCurrentUserRegistryValue(string key, string name)
|
||||||
|
{
|
||||||
|
RegistryKey currentuser = Environment.Is64BitOperatingSystem ? RegistryKey.OpenBaseKey(RegistryHive.CurrentUser, RegistryView.Registry64) : Registry.CurrentUser;
|
||||||
|
RegistryKey openKey = currentuser.OpenSubKey(key);
|
||||||
|
if (openKey == null)
|
||||||
|
return null;
|
||||||
|
using (openKey)
|
||||||
|
return (string)openKey.GetValue(name);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
|
@ -25,12 +25,6 @@ namespace StardewModdingAPI.Toolkit.Framework.ModData
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public string FormerIDs { get; set; }
|
public string FormerIDs { get; set; }
|
||||||
|
|
||||||
/// <summary>Maps local versions to a semantic version for update checks.</summary>
|
|
||||||
public IDictionary<string, string> MapLocalVersions { get; set; } = new Dictionary<string, string>();
|
|
||||||
|
|
||||||
/// <summary>Maps remote versions to a semantic version for update checks.</summary>
|
|
||||||
public IDictionary<string, string> MapRemoteVersions { get; set; } = new Dictionary<string, string>();
|
|
||||||
|
|
||||||
/// <summary>The mod warnings to suppress, even if they'd normally be shown.</summary>
|
/// <summary>The mod warnings to suppress, even if they'd normally be shown.</summary>
|
||||||
public ModWarning SuppressWarnings { get; set; }
|
public ModWarning SuppressWarnings { get; set; }
|
||||||
|
|
||||||
|
@ -112,8 +106,8 @@ namespace StardewModdingAPI.Toolkit.Framework.ModData
|
||||||
/*********
|
/*********
|
||||||
** Private methods
|
** Private methods
|
||||||
*********/
|
*********/
|
||||||
/// <summary>The method invoked after JSON deserialisation.</summary>
|
/// <summary>The method invoked after JSON deserialization.</summary>
|
||||||
/// <param name="context">The deserialisation context.</param>
|
/// <param name="context">The deserialization context.</param>
|
||||||
[OnDeserialized]
|
[OnDeserialized]
|
||||||
private void OnDeserialized(StreamingContext context)
|
private void OnDeserialized(StreamingContext context)
|
||||||
{
|
{
|
||||||
|
|
|
@ -22,12 +22,6 @@ namespace StardewModdingAPI.Toolkit.Framework.ModData
|
||||||
/// <summary>The mod warnings to suppress, even if they'd normally be shown.</summary>
|
/// <summary>The mod warnings to suppress, even if they'd normally be shown.</summary>
|
||||||
public ModWarning SuppressWarnings { get; set; }
|
public ModWarning SuppressWarnings { get; set; }
|
||||||
|
|
||||||
/// <summary>Maps local versions to a semantic version for update checks.</summary>
|
|
||||||
public IDictionary<string, string> MapLocalVersions { get; }
|
|
||||||
|
|
||||||
/// <summary>Maps remote versions to a semantic version for update checks.</summary>
|
|
||||||
public IDictionary<string, string> MapRemoteVersions { get; }
|
|
||||||
|
|
||||||
/// <summary>The versioned field data.</summary>
|
/// <summary>The versioned field data.</summary>
|
||||||
public ModDataField[] Fields { get; }
|
public ModDataField[] Fields { get; }
|
||||||
|
|
||||||
|
@ -44,8 +38,6 @@ namespace StardewModdingAPI.Toolkit.Framework.ModData
|
||||||
this.ID = model.ID;
|
this.ID = model.ID;
|
||||||
this.FormerIDs = model.GetFormerIDs().ToArray();
|
this.FormerIDs = model.GetFormerIDs().ToArray();
|
||||||
this.SuppressWarnings = model.SuppressWarnings;
|
this.SuppressWarnings = model.SuppressWarnings;
|
||||||
this.MapLocalVersions = new Dictionary<string, string>(model.MapLocalVersions, StringComparer.InvariantCultureIgnoreCase);
|
|
||||||
this.MapRemoteVersions = new Dictionary<string, string>(model.MapRemoteVersions, StringComparer.InvariantCultureIgnoreCase);
|
|
||||||
this.Fields = model.GetFields().ToArray();
|
this.Fields = model.GetFields().ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,29 +59,6 @@ namespace StardewModdingAPI.Toolkit.Framework.ModData
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Get a semantic local version for update checks.</summary>
|
|
||||||
/// <param name="version">The remote version to normalise.</param>
|
|
||||||
public ISemanticVersion GetLocalVersionForUpdateChecks(ISemanticVersion version)
|
|
||||||
{
|
|
||||||
return this.MapLocalVersions != null && this.MapLocalVersions.TryGetValue(version.ToString(), out string newVersion)
|
|
||||||
? new SemanticVersion(newVersion)
|
|
||||||
: version;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Get a semantic remote version for update checks.</summary>
|
|
||||||
/// <param name="version">The remote version to normalise.</param>
|
|
||||||
public string GetRemoteVersionForUpdateChecks(string version)
|
|
||||||
{
|
|
||||||
// normalise version if possible
|
|
||||||
if (SemanticVersion.TryParse(version, out ISemanticVersion parsed))
|
|
||||||
version = parsed.ToString();
|
|
||||||
|
|
||||||
// fetch remote version
|
|
||||||
return this.MapRemoteVersions != null && this.MapRemoteVersions.TryGetValue(version, out string newVersion)
|
|
||||||
? newVersion
|
|
||||||
: version;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Get the possible mod IDs.</summary>
|
/// <summary>Get the possible mod IDs.</summary>
|
||||||
public IEnumerable<string> GetIDs()
|
public IEnumerable<string> GetIDs()
|
||||||
{
|
{
|
||||||
|
|
|
@ -26,29 +26,5 @@ namespace StardewModdingAPI.Toolkit.Framework.ModData
|
||||||
|
|
||||||
/// <summary>The upper version for which the <see cref="Status"/> applies (if any).</summary>
|
/// <summary>The upper version for which the <see cref="Status"/> applies (if any).</summary>
|
||||||
public ISemanticVersion StatusUpperVersion { get; set; }
|
public ISemanticVersion StatusUpperVersion { get; set; }
|
||||||
|
|
||||||
|
|
||||||
/*********
|
|
||||||
** Public methods
|
|
||||||
*********/
|
|
||||||
/// <summary>Get a semantic local version for update checks.</summary>
|
|
||||||
/// <param name="version">The remote version to normalise.</param>
|
|
||||||
public ISemanticVersion GetLocalVersionForUpdateChecks(ISemanticVersion version)
|
|
||||||
{
|
|
||||||
return this.DataRecord.GetLocalVersionForUpdateChecks(version);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Get a semantic remote version for update checks.</summary>
|
|
||||||
/// <param name="version">The remote version to normalise.</param>
|
|
||||||
public ISemanticVersion GetRemoteVersionForUpdateChecks(ISemanticVersion version)
|
|
||||||
{
|
|
||||||
if (version == null)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
string rawVersion = this.DataRecord.GetRemoteVersionForUpdateChecks(version.ToString());
|
|
||||||
return rawVersion != null
|
|
||||||
? new SemanticVersion(rawVersion)
|
|
||||||
: version;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ namespace StardewModdingAPI.Toolkit.Framework.ModData
|
||||||
BrokenCodeLoaded = 1,
|
BrokenCodeLoaded = 1,
|
||||||
|
|
||||||
/// <summary>The mod affects the save serializer in a way that may make saves unloadable without the mod.</summary>
|
/// <summary>The mod affects the save serializer in a way that may make saves unloadable without the mod.</summary>
|
||||||
ChangesSaveSerialiser = 2,
|
ChangesSaveSerializer = 2,
|
||||||
|
|
||||||
/// <summary>The mod patches the game in a way that may impact stability.</summary>
|
/// <summary>The mod patches the game in a way that may impact stability.</summary>
|
||||||
PatchesGame = 4,
|
PatchesGame = 4,
|
||||||
|
@ -21,16 +21,19 @@ namespace StardewModdingAPI.Toolkit.Framework.ModData
|
||||||
/// <summary>The mod uses the <c>dynamic</c> keyword which won't work on Linux/Mac.</summary>
|
/// <summary>The mod uses the <c>dynamic</c> keyword which won't work on Linux/Mac.</summary>
|
||||||
UsesDynamic = 8,
|
UsesDynamic = 8,
|
||||||
|
|
||||||
/// <summary>The mod references specialised 'unvalided update tick' events which may impact stability.</summary>
|
/// <summary>The mod references specialized 'unvalidated update tick' events which may impact stability.</summary>
|
||||||
UsesUnvalidatedUpdateTick = 16,
|
UsesUnvalidatedUpdateTick = 16,
|
||||||
|
|
||||||
/// <summary>The mod has no update keys set.</summary>
|
/// <summary>The mod has no update keys set.</summary>
|
||||||
NoUpdateKeys = 32,
|
NoUpdateKeys = 32,
|
||||||
|
|
||||||
|
/// <summary>Uses .NET APIs for reading and writing to the console.</summary>
|
||||||
|
AccessesConsole = 64,
|
||||||
|
|
||||||
/// <summary>Uses .NET APIs for filesystem access.</summary>
|
/// <summary>Uses .NET APIs for filesystem access.</summary>
|
||||||
AccessesFilesystem = 64,
|
AccessesFilesystem = 128,
|
||||||
|
|
||||||
/// <summary>Uses .NET APIs for shell or process access.</summary>
|
/// <summary>Uses .NET APIs for shell or process access.</summary>
|
||||||
AccessesShell = 128
|
AccessesShell = 256
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using StardewModdingAPI.Toolkit.Serialisation.Models;
|
using StardewModdingAPI.Toolkit.Serialization.Models;
|
||||||
using StardewModdingAPI.Toolkit.Utilities;
|
using StardewModdingAPI.Toolkit.Utilities;
|
||||||
|
|
||||||
namespace StardewModdingAPI.Toolkit.Framework.ModScanning
|
namespace StardewModdingAPI.Toolkit.Framework.ModScanning
|
||||||
|
@ -18,14 +18,17 @@ namespace StardewModdingAPI.Toolkit.Framework.ModScanning
|
||||||
/// <summary>The folder containing the mod's manifest.json.</summary>
|
/// <summary>The folder containing the mod's manifest.json.</summary>
|
||||||
public DirectoryInfo Directory { get; }
|
public DirectoryInfo Directory { get; }
|
||||||
|
|
||||||
|
/// <summary>The mod type.</summary>
|
||||||
|
public ModType Type { get; }
|
||||||
|
|
||||||
/// <summary>The mod manifest.</summary>
|
/// <summary>The mod manifest.</summary>
|
||||||
public Manifest Manifest { get; }
|
public Manifest Manifest { get; }
|
||||||
|
|
||||||
/// <summary>The error which occurred parsing the manifest, if any.</summary>
|
/// <summary>The error which occurred parsing the manifest, if any.</summary>
|
||||||
public string ManifestParseError { get; }
|
public ModParseError ManifestParseError { get; set; }
|
||||||
|
|
||||||
/// <summary>Whether the mod should be loaded by default. This is <c>false</c> if it was found within a folder whose name starts with a dot.</summary>
|
/// <summary>A human-readable message for the <see cref="ManifestParseError"/>, if any.</summary>
|
||||||
public bool ShouldBeLoaded { get; }
|
public string ManifestParseErrorText { get; set; }
|
||||||
|
|
||||||
|
|
||||||
/*********
|
/*********
|
||||||
|
@ -34,16 +37,26 @@ namespace StardewModdingAPI.Toolkit.Framework.ModScanning
|
||||||
/// <summary>Construct an instance.</summary>
|
/// <summary>Construct an instance.</summary>
|
||||||
/// <param name="root">The root folder containing mods.</param>
|
/// <param name="root">The root folder containing mods.</param>
|
||||||
/// <param name="directory">The folder containing the mod's manifest.json.</param>
|
/// <param name="directory">The folder containing the mod's manifest.json.</param>
|
||||||
|
/// <param name="type">The mod type.</param>
|
||||||
|
/// <param name="manifest">The mod manifest.</param>
|
||||||
|
public ModFolder(DirectoryInfo root, DirectoryInfo directory, ModType type, Manifest manifest)
|
||||||
|
: this(root, directory, type, manifest, ModParseError.None, null) { }
|
||||||
|
|
||||||
|
/// <summary>Construct an instance.</summary>
|
||||||
|
/// <param name="root">The root folder containing mods.</param>
|
||||||
|
/// <param name="directory">The folder containing the mod's manifest.json.</param>
|
||||||
|
/// <param name="type">The mod type.</param>
|
||||||
/// <param name="manifest">The mod manifest.</param>
|
/// <param name="manifest">The mod manifest.</param>
|
||||||
/// <param name="manifestParseError">The error which occurred parsing the manifest, if any.</param>
|
/// <param name="manifestParseError">The error which occurred parsing the manifest, if any.</param>
|
||||||
/// <param name="shouldBeLoaded">Whether the mod should be loaded by default. This should be <c>false</c> if it was found within a folder whose name starts with a dot.</param>
|
/// <param name="manifestParseErrorText">A human-readable message for the <paramref name="manifestParseError"/>, if any.</param>
|
||||||
public ModFolder(DirectoryInfo root, DirectoryInfo directory, Manifest manifest, string manifestParseError = null, bool shouldBeLoaded = true)
|
public ModFolder(DirectoryInfo root, DirectoryInfo directory, ModType type, Manifest manifest, ModParseError manifestParseError, string manifestParseErrorText)
|
||||||
{
|
{
|
||||||
// save info
|
// save info
|
||||||
this.Directory = directory;
|
this.Directory = directory;
|
||||||
|
this.Type = type;
|
||||||
this.Manifest = manifest;
|
this.Manifest = manifest;
|
||||||
this.ManifestParseError = manifestParseError;
|
this.ManifestParseError = manifestParseError;
|
||||||
this.ShouldBeLoaded = shouldBeLoaded;
|
this.ManifestParseErrorText = manifestParseErrorText;
|
||||||
|
|
||||||
// set display name
|
// set display name
|
||||||
this.DisplayName = manifest?.Name;
|
this.DisplayName = manifest?.Name;
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
namespace StardewModdingAPI.Toolkit.Framework.ModScanning
|
||||||
|
{
|
||||||
|
/// <summary>Indicates why a mod could not be parsed.</summary>
|
||||||
|
public enum ModParseError
|
||||||
|
{
|
||||||
|
/// <summary>No parse error.</summary>
|
||||||
|
None,
|
||||||
|
|
||||||
|
/// <summary>The folder is empty or contains only ignored files.</summary>
|
||||||
|
EmptyFolder,
|
||||||
|
|
||||||
|
/// <summary>The folder is ignored by convention.</summary>
|
||||||
|
IgnoredFolder,
|
||||||
|
|
||||||
|
/// <summary>The mod's <c>manifest.json</c> could not be parsed.</summary>
|
||||||
|
ManifestInvalid,
|
||||||
|
|
||||||
|
/// <summary>The folder contains non-ignored and non-XNB files, but none of them are <c>manifest.json</c>.</summary>
|
||||||
|
ManifestMissing,
|
||||||
|
|
||||||
|
/// <summary>The folder is an XNB mod, which can't be loaded through SMAPI.</summary>
|
||||||
|
XnbMod
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,8 +2,9 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using StardewModdingAPI.Toolkit.Serialisation;
|
using System.Text.RegularExpressions;
|
||||||
using StardewModdingAPI.Toolkit.Serialisation.Models;
|
using StardewModdingAPI.Toolkit.Serialization;
|
||||||
|
using StardewModdingAPI.Toolkit.Serialization.Models;
|
||||||
|
|
||||||
namespace StardewModdingAPI.Toolkit.Framework.ModScanning
|
namespace StardewModdingAPI.Toolkit.Framework.ModScanning
|
||||||
{
|
{
|
||||||
|
@ -17,20 +18,32 @@ namespace StardewModdingAPI.Toolkit.Framework.ModScanning
|
||||||
private readonly JsonHelper JsonHelper;
|
private readonly JsonHelper JsonHelper;
|
||||||
|
|
||||||
/// <summary>A list of filesystem entry names to ignore when checking whether a folder should be treated as a mod.</summary>
|
/// <summary>A list of filesystem entry names to ignore when checking whether a folder should be treated as a mod.</summary>
|
||||||
private readonly HashSet<string> IgnoreFilesystemEntries = new HashSet<string>(StringComparer.InvariantCultureIgnoreCase)
|
private readonly HashSet<Regex> IgnoreFilesystemEntries = new HashSet<Regex>
|
||||||
{
|
{
|
||||||
".DS_Store",
|
// OS metadata files
|
||||||
"mcs",
|
new Regex(@"^__folder_managed_by_vortex$", RegexOptions.Compiled | RegexOptions.IgnoreCase), // Vortex mod manager
|
||||||
"Thumbs.db"
|
new Regex(@"^(?:__MACOSX|\._\.DS_Store|\.DS_Store|mcs)$", RegexOptions.Compiled | RegexOptions.IgnoreCase), // MacOS
|
||||||
|
new Regex(@"^(?:desktop\.ini|Thumbs\.db)$", RegexOptions.Compiled | RegexOptions.IgnoreCase), // Windows
|
||||||
|
new Regex(@"\.(?:url|lnk)$", RegexOptions.Compiled | RegexOptions.IgnoreCase), // Windows shortcut files
|
||||||
|
|
||||||
|
// other
|
||||||
|
new Regex(@"\.(?:bmp|gif|jpeg|jpg|png|psd|tif)$", RegexOptions.Compiled | RegexOptions.IgnoreCase), // image files
|
||||||
|
new Regex(@"\.(?:md|rtf|txt)$", RegexOptions.Compiled | RegexOptions.IgnoreCase), // text files
|
||||||
|
new Regex(@"\.(?:backup|bak|old)$", RegexOptions.Compiled | RegexOptions.IgnoreCase) // backup file
|
||||||
};
|
};
|
||||||
|
|
||||||
/// <summary>The extensions for files which an XNB mod may contain. If a mod contains *only* these file extensions, it should be considered an XNB mod.</summary>
|
/// <summary>The extensions for files which an XNB mod may contain. If a mod doesn't have a <c>manifest.json</c> and contains *only* these file extensions, it should be considered an XNB mod.</summary>
|
||||||
private readonly HashSet<string> PotentialXnbModExtensions = new HashSet<string>(StringComparer.InvariantCultureIgnoreCase)
|
private readonly HashSet<string> PotentialXnbModExtensions = new HashSet<string>(StringComparer.InvariantCultureIgnoreCase)
|
||||||
{
|
{
|
||||||
".md",
|
// XNB files
|
||||||
".png",
|
".xgs",
|
||||||
".txt",
|
".xnb",
|
||||||
".xnb"
|
".xsb",
|
||||||
|
".xwb",
|
||||||
|
|
||||||
|
// unpacking artifacts
|
||||||
|
".json",
|
||||||
|
".yaml"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -52,6 +65,15 @@ namespace StardewModdingAPI.Toolkit.Framework.ModScanning
|
||||||
return this.GetModFolders(root, root);
|
return this.GetModFolders(root, root);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Extract information about all mods in the given folder.</summary>
|
||||||
|
/// <param name="rootPath">The root folder containing mods. Only the <paramref name="modPath"/> will be searched, but this field allows it to be treated as a potential mod folder of its own.</param>
|
||||||
|
/// <param name="modPath">The mod path to search.</param>
|
||||||
|
// /// <param name="tryConsolidateMod">If the folder contains multiple XNB mods, treat them as subfolders of a single mod. This is useful when reading a single mod archive, as opposed to a mods folder.</param>
|
||||||
|
public IEnumerable<ModFolder> GetModFolders(string rootPath, string modPath)
|
||||||
|
{
|
||||||
|
return this.GetModFolders(root: new DirectoryInfo(rootPath), folder: new DirectoryInfo(modPath));
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>Extract information from a mod folder.</summary>
|
/// <summary>Extract information from a mod folder.</summary>
|
||||||
/// <param name="root">The root folder containing mods.</param>
|
/// <param name="root">The root folder containing mods.</param>
|
||||||
/// <param name="searchFolder">The folder to search for a mod.</param>
|
/// <param name="searchFolder">The folder to search for a mod.</param>
|
||||||
|
@ -63,34 +85,40 @@ namespace StardewModdingAPI.Toolkit.Framework.ModScanning
|
||||||
// set appropriate invalid-mod error
|
// set appropriate invalid-mod error
|
||||||
if (manifestFile == null)
|
if (manifestFile == null)
|
||||||
{
|
{
|
||||||
FileInfo[] files = searchFolder.GetFiles("*", SearchOption.AllDirectories).Where(this.IsRelevant).ToArray();
|
FileInfo[] files = this.RecursivelyGetRelevantFiles(searchFolder).ToArray();
|
||||||
if (!files.Any())
|
if (!files.Any())
|
||||||
return new ModFolder(root, searchFolder, null, "it's an empty folder.");
|
return new ModFolder(root, searchFolder, ModType.Invalid, null, ModParseError.EmptyFolder, "it's an empty folder.");
|
||||||
if (files.All(file => this.PotentialXnbModExtensions.Contains(file.Extension)))
|
if (files.All(this.IsPotentialXnbFile))
|
||||||
return new ModFolder(root, searchFolder, null, "it's not a SMAPI mod (see https://smapi.io/xnb for info).");
|
return new ModFolder(root, searchFolder, ModType.Xnb, null, ModParseError.XnbMod, "it's not a SMAPI mod (see https://smapi.io/xnb for info).");
|
||||||
return new ModFolder(root, searchFolder, null, "it contains files, but none of them are manifest.json.");
|
return new ModFolder(root, searchFolder, ModType.Invalid, null, ModParseError.ManifestMissing, "it contains files, but none of them are manifest.json.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// read mod info
|
// read mod info
|
||||||
Manifest manifest = null;
|
Manifest manifest = null;
|
||||||
string manifestError = null;
|
ModParseError error = ModParseError.None;
|
||||||
|
string errorText = null;
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (!this.JsonHelper.ReadJsonFileIfExists<Manifest>(manifestFile.FullName, out manifest) || manifest == null)
|
if (!this.JsonHelper.ReadJsonFileIfExists<Manifest>(manifestFile.FullName, out manifest) || manifest == null)
|
||||||
manifestError = "its manifest is invalid.";
|
{
|
||||||
|
error = ModParseError.ManifestInvalid;
|
||||||
|
errorText = "its manifest is invalid.";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (SParseException ex)
|
catch (SParseException ex)
|
||||||
{
|
{
|
||||||
manifestError = $"parsing its manifest failed: {ex.Message}";
|
error = ModParseError.ManifestInvalid;
|
||||||
|
errorText = $"parsing its manifest failed: {ex.Message}";
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
manifestError = $"parsing its manifest failed:\n{ex}";
|
error = ModParseError.ManifestInvalid;
|
||||||
|
errorText = $"parsing its manifest failed:\n{ex}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// normalise display fields
|
// normalize display fields
|
||||||
if (manifest != null)
|
if (manifest != null)
|
||||||
{
|
{
|
||||||
manifest.Name = this.StripNewlines(manifest.Name);
|
manifest.Name = this.StripNewlines(manifest.Name);
|
||||||
|
@ -98,7 +126,17 @@ namespace StardewModdingAPI.Toolkit.Framework.ModScanning
|
||||||
manifest.Author = this.StripNewlines(manifest.Author);
|
manifest.Author = this.StripNewlines(manifest.Author);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new ModFolder(root, manifestFile.Directory, manifest, manifestError);
|
// get mod type
|
||||||
|
ModType type = ModType.Invalid;
|
||||||
|
if (manifest != null)
|
||||||
|
{
|
||||||
|
type = !string.IsNullOrWhiteSpace(manifest.ContentPackFor?.UniqueID)
|
||||||
|
? ModType.ContentPack
|
||||||
|
: ModType.Smapi;
|
||||||
|
}
|
||||||
|
|
||||||
|
// build result
|
||||||
|
return new ModFolder(root, manifestFile.Directory, type, manifest, error, errorText);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -108,20 +146,30 @@ namespace StardewModdingAPI.Toolkit.Framework.ModScanning
|
||||||
/// <summary>Recursively extract information about all mods in the given folder.</summary>
|
/// <summary>Recursively extract information about all mods in the given folder.</summary>
|
||||||
/// <param name="root">The root mod folder.</param>
|
/// <param name="root">The root mod folder.</param>
|
||||||
/// <param name="folder">The folder to search for mods.</param>
|
/// <param name="folder">The folder to search for mods.</param>
|
||||||
public IEnumerable<ModFolder> GetModFolders(DirectoryInfo root, DirectoryInfo folder)
|
private IEnumerable<ModFolder> GetModFolders(DirectoryInfo root, DirectoryInfo folder)
|
||||||
{
|
{
|
||||||
// skip
|
bool isRoot = folder.FullName == root.FullName;
|
||||||
if (folder.FullName != root.FullName && folder.Name.StartsWith("."))
|
|
||||||
yield return new ModFolder(root, folder, null, "ignored folder because its name starts with a dot.", shouldBeLoaded: false);
|
|
||||||
|
|
||||||
// recurse into subfolders
|
// skip
|
||||||
else if (this.IsModSearchFolder(root, folder))
|
if (!isRoot)
|
||||||
{
|
{
|
||||||
foreach (DirectoryInfo subfolder in folder.EnumerateDirectories())
|
if (folder.Name.StartsWith("."))
|
||||||
{
|
{
|
||||||
foreach (ModFolder match in this.GetModFolders(root, subfolder))
|
yield return new ModFolder(root, folder, ModType.Ignored, null, ModParseError.IgnoredFolder, "ignored folder because its name starts with a dot.");
|
||||||
yield return match;
|
yield break;
|
||||||
}
|
}
|
||||||
|
if (!this.IsRelevant(folder))
|
||||||
|
yield break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// find mods in subfolders
|
||||||
|
if (this.IsModSearchFolder(root, folder))
|
||||||
|
{
|
||||||
|
IEnumerable<ModFolder> subfolders = folder.EnumerateDirectories().SelectMany(sub => this.GetModFolders(root, sub));
|
||||||
|
if (!isRoot)
|
||||||
|
subfolders = this.TryConsolidate(root, folder, subfolders.ToArray());
|
||||||
|
foreach (ModFolder subfolder in subfolders)
|
||||||
|
yield return subfolder;
|
||||||
}
|
}
|
||||||
|
|
||||||
// treat as mod folder
|
// treat as mod folder
|
||||||
|
@ -129,6 +177,26 @@ namespace StardewModdingAPI.Toolkit.Framework.ModScanning
|
||||||
yield return this.ReadFolder(root, folder);
|
yield return this.ReadFolder(root, folder);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Consolidate adjacent folders into one mod folder, if possible.</summary>
|
||||||
|
/// <param name="root">The folder containing both parent and subfolders.</param>
|
||||||
|
/// <param name="parentFolder">The parent folder to consolidate, if possible.</param>
|
||||||
|
/// <param name="subfolders">The subfolders to consolidate, if possible.</param>
|
||||||
|
private IEnumerable<ModFolder> TryConsolidate(DirectoryInfo root, DirectoryInfo parentFolder, ModFolder[] subfolders)
|
||||||
|
{
|
||||||
|
if (subfolders.Length > 1)
|
||||||
|
{
|
||||||
|
// a collection of empty folders
|
||||||
|
if (subfolders.All(p => p.ManifestParseError == ModParseError.EmptyFolder))
|
||||||
|
return new[] { new ModFolder(root, parentFolder, ModType.Invalid, null, ModParseError.EmptyFolder, subfolders[0].ManifestParseErrorText) };
|
||||||
|
|
||||||
|
// an XNB mod
|
||||||
|
if (subfolders.All(p => p.Type == ModType.Xnb || p.ManifestParseError == ModParseError.EmptyFolder))
|
||||||
|
return new[] { new ModFolder(root, parentFolder, ModType.Xnb, null, ModParseError.XnbMod, subfolders[0].ManifestParseErrorText) };
|
||||||
|
}
|
||||||
|
|
||||||
|
return subfolders;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>Find the manifest for a mod folder.</summary>
|
/// <summary>Find the manifest for a mod folder.</summary>
|
||||||
/// <param name="folder">The folder to search.</param>
|
/// <param name="folder">The folder to search.</param>
|
||||||
private FileInfo FindManifest(DirectoryInfo folder)
|
private FileInfo FindManifest(DirectoryInfo folder)
|
||||||
|
@ -166,11 +234,41 @@ namespace StardewModdingAPI.Toolkit.Framework.ModScanning
|
||||||
return subfolders.Any() && !files.Any();
|
return subfolders.Any() && !files.Any();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Recursively get all relevant files in a folder based on the result of <see cref="IsRelevant"/>.</summary>
|
||||||
|
/// <param name="folder">The root folder to search.</param>
|
||||||
|
private IEnumerable<FileInfo> RecursivelyGetRelevantFiles(DirectoryInfo folder)
|
||||||
|
{
|
||||||
|
foreach (FileSystemInfo entry in folder.GetFileSystemInfos())
|
||||||
|
{
|
||||||
|
if (!this.IsRelevant(entry))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (entry is FileInfo file)
|
||||||
|
yield return file;
|
||||||
|
|
||||||
|
if (entry is DirectoryInfo subfolder)
|
||||||
|
{
|
||||||
|
foreach (FileInfo subfolderFile in this.RecursivelyGetRelevantFiles(subfolder))
|
||||||
|
yield return subfolderFile;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>Get whether a file or folder is relevant when deciding how to process a mod folder.</summary>
|
/// <summary>Get whether a file or folder is relevant when deciding how to process a mod folder.</summary>
|
||||||
/// <param name="entry">The file or folder.</param>
|
/// <param name="entry">The file or folder.</param>
|
||||||
private bool IsRelevant(FileSystemInfo entry)
|
private bool IsRelevant(FileSystemInfo entry)
|
||||||
{
|
{
|
||||||
return !this.IgnoreFilesystemEntries.Contains(entry.Name);
|
return !this.IgnoreFilesystemEntries.Any(p => p.IsMatch(entry.Name));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Get whether a file is potentially part of an XNB mod.</summary>
|
||||||
|
/// <param name="entry">The file.</param>
|
||||||
|
private bool IsPotentialXnbFile(FileInfo entry)
|
||||||
|
{
|
||||||
|
if (!this.IsRelevant(entry))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return this.PotentialXnbModExtensions.Contains(entry.Extension); // use EndsWith to handle cases like image..png
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Strip newlines from a string.</summary>
|
/// <summary>Strip newlines from a string.</summary>
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
namespace StardewModdingAPI.Toolkit.Framework.ModScanning
|
||||||
|
{
|
||||||
|
/// <summary>A general mod type.</summary>
|
||||||
|
public enum ModType
|
||||||
|
{
|
||||||
|
/// <summary>The mod is invalid and its type could not be determined.</summary>
|
||||||
|
Invalid,
|
||||||
|
|
||||||
|
/// <summary>The folder is ignored by convention.</summary>
|
||||||
|
Ignored,
|
||||||
|
|
||||||
|
/// <summary>A mod which uses SMAPI directly.</summary>
|
||||||
|
Smapi,
|
||||||
|
|
||||||
|
/// <summary>A mod which contains files loaded by a SMAPI mod.</summary>
|
||||||
|
ContentPack,
|
||||||
|
|
||||||
|
/// <summary>A legacy mod which replaces game files directly.</summary>
|
||||||
|
Xnb
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,6 +9,9 @@ namespace StardewModdingAPI.Toolkit.Framework.UpdateData
|
||||||
/// <summary>The Chucklefish mod repository.</summary>
|
/// <summary>The Chucklefish mod repository.</summary>
|
||||||
Chucklefish,
|
Chucklefish,
|
||||||
|
|
||||||
|
/// <summary>The CurseForge mod repository.</summary>
|
||||||
|
CurseForge,
|
||||||
|
|
||||||
/// <summary>A GitHub project containing releases.</summary>
|
/// <summary>A GitHub project containing releases.</summary>
|
||||||
GitHub,
|
GitHub,
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ using System;
|
||||||
namespace StardewModdingAPI.Toolkit.Framework.UpdateData
|
namespace StardewModdingAPI.Toolkit.Framework.UpdateData
|
||||||
{
|
{
|
||||||
/// <summary>A namespaced mod ID which uniquely identifies a mod within a mod repository.</summary>
|
/// <summary>A namespaced mod ID which uniquely identifies a mod within a mod repository.</summary>
|
||||||
public class UpdateKey
|
public class UpdateKey : IEquatable<UpdateKey>
|
||||||
{
|
{
|
||||||
/*********
|
/*********
|
||||||
** Accessors
|
** Accessors
|
||||||
|
@ -38,6 +38,12 @@ namespace StardewModdingAPI.Toolkit.Framework.UpdateData
|
||||||
&& !string.IsNullOrWhiteSpace(id);
|
&& !string.IsNullOrWhiteSpace(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Construct an instance.</summary>
|
||||||
|
/// <param name="repository">The mod repository containing the mod.</param>
|
||||||
|
/// <param name="id">The mod ID within the repository.</param>
|
||||||
|
public UpdateKey(ModRepositoryKey repository, string id)
|
||||||
|
: this($"{repository}:{id}", repository, id) { }
|
||||||
|
|
||||||
/// <summary>Parse a raw update key.</summary>
|
/// <summary>Parse a raw update key.</summary>
|
||||||
/// <param name="raw">The raw update key to parse.</param>
|
/// <param name="raw">The raw update key to parse.</param>
|
||||||
public static UpdateKey Parse(string raw)
|
public static UpdateKey Parse(string raw)
|
||||||
|
@ -69,5 +75,29 @@ namespace StardewModdingAPI.Toolkit.Framework.UpdateData
|
||||||
? $"{this.Repository}:{this.ID}"
|
? $"{this.Repository}:{this.ID}"
|
||||||
: this.RawText;
|
: this.RawText;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Indicates whether the current object is equal to another object of the same type.</summary>
|
||||||
|
/// <param name="other">An object to compare with this object.</param>
|
||||||
|
public bool Equals(UpdateKey other)
|
||||||
|
{
|
||||||
|
return
|
||||||
|
other != null
|
||||||
|
&& this.Repository == other.Repository
|
||||||
|
&& string.Equals(this.ID, other.ID, StringComparison.InvariantCultureIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Determines whether the specified object is equal to the current object.</summary>
|
||||||
|
/// <param name="obj">The object to compare with the current object.</param>
|
||||||
|
public override bool Equals(object obj)
|
||||||
|
{
|
||||||
|
return obj is UpdateKey other && this.Equals(other);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Serves as the default hash function. </summary>
|
||||||
|
/// <returns>A hash code for the current object.</returns>
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return $"{this.Repository}:{this.ID}".ToLower().GetHashCode();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,13 +2,17 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using StardewModdingAPI.Toolkit.Framework.Clients.Wiki;
|
using StardewModdingAPI.Toolkit.Framework.Clients.Wiki;
|
||||||
|
using StardewModdingAPI.Toolkit.Framework.GameScanning;
|
||||||
using StardewModdingAPI.Toolkit.Framework.ModData;
|
using StardewModdingAPI.Toolkit.Framework.ModData;
|
||||||
using StardewModdingAPI.Toolkit.Framework.ModScanning;
|
using StardewModdingAPI.Toolkit.Framework.ModScanning;
|
||||||
using StardewModdingAPI.Toolkit.Serialisation;
|
using StardewModdingAPI.Toolkit.Serialization;
|
||||||
|
|
||||||
|
[assembly: InternalsVisibleTo("StardewModdingAPI")]
|
||||||
|
[assembly: InternalsVisibleTo("SMAPI.Web")]
|
||||||
namespace StardewModdingAPI.Toolkit
|
namespace StardewModdingAPI.Toolkit
|
||||||
{
|
{
|
||||||
/// <summary>A convenience wrapper for the various tools.</summary>
|
/// <summary>A convenience wrapper for the various tools.</summary>
|
||||||
|
@ -46,6 +50,13 @@ namespace StardewModdingAPI.Toolkit
|
||||||
this.UserAgent = $"SMAPI Mod Handler Toolkit/{version}";
|
this.UserAgent = $"SMAPI Mod Handler Toolkit/{version}";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Find valid Stardew Valley install folders.</summary>
|
||||||
|
/// <remarks>This checks default game locations, and on Windows checks the Windows registry for GOG/Steam install data. A folder is considered 'valid' if it contains the Stardew Valley executable for the current OS.</remarks>
|
||||||
|
public IEnumerable<DirectoryInfo> GetGameFolders()
|
||||||
|
{
|
||||||
|
return new GameScanner().Scan();
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>Extract mod metadata from the wiki compatibility list.</summary>
|
/// <summary>Extract mod metadata from the wiki compatibility list.</summary>
|
||||||
public async Task<WikiModList> GetWikiCompatibilityListAsync()
|
public async Task<WikiModList> GetWikiCompatibilityListAsync()
|
||||||
{
|
{
|
||||||
|
@ -69,6 +80,14 @@ namespace StardewModdingAPI.Toolkit
|
||||||
return new ModScanner(this.JsonHelper).GetModFolders(rootPath);
|
return new ModScanner(this.JsonHelper).GetModFolders(rootPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Extract information about all mods in the given folder.</summary>
|
||||||
|
/// <param name="rootPath">The root folder containing mods. Only the <paramref name="modPath"/> will be searched, but this field allows it to be treated as a potential mod folder of its own.</param>
|
||||||
|
/// <param name="modPath">The mod path to search.</param>
|
||||||
|
public IEnumerable<ModFolder> GetModFolders(string rootPath, string modPath)
|
||||||
|
{
|
||||||
|
return new ModScanner(this.JsonHelper).GetModFolders(rootPath, modPath);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>Get an update URL for an update key (if valid).</summary>
|
/// <summary>Get an update URL for an update key (if valid).</summary>
|
||||||
/// <param name="updateKey">The update key.</param>
|
/// <param name="updateKey">The update key.</param>
|
||||||
public string GetUpdateUrl(string updateKey)
|
public string GetUpdateUrl(string updateKey)
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
using System.Reflection;
|
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
|
|
||||||
[assembly: AssemblyTitle("SMAPI.Toolkit")]
|
|
||||||
[assembly: AssemblyDescription("A library which encapsulates mod-handling logic for mod managers and tools. Not intended for use by mods.")]
|
|
||||||
[assembly: InternalsVisibleTo("StardewModdingAPI")]
|
|
||||||
[assembly: InternalsVisibleTo("StardewModdingAPI.Web")]
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<AssemblyName>SMAPI.Toolkit</AssemblyName>
|
||||||
|
<RootNamespace>StardewModdingAPI.Toolkit</RootNamespace>
|
||||||
|
<Description>A library which encapsulates mod-handling logic for mod managers and tools. Not intended for use by mods.</Description>
|
||||||
|
<TargetFrameworks>net4.5;netstandard2.0</TargetFrameworks>
|
||||||
|
<OutputPath>..\..\bin\$(Configuration)\SMAPI.Toolkit</OutputPath>
|
||||||
|
<DocumentationFile>..\..\bin\$(Configuration)\SMAPI.Toolkit\$(TargetFramework)\SMAPI.Toolkit.xml</DocumentationFile>
|
||||||
|
<LangVersion>latest</LangVersion>
|
||||||
|
<PlatformTarget Condition="'$(TargetFramework)' == 'net4.5'">x86</PlatformTarget>
|
||||||
|
<RootNamespace>StardewModdingAPI.Toolkit</RootNamespace>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="HtmlAgilityPack" Version="1.11.16" />
|
||||||
|
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||||
|
<PackageReference Include="Pathoschild.Http.FluentClient" Version="3.3.1" />
|
||||||
|
<PackageReference Include="System.Management" Version="4.5.0" Condition="'$(OS)' == 'Windows_NT'" />
|
||||||
|
<PackageReference Include="Microsoft.Win32.Registry" Version="4.5.0" Condition="'$(OS)' == 'Windows_NT' AND '$(TargetFramework)' == 'netstandard2.0'" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\SMAPI.Toolkit.CoreInterfaces\SMAPI.Toolkit.CoreInterfaces.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<Import Project="..\..\build\common.targets" />
|
||||||
|
|
||||||
|
</Project>
|
|
@ -7,8 +7,7 @@ namespace StardewModdingAPI.Toolkit
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// The implementation is defined by Semantic Version 2.0 (https://semver.org/), with a few deviations:
|
/// The implementation is defined by Semantic Version 2.0 (https://semver.org/), with a few deviations:
|
||||||
/// - short-form "x.y" versions are supported (equivalent to "x.y.0");
|
/// - short-form "x.y" versions are supported (equivalent to "x.y.0");
|
||||||
/// - hyphens are synonymous with dots in prerelease tags (like "-unofficial.3-pathoschild");
|
/// - hyphens are synonymous with dots in prerelease tags and build metadata (like "-unofficial.3-pathoschild");
|
||||||
/// - +build suffixes are not supported;
|
|
||||||
/// - and "-unofficial" in prerelease tags is always lower-precedence (e.g. "1.0-beta" is newer than "1.0-unofficial").
|
/// - and "-unofficial" in prerelease tags is always lower-precedence (e.g. "1.0-beta" is newer than "1.0-unofficial").
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public class SemanticVersion : ISemanticVersion
|
public class SemanticVersion : ISemanticVersion
|
||||||
|
@ -16,11 +15,11 @@ namespace StardewModdingAPI.Toolkit
|
||||||
/*********
|
/*********
|
||||||
** Fields
|
** Fields
|
||||||
*********/
|
*********/
|
||||||
/// <summary>A regex pattern matching a valid prerelease tag.</summary>
|
/// <summary>A regex pattern matching a valid prerelease or build metadata tag.</summary>
|
||||||
internal const string TagPattern = @"(?>[a-z0-9]+[\-\.]?)+";
|
internal const string TagPattern = @"(?>[a-z0-9]+[\-\.]?)+";
|
||||||
|
|
||||||
/// <summary>A regex pattern matching a version within a larger string.</summary>
|
/// <summary>A regex pattern matching a version within a larger string.</summary>
|
||||||
internal const string UnboundedVersionPattern = @"(?>(?<major>0|[1-9]\d*))\.(?>(?<minor>0|[1-9]\d*))(?>(?:\.(?<patch>0|[1-9]\d*))?)(?:-(?<prerelease>" + SemanticVersion.TagPattern + "))?";
|
internal const string UnboundedVersionPattern = @"(?>(?<major>0|[1-9]\d*))\.(?>(?<minor>0|[1-9]\d*))(?>(?:\.(?<patch>0|[1-9]\d*))?)(?:-(?<prerelease>" + SemanticVersion.TagPattern + "))?(?:\\+(?<buildmetadata>" + SemanticVersion.TagPattern + "))?";
|
||||||
|
|
||||||
/// <summary>A regular expression matching a semantic version string.</summary>
|
/// <summary>A regular expression matching a semantic version string.</summary>
|
||||||
/// <remarks>This pattern is derived from the BNF documentation in the <a href="https://github.com/mojombo/semver">semver repo</a>, with deviations to support the Stardew Valley mod conventions (see remarks on <see cref="SemanticVersion"/>).</remarks>
|
/// <remarks>This pattern is derived from the BNF documentation in the <a href="https://github.com/mojombo/semver">semver repo</a>, with deviations to support the Stardew Valley mod conventions (see remarks on <see cref="SemanticVersion"/>).</remarks>
|
||||||
|
@ -42,14 +41,8 @@ namespace StardewModdingAPI.Toolkit
|
||||||
/// <summary>An optional prerelease tag.</summary>
|
/// <summary>An optional prerelease tag.</summary>
|
||||||
public string PrereleaseTag { get; }
|
public string PrereleaseTag { get; }
|
||||||
|
|
||||||
#if !SMAPI_3_0_STRICT
|
/// <summary>Optional build metadata. This is ignored when determining version precedence.</summary>
|
||||||
/// <summary>An optional prerelease tag.</summary>
|
public string BuildMetadata { get; }
|
||||||
[Obsolete("Use " + nameof(ISemanticVersion.PrereleaseTag) + " instead")]
|
|
||||||
public string Build => this.PrereleaseTag;
|
|
||||||
|
|
||||||
/// <summary>Whether the version was parsed from the legacy object format.</summary>
|
|
||||||
public bool IsLegacyFormat { get; }
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
/*********
|
/*********
|
||||||
|
@ -60,20 +53,14 @@ namespace StardewModdingAPI.Toolkit
|
||||||
/// <param name="minor">The minor version incremented for backwards-compatible changes.</param>
|
/// <param name="minor">The minor version incremented for backwards-compatible changes.</param>
|
||||||
/// <param name="patch">The patch version for backwards-compatible fixes.</param>
|
/// <param name="patch">The patch version for backwards-compatible fixes.</param>
|
||||||
/// <param name="prereleaseTag">An optional prerelease tag.</param>
|
/// <param name="prereleaseTag">An optional prerelease tag.</param>
|
||||||
/// <param name="isLegacyFormat">Whether the version was parsed from the legacy object format.</param>
|
/// <param name="buildMetadata">Optional build metadata. This is ignored when determining version precedence.</param>
|
||||||
public SemanticVersion(int major, int minor, int patch, string prereleaseTag = null
|
public SemanticVersion(int major, int minor, int patch, string prereleaseTag = null, string buildMetadata = null)
|
||||||
#if !SMAPI_3_0_STRICT
|
|
||||||
, bool isLegacyFormat = false
|
|
||||||
#endif
|
|
||||||
)
|
|
||||||
{
|
{
|
||||||
this.MajorVersion = major;
|
this.MajorVersion = major;
|
||||||
this.MinorVersion = minor;
|
this.MinorVersion = minor;
|
||||||
this.PatchVersion = patch;
|
this.PatchVersion = patch;
|
||||||
this.PrereleaseTag = this.GetNormalisedTag(prereleaseTag);
|
this.PrereleaseTag = this.GetNormalizedTag(prereleaseTag);
|
||||||
#if !SMAPI_3_0_STRICT
|
this.BuildMetadata = this.GetNormalizedTag(buildMetadata);
|
||||||
this.IsLegacyFormat = isLegacyFormat;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
this.AssertValid();
|
this.AssertValid();
|
||||||
}
|
}
|
||||||
|
@ -106,16 +93,17 @@ namespace StardewModdingAPI.Toolkit
|
||||||
if (!match.Success)
|
if (!match.Success)
|
||||||
throw new FormatException($"The input '{version}' isn't a valid semantic version.");
|
throw new FormatException($"The input '{version}' isn't a valid semantic version.");
|
||||||
|
|
||||||
// initialise
|
// initialize
|
||||||
this.MajorVersion = int.Parse(match.Groups["major"].Value);
|
this.MajorVersion = int.Parse(match.Groups["major"].Value);
|
||||||
this.MinorVersion = match.Groups["minor"].Success ? int.Parse(match.Groups["minor"].Value) : 0;
|
this.MinorVersion = match.Groups["minor"].Success ? int.Parse(match.Groups["minor"].Value) : 0;
|
||||||
this.PatchVersion = match.Groups["patch"].Success ? int.Parse(match.Groups["patch"].Value) : 0;
|
this.PatchVersion = match.Groups["patch"].Success ? int.Parse(match.Groups["patch"].Value) : 0;
|
||||||
this.PrereleaseTag = match.Groups["prerelease"].Success ? this.GetNormalisedTag(match.Groups["prerelease"].Value) : null;
|
this.PrereleaseTag = match.Groups["prerelease"].Success ? this.GetNormalizedTag(match.Groups["prerelease"].Value) : null;
|
||||||
|
this.BuildMetadata = match.Groups["buildmetadata"].Success ? this.GetNormalizedTag(match.Groups["buildmetadata"].Value) : null;
|
||||||
|
|
||||||
this.AssertValid();
|
this.AssertValid();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Get an integer indicating whether this version precedes (less than 0), supercedes (more than 0), or is equivalent to (0) the specified version.</summary>
|
/// <summary>Get an integer indicating whether this version precedes (less than 0), supersedes (more than 0), or is equivalent to (0) the specified version.</summary>
|
||||||
/// <param name="other">The version to compare with this instance.</param>
|
/// <param name="other">The version to compare with this instance.</param>
|
||||||
/// <exception cref="ArgumentNullException">The <paramref name="other"/> value is null.</exception>
|
/// <exception cref="ArgumentNullException">The <paramref name="other"/> value is null.</exception>
|
||||||
public int CompareTo(ISemanticVersion other)
|
public int CompareTo(ISemanticVersion other)
|
||||||
|
@ -133,7 +121,7 @@ namespace StardewModdingAPI.Toolkit
|
||||||
return other != null && this.CompareTo(other) == 0;
|
return other != null && this.CompareTo(other) == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Whether this is a pre-release version.</summary>
|
/// <summary>Whether this is a prerelease version.</summary>
|
||||||
public bool IsPrerelease()
|
public bool IsPrerelease()
|
||||||
{
|
{
|
||||||
return !string.IsNullOrWhiteSpace(this.PrereleaseTag);
|
return !string.IsNullOrWhiteSpace(this.PrereleaseTag);
|
||||||
|
@ -189,16 +177,12 @@ namespace StardewModdingAPI.Toolkit
|
||||||
/// <summary>Get a string representation of the version.</summary>
|
/// <summary>Get a string representation of the version.</summary>
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
// version
|
string version = $"{this.MajorVersion}.{this.MinorVersion}.{this.PatchVersion}";
|
||||||
string result = this.PatchVersion != 0
|
if (this.PrereleaseTag != null)
|
||||||
? $"{this.MajorVersion}.{this.MinorVersion}.{this.PatchVersion}"
|
version += $"-{this.PrereleaseTag}";
|
||||||
: $"{this.MajorVersion}.{this.MinorVersion}";
|
if (this.BuildMetadata != null)
|
||||||
|
version += $"+{this.BuildMetadata}";
|
||||||
// tag
|
return version;
|
||||||
string tag = this.PrereleaseTag;
|
|
||||||
if (tag != null)
|
|
||||||
result += $"-{tag}";
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Parse a version string without throwing an exception if it fails.</summary>
|
/// <summary>Parse a version string without throwing an exception if it fails.</summary>
|
||||||
|
@ -223,15 +207,15 @@ namespace StardewModdingAPI.Toolkit
|
||||||
/*********
|
/*********
|
||||||
** Private methods
|
** Private methods
|
||||||
*********/
|
*********/
|
||||||
/// <summary>Get a normalised build tag.</summary>
|
/// <summary>Get a normalized prerelease or build tag.</summary>
|
||||||
/// <param name="tag">The tag to normalise.</param>
|
/// <param name="tag">The tag to normalize.</param>
|
||||||
private string GetNormalisedTag(string tag)
|
private string GetNormalizedTag(string tag)
|
||||||
{
|
{
|
||||||
tag = tag?.Trim();
|
tag = tag?.Trim();
|
||||||
return !string.IsNullOrWhiteSpace(tag) ? tag : null;
|
return !string.IsNullOrWhiteSpace(tag) ? tag : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Get an integer indicating whether this version precedes (less than 0), supercedes (more than 0), or is equivalent to (0) the specified version.</summary>
|
/// <summary>Get an integer indicating whether this version precedes (less than 0), supersedes (more than 0), or is equivalent to (0) the specified version.</summary>
|
||||||
/// <param name="otherMajor">The major version to compare with this instance.</param>
|
/// <param name="otherMajor">The major version to compare with this instance.</param>
|
||||||
/// <param name="otherMinor">The minor version to compare with this instance.</param>
|
/// <param name="otherMinor">The minor version to compare with this instance.</param>
|
||||||
/// <param name="otherPatch">The patch version to compare with this instance.</param>
|
/// <param name="otherPatch">The patch version to compare with this instance.</param>
|
||||||
|
@ -252,7 +236,7 @@ namespace StardewModdingAPI.Toolkit
|
||||||
if (this.PrereleaseTag == otherTag)
|
if (this.PrereleaseTag == otherTag)
|
||||||
return same;
|
return same;
|
||||||
|
|
||||||
// stable supercedes pre-release
|
// stable supersedes prerelease
|
||||||
bool curIsStable = string.IsNullOrWhiteSpace(this.PrereleaseTag);
|
bool curIsStable = string.IsNullOrWhiteSpace(this.PrereleaseTag);
|
||||||
bool otherIsStable = string.IsNullOrWhiteSpace(otherTag);
|
bool otherIsStable = string.IsNullOrWhiteSpace(otherTag);
|
||||||
if (curIsStable)
|
if (curIsStable)
|
||||||
|
@ -260,12 +244,12 @@ namespace StardewModdingAPI.Toolkit
|
||||||
if (otherIsStable)
|
if (otherIsStable)
|
||||||
return curOlder;
|
return curOlder;
|
||||||
|
|
||||||
// compare two pre-release tag values
|
// compare two prerelease tag values
|
||||||
string[] curParts = this.PrereleaseTag.Split('.', '-');
|
string[] curParts = this.PrereleaseTag.Split('.', '-');
|
||||||
string[] otherParts = otherTag.Split('.', '-');
|
string[] otherParts = otherTag.Split('.', '-');
|
||||||
for (int i = 0; i < curParts.Length; i++)
|
for (int i = 0; i < curParts.Length; i++)
|
||||||
{
|
{
|
||||||
// longer prerelease tag supercedes if otherwise equal
|
// longer prerelease tag supersedes if otherwise equal
|
||||||
if (otherParts.Length <= i)
|
if (otherParts.Length <= i)
|
||||||
return curNewer;
|
return curNewer;
|
||||||
|
|
||||||
|
@ -300,12 +284,21 @@ namespace StardewModdingAPI.Toolkit
|
||||||
throw new FormatException($"{this} isn't a valid semantic version. The major, minor, and patch numbers can't be negative.");
|
throw new FormatException($"{this} isn't a valid semantic version. The major, minor, and patch numbers can't be negative.");
|
||||||
if (this.MajorVersion == 0 && this.MinorVersion == 0 && this.PatchVersion == 0)
|
if (this.MajorVersion == 0 && this.MinorVersion == 0 && this.PatchVersion == 0)
|
||||||
throw new FormatException($"{this} isn't a valid semantic version. At least one of the major, minor, and patch numbers must be more than zero.");
|
throw new FormatException($"{this} isn't a valid semantic version. At least one of the major, minor, and patch numbers must be more than zero.");
|
||||||
|
|
||||||
if (this.PrereleaseTag != null)
|
if (this.PrereleaseTag != null)
|
||||||
{
|
{
|
||||||
if (this.PrereleaseTag.Trim() == "")
|
if (this.PrereleaseTag.Trim() == "")
|
||||||
throw new FormatException($"{this} isn't a valid semantic version. The tag cannot be a blank string (but may be omitted).");
|
throw new FormatException($"{this} isn't a valid semantic version. The prerelease tag cannot be a blank string (but may be omitted).");
|
||||||
if (!Regex.IsMatch(this.PrereleaseTag, $"^{SemanticVersion.TagPattern}$", RegexOptions.IgnoreCase))
|
if (!Regex.IsMatch(this.PrereleaseTag, $"^{SemanticVersion.TagPattern}$", RegexOptions.IgnoreCase))
|
||||||
throw new FormatException($"{this} isn't a valid semantic version. The tag is invalid.");
|
throw new FormatException($"{this} isn't a valid semantic version. The prerelease tag is invalid.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.BuildMetadata != null)
|
||||||
|
{
|
||||||
|
if (this.BuildMetadata.Trim() == "")
|
||||||
|
throw new FormatException($"{this} isn't a valid semantic version. The build metadata cannot be a blank string (but may be omitted).");
|
||||||
|
if (!Regex.IsMatch(this.BuildMetadata, $"^{SemanticVersion.TagPattern}$", RegexOptions.IgnoreCase))
|
||||||
|
throw new FormatException($"{this} isn't a valid semantic version. The build metadata is invalid.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
using System;
|
using System;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using StardewModdingAPI.Toolkit.Serialisation.Models;
|
using StardewModdingAPI.Toolkit.Serialization.Models;
|
||||||
|
|
||||||
namespace StardewModdingAPI.Toolkit.Serialisation.Converters
|
namespace StardewModdingAPI.Toolkit.Serialization.Converters
|
||||||
{
|
{
|
||||||
/// <summary>Handles deserialisation of <see cref="ManifestContentPackFor"/> arrays.</summary>
|
/// <summary>Handles deserialization of <see cref="ManifestContentPackFor"/> arrays.</summary>
|
||||||
public class ManifestContentPackForConverter : JsonConverter
|
public class ManifestContentPackForConverter : JsonConverter
|
||||||
{
|
{
|
||||||
/*********
|
/*********
|
|
@ -2,11 +2,11 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using StardewModdingAPI.Toolkit.Serialisation.Models;
|
using StardewModdingAPI.Toolkit.Serialization.Models;
|
||||||
|
|
||||||
namespace StardewModdingAPI.Toolkit.Serialisation.Converters
|
namespace StardewModdingAPI.Toolkit.Serialization.Converters
|
||||||
{
|
{
|
||||||
/// <summary>Handles deserialisation of <see cref="ManifestDependency"/> arrays.</summary>
|
/// <summary>Handles deserialization of <see cref="ManifestDependency"/> arrays.</summary>
|
||||||
internal class ManifestDependencyArrayConverter : JsonConverter
|
internal class ManifestDependencyArrayConverter : JsonConverter
|
||||||
{
|
{
|
||||||
/*********
|
/*********
|
|
@ -2,9 +2,9 @@ using System;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
namespace StardewModdingAPI.Toolkit.Serialisation.Converters
|
namespace StardewModdingAPI.Toolkit.Serialization.Converters
|
||||||
{
|
{
|
||||||
/// <summary>Handles deserialisation of <see cref="ISemanticVersion"/>.</summary>
|
/// <summary>Handles deserialization of <see cref="ISemanticVersion"/>.</summary>
|
||||||
internal class SemanticVersionConverter : JsonConverter
|
internal class SemanticVersionConverter : JsonConverter
|
||||||
{
|
{
|
||||||
/*********
|
/*********
|
||||||
|
@ -67,20 +67,8 @@ namespace StardewModdingAPI.Toolkit.Serialisation.Converters
|
||||||
int minor = obj.ValueIgnoreCase<int>(nameof(ISemanticVersion.MinorVersion));
|
int minor = obj.ValueIgnoreCase<int>(nameof(ISemanticVersion.MinorVersion));
|
||||||
int patch = obj.ValueIgnoreCase<int>(nameof(ISemanticVersion.PatchVersion));
|
int patch = obj.ValueIgnoreCase<int>(nameof(ISemanticVersion.PatchVersion));
|
||||||
string prereleaseTag = obj.ValueIgnoreCase<string>(nameof(ISemanticVersion.PrereleaseTag));
|
string prereleaseTag = obj.ValueIgnoreCase<string>(nameof(ISemanticVersion.PrereleaseTag));
|
||||||
#if !SMAPI_3_0_STRICT
|
|
||||||
if (string.IsNullOrWhiteSpace(prereleaseTag))
|
|
||||||
{
|
|
||||||
prereleaseTag = obj.ValueIgnoreCase<string>("Build");
|
|
||||||
if (prereleaseTag == "0")
|
|
||||||
prereleaseTag = null; // '0' from incorrect examples in old SMAPI documentation
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return new SemanticVersion(major, minor, patch, prereleaseTag
|
return new SemanticVersion(major, minor, patch, prereleaseTag);
|
||||||
#if !SMAPI_3_0_STRICT
|
|
||||||
, isLegacyFormat: true
|
|
||||||
#endif
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Read a JSON string.</summary>
|
/// <summary>Read a JSON string.</summary>
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue