Compare commits

...

71 Commits

Author SHA1 Message Date
yangzhi bfb0adb849 Merge remote-tracking branch 'pathoschild/stable' into develop
# Conflicts:
#	src/SMAPI.Toolkit/SMAPI.Toolkit.csproj
#	src/SMAPI/Framework/SCore.cs
2023-07-18 09:58:29 +08:00
yangzhi 24a0af3d58 Compatibility fix 2023-07-11 20:32:40 +08:00
yangzhi a5a10e60cb Minor fix 2023-07-11 08:06:06 +08:00
yangzhi fc6dc349da Fix String.Split methods 2023-07-10 16:47:39 +08:00
yangzhi 0de3a3ad41 1. improve performance 2023-07-10 15:20:12 +08:00
yangzhi 4317b930aa Fix for CJBCheatsMenu 2023-07-08 17:02:20 +08:00
yangzhi 77c15400b8 1. Fix save error when create new game
2. Compatibility fix
2023-07-08 10:12:57 +08:00
yangzhi ac1465bedf Fix serialization/deserialization bug in System.Xml 2023-07-07 10:43:35 +08:00
yangzhi 917eaf48c4 Fix save load/save error 2023-06-29 09:00:23 +08:00
yangzhi bf0903b173 Compatibility fix 2023-06-26 16:41:39 +08:00
yangzhi 68a88ef3ba 1. Fix virtual keyboard mod
2. Fix game console
2023-06-26 09:40:49 +08:00
yangzhi f628466adb Fix content patch logic 2023-06-25 09:27:26 +08:00
Jesse Plamondon-Willard 0ce39e2330
Merge branch 'develop' into stable 2023-06-24 16:53:42 -04:00
Jesse Plamondon-Willard 4e27841fb2
prepare for release 2023-06-24 16:53:13 -04:00
Jesse Plamondon-Willard eeb4e12f75
fix Linux/macOS build error 2023-06-24 15:48:21 -04:00
Jesse Plamondon-Willard 7d96f29aff
remove test_input time limit 2023-06-24 14:20:37 -04:00
Jesse Plamondon-Willard db33481dff
update release notes 2023-06-24 12:57:12 -04:00
Jesse Plamondon-Willard f52b2a5ad4
Merge pull request #908 from romangraef/feat/wezterm
Add wezterm terminal support for linux
2023-06-24 12:50:01 -04:00
Jesse Plamondon-Willard 421dfbe912
Merge pull request #907 from atravita-mods/error-missing-imagesource
Add validation for missing tilesheet image sources
2023-06-24 12:49:56 -04:00
Jesse Plamondon-Willard dd352d409f
Merge pull request #906 from NyCodeGHG/feature/headless-option
Add --no-prompt installer argument for unattended execution
2023-06-24 12:49:49 -04:00
Jesse Plamondon-Willard 36606e4a4a
Merge pull request #904 from boneskull/patch-1
Fix examples field in manifest schema
2023-06-24 12:49:41 -04:00
nea ebdb17bf81
Add wezterm terminal support for linux 2023-06-24 12:45:44 -04:00
Jesse Plamondon-Willard ae36b9b166
tweak new validation error text 2023-06-24 12:43:44 -04:00
atravita-mods 7c6da8e5ec
adds validation for missing image sources on tilesheets 2023-06-24 12:39:29 -04:00
Jesse Plamondon-Willard 5196c3bad9
rename `--headless` to `--no-prompt`, and parse with the other args 2023-06-24 12:36:02 -04:00
Jesse Plamondon-Willard 05695be390
remove unrelated `set -ex` in build script 2023-06-24 12:36:02 -04:00
Marie Ramlow 654ad4b4b2
Add headless flag for unattended execution 2023-06-24 12:06:10 -04:00
Christopher Hiller 15ab3ccd09
fix invalid field in manifest.json
VS is complaining that this line should be an array.
2023-06-24 12:04:09 -04:00
Jesse Plamondon-Willard 251d83472c
update dependencies 2023-06-24 12:01:49 -04:00
Jesse Plamondon-Willard 2f1f274b2d
fix Context.HasRemotePlayers being true when there's no farmhands 2023-06-24 11:58:25 -04:00
Jesse Plamondon-Willard 3c46bdaeb3
fix install error if a game folder has an invalid symlink 2023-06-24 11:53:04 -04:00
Jesse Plamondon-Willard 5d2afc0aa4
fix error deserializing null semantic version 2023-06-24 11:52:48 -04:00
Jesse Plamondon-Willard 76a0497684
fix ModFolder not being JSON-serializable 2023-06-24 11:51:29 -04:00
Jesse Plamondon-Willard 342b91ff80
add multiplayer player type to window titles 2023-06-24 11:50:51 -04:00
Jesse Plamondon-Willard 445370b75b
embed debug symbols for Linux/macOS compatibility in mod build package 2023-06-24 11:50:05 -04:00
Jesse Plamondon-Willard 9dd702a162
embed debug symbols for Linux/macOS compatibility
This fixes error stack traces not having line numbers on Linux/macOS.
2023-06-24 11:49:04 -04:00
yangzhi bdd506b0db Optimize migration logic 2023-06-21 08:05:41 +08:00
yangzhi f59c4f194b Imporve compatibility 2023-06-20 14:48:34 +08:00
yangzhi 18f34debcc Merge remote-tracking branch 'pathoschild/stable' into develop
# Conflicts:
#	src/SMAPI.Toolkit/SMAPI.Toolkit.csproj
#	src/SMAPI/SMAPI.csproj
2023-06-19 10:46:03 +08:00
yangzhi 8dbd00eade Fix loader's link error problem 2023-06-16 16:19:45 +08:00
zhiyang7 4ebf03ee48 1. Fix for saf access
2. Fix virtual keyboard
2023-04-10 14:31:34 +08:00
Jesse Plamondon-Willard 53e0e8cd24
Merge branch 'develop' into stable 2023-04-09 13:09:47 -04:00
Jesse Plamondon-Willard 46947683cd
prepare for release 2023-04-09 13:08:06 -04:00
ZaneYork d9440155ca Initial logic fix 2023-04-08 10:05:21 +08:00
zhiyang7 2801638122 Bug fix 2023-04-04 11:22:32 +08:00
Jesse Plamondon-Willard 2e618672aa
update schema for Content Patcher 1.29.0 2023-04-02 15:44:05 -04:00
Jesse Plamondon-Willard c2e9aad698
adjust ModContentManager.HandleUnknownFileTypes to let mods patch it 2023-04-02 15:44:02 -04:00
Jesse Plamondon-Willard c0ac58f277
fix IsWorldReady being editable by mods 2023-04-02 15:43:58 -04:00
Jesse Plamondon-Willard d2134f0f70
update release notes & tweak recent changes 2023-04-02 15:43:50 -04:00
Jesse Plamondon-Willard d13046edb6
Merge pull request #901 from atravita-mods/develop
Avoid resolving empty folders
2023-04-02 15:39:46 -04:00
Jesse Plamondon-Willard 45903dbf3e
Merge pull request #899 from freyacoded/portable-shell-scripts
Use more portable shebangs
2023-04-02 15:38:58 -04:00
Jesse Plamondon-Willard dc027f4eff
Merge pull request #895 from stylemate/patch-2
Update Korean translations
2023-04-02 15:38:36 -04:00
atravita-mods c12e9d788e
as per comments, remove where in favor of nullchecking id instead 2023-04-02 15:37:40 -04:00
atravita-mods 58ccaf9a1e
avoid resolving empty folders. 2023-04-02 15:37:37 -04:00
Freya Arbjerg fe43ec51d0
Use more portable shebangs 2023-04-02 15:37:17 -04:00
ORMEEHYUNGKEUN CHA 5e578725bf
Update ko.json
fixed blatantly wrong translation
2023-04-02 15:36:44 -04:00
Jesse Plamondon-Willard 7b5566d075
add PerScreen.IsActiveForScreen() 2023-04-02 15:31:15 -04:00
Jesse Plamondon-Willard 7f7561469f
update to FluentHttpClient 4.3.0 2023-04-02 15:30:31 -04:00
Jesse Plamondon-Willard 072efba6dc
add NuGet package readme note about using constants before they're defined 2023-04-02 15:30:26 -04:00
Jesse Plamondon-Willard eb2a9b69bc
remove LargeAddressAware flag
This isn't needed anymore since SMAPI is 64-bit now.
2023-04-02 15:30:14 -04:00
zhiyang7 e618785e93 Fix require permission logic 2023-02-09 17:29:42 +08:00
zhiyang7 3c59a5fc6a Compatibility fix 2023-02-08 18:04:24 +08:00
zhiyang7 48fd5a490b Fix assemblies ref rewrite logic 2023-02-08 13:38:15 +08:00
zhiyang7 a406411a04 Fix Virtual Keyboard mod 2023-02-06 16:09:33 +08:00
zhiyang7 0f94f4609d Update drawImpl logic 2023-02-04 18:01:46 +08:00
zhiyang7 a797608622 Make it bootable 2023-02-04 14:45:02 +08:00
zhiyang7 7c6fc4cd19 Minor fix 2023-02-02 15:56:19 +08:00
zhiyang7 ab67f2b233 Fix Loader project link behavior 2023-02-02 10:23:27 +08:00
zhiyang7 8fcb6e9041 Upgrade to .net5.0-adnroid framework 2023-02-02 09:40:04 +08:00
Jesse Plamondon-Willard b9fe45b091
rework GitHub issue templates to further discourage creating support issues 2023-01-28 16:40:52 -05:00
Jesse Plamondon-Willard 8ee555c07a
suppress findstr installer error for some players 2023-01-22 10:18:37 -05:00
118 changed files with 2009 additions and 3116 deletions

View File

@ -1,30 +0,0 @@
---
name: Bug report
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 https://smapi.io/community.
Replace the instructions below with the bug details.
-->
**Describe the bug**
A clear and concise description of what the bug is. Provide any other details you think might be relevant here.
**To Reproduce**
Exact steps which reproduce the bug, if possible. For example:
1. Load save '...'.
2. Walk to '....'.
3. Click '....'.
4. Error occurs.
**Log file**
Upload your SMAPI log to https://smapi.io/log and post a link here.
**Screenshots**
If applicable, add screenshots to help explain your problem.

8
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@ -0,0 +1,8 @@
blank_issues_enabled: false
contact_links:
- name: Troubleshooting guide for players
url: https://smapi.io/troubleshoot
about: See if your question is already answered first!
- name: Get help or discuss
url: https://smapi.io/help
about: Ask for help from the community, or join the Stardew Valley Discord to ask questions, report issues, or discuss with the SMAPI developer, players, and mod authors. The SMAPI developer is @Pathoschild#0001 on Discord.

37
.github/ISSUE_TEMPLATE/custom.md vendored Normal file
View File

@ -0,0 +1,37 @@
---
name: Create a development task
about: DON'T DO THIS BEFORE READING. This is for specific changes to the code or technical bug reports. See below if something isn't working, you have questions or ideas, or you want to discuss something.
---
<!--
STOP!
Is this a specific development task? Don't create an issue if not!
See https://smapi.io/community if something isn't working, you have questions or ideas, or you want
to discuss something.
If you're absolutely sure it's a specific development task (e.g. a specific bug, not just
'something went wrong on my computer'), edit the template below.
-->
**Describe the bug**
A clear and concise description of what the bug is. Provide any other details you think might be relevant here.
**To Reproduce**
Exact steps which reproduce the bug, if possible. For example:
1. Load save '...'.
2. Walk to '....'.
3. Click '....'.
4. Error occurs.
**Log file**
Upload your SMAPI log to https://smapi.io/log and post a link here.
**Screenshots**
If applicable, add screenshots to help explain your problem.

View File

@ -1,12 +0,0 @@
---
name: Feature request
about: Suggest an idea for SMAPI.
---
<!--
GitHub issues are only used for development tasks. Please don't submit feature requests here!
Instead, see https://smapi.io/community to discuss SMAPI.
-->

View File

@ -1,12 +0,0 @@
---
name: General
about: Create a ticket about something else.
---
<!--
GitHub issues are only used for development tasks.
For support and questions, see https://smapi.io/community instead.
-->

1
.gitignore vendored
View File

@ -44,3 +44,4 @@ src/Loader/libs/
# VSHistory
**/.vshistory/
/build/StardewValleyAndroidStuff/

Binary file not shown.

BIN
build/Mono.Cecil.dll Normal file

Binary file not shown.

BIN
build/Newtonsoft.Json.dll Normal file

Binary file not shown.

BIN
build/Pintail.dll Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
build/TMXTile.dll Normal file

Binary file not shown.

View File

@ -7,13 +7,16 @@ repo. It imports the other MSBuild files as needed.
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<!--set general build properties -->
<Version>3.18.2</Version>
<Version>3.18.4</Version>
<Product>SMAPI</Product>
<LangVersion>latest</LangVersion>
<AssemblySearchPaths>$(AssemblySearchPaths);{GAC}</AssemblySearchPaths>
<DefineConstants Condition="$(OS) == 'Windows_NT' AND '$(BUILD_FOR_MOBILE)' == ''">$(DefineConstants);SMAPI_DEPRECATED;SMAPI_FOR_WINDOWS</DefineConstants>
<DebugSymbols>true</DebugSymbols>
<!--embed symbols for error stack trace line numbers on Linux/macOS: https://github.com/dotnet/runtime/issues/39987-->
<DebugType>embedded</DebugType>
<!--enable nullable annotations, except in .NET Standard 2.0 where they aren't supported-->
<Nullable Condition="'$(TargetFramework)' != 'netstandard2.0'">enable</Nullable>
<NoWarn Condition="'$(TargetFramework)' == 'netstandard2.0'">$(NoWarn);CS8632</NoWarn>

View File

@ -8,6 +8,6 @@
<!-- <BUILD_FOR_MOBILE>SAMSUNG</BUILD_FOR_MOBILE>-->
<!-- <BUILD_FOR_MOBILE>GOOGLE_LEGACY</BUILD_FOR_MOBILE>-->
<COMPILE_WITH_PLUGIN>False</COMPILE_WITH_PLUGIN>
<DefineConstants Condition="'$(BUILD_FOR_MOBILE)' == 'GOOGLE'">$(DefineConstants);SMAPI_FOR_MOBILE;ANDROID_TARGET_GOOGLE;MonoAndroid10</DefineConstants>
<DefineConstants Condition="'$(BUILD_FOR_MOBILE)' == 'GOOGLE'">SMAPI_DEPRECATED;SMAPI_FOR_MOBILE;ANDROID_TARGET_GOOGLE</DefineConstants>
</PropertyGroup>
</Project>

View File

@ -18,7 +18,6 @@ This assumes `find-game-folder.targets` has already been imported and validated.
<Copy SourceFiles="$(TargetDir)\$(TargetName).dll" DestinationFolder="$(GamePath)" />
<Copy SourceFiles="$(TargetDir)\$(TargetName).exe" DestinationFolder="$(GamePath)" Condition="$(OS) == 'Windows_NT'" />
<Copy SourceFiles="$(TargetDir)\$(TargetName)" DestinationFolder="$(GamePath)" Condition="$(OS) != 'Windows_NT'" />
<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" />
@ -62,7 +61,6 @@ This assumes `find-game-folder.targets` has already been imported and validated.
</ItemGroup>
<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)" />
<Copy SourceFiles="@(TranslationFiles)" DestinationFolder="$(GamePath)\Mods\$(AssemblyName)\i18n" />
</Target>
@ -70,12 +68,10 @@ This assumes `find-game-folder.targets` has already been imported and validated.
<!-- toolkit -->
<Target Name="CopyToolkit" Condition="'$(MSBuildProjectName)' == 'SMAPI.Toolkit'" 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'" 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>
</Project>

View File

@ -1,4 +1,4 @@
#!/bin/bash
#!/usr/bin/env bash
#
#
@ -122,7 +122,7 @@ for folder in ${folders[@]}; do
fi
# bundle root files
for name in "StardewModdingAPI" "StardewModdingAPI.dll" "StardewModdingAPI.pdb" "StardewModdingAPI.xml" "steam_appid.txt"; do
for name in "StardewModdingAPI" "StardewModdingAPI.dll" "StardewModdingAPI.xml" "steam_appid.txt"; do
if [ $name == "StardewModdingAPI" ] && [ $folder == "windows" ]; then
name="$name.exe"
fi
@ -134,7 +134,7 @@ for folder in ${folders[@]}; do
cp -r "$smapiBin/i18n" "$bundlePath/smapi-internal"
# bundle smapi-internal
for name in "0Harmony.dll" "0Harmony.xml" "Mono.Cecil.dll" "Mono.Cecil.Mdb.dll" "Mono.Cecil.Pdb.dll" "MonoMod.Common.dll" "Newtonsoft.Json.dll" "Pathoschild.Http.Client.dll" "Pintail.dll" "TMXTile.dll" "SMAPI.Toolkit.dll" "SMAPI.Toolkit.pdb" "SMAPI.Toolkit.xml" "SMAPI.Toolkit.CoreInterfaces.dll" "SMAPI.Toolkit.CoreInterfaces.pdb" "SMAPI.Toolkit.CoreInterfaces.xml" "System.Net.Http.Formatting.dll"; do
for name in "0Harmony.dll" "0Harmony.xml" "Mono.Cecil.dll" "Mono.Cecil.Mdb.dll" "Mono.Cecil.Pdb.dll" "MonoMod.Common.dll" "Newtonsoft.Json.dll" "Pathoschild.Http.Client.dll" "Pintail.dll" "TMXTile.dll" "SMAPI.Toolkit.dll" "SMAPI.Toolkit.xml" "SMAPI.Toolkit.CoreInterfaces.dll" "SMAPI.Toolkit.CoreInterfaces.xml" "System.Net.Http.Formatting.dll"; do
cp "$smapiBin/$name" "$bundlePath/smapi-internal"
done
@ -164,7 +164,6 @@ for folder in ${folders[@]}; do
mkdir "$targetPath" --parents
cp "$fromPath/$modName.dll" "$targetPath"
cp "$fromPath/$modName.pdb" "$targetPath"
cp "$fromPath/manifest.json" "$targetPath"
if [ -d "$fromPath/i18n" ]; then
cp -r "$fromPath/i18n" "$targetPath"

View File

@ -1,4 +1,4 @@
#!/bin/bash
#!/usr/bin/env bash
#
#

View File

@ -1,4 +1,4 @@
#!/bin/bash
#!/usr/bin/env bash
##########
## Read config

View File

@ -142,7 +142,7 @@ foreach ($folder in $folders) {
}
# bundle root files
foreach ($name in @("StardewModdingAPI", "StardewModdingAPI.dll", "StardewModdingAPI.pdb", "StardewModdingAPI.xml", "steam_appid.txt")) {
foreach ($name in @("StardewModdingAPI", "StardewModdingAPI.dll", "StardewModdingAPI.xml", "steam_appid.txt")) {
if ($name -eq "StardewModdingAPI" -and $folder -eq "windows") {
$name = "$name.exe"
}
@ -154,7 +154,7 @@ foreach ($folder in $folders) {
cp -Recurse "$smapiBin/i18n" "$bundlePath/smapi-internal"
# bundle smapi-internal
foreach ($name in @("0Harmony.dll", "0Harmony.xml", "Mono.Cecil.dll", "Mono.Cecil.Mdb.dll", "Mono.Cecil.Pdb.dll", "MonoMod.Common.dll", "Newtonsoft.Json.dll", "Pathoschild.Http.Client.dll", "Pintail.dll", "TMXTile.dll", "SMAPI.Toolkit.dll", "SMAPI.Toolkit.pdb", "SMAPI.Toolkit.xml", "SMAPI.Toolkit.CoreInterfaces.dll", "SMAPI.Toolkit.CoreInterfaces.pdb", "SMAPI.Toolkit.CoreInterfaces.xml", "System.Net.Http.Formatting.dll")) {
foreach ($name in @("0Harmony.dll", "0Harmony.xml", "Mono.Cecil.dll", "Mono.Cecil.Mdb.dll", "Mono.Cecil.Pdb.dll", "MonoMod.Common.dll", "Newtonsoft.Json.dll", "Pathoschild.Http.Client.dll", "Pintail.dll", "TMXTile.dll", "SMAPI.Toolkit.dll", "SMAPI.Toolkit.xml", "SMAPI.Toolkit.CoreInterfaces.dll", "SMAPI.Toolkit.CoreInterfaces.xml", "System.Net.Http.Formatting.dll")) {
cp "$smapiBin/$name" "$bundlePath/smapi-internal"
}
@ -189,7 +189,6 @@ foreach ($folder in $folders) {
mkdir "$targetPath" > $null
cp "$fromPath/$modName.dll" "$targetPath"
cp "$fromPath/$modName.pdb" "$targetPath"
cp "$fromPath/manifest.json" "$targetPath"
if (Test-Path "$fromPath/i18n" -PathType Container) {
cp -Recurse "$fromPath/i18n" "$targetPath"

View File

@ -7,6 +7,48 @@
_If needed, you can update to SMAPI 3.16.0 first and then install the latest version._
-->
## 3.18.4
Released 24 June 2023 for Stardew Valley 1.5.6 or later.
* For players:
* In multiplayer, the game/SMAPI window titles now show whether you're the main player or a farmhand.
* The `test_input` console command now logs input until the command is run again (instead of for 30 seconds).
* Fixed logged SMAPI errors not having line numbers on Linux/macOS.
* Fixed wezterm terminal support on Linux/macoS (thanks to romangraef!).
* Fixed install error if a game folder has an invalid symlink.
* For mod authors:
* Added `--no-prompt` installer command-line argument for automated tools (thanks to NyCodeGHG!).
* Added clearer error message when a map tilesheet has no image source (thanks to atravita!).
* Fixed `Context.HasRemotePlayers` being true when there's no farmhands connected.
* Fixed error loading a mod if it explicitly sets `"MinimumApiVersion": null`.
* Updated Newtonsoft.Json 13.0.2 → 13.0.3 (see [changes](https://github.com/JamesNK/Newtonsoft.Json/releases/tag/13.0.3)) and Pintail 2.2.2 → 2.3.0 (see [changes](https://github.com/Nanoray-pl/Pintail/blob/master/docs/release-notes.md#230)).
* For SMAPI toolkit users:
* Fixed `ModFolder` not being JSON-serializable.
* For the web API:
* Fixed manifest schema format for the `examples` field (thanks to boneskull!).
## 3.18.3
Released 09 April 2023 for Stardew Valley 1.5.6 or later.
* For players:
* Fixed `findstr` installer error for some players.
* Fixed installer error for some Linux players due to a non-portable shebang (thanks to freyacoded!).
* Fixed error using load order overrides when there are broken mods installed (thanks to atravita!).
* Removed `LargeAddressAware` flag on SMAPI (no longer needed since it's 64-bit now).
* Improved translations. Thanks to stylemate (updated Korean)!
* For mod authors:
* Added `IsActiveForScreen()` method to `PerScreen<T>`.
* Updated to [FluentHttpClient](https://github.com/Pathoschild/FluentHttpClient#readme) 4.3.0 (see [changes](https://github.com/Pathoschild/FluentHttpClient/blob/develop/RELEASE-NOTES.md#430)).
* Adjusted `ModContentManager.HandleUnknownFileType` to let mods patch it.
* Fixed `Context.IsWorldReady` being editable by mods.
* For the web UI:
* Updated the JSON validator/schema for Content Patcher 1.29.0.
## 3.18.2
Released 09 January 2023 for Stardew Valley 1.5.6 or later.

View File

@ -99,6 +99,10 @@ There are two places you can put them:
2. Open the `.csproj` file in a text editor (Notepad is fine).
3. Add the properties between the first `<PropertyGroup>` and `</PropertyGroup>` tags you find.
**Note:** you can't use a property before it's defined. That mainly means that when setting
`GameModsPath`, you'll need to either specify `GamePath` manually or put the full path in
`GameModsPath`.
### Available properties
These are the options you can set:
@ -412,28 +416,33 @@ The NuGet package is generated automatically in `StardewModdingAPI.ModBuildConfi
when you compile it.
## Release notes
## 4.1.1
Released 24 June 2023 for SMAPI 3.13.0 or later.
* Replaced `.pdb` files with embedded symbols by default. This fixes logged errors not having line numbers on Linux/macOS.
### 4.1.0
Released 08 January 2023.
Released 08 January 2023 for SMAPI 3.13.0 or later.
* Added `manifest.json` format validation on build (thanks to tylergibbs2!).
* Fixed game DLLs not excluded from the release zip when they're referenced explicitly but `BundleExtraAssemblies` isn't set.
### 4.0.2
Released 09 October 2022.
Released 09 October 2022 for SMAPI 3.13.0 or later.
* Switched to the newer crossplatform `portable` debug symbols (thanks to lanturnalis!).
* Fixed `BundleExtraAssemblies` option being partly case-sensitive.
* Fixed `BundleExtraAssemblies` not applying `All` value to game assemblies.
### 4.0.1
Released 14 April 2022.
Released 14 April 2022 for SMAPI 3.13.0 or later.
* Added detection for Xbox app game folders.
* Fixed "_conflicts between different versions of Microsoft.Win32.Registry_" warnings in recent SMAPI versions.
* Internal refactoring.
### 4.0.0
Released 30 November 2021.
Released 30 November 2021 for SMAPI 3.13.0 or later.
* Updated for Stardew Valley 1.5.5 and SMAPI 3.13.0. (Older versions are no longer supported.)
* Added `IgnoreModFilePaths` option to ignore literal paths.
@ -455,7 +464,7 @@ Released 30 November 2021.
documentation](#configure).
### 3.3.0
Released 30 March 2021.
Released 30 March 2021 for SMAPI 3.0.0 or later.
* Added a build warning when the mod isn't compiled for `Any CPU`.
* Added a `GameFramework` build property set to `MonoGame` or `Xna` based on the platform. This can
@ -464,32 +473,32 @@ Released 30 March 2021.
* The package now suppresses the misleading 'processor architecture mismatch' warnings.
### 3.2.2
Released 23 September 2020.
Released 23 September 2020 for SMAPI 3.0.0 or later.
* Reworked and streamlined how the package is compiled.
* Added [SMAPI-ModTranslationClassBuilder](https://github.com/Pathoschild/SMAPI-ModTranslationClassBuilder)
files to the ignore list.
### 3.2.1
Released 11 September 2020.
Released 11 September 2020 for SMAPI 3.0.0 or later.
* Added more detailed logging.
* Fixed _path's format is not supported_ error when using default `Mods` path in 3.2.
### 3.2.0
Released 07 September 2020.
Released 07 September 2020 for SMAPI 3.0.0 or later.
* Added option to change `Mods` folder path.
* Rewrote documentation to make it easier to read.
### 3.1.0
Released 01 February 2020.
Released 01 February 2020 for SMAPI 3.0.0 or later.
* Added support for semantic versioning 2.0.
* `0Harmony.dll` is now ignored if the mod references Harmony directly (it's bundled with SMAPI).
### 3.0.0
Released 26 November 2019.
Released 26 November 2019 for SMAPI 3.0.0 or later.
* Updated for SMAPI 3.0 and Stardew Valley 1.4.
* Added automatic support for `assets` folders.

View File

@ -32,6 +32,7 @@ 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.
`--no-prompt` | Don't let the installer wait for user input (e.g. for cases where it's being run by a script). If the installer is unable to continue without user input, it'll fail instead.
SMAPI itself recognises five arguments, but these are meant for internal use or testing, and might
change without warning. **On Linux/macOS**, command-line arguments won't work; see _environment

View File

@ -18,10 +18,12 @@
<GenerateSerializationAssemblies>Off</GenerateSerializationAssemblies>
<AndroidUseLatestPlatformSdk>false</AndroidUseLatestPlatformSdk>
<TargetFrameworkVersion>v12.0</TargetFrameworkVersion>
<TargetFramework>net5.0-android</TargetFramework>
<AndroidManifest>Properties\AndroidManifest.xml</AndroidManifest>
<MonoAndroidResourcePrefix>Resources</MonoAndroidResourcePrefix>
<MonoAndroidAssetsPrefix>Assets</MonoAndroidAssetsPrefix>
<AndroidEnableSGenConcurrent>true</AndroidEnableSGenConcurrent>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>True</DebugSymbols>
@ -31,29 +33,31 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<AndroidUseSharedRuntime>false</AndroidUseSharedRuntime>
<AndroidUseSharedRuntime>true</AndroidUseSharedRuntime>
<AndroidLinkMode>None</AndroidLinkMode>
<EmbedAssembliesIntoApk>True</EmbedAssembliesIntoApk>
<AndroidManagedSymbols>false</AndroidManagedSymbols>
<AndroidUseAapt2>false</AndroidUseAapt2>
<AndroidUseAapt2>true</AndroidUseAapt2>
<AndroidSupportedAbis>armeabi-v7a;x86;arm64-v8a;x86_64</AndroidSupportedAbis>
<AndroidDexTool>dx</AndroidDexTool>
<AndroidDexTool>d8</AndroidDexTool>
<!-- <AndroidLinkSkip>Mono.Android;MonoGame.Framework;mscorlib;System.Core;System;System.Drawing.Common;System.Numerics;System.Runtime.Serialization;System.Xml;System.Xml.Linq</AndroidLinkSkip>-->
<AndroidEnableMultiDex>true</AndroidEnableMultiDex>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugSymbols>True</DebugSymbols>
<DebugSymbols>False</DebugSymbols>
<DebugType>portable</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<AndroidManagedSymbols>true</AndroidManagedSymbols>
<AndroidManagedSymbols>false</AndroidManagedSymbols>
<AndroidUseSharedRuntime>false</AndroidUseSharedRuntime>
<AndroidLinkMode>None</AndroidLinkMode>
<EmbedAssembliesIntoApk>True</EmbedAssembliesIntoApk>
<AndroidUseAapt2>false</AndroidUseAapt2>
<AndroidUseAapt2>true</AndroidUseAapt2>
<AndroidSupportedAbis>armeabi-v7a;x86;arm64-v8a;x86_64</AndroidSupportedAbis>
<AndroidDexTool>dx</AndroidDexTool>
<AndroidDexTool>d8</AndroidDexTool>
</PropertyGroup>
<ItemGroup>
<Compile Include="Resources\Resource.designer.cs" />
@ -76,44 +80,12 @@
<AndroidResource Include="Resources\values\styles.xml" />
</ItemGroup>
<ItemGroup>
<Reference Include="Mono.Android" />
<Reference Include="Mono.Security" />
<ProjectReference Include="..\SMAPI\SMAPI.csproj" />
<Reference Include="Microsoft.AppCenter, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null">
<HintPath>..\packages\Microsoft.AppCenter.2.6.4\lib\MonoAndroid403\Microsoft.AppCenter.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.AppCenter.Analytics, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null">
<HintPath>..\packages\Microsoft.AppCenter.Analytics.2.6.4\lib\MonoAndroid403\Microsoft.AppCenter.Analytics.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.AppCenter.Analytics.Android.Bindings, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null">
<HintPath>..\packages\Microsoft.AppCenter.Analytics.2.6.4\lib\MonoAndroid403\Microsoft.AppCenter.Analytics.Android.Bindings.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.AppCenter.Android.Bindings, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null">
<HintPath>..\packages\Microsoft.AppCenter.2.6.4\lib\MonoAndroid403\Microsoft.AppCenter.Android.Bindings.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.AppCenter.Crashes, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null">
<HintPath>..\packages\Microsoft.AppCenter.Crashes.2.6.4\lib\MonoAndroid403\Microsoft.AppCenter.Crashes.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.AppCenter.Crashes.Android.Bindings, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null">
<HintPath>..\packages\Microsoft.AppCenter.Crashes.2.6.4\lib\MonoAndroid403\Microsoft.AppCenter.Crashes.Android.Bindings.dll</HintPath>
<Private>True</Private>
</Reference>
<PackageReference Include="MonoGame.Framework.Android">
<Version>3.6.0.862</Version>
</PackageReference>
<Reference Include="MonoGame.Framework, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null">
<HintPath>..\..\..\Downloads\StardewValleyAndroidStuff\base_1.4.5.145\assemblies\MonoGame.Framework.dll</HintPath>
</Reference>
<Reference Include="MonoGame.Framework.Content.Pipeline">
<HintPath>..\..\..\Downloads\StardewValleyAndroidStuff\base_1.4.5.145\assemblies\MonoGame.Framework.Content.Pipeline.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Xml" />
<Reference Include="System.Core" />
<Reference Include="Mono.Android" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\mipmap\ic_launcher.png" />

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="151" android:versionName="1.4.5.151" android:installLocation="auto" package="com.zane.stardewvalley2" platformBuildVersionCode="29" platformBuildVersionName="9">
<uses-sdk android:minSdkVersion="19" android:targetSdkVersion="29" />
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="156" android:versionName="1.5.6.31" android:installLocation="auto" package="com.zane.stardewvalley2" platformBuildVersionCode="31" platformBuildVersionName="9">
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="31" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
@ -15,6 +15,7 @@
android:allowBackup="true"
android:resizeableActivity="false"
android:debuggable="true"
android:requestLegacyExternalStorage="true"></application>
android:requestLegacyExternalStorage="true">
</application>
<uses-permission android:name="com.android.vending.CHECK_LICENSE" />
</manifest>

View File

@ -1,9 +1,7 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Android.App;
// General Information about an assembly is controlled through the following
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Loader")]
@ -19,11 +17,11 @@ using Android.App;
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]

View File

@ -14,7 +14,7 @@ namespace Loader
{
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Android.Build.Tasks", "1.0.0.0")]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Android.Build.Tasks", "13.2.0.99")]
public partial class Resource
{
@ -25,217 +25,13 @@ namespace Loader
public static void UpdateIdValues()
{
global::StardewModdingAPI.Resource.Attribute.alpha = global::Loader.Resource.Attribute.alpha;
global::StardewModdingAPI.Resource.Attribute.font = global::Loader.Resource.Attribute.font;
global::StardewModdingAPI.Resource.Attribute.fontProviderAuthority = global::Loader.Resource.Attribute.fontProviderAuthority;
global::StardewModdingAPI.Resource.Attribute.fontProviderCerts = global::Loader.Resource.Attribute.fontProviderCerts;
global::StardewModdingAPI.Resource.Attribute.fontProviderFetchStrategy = global::Loader.Resource.Attribute.fontProviderFetchStrategy;
global::StardewModdingAPI.Resource.Attribute.fontProviderFetchTimeout = global::Loader.Resource.Attribute.fontProviderFetchTimeout;
global::StardewModdingAPI.Resource.Attribute.fontProviderPackage = global::Loader.Resource.Attribute.fontProviderPackage;
global::StardewModdingAPI.Resource.Attribute.fontProviderQuery = global::Loader.Resource.Attribute.fontProviderQuery;
global::StardewModdingAPI.Resource.Attribute.fontStyle = global::Loader.Resource.Attribute.fontStyle;
global::StardewModdingAPI.Resource.Attribute.fontVariationSettings = global::Loader.Resource.Attribute.fontVariationSettings;
global::StardewModdingAPI.Resource.Attribute.fontWeight = global::Loader.Resource.Attribute.fontWeight;
global::StardewModdingAPI.Resource.Attribute.ttcIndex = global::Loader.Resource.Attribute.ttcIndex;
global::StardewModdingAPI.Resource.Color.notification_action_color_filter = global::Loader.Resource.Color.notification_action_color_filter;
global::StardewModdingAPI.Resource.Color.notification_icon_bg_color = global::Loader.Resource.Color.notification_icon_bg_color;
global::StardewModdingAPI.Resource.Color.ripple_material_light = global::Loader.Resource.Color.ripple_material_light;
global::StardewModdingAPI.Resource.Color.secondary_text_default_material_light = global::Loader.Resource.Color.secondary_text_default_material_light;
global::StardewModdingAPI.Resource.Dimension.compat_button_inset_horizontal_material = global::Loader.Resource.Dimension.compat_button_inset_horizontal_material;
global::StardewModdingAPI.Resource.Dimension.compat_button_inset_vertical_material = global::Loader.Resource.Dimension.compat_button_inset_vertical_material;
global::StardewModdingAPI.Resource.Dimension.compat_button_padding_horizontal_material = global::Loader.Resource.Dimension.compat_button_padding_horizontal_material;
global::StardewModdingAPI.Resource.Dimension.compat_button_padding_vertical_material = global::Loader.Resource.Dimension.compat_button_padding_vertical_material;
global::StardewModdingAPI.Resource.Dimension.compat_control_corner_material = global::Loader.Resource.Dimension.compat_control_corner_material;
global::StardewModdingAPI.Resource.Dimension.compat_notification_large_icon_max_height = global::Loader.Resource.Dimension.compat_notification_large_icon_max_height;
global::StardewModdingAPI.Resource.Dimension.compat_notification_large_icon_max_width = global::Loader.Resource.Dimension.compat_notification_large_icon_max_width;
global::StardewModdingAPI.Resource.Dimension.notification_action_icon_size = global::Loader.Resource.Dimension.notification_action_icon_size;
global::StardewModdingAPI.Resource.Dimension.notification_action_text_size = global::Loader.Resource.Dimension.notification_action_text_size;
global::StardewModdingAPI.Resource.Dimension.notification_big_circle_margin = global::Loader.Resource.Dimension.notification_big_circle_margin;
global::StardewModdingAPI.Resource.Dimension.notification_content_margin_start = global::Loader.Resource.Dimension.notification_content_margin_start;
global::StardewModdingAPI.Resource.Dimension.notification_large_icon_height = global::Loader.Resource.Dimension.notification_large_icon_height;
global::StardewModdingAPI.Resource.Dimension.notification_large_icon_width = global::Loader.Resource.Dimension.notification_large_icon_width;
global::StardewModdingAPI.Resource.Dimension.notification_main_column_padding_top = global::Loader.Resource.Dimension.notification_main_column_padding_top;
global::StardewModdingAPI.Resource.Dimension.notification_media_narrow_margin = global::Loader.Resource.Dimension.notification_media_narrow_margin;
global::StardewModdingAPI.Resource.Dimension.notification_right_icon_size = global::Loader.Resource.Dimension.notification_right_icon_size;
global::StardewModdingAPI.Resource.Dimension.notification_right_side_padding_top = global::Loader.Resource.Dimension.notification_right_side_padding_top;
global::StardewModdingAPI.Resource.Dimension.notification_small_icon_background_padding = global::Loader.Resource.Dimension.notification_small_icon_background_padding;
global::StardewModdingAPI.Resource.Dimension.notification_small_icon_size_as_large = global::Loader.Resource.Dimension.notification_small_icon_size_as_large;
global::StardewModdingAPI.Resource.Dimension.notification_subtext_size = global::Loader.Resource.Dimension.notification_subtext_size;
global::StardewModdingAPI.Resource.Dimension.notification_top_pad = global::Loader.Resource.Dimension.notification_top_pad;
global::StardewModdingAPI.Resource.Dimension.notification_top_pad_large_text = global::Loader.Resource.Dimension.notification_top_pad_large_text;
global::StardewModdingAPI.Resource.Drawable.notification_action_background = global::Loader.Resource.Drawable.notification_action_background;
global::StardewModdingAPI.Resource.Drawable.notification_bg = global::Loader.Resource.Drawable.notification_bg;
global::StardewModdingAPI.Resource.Drawable.notification_bg_low = global::Loader.Resource.Drawable.notification_bg_low;
global::StardewModdingAPI.Resource.Drawable.notification_bg_low_normal = global::Loader.Resource.Drawable.notification_bg_low_normal;
global::StardewModdingAPI.Resource.Drawable.notification_bg_low_pressed = global::Loader.Resource.Drawable.notification_bg_low_pressed;
global::StardewModdingAPI.Resource.Drawable.notification_bg_normal = global::Loader.Resource.Drawable.notification_bg_normal;
global::StardewModdingAPI.Resource.Drawable.notification_bg_normal_pressed = global::Loader.Resource.Drawable.notification_bg_normal_pressed;
global::StardewModdingAPI.Resource.Drawable.notification_icon_background = global::Loader.Resource.Drawable.notification_icon_background;
global::StardewModdingAPI.Resource.Drawable.notification_template_icon_bg = global::Loader.Resource.Drawable.notification_template_icon_bg;
global::StardewModdingAPI.Resource.Drawable.notification_template_icon_low_bg = global::Loader.Resource.Drawable.notification_template_icon_low_bg;
global::StardewModdingAPI.Resource.Drawable.notification_tile_bg = global::Loader.Resource.Drawable.notification_tile_bg;
global::StardewModdingAPI.Resource.Drawable.notify_panel_notification_icon_bg = global::Loader.Resource.Drawable.notify_panel_notification_icon_bg;
global::StardewModdingAPI.Resource.Id.actions = global::Loader.Resource.Id.actions;
global::StardewModdingAPI.Resource.Id.action_container = global::Loader.Resource.Id.action_container;
global::StardewModdingAPI.Resource.Id.action_divider = global::Loader.Resource.Id.action_divider;
global::StardewModdingAPI.Resource.Id.action_image = global::Loader.Resource.Id.action_image;
global::StardewModdingAPI.Resource.Id.action_text = global::Loader.Resource.Id.action_text;
global::StardewModdingAPI.Resource.Id.appIcon = global::Loader.Resource.Id.appIcon;
global::StardewModdingAPI.Resource.Id.async = global::Loader.Resource.Id.async;
global::StardewModdingAPI.Resource.Id.blocking = global::Loader.Resource.Id.blocking;
global::StardewModdingAPI.Resource.Id.chronometer = global::Loader.Resource.Id.chronometer;
global::StardewModdingAPI.Resource.Id.description = global::Loader.Resource.Id.description;
global::StardewModdingAPI.Resource.Id.forever = global::Loader.Resource.Id.forever;
global::StardewModdingAPI.Resource.Id.icon = global::Loader.Resource.Id.icon;
global::StardewModdingAPI.Resource.Id.icon_group = global::Loader.Resource.Id.icon_group;
global::StardewModdingAPI.Resource.Id.info = global::Loader.Resource.Id.info;
global::StardewModdingAPI.Resource.Id.italic = global::Loader.Resource.Id.italic;
global::StardewModdingAPI.Resource.Id.line1 = global::Loader.Resource.Id.line1;
global::StardewModdingAPI.Resource.Id.line3 = global::Loader.Resource.Id.line3;
global::StardewModdingAPI.Resource.Id.normal = global::Loader.Resource.Id.normal;
global::StardewModdingAPI.Resource.Id.notificationLayout = global::Loader.Resource.Id.notificationLayout;
global::StardewModdingAPI.Resource.Id.notification_background = global::Loader.Resource.Id.notification_background;
global::StardewModdingAPI.Resource.Id.notification_main_column = global::Loader.Resource.Id.notification_main_column;
global::StardewModdingAPI.Resource.Id.notification_main_column_container = global::Loader.Resource.Id.notification_main_column_container;
global::StardewModdingAPI.Resource.Id.progress_bar = global::Loader.Resource.Id.progress_bar;
global::StardewModdingAPI.Resource.Id.progress_bar_frame = global::Loader.Resource.Id.progress_bar_frame;
global::StardewModdingAPI.Resource.Id.progress_text = global::Loader.Resource.Id.progress_text;
global::StardewModdingAPI.Resource.Id.right_icon = global::Loader.Resource.Id.right_icon;
global::StardewModdingAPI.Resource.Id.right_side = global::Loader.Resource.Id.right_side;
global::StardewModdingAPI.Resource.Id.spacer = global::Loader.Resource.Id.spacer;
global::StardewModdingAPI.Resource.Id.tag_transition_group = global::Loader.Resource.Id.tag_transition_group;
global::StardewModdingAPI.Resource.Id.tag_unhandled_key_event_manager = global::Loader.Resource.Id.tag_unhandled_key_event_manager;
global::StardewModdingAPI.Resource.Id.tag_unhandled_key_listeners = global::Loader.Resource.Id.tag_unhandled_key_listeners;
global::StardewModdingAPI.Resource.Id.text = global::Loader.Resource.Id.text;
global::StardewModdingAPI.Resource.Id.text2 = global::Loader.Resource.Id.text2;
global::StardewModdingAPI.Resource.Id.time = global::Loader.Resource.Id.time;
global::StardewModdingAPI.Resource.Id.time_remaining = global::Loader.Resource.Id.time_remaining;
global::StardewModdingAPI.Resource.Id.title = global::Loader.Resource.Id.title;
global::StardewModdingAPI.Resource.Integer.status_bar_notification_info_maxnum = global::Loader.Resource.Integer.status_bar_notification_info_maxnum;
global::StardewModdingAPI.Resource.Layout.notification_action = global::Loader.Resource.Layout.notification_action;
global::StardewModdingAPI.Resource.Layout.notification_action_tombstone = global::Loader.Resource.Layout.notification_action_tombstone;
global::StardewModdingAPI.Resource.Layout.notification_template_custom_big = global::Loader.Resource.Layout.notification_template_custom_big;
global::StardewModdingAPI.Resource.Layout.notification_template_icon_group = global::Loader.Resource.Layout.notification_template_icon_group;
global::StardewModdingAPI.Resource.Layout.notification_template_part_chronometer = global::Loader.Resource.Layout.notification_template_part_chronometer;
global::StardewModdingAPI.Resource.Layout.notification_template_part_time = global::Loader.Resource.Layout.notification_template_part_time;
global::StardewModdingAPI.Resource.Layout.status_bar_ongoing_event_progress_bar = global::Loader.Resource.Layout.status_bar_ongoing_event_progress_bar;
global::StardewModdingAPI.Resource.String.app_name = global::Loader.Resource.String.app_name;
global::StardewModdingAPI.Resource.String.hello = global::Loader.Resource.String.hello;
global::StardewModdingAPI.Resource.String.kilobytes_per_second = global::Loader.Resource.String.kilobytes_per_second;
global::StardewModdingAPI.Resource.String.notification_download_complete = global::Loader.Resource.String.notification_download_complete;
global::StardewModdingAPI.Resource.String.notification_download_failed = global::Loader.Resource.String.notification_download_failed;
global::StardewModdingAPI.Resource.String.state_completed = global::Loader.Resource.String.state_completed;
global::StardewModdingAPI.Resource.String.state_connecting = global::Loader.Resource.String.state_connecting;
global::StardewModdingAPI.Resource.String.state_downloading = global::Loader.Resource.String.state_downloading;
global::StardewModdingAPI.Resource.String.state_failed = global::Loader.Resource.String.state_failed;
global::StardewModdingAPI.Resource.String.state_failed_cancelled = global::Loader.Resource.String.state_failed_cancelled;
global::StardewModdingAPI.Resource.String.state_failed_fetching_url = global::Loader.Resource.String.state_failed_fetching_url;
global::StardewModdingAPI.Resource.String.state_failed_sdcard_full = global::Loader.Resource.String.state_failed_sdcard_full;
global::StardewModdingAPI.Resource.String.state_failed_unlicensed = global::Loader.Resource.String.state_failed_unlicensed;
global::StardewModdingAPI.Resource.String.state_fetching_url = global::Loader.Resource.String.state_fetching_url;
global::StardewModdingAPI.Resource.String.state_idle = global::Loader.Resource.String.state_idle;
global::StardewModdingAPI.Resource.String.state_paused_by_request = global::Loader.Resource.String.state_paused_by_request;
global::StardewModdingAPI.Resource.String.state_paused_network_setup_failure = global::Loader.Resource.String.state_paused_network_setup_failure;
global::StardewModdingAPI.Resource.String.state_paused_network_unavailable = global::Loader.Resource.String.state_paused_network_unavailable;
global::StardewModdingAPI.Resource.String.state_paused_roaming = global::Loader.Resource.String.state_paused_roaming;
global::StardewModdingAPI.Resource.String.state_paused_sdcard_unavailable = global::Loader.Resource.String.state_paused_sdcard_unavailable;
global::StardewModdingAPI.Resource.String.state_paused_wifi_disabled = global::Loader.Resource.String.state_paused_wifi_disabled;
global::StardewModdingAPI.Resource.String.state_paused_wifi_unavailable = global::Loader.Resource.String.state_paused_wifi_unavailable;
global::StardewModdingAPI.Resource.String.state_unknown = global::Loader.Resource.String.state_unknown;
global::StardewModdingAPI.Resource.String.status_bar_notification_info_overflow = global::Loader.Resource.String.status_bar_notification_info_overflow;
global::StardewModdingAPI.Resource.String.time_remaining = global::Loader.Resource.String.time_remaining;
global::StardewModdingAPI.Resource.String.time_remaining_notification = global::Loader.Resource.String.time_remaining_notification;
global::StardewModdingAPI.Resource.Style.ButtonBackground = global::Loader.Resource.Style.ButtonBackground;
global::StardewModdingAPI.Resource.Style.NotificationText = global::Loader.Resource.Style.NotificationText;
global::StardewModdingAPI.Resource.Style.NotificationTextSecondary = global::Loader.Resource.Style.NotificationTextSecondary;
global::StardewModdingAPI.Resource.Style.NotificationTextShadow = global::Loader.Resource.Style.NotificationTextShadow;
global::StardewModdingAPI.Resource.Style.NotificationTitle = global::Loader.Resource.Style.NotificationTitle;
global::StardewModdingAPI.Resource.Style.TextAppearance_Compat_Notification = global::Loader.Resource.Style.TextAppearance_Compat_Notification;
global::StardewModdingAPI.Resource.Style.TextAppearance_Compat_Notification_Info = global::Loader.Resource.Style.TextAppearance_Compat_Notification_Info;
global::StardewModdingAPI.Resource.Style.TextAppearance_Compat_Notification_Line2 = global::Loader.Resource.Style.TextAppearance_Compat_Notification_Line2;
global::StardewModdingAPI.Resource.Style.TextAppearance_Compat_Notification_Time = global::Loader.Resource.Style.TextAppearance_Compat_Notification_Time;
global::StardewModdingAPI.Resource.Style.TextAppearance_Compat_Notification_Title = global::Loader.Resource.Style.TextAppearance_Compat_Notification_Title;
global::StardewModdingAPI.Resource.Style.Widget_Compat_NotificationActionContainer = global::Loader.Resource.Style.Widget_Compat_NotificationActionContainer;
global::StardewModdingAPI.Resource.Style.Widget_Compat_NotificationActionText = global::Loader.Resource.Style.Widget_Compat_NotificationActionText;
global::StardewModdingAPI.Resource.Styleable.ColorStateListItem = global::Loader.Resource.Styleable.ColorStateListItem;
global::StardewModdingAPI.Resource.Styleable.ColorStateListItem_alpha = global::Loader.Resource.Styleable.ColorStateListItem_alpha;
global::StardewModdingAPI.Resource.Styleable.ColorStateListItem_android_alpha = global::Loader.Resource.Styleable.ColorStateListItem_android_alpha;
global::StardewModdingAPI.Resource.Styleable.ColorStateListItem_android_color = global::Loader.Resource.Styleable.ColorStateListItem_android_color;
global::StardewModdingAPI.Resource.Styleable.FontFamily = global::Loader.Resource.Styleable.FontFamily;
global::StardewModdingAPI.Resource.Styleable.FontFamilyFont = global::Loader.Resource.Styleable.FontFamilyFont;
global::StardewModdingAPI.Resource.Styleable.FontFamilyFont_android_font = global::Loader.Resource.Styleable.FontFamilyFont_android_font;
global::StardewModdingAPI.Resource.Styleable.FontFamilyFont_android_fontStyle = global::Loader.Resource.Styleable.FontFamilyFont_android_fontStyle;
global::StardewModdingAPI.Resource.Styleable.FontFamilyFont_android_fontVariationSettings = global::Loader.Resource.Styleable.FontFamilyFont_android_fontVariationSettings;
global::StardewModdingAPI.Resource.Styleable.FontFamilyFont_android_fontWeight = global::Loader.Resource.Styleable.FontFamilyFont_android_fontWeight;
global::StardewModdingAPI.Resource.Styleable.FontFamilyFont_android_ttcIndex = global::Loader.Resource.Styleable.FontFamilyFont_android_ttcIndex;
global::StardewModdingAPI.Resource.Styleable.FontFamilyFont_font = global::Loader.Resource.Styleable.FontFamilyFont_font;
global::StardewModdingAPI.Resource.Styleable.FontFamilyFont_fontStyle = global::Loader.Resource.Styleable.FontFamilyFont_fontStyle;
global::StardewModdingAPI.Resource.Styleable.FontFamilyFont_fontVariationSettings = global::Loader.Resource.Styleable.FontFamilyFont_fontVariationSettings;
global::StardewModdingAPI.Resource.Styleable.FontFamilyFont_fontWeight = global::Loader.Resource.Styleable.FontFamilyFont_fontWeight;
global::StardewModdingAPI.Resource.Styleable.FontFamilyFont_ttcIndex = global::Loader.Resource.Styleable.FontFamilyFont_ttcIndex;
global::StardewModdingAPI.Resource.Styleable.FontFamily_fontProviderAuthority = global::Loader.Resource.Styleable.FontFamily_fontProviderAuthority;
global::StardewModdingAPI.Resource.Styleable.FontFamily_fontProviderCerts = global::Loader.Resource.Styleable.FontFamily_fontProviderCerts;
global::StardewModdingAPI.Resource.Styleable.FontFamily_fontProviderFetchStrategy = global::Loader.Resource.Styleable.FontFamily_fontProviderFetchStrategy;
global::StardewModdingAPI.Resource.Styleable.FontFamily_fontProviderFetchTimeout = global::Loader.Resource.Styleable.FontFamily_fontProviderFetchTimeout;
global::StardewModdingAPI.Resource.Styleable.FontFamily_fontProviderPackage = global::Loader.Resource.Styleable.FontFamily_fontProviderPackage;
global::StardewModdingAPI.Resource.Styleable.FontFamily_fontProviderQuery = global::Loader.Resource.Styleable.FontFamily_fontProviderQuery;
global::StardewModdingAPI.Resource.Styleable.GradientColor = global::Loader.Resource.Styleable.GradientColor;
global::StardewModdingAPI.Resource.Styleable.GradientColorItem = global::Loader.Resource.Styleable.GradientColorItem;
global::StardewModdingAPI.Resource.Styleable.GradientColorItem_android_color = global::Loader.Resource.Styleable.GradientColorItem_android_color;
global::StardewModdingAPI.Resource.Styleable.GradientColorItem_android_offset = global::Loader.Resource.Styleable.GradientColorItem_android_offset;
global::StardewModdingAPI.Resource.Styleable.GradientColor_android_centerColor = global::Loader.Resource.Styleable.GradientColor_android_centerColor;
global::StardewModdingAPI.Resource.Styleable.GradientColor_android_centerX = global::Loader.Resource.Styleable.GradientColor_android_centerX;
global::StardewModdingAPI.Resource.Styleable.GradientColor_android_centerY = global::Loader.Resource.Styleable.GradientColor_android_centerY;
global::StardewModdingAPI.Resource.Styleable.GradientColor_android_endColor = global::Loader.Resource.Styleable.GradientColor_android_endColor;
global::StardewModdingAPI.Resource.Styleable.GradientColor_android_endX = global::Loader.Resource.Styleable.GradientColor_android_endX;
global::StardewModdingAPI.Resource.Styleable.GradientColor_android_endY = global::Loader.Resource.Styleable.GradientColor_android_endY;
global::StardewModdingAPI.Resource.Styleable.GradientColor_android_gradientRadius = global::Loader.Resource.Styleable.GradientColor_android_gradientRadius;
global::StardewModdingAPI.Resource.Styleable.GradientColor_android_startColor = global::Loader.Resource.Styleable.GradientColor_android_startColor;
global::StardewModdingAPI.Resource.Styleable.GradientColor_android_startX = global::Loader.Resource.Styleable.GradientColor_android_startX;
global::StardewModdingAPI.Resource.Styleable.GradientColor_android_startY = global::Loader.Resource.Styleable.GradientColor_android_startY;
global::StardewModdingAPI.Resource.Styleable.GradientColor_android_tileMode = global::Loader.Resource.Styleable.GradientColor_android_tileMode;
global::StardewModdingAPI.Resource.Styleable.GradientColor_android_type = global::Loader.Resource.Styleable.GradientColor_android_type;
}
public partial class Attribute
{
// aapt resource value: 0x7f010000
public const int alpha = 2130771968;
// aapt resource value: 0x7f010008
public const int font = 2130771976;
// aapt resource value: 0x7f010001
public const int fontProviderAuthority = 2130771969;
// aapt resource value: 0x7f010004
public const int fontProviderCerts = 2130771972;
// aapt resource value: 0x7f010005
public const int fontProviderFetchStrategy = 2130771973;
// aapt resource value: 0x7f010006
public const int fontProviderFetchTimeout = 2130771974;
// aapt resource value: 0x7f010002
public const int fontProviderPackage = 2130771970;
// aapt resource value: 0x7f010003
public const int fontProviderQuery = 2130771971;
// aapt resource value: 0x7f010007
public const int fontStyle = 2130771975;
// aapt resource value: 0x7f01000a
public const int fontVariationSettings = 2130771978;
// aapt resource value: 0x7f010009
public const int fontWeight = 2130771977;
// aapt resource value: 0x7f01000b
public const int ttcIndex = 2130771979;
static Attribute()
{
global::Android.Runtime.ResourceIdManager.UpdateIdValues();
@ -249,29 +45,17 @@ namespace Loader
public partial class Color
{
// aapt resource value: 0x7f080006
public const int colorAccent = 2131230726;
// aapt resource value: 0x7F010000
public const int colorAccent = 2130771968;
// aapt resource value: 0x7f080004
public const int colorPrimary = 2131230724;
// aapt resource value: 0x7F010001
public const int colorPrimary = 2130771969;
// aapt resource value: 0x7f080005
public const int colorPrimaryDark = 2131230725;
// aapt resource value: 0x7F010002
public const int colorPrimaryDark = 2130771970;
// aapt resource value: 0x7f080007
public const int ic_launcher_background = 2131230727;
// aapt resource value: 0x7f080000
public const int notification_action_color_filter = 2131230720;
// aapt resource value: 0x7f080001
public const int notification_icon_bg_color = 2131230721;
// aapt resource value: 0x7f080002
public const int ripple_material_light = 2131230722;
// aapt resource value: 0x7f080003
public const int secondary_text_default_material_light = 2131230723;
// aapt resource value: 0x7F010003
public const int ic_launcher_background = 2130771971;
static Color()
{
@ -283,135 +67,20 @@ namespace Loader
}
}
public partial class Dimension
{
// aapt resource value: 0x7f070004
public const int compat_button_inset_horizontal_material = 2131165188;
// aapt resource value: 0x7f070005
public const int compat_button_inset_vertical_material = 2131165189;
// aapt resource value: 0x7f070006
public const int compat_button_padding_horizontal_material = 2131165190;
// aapt resource value: 0x7f070007
public const int compat_button_padding_vertical_material = 2131165191;
// aapt resource value: 0x7f070008
public const int compat_control_corner_material = 2131165192;
// aapt resource value: 0x7f070009
public const int compat_notification_large_icon_max_height = 2131165193;
// aapt resource value: 0x7f07000a
public const int compat_notification_large_icon_max_width = 2131165194;
// aapt resource value: 0x7f07000b
public const int notification_action_icon_size = 2131165195;
// aapt resource value: 0x7f07000c
public const int notification_action_text_size = 2131165196;
// aapt resource value: 0x7f07000d
public const int notification_big_circle_margin = 2131165197;
// aapt resource value: 0x7f070001
public const int notification_content_margin_start = 2131165185;
// aapt resource value: 0x7f07000e
public const int notification_large_icon_height = 2131165198;
// aapt resource value: 0x7f07000f
public const int notification_large_icon_width = 2131165199;
// aapt resource value: 0x7f070002
public const int notification_main_column_padding_top = 2131165186;
// aapt resource value: 0x7f070003
public const int notification_media_narrow_margin = 2131165187;
// aapt resource value: 0x7f070010
public const int notification_right_icon_size = 2131165200;
// aapt resource value: 0x7f070000
public const int notification_right_side_padding_top = 2131165184;
// aapt resource value: 0x7f070011
public const int notification_small_icon_background_padding = 2131165201;
// aapt resource value: 0x7f070012
public const int notification_small_icon_size_as_large = 2131165202;
// aapt resource value: 0x7f070013
public const int notification_subtext_size = 2131165203;
// aapt resource value: 0x7f070014
public const int notification_top_pad = 2131165204;
// aapt resource value: 0x7f070015
public const int notification_top_pad_large_text = 2131165205;
static Dimension()
{
global::Android.Runtime.ResourceIdManager.UpdateIdValues();
}
private Dimension()
{
}
}
public partial class Drawable
{
// aapt resource value: 0x7f020000
// aapt resource value: 0x7F020000
public const int icon = 2130837504;
// aapt resource value: 0x7f020001
public const int notification_action_background = 2130837505;
// aapt resource value: 0x7F020001
public const int splash = 2130837505;
// aapt resource value: 0x7f020002
public const int notification_bg = 2130837506;
// aapt resource value: 0x7F020002
public const int splash_logos_crop = 2130837506;
// aapt resource value: 0x7f020003
public const int notification_bg_low = 2130837507;
// aapt resource value: 0x7f020004
public const int notification_bg_low_normal = 2130837508;
// aapt resource value: 0x7f020005
public const int notification_bg_low_pressed = 2130837509;
// aapt resource value: 0x7f020006
public const int notification_bg_normal = 2130837510;
// aapt resource value: 0x7f020007
public const int notification_bg_normal_pressed = 2130837511;
// aapt resource value: 0x7f020008
public const int notification_icon_background = 2130837512;
// aapt resource value: 0x7f02000e
public const int notification_template_icon_bg = 2130837518;
// aapt resource value: 0x7f02000f
public const int notification_template_icon_low_bg = 2130837519;
// aapt resource value: 0x7f020009
public const int notification_tile_bg = 2130837513;
// aapt resource value: 0x7f02000a
public const int notify_panel_notification_icon_bg = 2130837514;
// aapt resource value: 0x7f02000b
public const int splash = 2130837515;
// aapt resource value: 0x7f02000c
public const int splash_logos_crop = 2130837516;
// aapt resource value: 0x7f02000d
public const int splash_screen = 2130837517;
// aapt resource value: 0x7F020003
public const int splash_screen = 2130837507;
static Drawable()
{
@ -423,190 +92,19 @@ namespace Loader
}
}
public partial class Id
{
// aapt resource value: 0x7f0a000d
public const int action_container = 2131361805;
// aapt resource value: 0x7f0a0019
public const int action_divider = 2131361817;
// aapt resource value: 0x7f0a000e
public const int action_image = 2131361806;
// aapt resource value: 0x7f0a000f
public const int action_text = 2131361807;
// aapt resource value: 0x7f0a001a
public const int actions = 2131361818;
// aapt resource value: 0x7f0a001d
public const int appIcon = 2131361821;
// aapt resource value: 0x7f0a0008
public const int async = 2131361800;
// aapt resource value: 0x7f0a0009
public const int blocking = 2131361801;
// aapt resource value: 0x7f0a0018
public const int chronometer = 2131361816;
// aapt resource value: 0x7f0a0022
public const int description = 2131361826;
// aapt resource value: 0x7f0a000a
public const int forever = 2131361802;
// aapt resource value: 0x7f0a0011
public const int icon = 2131361809;
// aapt resource value: 0x7f0a001b
public const int icon_group = 2131361819;
// aapt resource value: 0x7f0a0014
public const int info = 2131361812;
// aapt resource value: 0x7f0a000b
public const int italic = 2131361803;
// aapt resource value: 0x7f0a0000
public const int line1 = 2131361792;
// aapt resource value: 0x7f0a0001
public const int line3 = 2131361793;
// aapt resource value: 0x7f0a000c
public const int normal = 2131361804;
// aapt resource value: 0x7f0a001c
public const int notificationLayout = 2131361820;
// aapt resource value: 0x7f0a0016
public const int notification_background = 2131361814;
// aapt resource value: 0x7f0a0012
public const int notification_main_column = 2131361810;
// aapt resource value: 0x7f0a0010
public const int notification_main_column_container = 2131361808;
// aapt resource value: 0x7f0a0021
public const int progress_bar = 2131361825;
// aapt resource value: 0x7f0a0020
public const int progress_bar_frame = 2131361824;
// aapt resource value: 0x7f0a001e
public const int progress_text = 2131361822;
// aapt resource value: 0x7f0a0015
public const int right_icon = 2131361813;
// aapt resource value: 0x7f0a0013
public const int right_side = 2131361811;
// aapt resource value: 0x7f0a0023
public const int spacer = 2131361827;
// aapt resource value: 0x7f0a0002
public const int tag_transition_group = 2131361794;
// aapt resource value: 0x7f0a0003
public const int tag_unhandled_key_event_manager = 2131361795;
// aapt resource value: 0x7f0a0004
public const int tag_unhandled_key_listeners = 2131361796;
// aapt resource value: 0x7f0a0005
public const int text = 2131361797;
// aapt resource value: 0x7f0a0006
public const int text2 = 2131361798;
// aapt resource value: 0x7f0a0017
public const int time = 2131361815;
// aapt resource value: 0x7f0a001f
public const int time_remaining = 2131361823;
// aapt resource value: 0x7f0a0007
public const int title = 2131361799;
static Id()
{
global::Android.Runtime.ResourceIdManager.UpdateIdValues();
}
private Id()
{
}
}
public partial class Integer
{
// aapt resource value: 0x7f0b0000
public const int status_bar_notification_info_maxnum = 2131427328;
static Integer()
{
global::Android.Runtime.ResourceIdManager.UpdateIdValues();
}
private Integer()
{
}
}
public partial class Layout
{
// aapt resource value: 0x7f040000
public const int notification_action = 2130968576;
// aapt resource value: 0x7f040001
public const int notification_action_tombstone = 2130968577;
// aapt resource value: 0x7f040002
public const int notification_template_custom_big = 2130968578;
// aapt resource value: 0x7f040003
public const int notification_template_icon_group = 2130968579;
// aapt resource value: 0x7f040004
public const int notification_template_part_chronometer = 2130968580;
// aapt resource value: 0x7f040005
public const int notification_template_part_time = 2130968581;
// aapt resource value: 0x7f040006
public const int status_bar_ongoing_event_progress_bar = 2130968582;
static Layout()
{
global::Android.Runtime.ResourceIdManager.UpdateIdValues();
}
private Layout()
{
}
}
public partial class Mipmap
{
// aapt resource value: 0x7f030000
// aapt resource value: 0x7F030000
public const int ic_launcher = 2130903040;
// aapt resource value: 0x7f030001
// aapt resource value: 0x7F030001
public const int ic_launcher_background = 2130903041;
// aapt resource value: 0x7f030002
// aapt resource value: 0x7F030002
public const int ic_launcher_foreground = 2130903042;
// aapt resource value: 0x7f030003
// aapt resource value: 0x7F030003
public const int ic_launcher_round = 2130903043;
static Mipmap()
@ -622,86 +120,14 @@ namespace Loader
public partial class String
{
// aapt resource value: 0x7f06001a
public const int action_settings = 2131099674;
// aapt resource value: 0x7F040000
public const int action_settings = 2130968576;
// aapt resource value: 0x7f060002
public const int app_name = 2131099650;
// aapt resource value: 0x7F040001
public const int app_name = 2130968577;
// aapt resource value: 0x7f060001
public const int hello = 2131099649;
// aapt resource value: 0x7f060003
public const int kilobytes_per_second = 2131099651;
// aapt resource value: 0x7f060004
public const int notification_download_complete = 2131099652;
// aapt resource value: 0x7f060005
public const int notification_download_failed = 2131099653;
// aapt resource value: 0x7f060006
public const int state_completed = 2131099654;
// aapt resource value: 0x7f060007
public const int state_connecting = 2131099655;
// aapt resource value: 0x7f060008
public const int state_downloading = 2131099656;
// aapt resource value: 0x7f060009
public const int state_failed = 2131099657;
// aapt resource value: 0x7f06000a
public const int state_failed_cancelled = 2131099658;
// aapt resource value: 0x7f06000b
public const int state_failed_fetching_url = 2131099659;
// aapt resource value: 0x7f06000c
public const int state_failed_sdcard_full = 2131099660;
// aapt resource value: 0x7f06000d
public const int state_failed_unlicensed = 2131099661;
// aapt resource value: 0x7f06000e
public const int state_fetching_url = 2131099662;
// aapt resource value: 0x7f06000f
public const int state_idle = 2131099663;
// aapt resource value: 0x7f060010
public const int state_paused_by_request = 2131099664;
// aapt resource value: 0x7f060011
public const int state_paused_network_setup_failure = 2131099665;
// aapt resource value: 0x7f060012
public const int state_paused_network_unavailable = 2131099666;
// aapt resource value: 0x7f060013
public const int state_paused_roaming = 2131099667;
// aapt resource value: 0x7f060014
public const int state_paused_sdcard_unavailable = 2131099668;
// aapt resource value: 0x7f060015
public const int state_paused_wifi_disabled = 2131099669;
// aapt resource value: 0x7f060016
public const int state_paused_wifi_unavailable = 2131099670;
// aapt resource value: 0x7f060017
public const int state_unknown = 2131099671;
// aapt resource value: 0x7f060000
public const int status_bar_notification_info_overflow = 2131099648;
// aapt resource value: 0x7f060018
public const int time_remaining = 2131099672;
// aapt resource value: 0x7f060019
public const int time_remaining_notification = 2131099673;
// aapt resource value: 0x7F040002
public const int hello = 2130968578;
static String()
{
@ -716,44 +142,8 @@ namespace Loader
public partial class Style
{
// aapt resource value: 0x7f09000a
public const int ButtonBackground = 2131296266;
// aapt resource value: 0x7f090008
public const int NotificationText = 2131296264;
// aapt resource value: 0x7f090007
public const int NotificationTextSecondary = 2131296263;
// aapt resource value: 0x7f09000b
public const int NotificationTextShadow = 2131296267;
// aapt resource value: 0x7f090009
public const int NotificationTitle = 2131296265;
// aapt resource value: 0x7f090000
public const int TextAppearance_Compat_Notification = 2131296256;
// aapt resource value: 0x7f090001
public const int TextAppearance_Compat_Notification_Info = 2131296257;
// aapt resource value: 0x7f090006
public const int TextAppearance_Compat_Notification_Line2 = 2131296262;
// aapt resource value: 0x7f090002
public const int TextAppearance_Compat_Notification_Time = 2131296258;
// aapt resource value: 0x7f090003
public const int TextAppearance_Compat_Notification_Title = 2131296259;
// aapt resource value: 0x7f09000c
public const int Theme_Splash = 2131296268;
// aapt resource value: 0x7f090004
public const int Widget_Compat_NotificationActionContainer = 2131296260;
// aapt resource value: 0x7f090005
public const int Widget_Compat_NotificationActionText = 2131296261;
// aapt resource value: 0x7F050000
public const int Theme_Splash = 2131034112;
static Style()
{
@ -768,8 +158,8 @@ namespace Loader
public partial class Xml
{
// aapt resource value: 0x7f050000
public const int provider_paths = 2131034112;
// aapt resource value: 0x7F060000
public const int provider_paths = 2131099648;
static Xml()
{
@ -780,161 +170,6 @@ namespace Loader
{
}
}
public partial class Styleable
{
public static int[] ColorStateListItem = new int[] {
16843173,
16843551,
2130771968};
// aapt resource value: 2
public const int ColorStateListItem_alpha = 2;
// aapt resource value: 1
public const int ColorStateListItem_android_alpha = 1;
// aapt resource value: 0
public const int ColorStateListItem_android_color = 0;
public static int[] FontFamily = new int[] {
2130771969,
2130771970,
2130771971,
2130771972,
2130771973,
2130771974};
// aapt resource value: 0
public const int FontFamily_fontProviderAuthority = 0;
// aapt resource value: 3
public const int FontFamily_fontProviderCerts = 3;
// aapt resource value: 4
public const int FontFamily_fontProviderFetchStrategy = 4;
// aapt resource value: 5
public const int FontFamily_fontProviderFetchTimeout = 5;
// aapt resource value: 1
public const int FontFamily_fontProviderPackage = 1;
// aapt resource value: 2
public const int FontFamily_fontProviderQuery = 2;
public static int[] FontFamilyFont = new int[] {
16844082,
16844083,
16844095,
16844143,
16844144,
2130771975,
2130771976,
2130771977,
2130771978,
2130771979};
// aapt resource value: 0
public const int FontFamilyFont_android_font = 0;
// aapt resource value: 2
public const int FontFamilyFont_android_fontStyle = 2;
// aapt resource value: 4
public const int FontFamilyFont_android_fontVariationSettings = 4;
// aapt resource value: 1
public const int FontFamilyFont_android_fontWeight = 1;
// aapt resource value: 3
public const int FontFamilyFont_android_ttcIndex = 3;
// aapt resource value: 6
public const int FontFamilyFont_font = 6;
// aapt resource value: 5
public const int FontFamilyFont_fontStyle = 5;
// aapt resource value: 8
public const int FontFamilyFont_fontVariationSettings = 8;
// aapt resource value: 7
public const int FontFamilyFont_fontWeight = 7;
// aapt resource value: 9
public const int FontFamilyFont_ttcIndex = 9;
public static int[] GradientColor = new int[] {
16843165,
16843166,
16843169,
16843170,
16843171,
16843172,
16843265,
16843275,
16844048,
16844049,
16844050,
16844051};
// aapt resource value: 7
public const int GradientColor_android_centerColor = 7;
// aapt resource value: 3
public const int GradientColor_android_centerX = 3;
// aapt resource value: 4
public const int GradientColor_android_centerY = 4;
// aapt resource value: 1
public const int GradientColor_android_endColor = 1;
// aapt resource value: 10
public const int GradientColor_android_endX = 10;
// aapt resource value: 11
public const int GradientColor_android_endY = 11;
// aapt resource value: 5
public const int GradientColor_android_gradientRadius = 5;
// aapt resource value: 0
public const int GradientColor_android_startColor = 0;
// aapt resource value: 8
public const int GradientColor_android_startX = 8;
// aapt resource value: 9
public const int GradientColor_android_startY = 9;
// aapt resource value: 6
public const int GradientColor_android_tileMode = 6;
// aapt resource value: 2
public const int GradientColor_android_type = 2;
public static int[] GradientColorItem = new int[] {
16843173,
16844052};
// aapt resource value: 0
public const int GradientColorItem_android_color = 0;
// aapt resource value: 1
public const int GradientColorItem_android_offset = 1;
static Styleable()
{
global::Android.Runtime.ResourceIdManager.UpdateIdValues();
}
private Styleable()
{
}
}
}
}
#pragma warning restore 1591

View File

@ -1,6 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.AppCenter" version="2.6.4" targetFramework="monoandroid10.0" />
<package id="Microsoft.AppCenter.Analytics" version="2.6.4" targetFramework="monoandroid10.0" />
<package id="Microsoft.AppCenter.Crashes" version="2.6.4" targetFramework="monoandroid10.0" />
</packages>
</packages>

View File

@ -0,0 +1,42 @@
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
#nullable enable
namespace System.Linq
{
public static class EnumerableExtension
{
/// <summary>Produces a sequence of tuples with elements from the two specified sequences.</summary>
/// <param name="first">The first sequence to merge.</param>
/// <param name="second">The second sequence to merge.</param>
/// <typeparam name="TFirst">The type of the elements of the first input sequence.</typeparam>
/// <typeparam name="TSecond">The type of the elements of the second input sequence.</typeparam>
/// <returns>A sequence of tuples with elements taken from the first and second sequences, in that order.</returns>
public static IEnumerable<(TFirst First, TSecond Second)> Zip<TFirst, TSecond>(
this IEnumerable<TFirst> first,
IEnumerable<TSecond> second)
{
if (first == null)
throw new ArgumentNullException("first");
if (second == null)
throw new ArgumentNullException("second");
return ZipIterator<TFirst, TSecond>(first, second);
}
#nullable disable
private static IEnumerable<(TFirst First, TSecond Second)> ZipIterator<TFirst, TSecond>(
IEnumerable<TFirst> first,
IEnumerable<TSecond> second)
{
using (IEnumerator<TFirst> e1 = first.GetEnumerator())
{
using (IEnumerator<TSecond> e2 = second.GetEnumerator())
{
while (e1.MoveNext() && e2.MoveNext())
yield return (e1.Current, e2.Current);
}
}
}
}
}

View File

@ -0,0 +1,29 @@
using System.Runtime.CompilerServices;
#nullable enable
namespace System.Collections.Generic
{
/// <summary>An <see cref="T:System.Collections.Generic.IEqualityComparer`1" /> that uses reference equality (<see cref="M:System.Object.ReferenceEquals(System.Object,System.Object)" />) instead of value equality (<see cref="M:System.Object.Equals(System.Object)" />) when comparing two object instances.</summary>
public sealed class ReferenceEqualityComparer : IEqualityComparer<object?>, IEqualityComparer
{
private ReferenceEqualityComparer()
{
}
/// <summary>Gets the singleton <see cref="T:System.Collections.Generic.ReferenceEqualityComparer" /> instance.</summary>
public static ReferenceEqualityComparer Instance { get; } = new ReferenceEqualityComparer();
/// <summary>Determines whether two object references refer to the same object instance.</summary>
/// <param name="x">The first object to compare.</param>
/// <param name="y">The second object to compare.</param>
/// <returns>
/// <see langword="true" /> if both <paramref name="x" /> and <paramref name="y" /> refer to the same object instance or if both are <see langword="null" />; otherwise, <see langword="false" />.</returns>
public bool Equals(object? x, object? y) => x == y;
/// <summary>Returns a hash code for the specified object. The returned hash code is based on the object identity, not on the contents of the object.</summary>
/// <param name="obj">The object for which to retrieve the hash code.</param>
/// <returns>A hash code for the identity of <paramref name="obj" />.</returns>
public int GetHashCode(object? obj) => RuntimeHelpers.GetHashCode(obj);
}
}

View File

@ -0,0 +1,72 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>8.0.30703</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{E5FE4C97-1A7C-4A97-9904-7351CA020C7F}</ProjectGuid>
<ProjectTypeGuids>{EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>MobilePatch</RootNamespace>
<AssemblyName>MobilePatch</AssemblyName>
<FileAlignment>512</FileAlignment>
<AndroidResgenFile>Resources\Resource.Designer.cs</AndroidResgenFile>
<GenerateSerializationAssemblies>Off</GenerateSerializationAssemblies>
<AndroidUseLatestPlatformSdk>false</AndroidUseLatestPlatformSdk>
<TargetFrameworkVersion>v12.0</TargetFrameworkVersion>
<TargetFramework>net5.0-android</TargetFramework>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="Mono.Android" />
<Reference Include="mscorlib" />
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Mobile\EnumerableExtension.cs" />
<Compile Include="Mobile\MobileMemoryExtension.cs" />
<Compile Include="Mobile\MobileTypeExtension.cs" />
<Compile Include="Mobile\ReferenceEqualityComparer.cs" />
<Compile Include="Resources\Resource.Designer.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<None Include="Resources\AboutResources.txt" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\values\strings.xml" />
</ItemGroup>
<ItemGroup>
<Folder Include="Resources\drawable\" />
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

View File

@ -0,0 +1,30 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Android.App;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("MobilePatch")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("MobilePatch")]
[assembly: AssemblyCopyright("Copyright © 2023")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: ComVisible(false)]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View File

@ -0,0 +1,59 @@
#pragma warning disable 1591
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
[assembly: global::Android.Runtime.ResourceDesignerAttribute("MobilePatch.Resource", IsApplication=false)]
namespace MobilePatch
{
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Android.Build.Tasks", "13.2.0.99")]
public partial class Resource
{
static Resource()
{
global::Android.Runtime.ResourceIdManager.UpdateIdValues();
}
public partial class Attribute
{
static Attribute()
{
global::Android.Runtime.ResourceIdManager.UpdateIdValues();
}
private Attribute()
{
}
}
public partial class String
{
// aapt resource value: 0x7F010000
public static int app_name = 2130771968;
// aapt resource value: 0x7F010001
public static int hello = 2130771969;
static String()
{
global::Android.Runtime.ResourceIdManager.UpdateIdValues();
}
private String()
{
}
}
}
}
#pragma warning restore 1591

View File

@ -1,4 +1,4 @@
<resources>
<string name="hello">Hello World, Click Me!</string>
<string name="app_name">SMAPI</string>
<string name="app_name">MobilePatch</string>
</resources>

View File

@ -47,8 +47,8 @@ namespace StardewModdingApi.Installer
yield return GetInstallPath("StardewModdingAPI.dll");
yield return GetInstallPath("StardewModdingAPI.exe");
yield return GetInstallPath("StardewModdingAPI.exe.config");
yield return GetInstallPath("StardewModdingAPI.exe.mdb"); // Linux/macOS only
yield return GetInstallPath("StardewModdingAPI.pdb"); // Windows only
yield return GetInstallPath("StardewModdingAPI.exe.mdb"); // before 3.18.4 (Linux/macOS only)
yield return GetInstallPath("StardewModdingAPI.pdb"); // before 3.18.4 (Windows only)
yield return GetInstallPath("StardewModdingAPI.runtimeconfig.json");
yield return GetInstallPath("StardewModdingAPI.xml");
yield return GetInstallPath("smapi-internal");
@ -135,35 +135,24 @@ namespace StardewModdingApi.Installer
Console.Title = $"SMAPI {context.GetInstallerVersion()} installer on {context.Platform} {context.PlatformName}";
Console.WriteLine();
/****
** Check if correct installer
****/
#if SMAPI_FOR_WINDOWS
if (context.IsUnix)
{
this.PrintError($"This is the installer for Windows. Run the 'install on {context.Platform}.{(context.Platform == Platform.Mac ? "command" : "sh")}' file instead.");
Console.ReadLine();
return;
}
#else
if (context.IsWindows)
{
this.PrintError($"This is the installer for Linux/macOS. Run the 'install on Windows.exe' file instead.");
Console.ReadLine();
return;
}
#endif
/****
** read command-line arguments
****/
// get action from CLI
// get input mode
bool allowUserInput = !args.Contains("--no-prompt");
// get action
bool installArg = args.Contains("--install");
bool uninstallArg = args.Contains("--uninstall");
if (installArg && uninstallArg)
{
this.PrintError("You can't specify both --install and --uninstall command-line flags.");
Console.ReadLine();
this.AwaitConfirmation(allowUserInput);
return;
}
if (!allowUserInput && !installArg && !uninstallArg)
{
this.PrintError("You must specify --install or --uninstall when running with --no-prompt.");
return;
}
@ -175,12 +164,31 @@ namespace StardewModdingApi.Installer
gamePathArg = args[pathIndex];
}
/****
** Check if correct installer
****/
#if SMAPI_FOR_WINDOWS
if (context.IsUnix)
{
this.PrintError($"This is the installer for Windows. Run the 'install on {context.Platform}.{(context.Platform == Platform.Mac ? "command" : "sh")}' file instead.");
this.AwaitConfirmation(allowUserInput);
return;
}
#else
if (context.IsWindows)
{
this.PrintError($"This is the installer for Linux/macOS. Run the 'install on Windows.exe' file instead.");
this.AwaitConfirmation(allowUserInput);
return;
}
#endif
/*********
** Step 2: choose a theme (can't auto-detect on Linux/macOS)
*********/
MonitorColorScheme scheme = MonitorColorScheme.AutoDetect;
if (context.IsUnix)
if (context.IsUnix && allowUserInput)
{
/****
** print header
@ -245,7 +253,7 @@ namespace StardewModdingApi.Installer
if (installDir == null)
{
this.PrintError("Failed finding your game path.");
Console.ReadLine();
this.AwaitConfirmation(allowUserInput);
return;
}
@ -262,7 +270,7 @@ namespace StardewModdingApi.Installer
if (!File.Exists(paths.GameDllPath))
{
this.PrintError("The detected game install path doesn't contain a Stardew Valley executable.");
Console.ReadLine();
this.AwaitConfirmation(allowUserInput);
return;
}
Console.Clear();
@ -340,7 +348,7 @@ namespace StardewModdingApi.Installer
if (context.IsUnix && File.Exists(paths.BackupLaunchScriptPath))
{
this.PrintDebug("Removing SMAPI launcher...");
this.InteractivelyDelete(paths.VanillaLaunchScriptPath);
this.InteractivelyDelete(paths.VanillaLaunchScriptPath, allowUserInput);
File.Move(paths.BackupLaunchScriptPath, paths.VanillaLaunchScriptPath);
}
@ -352,7 +360,7 @@ namespace StardewModdingApi.Installer
{
this.PrintDebug(action == ScriptAction.Install ? "Removing previous SMAPI files..." : "Removing SMAPI files...");
foreach (string path in removePaths)
this.InteractivelyDelete(path);
this.InteractivelyDelete(path, allowUserInput);
}
// move global save data folder (changed in 3.2)
@ -364,7 +372,7 @@ namespace StardewModdingApi.Installer
if (oldDir.Exists)
{
if (newDir.Exists)
this.InteractivelyDelete(oldDir.FullName);
this.InteractivelyDelete(oldDir.FullName, allowUserInput);
else
oldDir.MoveTo(newDir.FullName);
}
@ -379,7 +387,7 @@ namespace StardewModdingApi.Installer
this.PrintDebug("Adding SMAPI files...");
foreach (FileSystemInfo sourceEntry in paths.BundleDir.EnumerateFileSystemInfos().Where(this.ShouldCopy))
{
this.InteractivelyDelete(Path.Combine(paths.GameDir.FullName, sourceEntry.Name));
this.InteractivelyDelete(Path.Combine(paths.GameDir.FullName, sourceEntry.Name), allowUserInput);
this.RecursiveCopy(sourceEntry, paths.GameDir);
}
@ -394,7 +402,7 @@ namespace StardewModdingApi.Installer
if (!File.Exists(paths.BackupLaunchScriptPath))
File.Move(paths.VanillaLaunchScriptPath, paths.BackupLaunchScriptPath);
else
this.InteractivelyDelete(paths.VanillaLaunchScriptPath);
this.InteractivelyDelete(paths.VanillaLaunchScriptPath, allowUserInput);
}
// add new launcher
@ -464,7 +472,7 @@ namespace StardewModdingApi.Installer
// remove existing folder
if (targetFolder.Exists)
this.InteractivelyDelete(targetFolder.FullName);
this.InteractivelyDelete(targetFolder.FullName, allowUserInput);
// copy files
this.RecursiveCopy(sourceMod.Directory, paths.ModsDir, filter: this.ShouldCopy);
@ -482,7 +490,7 @@ namespace StardewModdingApi.Installer
#if SMAPI_DEPRECATED
// remove obsolete appdata mods
this.InteractivelyRemoveAppDataMods(paths.ModsDir, bundledModsDir);
this.InteractivelyRemoveAppDataMods(paths.ModsDir, bundledModsDir, allowUserInput);
#endif
}
}
@ -513,7 +521,7 @@ namespace StardewModdingApi.Installer
);
}
Console.ReadKey();
this.AwaitConfirmation(allowUserInput);
}
@ -581,7 +589,8 @@ namespace StardewModdingApi.Installer
/// <summary>Interactively delete a file or folder path, and block until deletion completes.</summary>
/// <param name="path">The file or folder path.</param>
private void InteractivelyDelete(string path)
/// <param name="allowUserInput">Whether the installer can ask for user input from the terminal.</param>
private void InteractivelyDelete(string path, bool allowUserInput)
{
while (true)
{
@ -594,7 +603,7 @@ namespace StardewModdingApi.Installer
{
this.PrintError($"Oops! The installer couldn't delete {path}: [{ex.GetType().Name}] {ex.Message}.");
this.PrintError("Try rebooting your computer and then run the installer again. If that doesn't work, try deleting it yourself then press any key to retry.");
Console.ReadKey();
this.AwaitConfirmation(allowUserInput);
}
}
}
@ -814,7 +823,8 @@ namespace StardewModdingApi.Installer
/// <summary>Interactively move mods out of the app data directory.</summary>
/// <param name="properModsDir">The directory which should contain all mods.</param>
/// <param name="packagedModsDir">The installer directory containing packaged mods.</param>
private void InteractivelyRemoveAppDataMods(DirectoryInfo properModsDir, DirectoryInfo packagedModsDir)
/// <param name="allowUserInput">Whether the installer can ask for user input from the terminal.</param>
private void InteractivelyRemoveAppDataMods(DirectoryInfo properModsDir, DirectoryInfo packagedModsDir, bool allowUserInput)
{
// get packaged mods to delete
string[] packagedModNames = packagedModsDir.GetDirectories().Select(p => p.Name).ToArray();
@ -841,7 +851,7 @@ namespace StardewModdingApi.Installer
if (isDir && packagedModNames.Contains(entry.Name, StringComparer.OrdinalIgnoreCase))
{
this.PrintDebug($" Deleting {entry.Name} because it's bundled into SMAPI...");
this.InteractivelyDelete(entry.FullName);
this.InteractivelyDelete(entry.FullName, allowUserInput);
continue;
}
@ -906,5 +916,13 @@ namespace StardewModdingApi.Installer
_ => true
};
}
/// <summary>Wait until the user presses enter to confirm, if user input is allowed.</summary>
/// <param name="allowUserInput">Whether the installer can ask for user input from the terminal.</param>
private void AwaitConfirmation(bool allowUserInput)
{
if (allowUserInput)
Console.ReadLine();
}
}
}

View File

@ -1,4 +1,4 @@
#!/bin/bash
#!/usr/bin/env bash
cd "`dirname "$0"`"
internal/linux/SMAPI.Installer

View File

@ -4,7 +4,9 @@ setlocal enabledelayedexpansion
SET installerDir="%~dp0"
REM make sure we're not running within a zip folder
echo %installerDir% | findstr /C:"%TEMP%" 1>nul
REM The error level is usually 0 (install dir contains temp path), 1 (it doesn't), or 9009 (findstr doesn't exist due to a Windows issue).
REM If the command doesn't exist, just skip this check.
echo %installerDir% | findstr /C:"%TEMP%" 1>nul 2>null
if %ERRORLEVEL% EQU 0 (
echo Oops! It looks like you're running the installer from inside a zip file. Make sure you unzip the download first.
echo.

View File

@ -1,4 +1,4 @@
#!/bin/bash
#!/usr/bin/env bash
cd "`dirname "$0"`"

View File

@ -93,7 +93,7 @@ else
# run in terminal
if [ "$USE_CURRENT_SHELL" == "false" ]; then
# select terminal (prefer xterm for best compatibility, then known supported terminals)
for terminal in xterm gnome-terminal kitty terminator xfce4-terminal konsole terminal termite alacritty mate-terminal x-terminal-emulator; do
for terminal in xterm gnome-terminal kitty terminator xfce4-terminal konsole terminal termite alacritty mate-terminal x-terminal-emulator wezterm; do
if command -v "$terminal" 2>/dev/null; then
export TERMINAL_NAME=$terminal
break;
@ -132,6 +132,11 @@ else
exec "$TERMINAL_NAME" -- env TERM=xterm $LAUNCH_FILE "$@"
;;
wezterm)
# consumes all arguments after start --. does not copy working directory automatically, needs --cwd for that.
exec "$TERMINAL_NAME" start --cwd "$(pwd)" -- env TERM=xterm $LAUNCH_FILE "$@"
;;
kitty)
# consumes all trailing arguments
exec "$TERMINAL_NAME" env TERM=xterm $LAUNCH_FILE "$@"

View File

@ -15,7 +15,9 @@ namespace StardewModdingAPI.Internal.ConsoleWriting
private readonly IDictionary<ConsoleLogLevel, ConsoleColor>? Colors;
/// <summary>Whether the current console supports color formatting.</summary>
// [MemberNotNullWhen(true, nameof(ColorfulConsoleWriter.Colors))]
#if !SMAPI_FOR_MOBILE
[MemberNotNullWhen(true, nameof(ColorfulConsoleWriter.Colors))]
#endif
private bool SupportsColor { get; }

View File

@ -6,9 +6,9 @@
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="3.10.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.2" />
<PackageReference Include="NUnit" Version="3.13.3" />
<PackageReference Include="NUnit3TestAdapter" Version="4.3.1" PrivateAssets="all" IncludeAssets="runtime; build; native; contentfiles; analyzers; buildtransitive" />
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" PrivateAssets="all" IncludeAssets="runtime; build; native; contentfiles; analyzers; buildtransitive" />
</ItemGroup>
<ItemGroup>

View File

@ -10,7 +10,7 @@
<!--NuGet package-->
<PackageId>Pathoschild.Stardew.ModBuildConfig</PackageId>
<Title>Build package for SMAPI mods</Title>
<Version>4.1.0</Version>
<Version>4.1.1</Version>
<Authors>Pathoschild</Authors>
<Description>Automates the build configuration for crossplatform Stardew Valley SMAPI mods. For SMAPI 3.13.0 or later.</Description>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
@ -24,7 +24,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.Build.Utilities.Core" Version="16.10" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<!--
This is imported through Microsoft.Build.Utilities.Core. When installed by a mod, NuGet

View File

@ -8,8 +8,9 @@
** Set build options
**********************************************-->
<PropertyGroup>
<!-- enable line numbers in stack traces -->
<!-- enable line numbers in stack traces (needs to embedded for Linux/macOS: https://github.com/dotnet/runtime/issues/39987) -->
<DebugSymbols>true</DebugSymbols>
<DebugType>embedded</DebugType>
<!-- don't create the 'refs' folder (which isn't useful for mods) -->
<ProduceReferenceAssembly>false</ProduceReferenceAssembly>

View File

@ -1,4 +1,3 @@
using System;
using System.Diagnostics.CodeAnalysis;
namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Other
@ -10,11 +9,8 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Other
/*********
** Fields
*********/
/// <summary>The number of seconds for which to log input.</summary>
private readonly int LogSeconds = 30;
/// <summary>When the command should stop printing input, or <c>null</c> if currently disabled.</summary>
private long? ExpiryTicks;
/// <summary>Whether the command should print input.</summary>
private bool Enabled;
/*********
@ -30,21 +26,12 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Other
/// <param name="args">The command arguments.</param>
public override void Handle(IMonitor monitor, string command, ArgumentParser args)
{
this.ExpiryTicks = DateTime.UtcNow.Add(TimeSpan.FromSeconds(this.LogSeconds)).Ticks;
monitor.Log($"OK, logging all player input for {this.LogSeconds} seconds.", LogLevel.Info);
}
this.Enabled = !this.Enabled;
/// <summary>Perform any logic needed on update tick.</summary>
/// <param name="monitor">Writes messages to the console and log file.</param>
public override void OnUpdated(IMonitor monitor)
{
// handle expiry
if (this.ExpiryTicks != null && this.ExpiryTicks <= DateTime.UtcNow.Ticks)
{
monitor.Log("No longer logging input.", LogLevel.Info);
this.ExpiryTicks = null;
return;
}
monitor.Log(
this.Enabled ? "OK, logging all player input until you run this command again." : "OK, no longer logging player input.",
LogLevel.Info
);
}
/// <summary>Perform any logic when input is received.</summary>
@ -52,7 +39,7 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Other
/// <param name="button">The button that was pressed.</param>
public override void OnButtonPressed(IMonitor monitor, SButton button)
{
if (this.ExpiryTicks != null)
if (this.Enabled)
monitor.Log($"Pressed {button}", LogLevel.Info);
}
}

View File

@ -1,53 +0,0 @@
#if SMAPI_FOR_MOBILE
using System.Linq;
using System.Reflection;
using StardewValley;
using StardewValley.Mobile;
namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Other
{
/// <summary>A command which sends a debug command to the game.</summary>
internal class ZoomCommand : TrainerCommand
{
/*********
** Public methods
*********/
/// <summary>Construct an instance.</summary>
public ZoomCommand()
: base("zoom", "Modify game's zoom level.\n\nUsage: zoom <zoomLevel>\n- zoomLevel: the target zoomLevel (a number).\nFor example, 'zoom 1.5' set zoom level to 1.5 * NativeZoomLevel.") { }
/// <summary>Handle the command.</summary>
/// <param name="monitor">Writes messages to the console and log file.</param>
/// <param name="command">The command name.</param>
/// <param name="args">The command arguments.</param>
public override void Handle(IMonitor monitor, string command, ArgumentParser args)
{
// submit command
decimal zoomLevel;
if (!args.Any())
{
zoomLevel = 1.0m;
}
else if (!args.TryGetDecimal(0, "zoomLevel", out zoomLevel, min: 0.1m, max: 10m))
return;
object viewport = typeof(Game1).GetField("viewport", BindingFlags.Static | BindingFlags.Public).GetValue(null);
PropertyInfo x = viewport.GetType().GetProperty("X");
PropertyInfo y = viewport.GetType().GetProperty("Y");
int oldX = (int)x.GetValue(viewport);
int oldY = (int)y.GetValue(viewport);
FieldInfo _lastPinchZoomLevel = typeof(PinchZoom).GetField("_lastPinchZoomLevel", BindingFlags.Instance | BindingFlags.NonPublic);
FieldInfo _pinchZoomLevel = typeof(PinchZoom).GetField("_pinchZoomLevel", BindingFlags.Instance | BindingFlags.NonPublic);
Game1.options.zoomLevel = Game1.NativeZoomLevel * (float)zoomLevel;
float oldZoom = (float)_lastPinchZoomLevel.GetValue(PinchZoom.Instance);
_lastPinchZoomLevel.SetValue(PinchZoom.Instance, _pinchZoomLevel.GetValue(PinchZoom.Instance));
_pinchZoomLevel.SetValue(PinchZoom.Instance, Game1.options.zoomLevel);
Game1.game1.refreshWindowSettings();
PinchZoom.Instance.Center();
WeatherDebrisManager.Instance.RepositionOnZoomChange(oldX, oldY, (int)x.GetValue(viewport), (int)y.GetValue(viewport), oldZoom, Game1.options.zoomLevel);
RainManager.Instance.UpdateRainPositionForPinchZoom((float)(oldX - (int)x.GetValue(viewport)), (float)(oldY - (int)y.GetValue(viewport)));
// show result
monitor.Log("Zoom level changed.", LogLevel.Info);
}
}
}
#endif

View File

@ -30,16 +30,16 @@
<ItemGroup Condition="'$(BUILD_FOR_MOBILE)' != ''">
<Reference Include="MonoGame.Framework">
<HintPath>..\..\..\Downloads\StardewValleyAndroidStuff\base_1.4.5.145\assemblies\MonoGame.Framework.dll</HintPath>
<HintPath>..\..\build\StardewValleyAndroidStuff\base_1.5.6.39\assemblies\MonoGame.Framework.dll</HintPath>
</Reference>
<Reference Include="StardewModdingAPI">
<HintPath>..\SMAPI\bin\Release\StardewModdingAPI.dll</HintPath>
<HintPath>..\SMAPI\bin\Debug\StardewModdingAPI.dll</HintPath>
</Reference>
<Reference Include="StardewValley">
<HintPath>..\..\..\Downloads\StardewValleyAndroidStuff\base_1.4.5.151\assemblies_decrypt\StardewValley.dll</HintPath>
<HintPath>..\..\build\StardewValleyAndroidStuff\base_1.5.6.39\assemblies\StardewValley.dll</HintPath>
</Reference>
<Reference Include="StardewValley.GameData">
<HintPath>..\..\..\Downloads\StardewValleyAndroidStuff\base_1.4.5.151\assemblies_decrypt\StardewValley.GameData.dll</HintPath>
<HintPath>..\..\build\StardewValleyAndroidStuff\base_1.5.6.39\assemblies\StardewValley.GameData.dll</HintPath>
</Reference>
</ItemGroup>

View File

@ -1,9 +1,9 @@
{
"Name": "Console Commands",
"Author": "SMAPI",
"Version": "3.18.2",
"Version": "3.18.4",
"Description": "Adds SMAPI console commands that let you manipulate the game.",
"UniqueID": "SMAPI.ConsoleCommands",
"EntryDll": "ConsoleCommands.dll",
"MinimumApiVersion": "3.18.2"
"MinimumApiVersion": "3.18.4"
}

View File

@ -1,9 +1,9 @@
{
"Name": "Error Handler",
"Author": "SMAPI",
"Version": "3.18.2",
"Version": "3.18.4",
"Description": "Handles some common vanilla errors to log more useful info or avoid breaking the game.",
"UniqueID": "SMAPI.ErrorHandler",
"EntryDll": "ErrorHandler.dll",
"MinimumApiVersion": "3.18.2"
"MinimumApiVersion": "3.18.4"
}

View File

@ -32,7 +32,7 @@
<HintPath>..\SMAPI\bin\Release\StardewModdingAPI.dll</HintPath>
</Reference>
<Reference Include="StardewValley">
<HintPath>..\..\..\Downloads\StardewValleyAndroidStuff\base_1.4.5.151\assemblies_decrypt\StardewValley.dll</HintPath>
<HintPath>..\..\build\StardewValleyAndroidStuff\base_1.4.5.151\assemblies_decrypt\StardewValley.dll</HintPath>
</Reference>
</ItemGroup>

View File

@ -1,9 +1,9 @@
{
"Name": "Save Backup",
"Author": "SMAPI",
"Version": "3.18.2",
"Version": "3.18.4",
"Description": "Automatically backs up all your saves once per day into its folder.",
"UniqueID": "SMAPI.SaveBackup",
"EntryDll": "SaveBackup.dll",
"MinimumApiVersion": "3.18.2"
"MinimumApiVersion": "3.18.4"
}

View File

@ -1,74 +1,58 @@
using System;
using System.Collections.Concurrent;
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using StardewModdingAPI.Events;
using StardewModdingAPI.Framework;
using StardewModdingAPI.Framework.Input;
using StardewValley;
using StardewValley.Menus;
using System.Reflection;
using Microsoft.Xna.Framework.Input;
using static StardewModdingAPI.Mods.VirtualKeyboard.ModConfig;
using System.Threading.Tasks;
using Microsoft.Xna.Framework.Graphics;
namespace StardewModdingAPI.Mods.VirtualKeyboard
{
class KeyButton
{
private readonly IModHelper helper;
private readonly IMonitor Monitor;
private readonly Rectangle buttonRectangle;
private readonly Rectangle ButtonRectangle;
private object buttonPressed;
private object buttonReleased;
private readonly MethodBase RaiseButtonPressed;
private readonly MethodBase RaiseButtonReleased;
private readonly SButton buttonKey;
private readonly float transparency;
private readonly string alias;
private readonly string command;
public bool hidden;
private bool raisingPressed = false;
private bool raisingReleased = false;
private readonly SButton ButtonKey;
private readonly float Transparency;
private readonly string Alias;
private readonly string Command;
public bool Hidden;
private bool RaisingPressed;
private bool RaisingReleased;
public KeyButton(IModHelper helper, VirtualButton buttonDefine, IMonitor monitor)
{
this.Monitor = monitor;
this.helper = helper;
this.hidden = true;
this.buttonRectangle = new Rectangle(buttonDefine.rectangle.X, buttonDefine.rectangle.Y, buttonDefine.rectangle.Width, buttonDefine.rectangle.Height);
this.buttonKey = buttonDefine.key;
this.Hidden = true;
this.ButtonRectangle = new Rectangle(buttonDefine.rectangle.X, buttonDefine.rectangle.Y, buttonDefine.rectangle.Width, buttonDefine.rectangle.Height);
this.ButtonKey = buttonDefine.key;
if (buttonDefine.alias == null)
this.alias = this.buttonKey.ToString();
this.Alias = this.ButtonKey.ToString();
else
this.alias = buttonDefine.alias;
this.command = buttonDefine.command;
this.Alias = buttonDefine.alias;
this.Command = buttonDefine.command;
if (buttonDefine.transparency <= 0.01f || buttonDefine.transparency > 1f)
{
buttonDefine.transparency = 0.5f;
}
this.transparency = buttonDefine.transparency;
this.Transparency = buttonDefine.transparency;
helper.Events.Display.Rendered += this.OnRendered;
helper.Events.Input.ButtonReleased += this.EventInputButtonReleased;
helper.Events.Input.ButtonPressed += this.EventInputButtonPressed;
}
private object GetSCore(IModHelper helper)
private bool ShouldTrigger(Vector2 screenPixels, SButton button)
{
MainActivity activity = this.helper.Reflection.GetField<MainActivity>(typeof(MainActivity), "instance").GetValue();
object score = activity.GetType().GetField("core", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(activity);
return score;
}
private bool shouldTrigger(Vector2 screenPixels, SButton button)
{
if (this.buttonRectangle.Contains(screenPixels.X * Game1.options.zoomLevel, screenPixels.Y * Game1.options.zoomLevel) && !this.hidden && button == SButton.MouseLeft)
if (this.ButtonRectangle.Contains(screenPixels.X, screenPixels.Y) && !this.Hidden && button == SButton.MouseLeft)
{
if (!this.hidden)
if (!this.Hidden)
Toolbar.toolbarPressed = true;
return true;
}
@ -77,34 +61,34 @@ namespace StardewModdingAPI.Mods.VirtualKeyboard
private void EventInputButtonPressed(object sender, ButtonPressedEventArgs e)
{
if (this.raisingPressed)
if (this.RaisingPressed)
{
return;
}
Vector2 screenPixels = e.Cursor.ScreenPixels;
if (this.buttonKey != SButton.None && this.shouldTrigger(screenPixels, e.Button))
Vector2 screenPixels = Utility.ModifyCoordinatesForUIScale(e.Cursor.ScreenPixels);
if (this.ButtonKey != SButton.None && this.ShouldTrigger(screenPixels, e.Button))
{
object input = this.helper.Reflection.GetField<object>(typeof(Game1), "input").GetValue();
this.raisingPressed = true;
input.GetType().GetMethod("OverrideButton").Invoke(input, new object[] { this.buttonKey, true });
this.raisingPressed = false;
this.RaisingPressed = true;
SInputState input = Game1.input as SInputState;
input?.OverrideButton(this.ButtonKey, true);
this.RaisingPressed = false;
}
}
private void EventInputButtonReleased(object sender, ButtonReleasedEventArgs e)
{
if (this.raisingReleased)
if (this.RaisingReleased)
{
return;
}
Vector2 screenPixels = e.Cursor.ScreenPixels;
if (this.shouldTrigger(screenPixels, e.Button))
Vector2 screenPixels = Utility.ModifyCoordinatesForUIScale(e.Cursor.ScreenPixels);
if (this.ShouldTrigger(screenPixels, e.Button))
{
if (this.buttonKey == SButton.RightWindows)
if (this.ButtonKey == SButton.RightWindows)
{
KeyboardInput.Show("Command", "", "", false).ContinueWith<string>(delegate (Task<string> s) {
KeyboardInput.Show("Command", "").ContinueWith(delegate (Task<string> s) {
string command;
command = s.Result;
if (command.Length > 0)
@ -115,36 +99,31 @@ namespace StardewModdingAPI.Mods.VirtualKeyboard
});
return;
}
if (this.buttonKey == SButton.RightControl)
if (this.ButtonKey == SButton.RightControl)
{
SGameConsole.Instance.Show();
return;
}
if (!string.IsNullOrEmpty(this.command))
if (!string.IsNullOrEmpty(this.Command))
{
this.SendCommand(this.command);
this.SendCommand(this.Command);
return;
}
object input = this.helper.Reflection.GetField<object>(typeof(Game1), "input").GetValue();
this.raisingReleased = true;
input.GetType().GetMethod("OverrideButton").Invoke(input, new object[] { this.buttonKey, false });
this.raisingReleased = false;
this.RaisingReleased = true;
SInputState input = Game1.input as SInputState;
input?.OverrideButton(this.ButtonKey, false);
this.RaisingReleased = false;
}
}
private void SendCommand(string command)
{
object score = this.GetSCore(this.helper);
ConcurrentQueue<string> commandQueue = score.GetType().GetProperty("CommandQueue", BindingFlags.Public | BindingFlags.Instance)?.GetValue(score) as ConcurrentQueue<string>;
SCore score = SMainActivity.Instance.core;
CommandQueue commandQueue = score.RawCommandQueue;
if (commandQueue != null)
{
commandQueue.Enqueue(command);
return;
commandQueue.Add(command);
}
object sgame = score.GetType().GetField("Game", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)?.GetValue(score);
commandQueue = sgame.GetType().GetProperty("CommandQueue", BindingFlags.Public | BindingFlags.Instance)?.GetValue(sgame) as ConcurrentQueue<string>;
commandQueue?.Enqueue(command);
}
/// <summary>Raised before drawing the HUD (item toolbar, clock, etc) to the screen.</summary>
@ -152,19 +131,20 @@ namespace StardewModdingAPI.Mods.VirtualKeyboard
/// <param name="e">The event arguments.</param>
private void OnRendered(object sender, EventArgs e)
{
if (!this.hidden)
if (!this.Hidden)
{
float scale = this.transparency;
float scale = this.Transparency;
if (!Game1.eventUp && Game1.activeClickableMenu is GameMenu == false && Game1.activeClickableMenu is ShopMenu == false && Game1.activeClickableMenu is IClickableMenu == false)
{
scale *= 0.5f;
}
System.Reflection.FieldInfo matrixField = Game1.spriteBatch.GetType().GetField("_matrix", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
object originMatrix = matrixField.GetValue(Game1.spriteBatch);
System.Reflection.FieldInfo spriteEffectField = Game1.spriteBatch.GetType().GetField("_spriteEffect", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
SpriteEffect originSpriteEffect = spriteEffectField?.GetValue(Game1.spriteBatch) as SpriteEffect;
var originMatrix = originSpriteEffect?.TransformMatrix;
Game1.spriteBatch.End();
Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, null, null, null, Microsoft.Xna.Framework.Matrix.CreateScale(1f));
Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, null, null, null, Matrix.CreateScale(1f));
IClickableMenu.drawTextureBoxWithIconAndText(Game1.spriteBatch, Game1.smallFont, Game1.mouseCursors, new Rectangle(0x100, 0x100, 10, 10), null, new Rectangle(0, 0, 1, 1),
this.alias, this.buttonRectangle.X, this.buttonRectangle.Y, this.buttonRectangle.Width, this.buttonRectangle.Height, Color.BurlyWood * scale, 4f,
this.Alias, this.ButtonRectangle.X, this.ButtonRectangle.Y, this.ButtonRectangle.Width, this.ButtonRectangle.Height, Color.BurlyWood * scale, 4f,
true, false, true, false, false, false, false); // Remove bold to fix the text position issue
Game1.spriteBatch.End();
if(originMatrix != null)

View File

@ -2,18 +2,18 @@ namespace StardewModdingAPI.Mods.VirtualKeyboard
{
class ModConfig
{
public Toggle vToggle { get; set; } = new Toggle(new Rect(36, 12, 64, 64), true, SButton.None);
public Toggle vToggle { get; set; } = new(new Rect(36, 12, 64, 64), true, SButton.None);
public VirtualButton[] buttons { get; set;} = new VirtualButton[] {
new VirtualButton(SButton.Q, new Rect(190, 80, 90, 90), 0.5f),
new VirtualButton(SButton.I, new Rect(290, 80, 90, 90), 0.5f),
new VirtualButton(SButton.O, new Rect(390, 80, 90, 90), 0.5f),
new VirtualButton(SButton.P, new Rect(490, 80, 90, 90), 0.5f)
new(SButton.Q, new Rect(190, 80, 90, 90), 0.5f),
new(SButton.I, new Rect(290, 80, 90, 90), 0.5f),
new(SButton.O, new Rect(390, 80, 90, 90), 0.5f),
new(SButton.P, new Rect(490, 80, 90, 90), 0.5f)
};
public VirtualButton[] buttonsExtend { get; set; } = new VirtualButton[] {
new VirtualButton(SButton.MouseRight, new Rect(190, 170, 162, 90), 0.5f, "RightMouse"),
new VirtualButton(SButton.None, new Rect(360, 170, 92, 90), 0.5f, "Zoom", "zoom 1.0"),
new VirtualButton(SButton.RightWindows, new Rect(460, 170, 162, 90), 0.5f, "Command"),
new VirtualButton(SButton.RightControl, new Rect(630, 170, 162, 90), 0.5f, "Console")
new(SButton.MouseRight, new Rect(190, 170, 162, 90), 0.5f, "RightMouse"),
new(SButton.None, new Rect(360, 170, 92, 90), 0.5f, "Zoom", "zoom 1.0"),
new(SButton.RightWindows, new Rect(460, 170, 162, 90), 0.5f, "Command"),
new(SButton.RightControl, new Rect(630, 170, 162, 90), 0.5f, "Console")
};
internal class VirtualButton {
public SButton key { get;set; }

View File

@ -1,14 +1,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using StardewModdingAPI;
namespace StardewModdingAPI.Mods.VirtualKeyboard
{
class ModEntry : Mod
{
public static float ZoomScale;
public override void Entry(IModHelper helper)
{
new VirtualToggle(helper, this.Monitor);

View File

@ -1,36 +1 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("StardewModdingAPI.Mods.VirtualKeyboard")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("StardewModdingAPI.Mods.VirtualKeyboard")]
[assembly: AssemblyCopyright("Copyright © 2019")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("29cce9c9-6811-415d-a681-a6d47073924d")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]


View File

@ -1,18 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{29CCE9C9-6811-415D-A681-A6D47073924D}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>StardewModdingAPI.Mods.VirtualKeyboard</RootNamespace>
<AssemblyName>VirtualKeyboard</AssemblyName>
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
<TargetFramework>net5.0</TargetFramework>
<FileAlignment>512</FileAlignment>
<Deterministic>true</Deterministic>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
@ -32,54 +28,16 @@
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="Mono.Android">
<HintPath>..\..\..\..\..\Downloads\StardewValleyAndroidStuff\base_1.4.5.145\assemblies\Mono.Android.dll</HintPath>
</Reference>
<Reference Include="MonoGame.Framework, Version=3.7.1.189, Culture=neutral, processorArchitecture=MSIL">
<Reference Include="MonoGame.Framework">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\..\..\..\Downloads\StardewValleyAndroidStuff\base_1.4.5.145\assemblies\MonoGame.Framework.dll</HintPath>
<HintPath>..\..\build\StardewValleyAndroidStuff\base_1.5.6.39\assemblies\MonoGame.Framework.dll</HintPath>
</Reference>
<Reference Include="StardewModdingAPI">
<HintPath>..\SMAPI\bin\Release\StardewModdingAPI.dll</HintPath>
<HintPath>..\SMAPI\bin\Debug\StardewModdingAPI.dll</HintPath>
</Reference>
<Reference Include="StardewValley">
<HintPath>..\..\..\..\..\Downloads\StardewValleyAndroidStuff\base_1.4.5.145\assemblies\StardewValley.dll</HintPath>
<HintPath>..\..\build\StardewValleyAndroidStuff\base_1.5.6.39\assemblies\StardewValley.dll</HintPath>
</Reference>
<Reference Include="System">
<Private>True</Private>
</Reference>
<Reference Include="System.Core">
<Private>True</Private>
</Reference>
<Reference Include="System.Drawing">
<Private>True</Private>
</Reference>
<Reference Include="System.Windows.Forms">
<Private>True</Private>
</Reference>
<Reference Include="System.Xml.Linq">
<Private>True</Private>
</Reference>
<Reference Include="System.Data.DataSetExtensions">
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data">
<Private>True</Private>
</Reference>
<Reference Include="System.Net.Http">
<Private>True</Private>
</Reference>
<Reference Include="System.Xml">
<Private>True</Private>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="KeyButton.cs" />
<Compile Include="ModConfig.cs" />
<Compile Include="ModEntry.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="VirtualToggle.cs" />
</ItemGroup>
<ItemGroup>
<None Include="manifest.json" />
@ -93,5 +51,4 @@
<Name>SMAPI.Toolkit</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

View File

@ -10,105 +10,105 @@ namespace StardewModdingAPI.Mods.VirtualKeyboard
{
class VirtualToggle
{
private readonly IModHelper helper;
private readonly IModHelper Helper;
private readonly IMonitor Monitor;
private int enabledStage = 0;
private bool autoHidden = true;
private bool isDefault = true;
private ClickableTextureComponent virtualToggleButton;
private int EnabledStage = 0;
private bool AutoHidden = true;
private bool IsDefault = true;
private ClickableTextureComponent VirtualToggleButton;
private List<KeyButton> keyboard = new List<KeyButton>();
private List<KeyButton> keyboardExtend = new List<KeyButton>();
private ModConfig modConfig;
private Texture2D texture;
private int lastPressTick = 0;
private List<KeyButton> Keyboard = new();
private List<KeyButton> KeyboardExtend = new();
private ModConfig ModConfig;
private Texture2D Texture;
private int LastPressTick = 0;
public VirtualToggle(IModHelper helper, IMonitor monitor)
{
this.Monitor = monitor;
this.helper = helper;
this.texture = this.helper.Content.Load<Texture2D>("assets/togglebutton.png", ContentSource.ModFolder);
this.Helper = helper;
this.Texture = this.Helper.ModContent.Load<Texture2D>("assets/togglebutton.png");
this.modConfig = helper.ReadConfig<ModConfig>();
for (int i = 0; i < this.modConfig.buttons.Length; i++)
this.keyboard.Add(new KeyButton(helper, this.modConfig.buttons[i], this.Monitor));
for (int i = 0; i < this.modConfig.buttonsExtend.Length; i++)
this.keyboardExtend.Add(new KeyButton(helper, this.modConfig.buttonsExtend[i], this.Monitor));
this.ModConfig = helper.ReadConfig<ModConfig>();
for (int i = 0; i < this.ModConfig.buttons.Length; i++)
this.Keyboard.Add(new KeyButton(helper, this.ModConfig.buttons[i], this.Monitor));
for (int i = 0; i < this.ModConfig.buttonsExtend.Length; i++)
this.KeyboardExtend.Add(new KeyButton(helper, this.ModConfig.buttonsExtend[i], this.Monitor));
if (this.modConfig.vToggle.rectangle.X != 36 || this.modConfig.vToggle.rectangle.Y != 12)
this.isDefault = false;
this.autoHidden = this.modConfig.vToggle.autoHidden;
if (this.ModConfig.vToggle.rectangle.X != 36 || this.ModConfig.vToggle.rectangle.Y != 12)
this.IsDefault = false;
this.AutoHidden = this.ModConfig.vToggle.autoHidden;
this.virtualToggleButton = new ClickableTextureComponent(new Rectangle(Game1.toolbarPaddingX + 64, 12, 128, 128), this.texture, new Rectangle(0, 0, 16, 16), 5.75f, false);
helper.WriteConfig(this.modConfig);
this.VirtualToggleButton = new ClickableTextureComponent(new Rectangle(Game1.toolbarPaddingX + 64, 12, 128, 128), this.Texture, new Rectangle(0, 0, 16, 16), 4f, false);
helper.WriteConfig(this.ModConfig);
this.helper.Events.Display.Rendered += this.OnRendered;
this.helper.Events.Display.MenuChanged += this.OnMenuChanged;
this.helper.Events.Input.ButtonPressed += this.VirtualToggleButtonPressed;
this.Helper.Events.Display.Rendered += this.OnRendered;
this.Helper.Events.Display.MenuChanged += this.OnMenuChanged;
this.Helper.Events.Input.ButtonPressed += this.VirtualToggleButtonPressed;
}
private void OnMenuChanged(object sender, MenuChangedEventArgs e)
{
if(this.autoHidden && e.NewMenu != null) {
foreach (var keys in this.keyboard)
if(this.AutoHidden && e.NewMenu != null) {
foreach (var keys in this.Keyboard)
{
keys.hidden = true;
keys.Hidden = true;
}
foreach (var keys in this.keyboardExtend)
foreach (var keys in this.KeyboardExtend)
{
keys.hidden = true;
keys.Hidden = true;
}
this.enabledStage = 0;
this.EnabledStage = 0;
}
}
private void VirtualToggleButtonPressed(object sender, ButtonPressedEventArgs e)
{
Vector2 screenPixels = e.Cursor.ScreenPixels;
if (this.modConfig.vToggle.key != SButton.None && e.Button == this.modConfig.vToggle.key)
this.toggleLogic();
else if (e.Button == SButton.MouseLeft && this.shouldTrigger(screenPixels))
this.toggleLogic();
Vector2 screenPixels = Utility.ModifyCoordinatesForUIScale(e.Cursor.ScreenPixels);
if (this.ModConfig.vToggle.key != SButton.None && e.Button == this.ModConfig.vToggle.key)
this.ToggleLogic();
else if (e.Button == SButton.MouseLeft && this.ShouldTrigger(screenPixels))
this.ToggleLogic();
}
private void toggleLogic()
private void ToggleLogic()
{
switch (this.enabledStage)
switch (this.EnabledStage)
{
case 0:
foreach (var keys in this.keyboard)
foreach (var keys in this.Keyboard)
{
keys.hidden = false;
keys.Hidden = false;
}
foreach (var keys in this.keyboardExtend)
foreach (var keys in this.KeyboardExtend)
{
keys.hidden = true;
keys.Hidden = true;
}
this.enabledStage = 1;
this.EnabledStage = 1;
break;
case 1 when this.keyboardExtend.Count > 0:
foreach (var keys in this.keyboardExtend)
case 1 when this.KeyboardExtend.Count > 0:
foreach (var keys in this.KeyboardExtend)
{
keys.hidden = false;
keys.Hidden = false;
}
this.enabledStage = 2;
this.EnabledStage = 2;
break;
default:
foreach (var keys in this.keyboard)
foreach (var keys in this.Keyboard)
{
keys.hidden = true;
keys.Hidden = true;
}
foreach (var keys in this.keyboardExtend)
foreach (var keys in this.KeyboardExtend)
{
keys.hidden = true;
keys.Hidden = true;
}
this.enabledStage = 0;
this.EnabledStage = 0;
if (Game1.activeClickableMenu is IClickableMenu menu && !(Game1.activeClickableMenu is DialogueBox))
{
menu.exitThisMenu();
@ -119,16 +119,16 @@ namespace StardewModdingAPI.Mods.VirtualKeyboard
}
}
private bool shouldTrigger(Vector2 screenPixels)
private bool ShouldTrigger(Vector2 screenPixels)
{
int tick = Game1.ticks;
if(tick - this.lastPressTick <= 6)
if(tick - this.LastPressTick <= 6)
{
return false;
}
if (this.virtualToggleButton.containsPoint((int)(screenPixels.X * Game1.options.zoomLevel), (int)(screenPixels.Y * Game1.options.zoomLevel)))
if (this.VirtualToggleButton.containsPoint((int)screenPixels.X, (int)screenPixels.Y))
{
this.lastPressTick = tick;
this.LastPressTick = tick;
Toolbar.toolbarPressed = true;
return true;
}
@ -137,42 +137,43 @@ namespace StardewModdingAPI.Mods.VirtualKeyboard
private void OnRendered(object sender, EventArgs e)
{
if (this.isDefault)
if (this.IsDefault)
{
if (Game1.options.verticalToolbar)
this.virtualToggleButton.bounds.X = Game1.toolbarPaddingX + Game1.toolbar.itemSlotSize + 200;
this.VirtualToggleButton.bounds.X = Game1.toolbarPaddingX + Game1.toolbar.itemSlotSize + 200;
else
this.virtualToggleButton.bounds.X = Game1.toolbarPaddingX + Game1.toolbar.itemSlotSize + 50;
this.VirtualToggleButton.bounds.X = Game1.toolbarPaddingX + Game1.toolbar.itemSlotSize + 50;
if (Game1.toolbar.alignTop == true && !Game1.options.verticalToolbar)
{
object toolbarHeight = this.helper.Reflection.GetField<int>(Game1.toolbar, "toolbarHeight").GetValue();
this.virtualToggleButton.bounds.Y = (int)toolbarHeight + 50;
object toolbarHeight = this.Helper.Reflection.GetField<int>(Game1.toolbar, "toolbarHeight").GetValue();
this.VirtualToggleButton.bounds.Y = (int)toolbarHeight + 50;
}
else
{
this.virtualToggleButton.bounds.Y = 12;
this.VirtualToggleButton.bounds.Y = 12;
}
}
else
{
this.virtualToggleButton.bounds.X = this.modConfig.vToggle.rectangle.X;
this.virtualToggleButton.bounds.Y = this.modConfig.vToggle.rectangle.Y;
this.VirtualToggleButton.bounds.X = this.ModConfig.vToggle.rectangle.X;
this.VirtualToggleButton.bounds.Y = this.ModConfig.vToggle.rectangle.Y;
}
float scale = 1f;
if (this.enabledStage == 0)
if (this.EnabledStage == 0)
{
scale = 0.5f;
}
if (!Game1.eventUp && Game1.activeClickableMenu is GameMenu == false && Game1.activeClickableMenu is ShopMenu == false)
scale = 0.25f;
System.Reflection.FieldInfo matrixField = Game1.spriteBatch.GetType().GetField("_matrix", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
object originMatrix = matrixField.GetValue(Game1.spriteBatch);
System.Reflection.FieldInfo spriteEffectField = Game1.spriteBatch.GetType().GetField("_spriteEffect", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
SpriteEffect originSpriteEffect = spriteEffectField?.GetValue(Game1.spriteBatch) as SpriteEffect;
var originMatrix = originSpriteEffect?.TransformMatrix;
Game1.spriteBatch.End();
Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, null, null, null, Microsoft.Xna.Framework.Matrix.CreateScale(1f));
this.virtualToggleButton.draw(Game1.spriteBatch, Color.White * scale, 0.000001f);
this.VirtualToggleButton.draw(Game1.spriteBatch, Color.White * scale, 0.000001f);
Game1.spriteBatch.End();
if (originMatrix != null)
{

View File

@ -1,8 +1,8 @@
{
"Name": "VirtualKeyboard",
"Author": "SMAPI",
"Version": "3.3.1",
"MinimumApiVersion": "3.4.0",
"Version": "4.0.1",
"MinimumApiVersion": "3.18.2",
"Description": "A much needed Virtual Keyboard for SMAPI Android.",
"UniqueID": "VirtualKeyboard",
"EntryDll": "VirtualKeyboard.dll",

View File

@ -14,12 +14,12 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.8.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />
<PackageReference Include="FluentAssertions" Version="6.11.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.2" />
<PackageReference Include="Moq" Version="4.18.4" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="NUnit" Version="3.13.3" />
<PackageReference Include="NUnit3TestAdapter" Version="4.3.1" PrivateAssets="all" IncludeAssets="runtime; build; native; contentfiles; analyzers; buildtransitive" />
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" PrivateAssets="all" IncludeAssets="runtime; build; native; contentfiles; analyzers; buildtransitive" />
</ItemGroup>
<ItemGroup>

View File

@ -2,7 +2,9 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
#if !SMAPI_FOR_MOBILE
using Pathoschild.Http.Client;
#endif
using StardewModdingAPI.Toolkit.Serialization;
using StardewModdingAPI.Toolkit.Utilities;
@ -17,8 +19,10 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.WebApi
/// <summary>The API version number.</summary>
private readonly ISemanticVersion Version;
#if !SMAPI_FOR_MOBILE
/// <summary>The underlying HTTP client.</summary>
private readonly IClient Client;
#endif
/*********
@ -30,10 +34,12 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.WebApi
public WebApiClient(string baseUrl, ISemanticVersion version)
{
this.Version = version;
#if !SMAPI_FOR_MOBILE
this.Client = new FluentClient(baseUrl)
.SetUserAgent($"SMAPI/{version}");
this.Client.Formatters.JsonFormatter.SerializerSettings = JsonHelper.CreateDefaultSettings();
#endif
}
/// <summary>Get metadata about a set of mods from the web API.</summary>
@ -44,6 +50,7 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.WebApi
/// <param name="includeExtendedMetadata">Whether to include extended metadata for each mod.</param>
public async Task<IDictionary<string, ModEntryModel>> GetModInfoAsync(ModSearchEntryModel[] mods, ISemanticVersion apiVersion, ISemanticVersion gameVersion, Platform platform, bool includeExtendedMetadata = false)
{
#if !SMAPI_FOR_MOBILE
ModEntryModel[] result = await this.Client
.PostAsync(
$"v{this.Version}/mods",
@ -52,12 +59,16 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.WebApi
.As<ModEntryModel[]>();
return result.ToDictionary(p => p.ID);
#endif
return new Dictionary<string, ModEntryModel>();
}
/// <inheritdoc />
public void Dispose()
{
#if !SMAPI_FOR_MOBILE
this.Client.Dispose();
#endif
}
}
}

View File

@ -73,7 +73,7 @@ namespace StardewModdingAPI.Toolkit.Framework.GameScanning
return GameFolderType.NoGameFound;
// apparently valid
if (dir.EnumerateFiles("Stardew Valley.dll").Any())
if (File.Exists(Path.Combine(dir.FullName, "Stardew Valley.dll")))
return GameFolderType.Valid;
// doesn't contain any version of Stardew Valley
@ -201,6 +201,9 @@ namespace StardewModdingAPI.Toolkit.Framework.GameScanning
/// <summary>Get the custom install path from the <c>stardewvalley.targets</c> file in the home directory, if any.</summary>
private IEnumerable<string> GetCustomInstallPaths()
{
#if SMAPI_FOR_MOBILE
yield break;
#else
// get home path
string homePath = Environment.GetEnvironmentVariable(this.Platform == Platform.Windows ? "USERPROFILE" : "HOME")!;
if (string.IsNullOrWhiteSpace(homePath))
@ -227,6 +230,7 @@ namespace StardewModdingAPI.Toolkit.Framework.GameScanning
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();
#endif
}
#if SMAPI_FOR_WINDOWS

View File

@ -3,6 +3,9 @@ using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
#if SMAPI_FOR_WINDOWS
using System.Management;
#endif
using System.Runtime.InteropServices;
using StardewModdingAPI.Toolkit.Utilities;

View File

@ -1,6 +1,7 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Newtonsoft.Json;
using StardewModdingAPI.Toolkit.Serialization.Models;
using StardewModdingAPI.Toolkit.Utilities;
@ -9,14 +10,25 @@ namespace StardewModdingAPI.Toolkit.Framework.ModScanning
/// <summary>The info about a mod read from its folder.</summary>
public class ModFolder
{
/*********
** Fields
*********/
/// <summary>The backing field for <see cref="Directory"/>.</summary>
private DirectoryInfo? DirectoryImpl;
/*********
** Accessors
*********/
/// <summary>A suggested display name for the mod folder.</summary>
public string DisplayName { get; }
/// <summary>The folder path containing the mod's manifest.json.</summary>
public string DirectoryPath { get; }
/// <summary>The folder containing the mod's manifest.json.</summary>
public DirectoryInfo Directory { get; }
[JsonIgnore]
public DirectoryInfo Directory => this.DirectoryImpl ??= new DirectoryInfo(this.DirectoryPath);
/// <summary>The mod type.</summary>
public ModType Type { get; }
@ -52,7 +64,8 @@ namespace StardewModdingAPI.Toolkit.Framework.ModScanning
public ModFolder(DirectoryInfo root, DirectoryInfo directory, ModType type, Manifest? manifest, ModParseError manifestParseError, string? manifestParseErrorText)
{
// save info
this.Directory = directory;
this.DirectoryImpl = directory;
this.DirectoryPath = directory.FullName;
this.Type = type;
this.Manifest = manifest;
this.ManifestParseError = manifestParseError;
@ -64,6 +77,24 @@ namespace StardewModdingAPI.Toolkit.Framework.ModScanning
: PathUtilities.GetRelativePath(root.FullName, directory.FullName);
}
/// <summary>Construct an instance.</summary>
/// <param name="displayName">A suggested display name for the mod folder.</param>
/// <param name="directoryPath">The folder path containing the mod's manifest.json.</param>
/// <param name="type">The mod type.</param>
/// <param name="manifest">The mod manifest.</param>
/// <param name="manifestParseError">The error which occurred parsing the manifest, if any.</param>
/// <param name="manifestParseErrorText">A human-readable message for the <paramref name="manifestParseError"/>, if any.</param>
[JsonConstructor]
public ModFolder(string displayName, string directoryPath, ModType type, Manifest? manifest, ModParseError manifestParseError, string? manifestParseErrorText)
{
this.DisplayName = displayName;
this.DirectoryPath = directoryPath;
this.Type = type;
this.Manifest = manifest;
this.ManifestParseError = manifestParseError;
this.ManifestParseErrorText = manifestParseErrorText;
}
/// <summary>Get the update keys for a mod.</summary>
/// <param name="manifest">The mod manifest.</param>
public IEnumerable<string> GetUpdateKeys(Manifest manifest)

View File

@ -8,16 +8,18 @@
<LangVersion>latest</LangVersion>
</PropertyGroup>
<!-- <Import Project="..\..\build\common.targets" />-->
<!-- <Import Project="..\..\build\common.targets" />-->
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="Pathoschild.Http.FluentClient" Version="4.2.0" />
<ItemGroup Condition="'$(BUILD_FOR_MOBILE)' != ''">
<Reference Include="Newtonsoft.Json">
<HintPath>..\..\build\Newtonsoft.Json.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup Condition="'$(BUILD_FOR_MOBILE)' == ''">
<PackageReference Include="HtmlAgilityPack" Version="1.11.28" />
<PackageReference Include="Pathoschild.Http.FluentClient" Version="4.2.0" />
<PackageReference Include="HtmlAgilityPack" Version="1.11.48" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Pathoschild.Http.FluentClient" Version="4.3.0" />
<PackageReference Include="System.Management" Version="5.0.0" Condition="'$(OS)' == 'Windows_NT'" />
<PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" Condition="'$(OS)' == 'Windows_NT'" />
<PackageReference Include="VdfConverter" Version="1.0.3" Condition="'$(OS)' == 'Windows_NT'" Private="False" />

View File

@ -44,6 +44,9 @@ namespace StardewModdingAPI.Toolkit.Serialization.Converters
string path = reader.Path;
switch (reader.TokenType)
{
case JsonToken.Null:
return null;
case JsonToken.StartObject:
return this.ReadObject(JObject.Load(reader));

View File

@ -15,17 +15,17 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Azure.Storage.Blobs" Version="12.14.1" />
<PackageReference Include="Hangfire.AspNetCore" Version="1.7.32" />
<PackageReference Include="Hangfire.MemoryStorage" Version="1.7.0" />
<PackageReference Include="HtmlAgilityPack" Version="1.11.46" />
<PackageReference Include="Azure.Storage.Blobs" Version="12.16.0" />
<PackageReference Include="Hangfire.AspNetCore" Version="1.8.2" />
<PackageReference Include="Hangfire.MemoryStorage" Version="1.8.0" />
<PackageReference Include="HtmlAgilityPack" Version="1.11.48" />
<PackageReference Include="Humanizer.Core" Version="2.14.1" />
<PackageReference Include="JetBrains.Annotations" Version="2022.3.1" />
<PackageReference Include="Markdig" Version="0.30.4" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="7.0.1" />
<PackageReference Include="Newtonsoft.Json.Schema" Version="3.0.14" />
<PackageReference Include="Markdig" Version="0.31.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="7.0.8" />
<PackageReference Include="Newtonsoft.Json.Schema" Version="3.0.15" />
<PackageReference Include="Pathoschild.FluentNexus" Version="1.0.5" />
<PackageReference Include="Pathoschild.Http.FluentClient" Version="4.2.0" />
<PackageReference Include="Pathoschild.Http.FluentClient" Version="4.3.0" />
</ItemGroup>
<ItemGroup>
<DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="1.0.1" />

View File

@ -14,9 +14,9 @@
"title": "Format version",
"description": "The format version. You should always use the latest version to enable the latest features, avoid obsolete behavior, and reduce load times.",
"type": "string",
"pattern": "^1\\.28\\.[0-9]+$",
"pattern": "^1\\.29\\.[0-9]+$",
"@errorMessages": {
"pattern": "Incorrect value '@value'. You should always use the latest format version (currently 1.28.0) to enable the latest features, avoid obsolete behavior, and reduce load times."
"pattern": "Incorrect value '@value'. You should always use the latest format version (currently 1.29.0) to enable the latest features, avoid obsolete behavior, and reduce load times."
}
},
"ConfigSchema": {

View File

@ -43,7 +43,7 @@
"description": "The DLL filename SMAPI should load for this mod. Mutually exclusive with ContentPackFor.",
"type": "string",
"pattern": "^[a-zA-Z0-9_.-]+\\.dll$",
"examples": "LookupAnything.dll",
"examples": ["LookupAnything.dll"],
"@errorMessages": {
"pattern": "Invalid value; must be a filename ending with .dll."
}

View File

@ -19,9 +19,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{4B1C
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ISSUE_TEMPLATE", "ISSUE_TEMPLATE", "{F4453AB6-D7D6-447F-A973-956CC777968F}"
ProjectSection(SolutionItems) = preProject
..\.github\ISSUE_TEMPLATE\bug_report.md = ..\.github\ISSUE_TEMPLATE\bug_report.md
..\.github\ISSUE_TEMPLATE\feature_request.md = ..\.github\ISSUE_TEMPLATE\feature_request.md
..\.github\ISSUE_TEMPLATE\general.md = ..\.github\ISSUE_TEMPLATE\general.md
..\.github\ISSUE_TEMPLATE\config.yml = ..\.github\ISSUE_TEMPLATE\config.yml
..\.github\ISSUE_TEMPLATE\custom.md = ..\.github\ISSUE_TEMPLATE\custom.md
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{09CF91E5-5BAB-4650-A200-E5EA9A633046}"
@ -84,9 +83,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SMAPI.Web", "SMAPI.Web\SMAP
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Android", "Android", "{6BBCA4D1-213F-4843-B910-DF2D66E3D8CF}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SMAPI.Mods.VirtualKeyboard", "SMAPI.Mods.VirtualKeyboard\SMAPI.Mods.VirtualKeyboard.csproj", "{29CCE9C9-6811-415D-A681-A6D47073924D}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SMAPI.Mods.VirtualKeyboard", "SMAPI.Mods.VirtualKeyboard\SMAPI.Mods.VirtualKeyboard.csproj", "{29CCE9C9-6811-415D-A681-A6D47073924D}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Loader", "Loader\Loader.csproj", "{45D7D2FB-6B70-45D1-A595-6E289D6A3468}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Loader", "Loader\Loader.csproj", "{45D7D2FB-6B70-45D1-A595-6E289D6A3468}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "windows", "windows", "{4D661178-38FB-43E4-AA5F-9B0406919344}"
ProjectSection(SolutionItems) = preProject
@ -110,7 +109,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SMAPI.Tests.ModApiProvider"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SMAPI.Tests.ModApiConsumer", "SMAPI.Tests.ModApiConsumer\SMAPI.Tests.ModApiConsumer.csproj", "{2A4DF030-E8B1-4BBD-AA93-D4DE68CB9D85}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SMAPI", "SMAPI\SMAPI.csproj", "{EBD13EAB-E70B-4D9F-92C2-C34A21E1FA32}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SMAPI", "SMAPI\SMAPI.csproj", "{EBD13EAB-E70B-4D9F-92C2-C34A21E1FA32}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MobilePatch", "MobilePatch\MobilePatch.csproj", "{E5FE4C97-1A7C-4A97-9904-7351CA020C7F}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -179,6 +180,10 @@ Global
{EBD13EAB-E70B-4D9F-92C2-C34A21E1FA32}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EBD13EAB-E70B-4D9F-92C2-C34A21E1FA32}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EBD13EAB-E70B-4D9F-92C2-C34A21E1FA32}.Release|Any CPU.Build.0 = Release|Any CPU
{E5FE4C97-1A7C-4A97-9904-7351CA020C7F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E5FE4C97-1A7C-4A97-9904-7351CA020C7F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E5FE4C97-1A7C-4A97-9904-7351CA020C7F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E5FE4C97-1A7C-4A97-9904-7351CA020C7F}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -203,14 +208,15 @@ Global
{3B5BF14D-F612-4C83-9EF6-E3EBFCD08766} = {4D661178-38FB-43E4-AA5F-9B0406919344}
{239AEEAC-07D1-4A3F-AA99-8C74F5038F50} = {82D22ED7-A0A7-4D64-8E92-4B6A5E74ED11}
{2A4DF030-E8B1-4BBD-AA93-D4DE68CB9D85} = {82D22ED7-A0A7-4D64-8E92-4B6A5E74ED11}
{E5FE4C97-1A7C-4A97-9904-7351CA020C7F} = {6BBCA4D1-213F-4843-B910-DF2D66E3D8CF}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {70143042-A862-47A8-A677-7C819DDC90DC}
EndGlobalSection
GlobalSection(SharedMSBuildProjectFiles) = preSolution
SMAPI.Internal\SMAPI.Internal.projitems*{0634ea4c-3b8f-42db-aea6-ca9e4ef6e92f}*SharedItemsImports = 5
SMAPI.Internal.Patching\SMAPI.Internal.Patching.projitems*{6c16e948-3e5c-47a7-bf4b-07a7469a87a5}*SharedItemsImports = 13
SMAPI.Internal\SMAPI.Internal.projitems*{85208f8d-6fd1-4531-be05-7142490f59fe}*SharedItemsImports = 13
SMAPI.Internal\SMAPI.Internal.projitems*{cd53ad6f-97f4-4872-a212-50c2a0fd3601}*SharedItemsImports = 5
SMAPI.Internal.Patching\SMAPI.Internal.Patching.projitems*{ebd13eab-e70b-4d9f-92c2-c34a21e1fa32}*SharedItemsImports = 5
SMAPI.Internal\SMAPI.Internal.projitems*{ebd13eab-e70b-4d9f-92c2-c34a21e1fa32}*SharedItemsImports = 5
EndGlobalSection
EndGlobal

View File

@ -13,7 +13,7 @@ using StardewModdingAPI.Framework.ModLoading;
using StardewModdingAPI.Toolkit.Framework;
using StardewModdingAPI.Toolkit.Utilities;
using StardewValley;
using HarmonyLib;
namespace StardewModdingAPI
{
/// <summary>Contains constants that are accessed before the game itself has been loaded.</summary>
@ -35,9 +35,9 @@ namespace StardewModdingAPI
*********/
#if SMAPI_FOR_MOBILE
/// <summary>The path to the storage base folder.</summary>
public static string StorageBasePath { get; } = Android.OS.Environment.ExternalStorageDirectory.Path;
public static string StorageBasePath { get; } = SMainActivity.Instance.GetExternalFilesDir("").AbsolutePath;
/// <summary>The path to the game's save folder.</summary>
public static string StardewValleyBasePath { get; } = Path.Combine(EarlyConstants.StorageBasePath, "StardewValley");
public static string StardewValleyBasePath { get; } = EarlyConstants.StorageBasePath;
/// <summary>The path to the internal folder.</summary>
public static string ExecutionPath { get; } = Path.Combine(EarlyConstants.StardewValleyBasePath, "smapi-internal");
@ -65,13 +65,20 @@ namespace StardewModdingAPI
internal static GameFramework GameFramework { get; } = GameFramework.MonoGame;
/// <summary>The game's assembly name.</summary>
#if SMAPI_FOR_MOBILE
internal static string GameAssemblyName { get; } = "StardewValley";
#else
internal static string GameAssemblyName { get; } = "Stardew Valley";
#endif
/// <summary>The <see cref="Context.ScreenId"/> value which should appear in the SMAPI log, if any.</summary>
internal static int? LogScreenId { get; set; }
/// <summary>SMAPI's current raw semantic version.</summary>
internal static string RawApiVersion = "3.18.2";
#if !SMAPI_FOR_MOBILE
internal static string RawApiVersion = "3.18.4";
#else
internal static string RawApiVersion = "3.18.4.1";
#endif
}
/// <summary>Contains SMAPI's constants and assumptions.</summary>
@ -88,9 +95,6 @@ namespace StardewModdingAPI
public static ISemanticVersion ApiVersion { get; } = new Toolkit.SemanticVersion(EarlyConstants.RawApiVersion);
#else
public static ISemanticVersion ApiVersion { get; } = new Toolkit.SemanticVersion(EarlyConstants.RawApiVersion);
/// <summary>Android SMAPI's current semantic version.</summary>
public static ISemanticVersion AndroidApiVersion { get; } = new Toolkit.SemanticVersion("0.8.8");
#endif
/// <summary>The minimum supported version of Stardew Valley.</summary>
@ -139,11 +143,7 @@ namespace StardewModdingAPI
/// <summary>The directory path in which error logs should be stored.</summary>
public static string LogDir { get; } = Path.Combine(Constants.DataPath, "ErrorLogs");
/// <summary>The directory path where all saves are stored.</summary>
#if SMAPI_FOR_MOBILE
public static string SavesPath { get; } = Constants.DataPath;
#else
public static string SavesPath { get; } = Path.Combine(Constants.DataPath, "Saves");
#endif
/// <summary>The name of the current save folder (if save info is available, regardless of whether the save file exists yet).</summary>
public static string? SaveFolderName => Constants.GetSaveFolderName();
@ -289,6 +289,7 @@ namespace StardewModdingAPI
{
// add search paths
resolver.TryAddSearchDirectory(Constants.GamePath);
#if !SMAPI_FOR_MOBILE
resolver.TryAddSearchDirectory(Constants.InternalFilesPath);
// add SMAPI explicitly
@ -303,6 +304,7 @@ namespace StardewModdingAPI
// - 'Stardew Valley': assembly name on Windows;
// - 'Netcode': an assembly that was separate on Windows only before Stardew Valley 1.5.5.
resolver.AddWithExplicitNames(AssemblyDefinition.ReadAssembly(typeof(Game1).Assembly.Location), "StardewValley", "Stardew Valley", "Netcode");
#endif
}
/// <summary>Get metadata for mapping assemblies to the current platform.</summary>
@ -334,14 +336,17 @@ namespace StardewModdingAPI
);
// Stardew Valley reference
#if SMAPI_FOR_MOBILE
removeAssemblyReferences.Add("Stardew Valley");
#else
removeAssemblyReferences.Add("StardewValley");
#endif
targetAssemblies.Add(typeof(StardewValley.Game1).Assembly);
return new PlatformAssemblyMap(targetPlatform, removeAssemblyReferences.ToArray(), targetAssemblies.ToArray());
}
/*********
** Private methods
*********/
@ -427,7 +432,9 @@ namespace StardewModdingAPI
// ignore invalid path
}
}
#if SMAPI_FOR_MOBILE
if (folder != null && rawSaveName.Length > 0) folder.Create();
#endif
// if save doesn't exist yet, return the default one we expect to be created
return folder;
}

View File

@ -60,7 +60,7 @@ namespace StardewModdingAPI
public static bool IsWorldReady
{
get => Context.IsWorldReadyForScreen.Value;
set => Context.IsWorldReadyForScreen.Value = value;
internal set => Context.IsWorldReadyForScreen.Value = value;
}
/// <summary>Whether <see cref="IsWorldReady"/> is true and the player is free to act in the world (no menu is displayed, no cutscene is in progress, etc).</summary>
@ -88,7 +88,7 @@ namespace StardewModdingAPI
public static bool IsSplitScreen => LocalMultiplayer.IsLocalMultiplayer();
/// <summary>Whether there are players connected over the network.</summary>
public static bool HasRemotePlayers => Context.IsMultiplayer && !Game1.hasLocalClientsOnly;
public static bool HasRemotePlayers => Context.IsMultiplayer && !Game1.hasLocalClientsOnly && Game1.getOnlineFarmers().Count > 1;
/// <summary>Whether the current player is the main player. This is always true in single-player, and true when hosting in multiplayer.</summary>
#if SMAPI_FOR_MOBILE
@ -100,8 +100,7 @@ namespace StardewModdingAPI
/*********
** Public methods
*********/
/// <summary>Get whether a screen ID is still active.</summary>
/// <param name="id">The screen ID.</param>
/// <summary>Get whether a screen ID is still active.</summary> <param name="id">The screen ID.</param>
public static bool HasScreenId(int id)
{
return Context.ActiveScreenIds.Contains(id);

View File

@ -1,6 +1,9 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
#if SMAPI_FOR_MOBILE
using ReadOnlyCollectionsExtensions;
#endif
namespace StardewModdingAPI.Events
{
@ -11,18 +14,10 @@ namespace StardewModdingAPI.Events
** Accessors
*********/
/// <summary>The asset names that were invalidated.</summary>
#if SMAPI_FOR_MOBILE
public IReadOnlyCollection<IAssetName> Names { get; }
#else
public IReadOnlySet<IAssetName> Names { get; }
#endif
/// <summary>The <see cref="Names"/> with any locale codes stripped.</summary>
/// <remarks>For example, if <see cref="Names"/> contains a locale like <c>Data/Bundles.fr-FR</c>, this will have the name without locale like <c>Data/Bundles</c>. If the name has no locale, this field is equivalent.</remarks>
#if SMAPI_FOR_MOBILE
public IReadOnlyCollection<IAssetName> NamesWithoutLocale { get; }
#else
public IReadOnlySet<IAssetName> NamesWithoutLocale { get; }
#endif
/*********
@ -33,8 +28,13 @@ namespace StardewModdingAPI.Events
/// <param name="namesWithoutLocale">The <paramref name="names"/> with any locale codes stripped.</param>
internal AssetsInvalidatedEventArgs(IEnumerable<IAssetName> names, IEnumerable<IAssetName> namesWithoutLocale)
{
#if SMAPI_FOR_MOBILE
this.Names = names.ToReadOnlySet();
this.NamesWithoutLocale = namesWithoutLocale.ToReadOnlySet();
#else
this.Names = names.ToImmutableHashSet();
this.NamesWithoutLocale = namesWithoutLocale.ToImmutableHashSet();
#endif
}
}
}

View File

@ -412,17 +412,28 @@ namespace StardewModdingAPI.Framework
// cached assets
foreach (IContentManager contentManager in this.ContentManagers)
{
#if SMAPI_FOR_MOBILE
HashSet<IAssetName> removingAssets = new HashSet<IAssetName>();
#endif
foreach ((string key, object asset) in contentManager.GetCachedAssets())
{
if (!predicate(contentManager, key, asset.GetType()))
continue;
AssetName assetName = this.ParseAssetName(key, allowLocales: true);
#if !SMAPI_FOR_MOBILE
contentManager.InvalidateCache(assetName, dispose);
#else
removingAssets.Add(assetName);
#endif
if (!invalidatedAssets.ContainsKey(assetName))
invalidatedAssets[assetName] = asset.GetType();
}
#if SMAPI_FOR_MOBILE
foreach (IAssetName assetName in removingAssets)
contentManager.InvalidateCache(assetName, dispose);
#endif
}
// forget localized flags

View File

@ -1,12 +1,10 @@
using System;
using System.Collections.Generic;
using System.Buffers;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using BmFont;
using Microsoft.Xna.Framework;
@ -23,6 +21,10 @@ using StardewValley;
using xTile;
using xTile.Format;
using xTile.Tiles;
#if SMAPI_FOR_MOBILE
using System.Collections.Generic;
using System.Reflection;
#endif
namespace StardewModdingAPI.Framework.ContentManagers
{
@ -139,7 +141,7 @@ namespace StardewModdingAPI.Framework.ContentManagers
".png" => this.LoadImageFile<T>(assetName, file),
".tbin" or ".tmx" => this.LoadMapFile<T>(assetName, file),
".xnb" => this.LoadXnbFile<T>(assetName),
_ => this.HandleUnknownFileType<T>(assetName, file)
_ => (T)this.HandleUnknownFileType(assetName, file, typeof(T))
};
}
catch (Exception ex)
@ -347,13 +349,15 @@ namespace StardewModdingAPI.Framework.ContentManagers
}
/// <summary>Handle a request to load a file type that isn't supported by SMAPI.</summary>
/// <typeparam name="T">The expected file type.</typeparam>
/// <param name="assetName">The asset name relative to the loader root directory.</param>
/// <param name="file">The file to load.</param>
private T HandleUnknownFileType<T>(IAssetName assetName, FileInfo file)
/// <param name="assetType">The expected file type.</param>
private object HandleUnknownFileType(IAssetName assetName, FileInfo file, Type assetType)
{
this.ThrowLoadError(assetName, ContentLoadErrorType.InvalidName, $"unknown file extension '{file.Extension}'; must be one of '.fnt', '.json', '.png', '.tbin', '.tmx', or '.xnb'.");
return default;
return assetType.IsValueType
? Activator.CreateInstance(assetType)
: null;
}
/// <summary>Assert that the asset type is compatible with one of the allowed types.</summary>
@ -458,6 +462,10 @@ namespace StardewModdingAPI.Framework.ContentManagers
tilesheet.ImageSource = this.NormalizePathSeparators(tilesheet.ImageSource);
string imageSource = tilesheet.ImageSource;
// validate image source
if (string.IsNullOrWhiteSpace(imageSource))
throw new SContentLoadException(ContentLoadErrorType.InvalidData, $"{this.ModName} loaded map '{relativeMapPath}' with invalid tilesheet '{tilesheet.Id}'. This tilesheet has no image source.");
// reverse incorrect eager tilesheet path prefixing
if (fixEagerPathPrefixes && relativeMapFolder.Length > 0 && imageSource.StartsWith(relativeMapFolder))
imageSource = imageSource[(relativeMapFolder.Length + 1)..];

View File

@ -41,7 +41,9 @@ namespace StardewModdingAPI.Framework.Input
** Accessors
*********/
/// <summary>Whether the gamepad is currently connected.</summary>
// [MemberNotNullWhen(true, nameof(GamePadStateBuilder.ButtonStates))]
#if !SMAPI_FOR_MOBILE
[MemberNotNullWhen(true, nameof(GamePadStateBuilder.ButtonStates))]
#endif
public bool IsConnected { get; }
@ -201,11 +203,6 @@ namespace StardewModdingAPI.Framework.Input
leftTrigger: this.LeftTrigger,
rightTrigger: this.RightTrigger,
buttons: this.GetPressedGamePadButtons().ToArray()
// #if MonoAndroid10_
// buttons: new[] {this.GetButtonBitmask()}
// #else
// buttons: this.GetButtonBitmask() // MonoGame requires one bitmask here; don't specify multiple values
// #endif
);
return this.State.Value;

View File

@ -305,6 +305,7 @@ namespace StardewModdingAPI.Framework.Logging
// developer mode
if (settings.DeveloperMode)
this.Monitor.Log("You enabled developer mode, so the console will be much more verbose. You can disable it by installing the non-developer version of SMAPI.", LogLevel.Info);
// warnings
if (!settings.CheckForUpdates)
this.Monitor.Log("You disabled update checks, so you won't be notified of new SMAPI or mod updates. Running an old version of SMAPI is not recommended. You can undo this by reinstalling SMAPI.", LogLevel.Warn);

View File

@ -178,7 +178,14 @@ namespace StardewModdingAPI.Framework.ModLoading
using MemoryStream outSymbolStream = new();
assembly.Definition.Write(outAssemblyStream, new WriterParameters { WriteSymbols = true, SymbolStream = outSymbolStream, SymbolWriterProvider = this.SymbolWriterProvider });
byte[] bytes = outAssemblyStream.ToArray();
lastAssembly = Assembly.Load(bytes, outSymbolStream.ToArray());
if (assembly.File.Name != "MoonSharp.Interpreter.dll" && assembly.File.Name != "PyTK.dll" )
{
lastAssembly = Assembly.Load(bytes, outSymbolStream.ToArray());
}
else
{
lastAssembly = Assembly.Load(bytes);
}
}
else
{
@ -405,9 +412,12 @@ namespace StardewModdingAPI.Framework.ModLoading
}
// find or rewrite code
InstructionMetadata instructionMetadata = new InstructionMetadata(this.Monitor);
IInstructionHandler[] handlers = instructionMetadata.GetHandlers(this.ParanoidMode, platformChanged, this.RewriteMods).ToArray();
RecursiveRewriter rewriter = new RecursiveRewriter(
#if SMAPI_FOR_MOBILE
IInstructionHandler[] handlers = new InstructionMetadata(this.Monitor).GetHandlers(this.ParanoidMode, platformChanged, this.RewriteMods).ToArray();
#else
IInstructionHandler[] handlers = new InstructionMetadata().GetHandlers(this.ParanoidMode, platformChanged, this.RewriteMods).ToArray();
#endif
RecursiveRewriter rewriter = new(
module: module,
rewriteModule: curModule =>
{

View File

@ -21,7 +21,9 @@ namespace StardewModdingAPI.Framework.ModLoading
public AssemblyLoadStatus Status;
/// <summary>Whether the <see cref="Definition"/> is loaded and ready (i.e. the <see cref="Status"/> is not <see cref="AssemblyLoadStatus.AlreadyLoaded"/> or <see cref="AssemblyLoadStatus.Failed"/>).</summary>
// [MemberNotNullWhen(true, nameof(AssemblyParseResult.Definition))]
#if !SMAPI_FOR_MOBILE
[MemberNotNullWhen(true, nameof(AssemblyParseResult.Definition))]
#endif
public bool HasDefinition => this.Status == AssemblyLoadStatus.Okay;

View File

@ -62,6 +62,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders
return false;
}
#if SMAPI_FOR_MOBILE
/*********
** Protected methods
*********/
@ -75,5 +76,6 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders
&& fieldRef.DeclaringType.FullName == this.FullTypeName
&& this.FieldNames.Contains(fieldRef.Name);
}
#endif
}
}

View File

@ -82,7 +82,9 @@ namespace StardewModdingAPI.Framework.ModLoading
public ModEntryModel? UpdateCheckData { get; private set; }
/// <inheritdoc />
// [MemberNotNullWhen(true, nameof(ModMetadata.ContentPack))]
#if !SMAPI_FOR_MOBILE
[MemberNotNullWhen(true, nameof(ModMetadata.ContentPack))]
#endif
[SuppressMessage("ReSharper", "ConditionalAccessQualifierIsNonNullableAccordingToAPIContract", Justification = "The manifest may be null for broken mods while loading.")]
public bool IsContentPack => this.Manifest?.ContentPackFor != null;
@ -184,7 +186,9 @@ namespace StardewModdingAPI.Framework.ModLoading
}
/// <inheritdoc />
// [MemberNotNullWhen(true, nameof(IModInfo.Manifest))]
#if !SMAPI_FOR_MOBILE
[MemberNotNullWhen(true, nameof(IModInfo.Manifest))]
#endif
public bool HasManifest()
{
return this.Manifest != null;

View File

@ -180,13 +180,16 @@ namespace StardewModdingAPI.Framework.ModLoading
return mods
.OrderBy(mod =>
{
string id = mod.Manifest.UniqueID;
string? id = mod.Manifest?.UniqueID;
if (modIdsToLoadEarly.TryGetValue(id, out string? actualId))
return -int.MaxValue + Array.IndexOf(earlyArray, actualId);
if (id is not null)
{
if (modIdsToLoadEarly.TryGetValue(id, out string? actualId))
return -int.MaxValue + Array.IndexOf(earlyArray, actualId);
if (modIdsToLoadLate.TryGetValue(id, out actualId))
return int.MaxValue - Array.IndexOf(lateArray, actualId);
if (modIdsToLoadLate.TryGetValue(id, out actualId))
return int.MaxValue - Array.IndexOf(lateArray, actualId);
}
return 0;
})

View File

@ -0,0 +1,18 @@
using System;
namespace StardewModdingAPI.Framework.ModLoading.RewriteFacades;
public static class EnumMethods
{
public static string[] GetNames<TEnum>() where TEnum : struct, Enum
{
Type enumType = typeof(TEnum);
return Enum.GetNames(enumType);
}
public static bool IsDefined<TEnum>(TEnum value) where TEnum : struct, Enum
{
Type enumType = typeof(TEnum);
return Enum.IsDefined(enumType, value);
}
}

View File

@ -11,6 +11,15 @@ namespace StardewModdingAPI.Framework.ModLoading.RewriteFacades
{
public class Game1Methods : Game1
{
public static new IList<IClickableMenu> onScreenMenus => Game1.onScreenMenus;
public static new RainDrop[] RainDropsProp => Game1.rainDrops.ToArray();
public static new IList<GameLocation> LocationsGetter()
{
return Game1.locations;
}
#if SMAPI_LEGACY_PATCH
public static RainDrop[] RainDropsProp => (typeof(RainManager).GetField("_rainDropList", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(RainManager.Instance) as List<RainDrop>).ToArray();
@ -35,8 +44,6 @@ namespace StardewModdingAPI.Framework.ModLoading.RewriteFacades
}
public static new IList<IClickableMenu> onScreenMenus => Game1.onScreenMenus;
public static void updateDebrisWeatherForMovement(List<WeatherDebris> debris)
{
WeatherDebrisManager.Instance.UpdateDebrisWeatherForMovement();

View File

@ -37,10 +37,6 @@ namespace StardewModdingAPI.Framework.ModLoading.RewriteFacades
if (original != null)
original = original.GetDeclaredMember();
#if SMAPI_FOR_MOBILE
if (!Constants.HarmonyEnabled)
return null;
#endif
// call Harmony 2.0 and show a detailed exception if it fails
try
{

View File

@ -16,11 +16,7 @@ namespace StardewModdingAPI.Framework.ModLoading.RewriteFacades
HarmonyMethod finalizer = null)
{
if (Constants.HarmonyEnabled)
#if HARMONY_2
return instance.Patch(original, prefix, postfix, transpiler, finalizer);
#else
return instance.Patch(original, prefix, postfix, transpiler);
#endif
else
return null;
}

View File

@ -0,0 +1,46 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Threading;
using HarmonyLib;
using Microsoft.Xna.Framework.Graphics;
using StardewValley.Menus;
namespace StardewModdingAPI.Framework.ModLoading.RewriteFacades;
public class OptionsElementMethods
{
private static readonly ThreadLocal<int> _recursiveCounter = new(() => 0);
private static readonly Dictionary<Type, MethodInfo?> MethodInfos = new();
public static void draw(OptionsElement instance, SpriteBatch b, int slotX, int slotY, IClickableMenu context = null)
{
if (instance.GetType().IsAssignableFrom(typeof(OptionsElement)))
instance.draw(b, slotX, slotY);
else
{
if (!MethodInfos.ContainsKey(instance.GetType())) OptionsElementMethods.MethodInfos[instance.GetType()] = AccessTools.Method(instance.GetType(), "draw", new[] { typeof(SpriteBatch), typeof(int), typeof(int), typeof(IClickableMenu) });
MethodInfo? pcDraw = MethodInfos[instance.GetType()];
if (pcDraw != null)
{
if (_recursiveCounter.Value == 0)
{
_recursiveCounter.Value++;
try
{
pcDraw.Invoke(instance, new object[] { b, slotX, slotY, context });
}
finally
{
_recursiveCounter.Value--;
}
}
else
instance.draw(b, slotX, slotY);
}
else
instance.draw(b, slotX, slotY);
}
}
}

View File

@ -0,0 +1,37 @@
using System.Reflection;
using HarmonyLib;
using Microsoft.Xna.Framework.Audio;
using StardewValley;
namespace StardewModdingAPI.Framework.ModLoading.RewriteFacades;
public static class SoundBankMethods
{
public static void AddCue(this ISoundBank iSoundBank, CueDefinition cueDefinition)
{
if (iSoundBank is SoundBankWrapper soundBankWrapper)
{
SoundBank soundBank = (SoundBank)typeof(SoundBankWrapper)
.GetField("soundBank", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)
?.GetValue(soundBankWrapper);
soundBank?.AddCue(cueDefinition);
}
else
AccessTools.Method(iSoundBank.GetType(), "AddCue", new[] { typeof(CueDefinition) })
?.Invoke(iSoundBank, new[] { cueDefinition });
}
public static CueDefinition GetCueDefinition(this ISoundBank iSoundBank, string name)
{
if (iSoundBank is SoundBankWrapper soundBankWrapper)
{
SoundBank soundBank = (SoundBank)typeof(SoundBankWrapper)
.GetField("soundBank", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)
?.GetValue(soundBankWrapper);
return soundBank?.GetCueDefinition(name);
}
return (CueDefinition)AccessTools.Method(iSoundBank.GetType(), "GetCueDefinition", new[] { typeof(string) })
?.Invoke(iSoundBank, new[] { name });
}
}

View File

@ -15,7 +15,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters
** Fields
*********/
/// <summary>The assembly names to which to rewrite broken references.</summary>
private readonly HashSet<string> RewriteReferencesToAssemblies;
private readonly ISet<string> RewriteReferencesToAssemblies;
/*********
@ -23,7 +23,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters
*********/
/// <summary>Construct an instance.</summary>
/// <param name="rewriteReferencesToAssemblies">The assembly names to which to rewrite broken references.</param>
public HeuristicFieldAccessibilityRewriter(HashSet<string> rewriteReferencesToAssemblies)
public HeuristicFieldAccessibilityRewriter(ISet<string> rewriteReferencesToAssemblies)
: base(defaultPhrase: "field visibility changed to private") // ignored since we specify phrases
{
this.RewriteReferencesToAssemblies = rewriteReferencesToAssemblies;
@ -164,9 +164,9 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters
instruction.Operand = fieldRef.DeclaringType;
this.Phrases.Add($"{fieldRef.DeclaringType.Name}.{fieldRef.Name} (field ref => reflection ref)");
// #if SMAPI_FOR_MOBILE
// this.Phrases.Add($"{cil.Body.Method.FullName} => {cil.Body.Instructions.Select(ins => ins.ToString()).Join(null, ";")}");
// #endif
#if SMAPI_FOR_MOBILE
// this.Phrases.Add($"{cil.Body.Method.FullName} => {cil.Body.Instructions.Select(ins => ins.ToString()).Join(null, ";")}");
#endif
cil.Body.MaxStackSize += 5;
return this.MarkRewritten();
}

View File

@ -1,6 +1,8 @@
using System;
using HarmonyLib;
using Mono.Cecil;
using Mono.Cecil.Cil;
using MonoMod.Utils;
using StardewModdingAPI.Framework.ModLoading.Framework;
namespace StardewModdingAPI.Framework.ModLoading.Rewriters
@ -31,7 +33,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters
/// <param name="toType">The type with methods to map to.</param>
/// <param name="onlyIfPlatformChanged">Whether to only rewrite references if loading the assembly on a different platform than it was compiled on.</param>
public MethodToAnotherStaticMethodRewriter(Type fromType, Predicate<MethodReference> fromMethodSelector, Type toType, string toMethod)
: base( $"{fromType.Name} methods")
: base($"{fromType.Name} methods")
{
this.FromType = fromType;
this.FromMethodSelector = fromMethodSelector;
@ -49,7 +51,14 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters
if (!this.IsMatch(instruction))
return false;
instruction.Operand = module.ImportReference(this.ToType.GetMethod(this.ToMethod));
MethodReference newReference = module.ImportReference(this.ToType.GetMethod(this.ToMethod));
if (instruction.Operand is GenericInstanceMethod instructionOperand)
{
GenericInstanceMethod genericInstance = new(newReference);
genericInstance.GenericArguments.AddRange(instructionOperand.GenericArguments);
newReference = genericInstance;
}
instruction.Operand = newReference;
return this.MarkRewritten();
}

View File

@ -0,0 +1,156 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Mono.Cecil;
using Mono.Cecil.Cil;
namespace StardewModdingAPI.Framework.ModLoading.Rewriters
{
/// <summary>Rewrites all references to a type.</summary>
internal class ModuleReferenceRewriter : IInstructionHandler
{
/*********
** Accessors
*********/
/// <inheritdoc />
public string DefaultPhrase { get; }
/// <inheritdoc />
public ISet<InstructionHandleResult> Flags { get; } = new HashSet<InstructionHandleResult>();
/// <inheritdoc />
public ISet<string> Phrases { get; } = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
private readonly Dictionary<string, Version> AssemblyRules;
private readonly Dictionary<string, AssemblyNameReference> TargetMap = new();
public ModuleReferenceRewriter(string phrase, Dictionary<string, Version> assemblyRules, Assembly[] assemblies)
{
this.DefaultPhrase = $"{phrase} assembly ref";
this.AssemblyRules = assemblyRules;
foreach (var assembly in assemblies)
{
AssemblyNameReference target = AssemblyNameReference.Parse(assembly.FullName);
var map = assembly.GetTypes().ToDictionary(p => p.FullName, p => target);
foreach (KeyValuePair<string, AssemblyNameReference> pair in map)
{
this.TargetMap.TryAdd(pair.Key, pair.Value);
}
}
}
private bool IsMatch(AssemblyNameReference reference)
{
foreach ((string assemblyName, Version version) in this.AssemblyRules)
{
if (assemblyName.EndsWith('.'))
{
if (reference.Name.Equals(assemblyName) || reference.Name.StartsWith(assemblyName))
return reference.Version.CompareTo(version) >= 0;
}
else
{
if (reference.Name.Equals(assemblyName))
return reference.Version.CompareTo(version) >= 0;
}
}
return false;
}
private bool IsMatch(TypeReference reference)
{
foreach ((string assemblyName, Version _) in this.AssemblyRules)
{
if (assemblyName.EndsWith('.'))
{
if (reference.Scope.Name.Equals(assemblyName) || reference.Scope.Name.StartsWith(assemblyName))
{
return this.TargetMap.ContainsKey(reference.FullName.Split('/')[0]);
}
}
else if(reference.Scope.Name.Equals(assemblyName))
{
return this.TargetMap.ContainsKey(reference.FullName.Split('/')[0]);
}
}
return false;
}
public bool Handle(ModuleDefinition module)
{
if (!module.AssemblyReferences.Any(this.IsMatch))
{
return false;
}
// rewrite type scopes to use target assemblies
IEnumerable<TypeReference> typeReferences = module.GetTypeReferences()
.Where(this.IsMatch)
.OrderBy(p => p.FullName);
HashSet<string> assembliesAdded = new();
foreach (TypeReference type in typeReferences)
{
AssemblyNameReference target = this.TargetMap[type.FullName.Split('/')[0]];
// add target assembly references
if (!module.AssemblyReferences.Contains(target) && !assembliesAdded.Contains(target.FullName))
{
module.AssemblyReferences.Add(target);
assembliesAdded.Add(target.FullName);
}
type.Scope = target;
}
// rewrite types using custom attributes
foreach (TypeDefinition type in module.GetTypes())
{
foreach (CustomAttribute attr in type.CustomAttributes)
{
foreach (CustomAttributeArgument conField in attr.ConstructorArguments)
{
if (conField.Value is TypeReference typeRef)
if (this.TargetMap.ContainsKey(typeRef.FullName))
{
AssemblyNameReference target = this.TargetMap[type.FullName.Split('/')[0]];
// add target assembly references
if (!module.AssemblyReferences.Contains(target) && !assembliesAdded.Contains(target.FullName))
{
module.AssemblyReferences.Add(target);
assembliesAdded.Add(target.FullName);
}
type.Scope = target;
}
}
}
}
for (int i = module.AssemblyReferences.Count - 1; i >= 0; i--)
{
if (this.IsMatch(module.AssemblyReferences[i]))
{
module.AssemblyReferences.RemoveAt(i);
}
}
this.Flags.Add(InstructionHandleResult.Rewritten);
return true;
}
public bool Handle(ModuleDefinition module, TypeReference type, Action<TypeReference> replaceWith)
{
return false;
}
public bool Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction)
{
return false;
}
}
}

View File

@ -52,14 +52,12 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters
if (this.GetterName != null && methodRef.Name == "get_" + this.PropertyName)
{
methodRef = module.ImportReference(this.ToType.GetMethod(this.GetterName));
instruction.OpCode = OpCodes.Callvirt;
instruction.Operand = methodRef;
return true;
}
if(this.SetterName != null && methodRef.Name == "set_" + this.PropertyName)
{
methodRef = module.ImportReference(this.ToType.GetMethod(this.SetterName));
instruction.OpCode = OpCodes.Callvirt;
instruction.Operand = methodRef;
return true;
}

View File

@ -1,10 +1,5 @@
using System;
using System.Reflection;
#if SMAPI_FOR_MOBILE
using System.Collections.Generic;
#else
using System.Runtime.Caching;
#endif
using StardewModdingAPI.Framework.Utilities;
namespace StardewModdingAPI.Framework.Reflection
@ -17,15 +12,7 @@ namespace StardewModdingAPI.Framework.Reflection
** Fields
*********/
/// <summary>The cached fields and methods found via reflection.</summary>
#if SMAPI_FOR_MOBILE
// private readonly Dictionary<string, CacheEntry> Cache = new Dictionary<string, CacheEntry>();
private readonly IntervalMemoryCache<string, MemberInfo?> Cache = new();
#else
private readonly IntervalMemoryCache<string, MemberInfo?> Cache = new();
#endif
/// <summary>The sliding cache expiration time.</summary>
private readonly TimeSpan SlidingCacheExpiry = TimeSpan.FromMinutes(5);
/*********
@ -264,29 +251,8 @@ namespace StardewModdingAPI.Framework.Reflection
private TMemberInfo? GetCached<TMemberInfo>(char memberType, Type type, string memberName, bool isStatic, Func<TMemberInfo?> fetch)
where TMemberInfo : MemberInfo
{
// get from cache
// #if SMAPI_FOR_MOBILE
// if (this.Cache.ContainsKey(key))
// #else
// if (this.Cache.Contains(key))
// #endif
// {
// CacheEntry entry = (CacheEntry)this.Cache[key];
// return entry.IsValid
// ? (TMemberInfo)entry.MemberInfo
// : default;
// }
//
// // fetch & cache new value
// TMemberInfo result = fetch();
// CacheEntry cacheEntry = new CacheEntry(result != null, result);
#if SMAPI_FOR_MOBILE
string key = $"{memberType}{(isStatic ? 's' : 'i')}{type.FullName}:{memberName}";
return (TMemberInfo?)this.Cache.GetOrSet(key, fetch);
#else
string key = $"{memberType}{(isStatic ? 's' : 'i')}{type.FullName}:{memberName}";
return (TMemberInfo?)this.Cache.GetOrSet(key, fetch);
#endif
}
}
}

View File

@ -11,10 +11,6 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Xna.Framework;
#if SMAPI_FOR_MOBILE
using System.Threading.Tasks;
using Android.Widget;
#endif
#if SMAPI_FOR_WINDOWS
using Microsoft.Win32;
#endif
@ -107,7 +103,6 @@ namespace StardewModdingAPI.Framework
/// <summary>The underlying game instance.</summary>
#if SMAPI_FOR_MOBILE
internal SGameRunner Game = null!; // initialized very early
// internal SGame Game;
#else
private SGameRunner Game = null!; // initialized very early
#endif
@ -294,6 +289,12 @@ namespace StardewModdingAPI.Framework
MiniMonoModHotfix.Apply();
HarmonyPatcher.Apply("SMAPI", this.Monitor,
new Game1Patcher(this.Reflection, this.OnLoadStageChanged),
#if SMAPI_FOR_MOBILE
new StringPatcher(this.Reflection),
new ThreadSilenceExitPatch(this.Monitor),
new UIThreadPatch(this.Monitor),
new SaveGamePatch(this.Translator, this.Monitor),
#endif
new TitleMenuPatcher(this.OnLoadStageChanged)
);
// new GamePatcher(this.Monitor).Apply(
@ -347,8 +348,10 @@ namespace StardewModdingAPI.Framework
{
this.IsGameRunning = true;
StardewValley.Program.releaseBuild = true; // game's debug logic interferes with SMAPI opening the game window
#if !SMAPI_FOR_MOBILE
this.Game.Run();
this.Dispose(isError: false);
#endif
}
catch (Exception ex)
{
@ -356,12 +359,12 @@ namespace StardewModdingAPI.Framework
this.LogManager.PressAnyKeyToExit();
this.Dispose(isError: true);
}
#if !SMAPI_FOR_MOBILE
finally
{
#if !SMAPI_FOR_MOBILE
this.Dispose();
#endif
}
#endif
}
/// <summary>Get the core logger and monitor on behalf of the game.</summary>
@ -490,7 +493,7 @@ namespace StardewModdingAPI.Framework
// apply load order customizations
if (this.Settings.ModsToLoadEarly.Any() || this.Settings.ModsToLoadLate.Any())
{
HashSet<string> installedIds = new HashSet<string>(mods.Select(p => p.Manifest.UniqueID), StringComparer.OrdinalIgnoreCase);
HashSet<string> installedIds = new HashSet<string>(mods.Select(p => p.Manifest?.UniqueID).Where(p => p is not null), StringComparer.OrdinalIgnoreCase);
string[] missingEarlyMods = this.Settings.ModsToLoadEarly.Where(id => !installedIds.Contains(id)).OrderBy(p => p, StringComparer.OrdinalIgnoreCase).ToArray();
string[] missingLateMods = this.Settings.ModsToLoadLate.Where(id => !installedIds.Contains(id)).OrderBy(p => p, StringComparer.OrdinalIgnoreCase).ToArray();
@ -965,6 +968,9 @@ namespace StardewModdingAPI.Framework
this.Monitor.Log(context);
// add context to window titles
this.UpdateWindowTitles();
// raise events
this.OnLoadStageChanged(LoadStage.Ready);
events.SaveLoaded.RaiseEmpty();
@ -1352,6 +1358,7 @@ namespace StardewModdingAPI.Framework
case LoadStage.None:
this.JustReturnedToTitle = true;
this.UpdateWindowTitles();
break;
case LoadStage.Loaded:
@ -1603,15 +1610,14 @@ namespace StardewModdingAPI.Framework
string consoleTitle = $"SMAPI {Constants.ApiVersion} - running Stardew Valley {Constants.GameVersion}";
string gameTitle = $"Stardew Valley {Constants.GameVersion} - running SMAPI {Constants.ApiVersion}";
string suffix = "";
if (this.ModRegistry.AreAllModsLoaded)
{
int modsLoaded = this.ModRegistry.GetAll().Count();
consoleTitle += $" with {modsLoaded} mods";
gameTitle += $" with {modsLoaded} mods";
}
suffix += $" with {this.ModRegistry.GetAll().Count()} mods";
if (Context.IsMultiplayer)
suffix += $" [{(Context.IsMainPlayer ? "main player" : "farmhand")}]";
this.Game.Window.Title = gameTitle;
this.LogManager.SetConsoleTitle(consoleTitle);
this.Game.Window.Title = gameTitle + suffix;
this.LogManager.SetConsoleTitle(consoleTitle + suffix);
#endif
}
@ -1664,6 +1670,7 @@ namespace StardewModdingAPI.Framework
}
#endif
}
/// <summary>Asynchronously check for a new version of SMAPI and any installed mods, and print alerts to the console if an update is available.</summary>
/// <param name="mods">The mods to include in the update check (if eligible).</param>
private async Task CheckForUpdatesAsync(IModMetadata[] mods)
@ -2367,8 +2374,8 @@ namespace StardewModdingAPI.Framework
errors.Add($"{file.Name} file couldn't be read"); // mainly happens when the file is corrupted or empty
continue;
}
if(data != null)
translations[locale] = data;
translations[locale] = data;
}
catch (Exception ex)
{

File diff suppressed because it is too large Load Diff

View File

@ -65,6 +65,8 @@ namespace StardewModdingAPI.Framework.StateTracking.FieldWatchers
field.OnArrayReplaced += this.OnArrayReplaced;
#endif
}
#if SMAPI_FOR_MOBILE
private void hookField(int index, NetRef<TValue> field)
{
if (field == default)
@ -81,6 +83,7 @@ namespace StardewModdingAPI.Framework.StateTracking.FieldWatchers
this.hookField(index, array.Fields[index]);
//this.innerArray.OnFieldCreate += new NetArray<TValue, NetRef<TValue>>.FieldCreateEvent(this.hookField);
}
#endif
/// <inheritdoc />
public void Reset()

View File

@ -70,15 +70,8 @@ namespace StardewModdingAPI.Framework.StateTracking
this.Location = location;
// init watchers
this.BuildingsWatcher = location is BuildableGameLocation buildableLocation ?
WatcherFactory.ForNetCollection($"{this.Name}.{nameof(buildableLocation.buildings)}", buildableLocation.buildings) :
WatcherFactory.ForImmutableCollection<Building>();
#if SMAPI_FOR_MOBILE
// this.DebrisWatcher = WatcherFactory.ForNetCollection(location.debris.debrisNetCollection);
this.BuildingsWatcher = location is BuildableGameLocation buildableLocation ? WatcherFactory.ForNetCollection($"{this.Name}.{nameof(buildableLocation.buildings)}", buildableLocation.buildings) : WatcherFactory.ForImmutableCollection<Building>();
this.DebrisWatcher = WatcherFactory.ForNetCollection($"{this.Name}.{nameof(location.debris)}", location.debris);
#else
this.DebrisWatcher = WatcherFactory.ForNetCollection(location.debris);
#endif
this.LargeTerrainFeaturesWatcher = WatcherFactory.ForNetCollection($"{this.Name}.{nameof(location.largeTerrainFeatures)}", location.largeTerrainFeatures);
this.NpcsWatcher = WatcherFactory.ForNetCollection($"{this.Name}.{nameof(location.characters)}", location.characters);
this.ObjectsWatcher = WatcherFactory.ForNetDictionary($"{this.Name}.{nameof(location.netObjects)}", location.netObjects);

View File

@ -60,9 +60,15 @@ namespace StardewModdingAPI.Framework.StateTracking
/// <param name="locations">The game's list of locations.</param>
/// <param name="activeMineLocations">The game's list of active mine locations.</param>
/// <param name="activeVolcanoLocations">The game's list of active volcano locations.</param>
#if SMAPI_FOR_MOBILE
public WorldLocationsTracker(IList<GameLocation> locations, IList<MineShaft> activeMineLocations, IList<VolcanoDungeon> activeVolcanoLocations)
{
this.LocationListWatcher = WatcherFactory.ForReferenceList($"{this.Name}.{nameof(locations)}", locations);
#else
public WorldLocationsTracker(ObservableCollection<GameLocation> locations, IList<MineShaft> activeMineLocations, IList<VolcanoDungeon> activeVolcanoLocations)
{
this.LocationListWatcher = WatcherFactory.ForObservableCollection($"{this.Name}.{nameof(locations)}", locations);
#endif
this.MineLocationListWatcher = WatcherFactory.ForReferenceList($"{this.Name}.{nameof(activeMineLocations)}", activeMineLocations);
this.VolcanoLocationListWatcher = WatcherFactory.ForReferenceList($"{this.Name}.{nameof(activeVolcanoLocations)}", activeVolcanoLocations);
}

Some files were not shown because too many files have changed in this diff Show More