Mod loader 1.3, bug fix and mod management

This commit is contained in:
yangzhi 2019-05-16 18:02:59 +08:00
parent 4d6a82f341
commit 71f194d65a
23 changed files with 592 additions and 244 deletions

View File

@ -53,7 +53,7 @@
<TransformFile Include="Transforms\EnumMethods.xml" />
</ItemGroup>
<ItemGroup>
<EmbeddedJar Include="Jars\pgyer_sdk_3.0.5.jar" />
<EmbeddedJar Include="Jars\pgyer_sdk_3.0.4.jar" />
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.Bindings.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.

View File

@ -2,21 +2,21 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Threading;
using Android.App;
using Android.Content;
using Android.Content.PM;
using Android.Database;
using Android.OS;
using Android.Runtime;
using Android.Support.V4.App;
using Android.Views;
using Android.Widget;
using Com.Pgyersdk;
using Com.Pgyersdk.Update;
using Com.Pgyersdk.Update.Javabean;
using DllRewrite;
using Java.Lang;
using Microsoft.AppCenter;
using Microsoft.AppCenter.Analytics;
using Microsoft.AppCenter.Crashes;
using Microsoft.Xna.Framework;
using ModLoader.Common;
using ModLoader.Helper;
using Mono.Cecil;
@ -24,8 +24,9 @@ using StardewModdingAPI.Framework;
using StardewModdingAPI.Framework.ModLoading;
using StardewModdingAPI.Toolkit;
using StardewModdingAPI.Toolkit.Framework.ModData;
using StardewModdingAPI.Toolkit.Serialisation;
using File = Java.IO.File;
using Thread = System.Threading.Thread;
using Uri = Android.Net.Uri;
namespace ModLoader
{
@ -34,12 +35,12 @@ namespace ModLoader
, Icon = "@drawable/icon"
, Theme = "@style/Theme.Splash"
, AlwaysRetainTaskState = true
, LaunchMode = Android.Content.PM.LaunchMode.SingleInstance
, LaunchMode = LaunchMode.SingleInstance
, ScreenOrientation = ScreenOrientation.SensorLandscape
, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.Keyboard | ConfigChanges.KeyboardHidden | ConfigChanges.ScreenSize | ConfigChanges.ScreenLayout)]
public class Activity1 : Microsoft.Xna.Framework.AndroidGameActivity
public class Activity1 : AndroidGameActivity
{
private string[] requiredPermissions => new string[] { "android.permission.ACCESS_NETWORK_STATE", "android.permission.ACCESS_WIFI_STATE", "android.permission.INTERNET", "android.permission.READ_EXTERNAL_STORAGE", "android.permission.VIBRATE", "android.permission.WAKE_LOCK", "android.permission.WRITE_EXTERNAL_STORAGE", "com.android.vending.CHECK_LICENSE" };
private string[] requiredPermissions => new[] { "android.permission.ACCESS_NETWORK_STATE", "android.permission.ACCESS_WIFI_STATE", "android.permission.INTERNET", "android.permission.READ_EXTERNAL_STORAGE", "android.permission.VIBRATE", "android.permission.WAKE_LOCK", "android.permission.WRITE_EXTERNAL_STORAGE", "com.android.vending.CHECK_LICENSE" };
private string[] deniedPermissionsArray
{
get
@ -65,12 +66,33 @@ namespace ModLoader
private readonly Mutex _working = new Mutex(false);
public static Activity1 Instance { get; private set; }
private HttpClient _httpClient = new HttpClient();
private static Dictionary<int, Action> MessageHandler = new Dictionary<int, Action>();
private readonly Handler _handler = new Handler(message =>
{
if (MessageHandler.ContainsKey(message.What))
{
MessageHandler[message.What]();
}
});
public void InvokeActivityThread(int what, Action action)
{
Message msg = new Message();
msg.What = what;
MessageHandler[what] = action;
this._handler.SendMessage(msg);
}
protected override void OnCreate(Bundle bundle)
{
Type[] services = new Type[] { typeof(Microsoft.AppCenter.Analytics.Analytics), typeof(Microsoft.AppCenter.Crashes.Crashes) };
Microsoft.AppCenter.AppCenter.Start("b8eaba94-d276-4c97-9953-0c91e7357e21", services);
Instance = this;
Type[] services = { typeof(Analytics), typeof(Crashes) };
AppCenter.Start("b8eaba94-d276-4c97-9953-0c91e7357e21", services);
base.OnCreate(bundle);
base.RequestWindowFeature(WindowFeatures.NoTitle);
this.RequestWindowFeature(WindowFeatures.NoTitle);
if (Build.VERSION.SdkInt >= BuildVersionCodes.P)
{
this.Window.Attributes.LayoutInDisplayCutoutMode = LayoutInDisplayCutoutMode.ShortEdges;
@ -95,18 +117,24 @@ namespace ModLoader
try
{
PackageInfo packageInfo = this.PackageManager.GetInstalledPackages(PackageInfoFlags.MatchAll)
.FirstOrDefault(package => package.PackageName == "com.chucklefish.stardewvalley");
.FirstOrDefault(package => package.PackageName == Constants.GamePackageName);
if (packageInfo == null)
{
Utils.MakeToast(this, this.Resources.GetText(Resource.String.NotInstalledMessage), ToastLength.Short);
return;
}
Utils.MakeToast(this, this.Resources.GetText(Resource.String.ExtractingMessage),
ToastLength.Long);
AlertDialog dialog = null;
Utils.ShowProgressDialog(this, Resource.String.Extract, this.Resources.GetText(Resource.String.ExtractingMessage), dlg => { dialog = dlg; });
while (dialog == null)
{
Thread.Sleep(50);
}
string sourceDir = packageInfo.ApplicationInfo.SourceDir;
ZipHelper.UnZip(sourceDir, System.IO.Path.Combine(Constants.GamePath, "Game/"));
ZipHelper.UnZip(sourceDir, Path.Combine(Constants.GamePath, "Game/"));
dialog.Dismiss();
Utils.MakeToast(this, this.Resources.GetText(Resource.String.ExtractedMessage),
ToastLength.Long);
}
finally
{
@ -127,13 +155,18 @@ namespace ModLoader
Utils.MakeToast(this, this.Resources.GetText(Resource.String.NotExtractedMessage), ToastLength.Short);
return;
}
Utils.MakeToast(this, this.Resources.GetText(Resource.String.GeneratingMessage),
ToastLength.Long);
AlertDialog dialog = null;
Utils.ShowProgressDialog(this, Resource.String.Generate, this.Resources.GetText(Resource.String.GeneratingMessage), dlg => { dialog = dlg; });
while (dialog == null)
{
Thread.Sleep(50);
}
MethodPatcher mp = new MethodPatcher();
AssemblyDefinition StardewValley = mp.InsertModHooks();
StardewValley.Write(System.IO.Path.Combine(Constants.GamePath, "StardewValley.dll"));
StardewValley.Write(Path.Combine(Constants.GamePath, "StardewValley.dll"));
AssemblyDefinition MonoFramework = mp.InsertMonoHooks();
MonoFramework.Write(System.IO.Path.Combine(Constants.GamePath, "MonoGame.Framework.dll"));
MonoFramework.Write(Path.Combine(Constants.GamePath, "MonoGame.Framework.dll"));
dialog.Dismiss();
Utils.MakeToast(this, this.Resources.GetText(Resource.String.GeneratedMessage),
ToastLength.Long);
}
@ -148,23 +181,24 @@ namespace ModLoader
if (!this._working.WaitOne(10))
return;
this._working.ReleaseMutex();
if (!new File(System.IO.Path.Combine(Constants.ContentPath, "XACT/FarmerSounds.xgs")).Exists())
if (!new File(Path.Combine(Constants.ContentPath, "XACT/FarmerSounds.xgs".Replace('/',Path.DirectorySeparatorChar))).Exists())
{
Utils.MakeToast(this, this.Resources.GetText(Resource.String.NotExtractedMessage), ToastLength.Short);
return;
}
if (!new File(System.IO.Path.Combine(Constants.GamePath, "StardewValley.dll")).Exists() ||
!new File(System.IO.Path.Combine(Constants.GamePath, "MonoGame.Framework.dll")).Exists())
if (!new File(Path.Combine(Constants.GamePath, "StardewValley.dll")).Exists() ||
!new File(Path.Combine(Constants.GamePath, "MonoGame.Framework.dll")).Exists())
{
Utils.MakeToast(this, this.Resources.GetText(Resource.String.NotGeneratedMessage), ToastLength.Short);
return;
}
this.StartActivity(typeof(SMainActivity));
this.Finish();
};
this.FindViewById<Button>(Resource.Id.buttonWiki).Click += (sender, args) =>
{
Android.Net.Uri uri = Android.Net.Uri.Parse("http://smd.zaneyork.cn");
Uri uri = Uri.Parse("http://smd.zaneyork.cn");
Intent intent = new Intent(Intent.ActionView, uri);
this.StartActivity(intent);
};
@ -196,23 +230,169 @@ namespace ModLoader
{
Directory.CreateDirectory(Constants.GamePath);
}
if (!new File(System.IO.Path.Combine(Constants.GamePath, "smapi-internal/StardewModdingAPI.config.json")).Exists() ||
!new File(System.IO.Path.Combine(Constants.GamePath, "smapi-internal/StardewModdingAPI.metadata.json")).Exists()||
!new File(System.IO.Path.Combine(Constants.GamePath, "StardewModdingAPI.dll")).Exists()||
!new File(System.IO.Path.Combine(Constants.GamePath, "System.Xml.Linq.dll")).Exists()||
!new File(System.IO.Path.Combine(Constants.GamePath, "StardewModdingAPI.Toolkit.dll")).Exists() ||
!new File(System.IO.Path.Combine(Constants.GamePath, "StardewModdingAPI.Toolkit.CoreInterfaces.dll")).Exists())
if (!new File(Path.Combine(Constants.GameInternalPath, "StardewModdingAPI.config.json")).Exists() ||
!new File(Path.Combine(Constants.GameInternalPath, "StardewModdingAPI.metadata.json")).Exists()||
!new File(Path.Combine(Constants.GamePath, "StardewModdingAPI.dll")).Exists()||
!new File(Path.Combine(Constants.GamePath, "System.Xml.Linq.dll")).Exists()||
!new File(Path.Combine(Constants.GamePath, "StardewModdingAPI.Toolkit.dll")).Exists() ||
!new File(Path.Combine(Constants.GamePath, "StardewModdingAPI.Toolkit.CoreInterfaces.dll")).Exists())
{
Stream stream = this.Resources.OpenRawResource(Resource.Raw.SMDroidFiles);
ZipHelper.UnZip(stream, Constants.GamePath);
}
string modListFileName = Path.Combine(Constants.GameInternalPath, "ModList.json");
if (!new File(modListFileName).Exists())
{
Stream stream = this.Resources.OpenRawResource(Resource.Raw.ModList);
Utils.StreamToFile(stream, modListFileName);
}
this.PrepareModList();
new Thread(async () =>
{
try
{
HttpResponseMessage responseMessage = this._httpClient
.GetAsync("https://github.com/ZaneYork/SMAPI/raw/android/ModLoader/Resources/Raw/ModList.json")
.Result.EnsureSuccessStatusCode();
string modList = await responseMessage.Content.ReadAsStringAsync();
string originJson = System.IO.File.ReadAllText(modListFileName);
if (originJson != modList)
{
new JsonHelper().Deserialise<ModInfo[]>(modList);
System.IO.File.WriteAllText(modListFileName, modList);
this.InvokeActivityThread(0, this.PrepareModList);
}
}
catch (Exception)
{
// ignored
}
}).Start();
}
public void InstallMod(ModInfo mod)
{
new Thread(async () =>
{
if (!this._working.WaitOne(10))
return;
try
{
AlertDialog dialog = null;
Utils.ShowProgressDialog(this, Resource.String.ModInstall,
this.Resources.GetText(Resource.String.ModDownloadingMessage), dlg => { dialog = dlg; });
while (dialog == null)
{
Thread.Sleep(50);
}
try
{
HttpResponseMessage responseMessage = this._httpClient.GetAsync(mod.DownloadUrl).Result.EnsureSuccessStatusCode();
byte[] bytes = await responseMessage.Content.ReadAsByteArrayAsync();
if (bytes[0] == 80 && bytes[1] == 75)
{
ZipHelper.UnZip(new MemoryStream(bytes), Constants.ModPath + Path.DirectorySeparatorChar);
Utils.MakeToast(this, this.Resources.GetText(Resource.String.ModInstalledMessage),
ToastLength.Long);
this.InvokeActivityThread(0, this.PrepareModList);
}
else
{
Utils.MakeToast(this, this.Resources.GetText(Resource.String.NetworkErrorMessage),
ToastLength.Long);
}
}
catch (Exception)
{
Utils.MakeToast(this, this.Resources.GetText(Resource.String.NetworkErrorMessage),
ToastLength.Long);
}
finally
{
dialog.Dismiss();
}
}
finally
{
this._working.ReleaseMutex();
}
}).Start();
}
public void RemoveMod(ModInfo mod)
{
if (mod.Metadata?.DirectoryPath != null)
{
File file = new File(mod.Metadata.DirectoryPath);
if (file.Exists() && file.IsDirectory)
{
Utils.ShowConfirmDialog(this, Resource.String.Confirm, Resource.String.RemoveConfirmMessage, Resource.String.Confirm, Resource.String.Cancel,
() =>
{
Directory.Delete(mod.Metadata.DirectoryPath, true);
Utils.MakeToast(this, this.Resources.GetText(Resource.String.ModRemovedMessage),
ToastLength.Long);
this.InvokeActivityThread(0, this.PrepareModList);
});
}
}
}
private void PrepareModList()
{
string modListFileName = Path.Combine(Constants.GameInternalPath, "ModList.json");
new JsonHelper().ReadJsonFileIfExists(modListFileName, out ModInfo[] modInfos);
Dictionary<string, ModInfo> modInfoDictionary = modInfos.ToDictionary(info => info.UniqueID, info => info);
ListView listView = this.FindViewById<ListView>(Resource.Id.listView1);
ModToolkit toolkit = new ModToolkit();
ModDatabase modDatabase = toolkit.GetModDatabase(StardewModdingAPI.Constants.ApiMetadataPath);
ModResolver resolver = new ModResolver();
IModMetadata[] mods = resolver.ReadManifests(toolkit, Constants.ModPath, modDatabase).ToArray();
Array.Sort(mods, (a, b) => string.Compare(a.DisplayName, b.DisplayName, StringComparison.CurrentCulture));
listView.Adapter = new ModListAdapter(this, Resource.Layout.layout_mod_list, mods);
List<ModInfo> modList = new List<ModInfo>();
HashSet<string> installedModList = new HashSet<string>();
foreach (IModMetadata metadata in mods)
{
if (!metadata.HasManifest())
{
modList.Add(new ModInfo(metadata));
}
else if (modInfoDictionary.ContainsKey(metadata.Manifest.UniqueID))
{
modInfoDictionary[metadata.Manifest.UniqueID].Metadata = metadata;
modList.Add(modInfoDictionary[metadata.Manifest.UniqueID]);
installedModList.Add(metadata.Manifest.UniqueID);
}
else
{
modList.Add(new ModInfo(metadata));
}
}
foreach (ModInfo modInfo in modInfos)
{
if (!installedModList.Contains(modInfo.UniqueID))
{
modList.Add(modInfo);
}
}
listView.ScrollStateChanged -= this.ListView_ScrollStateChanged;
listView.ScrollStateChanged += this.ListView_ScrollStateChanged;
listView.Adapter = new ModListAdapter(this, Resource.Layout.layout_mod_list, modList);
if (this._position != -1)
{
listView.SetSelection(this._position);
}
}
private int _position = -1;
private void ListView_ScrollStateChanged(object sender, AbsListView.ScrollStateChangedEventArgs e)
{
if (e.ScrollState == ScrollState.Idle)
{
this._position = e.View.FirstVisiblePosition;
}
}
protected override void OnDestroy()

View File

@ -15,9 +15,11 @@ namespace ModLoader.Common
{
class Constants
{
public static string GamePath { get; } = Path.Combine(Android.OS.Environment.ExternalStorageDirectory.Path, "SMDroid/");
public static string AssemblyPath { get; } = Path.Combine(GamePath, "Game/assemblies/");
public static string GamePackageName { get; } = "com.chucklefish.stardewvalley";
public static string GamePath { get; } = Path.Combine(Android.OS.Environment.ExternalStorageDirectory.Path, "SMDroid" + Path.DirectorySeparatorChar);
public static string AssemblyPath { get; } = Path.Combine(GamePath, "Game/assemblies/".Replace('/', Path.DirectorySeparatorChar));
public static string ModPath { get; } = Path.Combine(GamePath, "Mods");
public static string ContentPath { get; } = Path.Combine(Constants.GamePath, "Game/assets/Content");
public static string ContentPath { get; } = Path.Combine(Constants.GamePath, "Game/assets/Content".Replace('/', Path.DirectorySeparatorChar));
public static string GameInternalPath { get; } = Path.Combine(Constants.GamePath, "smapi-internal");
}
}

View File

@ -0,0 +1,31 @@
using Newtonsoft.Json;
using StardewModdingAPI;
using StardewModdingAPI.Framework;
using StardewModdingAPI.Framework.ModLoading;
using StardewModdingAPI.Toolkit.Framework.ModData;
using StardewModdingAPI.Toolkit.Serialisation.Converters;
using StardewModdingAPI.Toolkit.Serialisation.Models;
namespace ModLoader.Common
{
public class ModInfo
{
public ModInfo() { }
internal ModInfo(IModMetadata metadata)
{
this.Metadata = metadata;
if(metadata != null)
this.Name = metadata?.DisplayName;
}
public string UniqueID { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public string DownloadUrl { get; set; }
public ISemanticVersion Version { get; set; }
[JsonConverter(typeof(ManifestDependencyArrayConverter))]
public IManifestDependency[] Dependencies { get; set; }
internal IModMetadata Metadata { get; set; }
}
}

View File

@ -1,36 +1,53 @@
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Threading.Tasks;
using Android.Content;
using Android.Graphics;
using Android.Views;
using Android.Widget;
using ModLoader.Helper;
using StardewModdingAPI.Framework;
namespace ModLoader.Common
{
class ModListAdapter : ArrayAdapter<IModMetadata>
class ModListAdapter : ArrayAdapter<ModInfo>
{
private int textViewResourceId;
public ModListAdapter(Context context, int textViewResourceId, IModMetadata[] mods) : base(context, textViewResourceId, mods)
public ModListAdapter(Context context, int textViewResourceId, List<ModInfo> mods) : base(context, textViewResourceId, mods)
{
this.textViewResourceId = textViewResourceId;
}
public override View GetView(int position, View convertView, ViewGroup parent)
{
IModMetadata mod = this.GetItem(position);
ModInfo mod = this.GetItem(position);
View view = LayoutInflater.From(this.Context).Inflate(this.textViewResourceId, parent, false);
TextView headText = view.FindViewById<TextView>(Resource.Id.textModName);
headText.Text = mod.DisplayName;
//Button disableButton = view.FindViewById<Button>(Resource.Id.buttonModDisable);
//if (mod.IsIgnored)
//{
// disableButton.Text = this.Context.Resources.GetText(Resource.String.Enable);
//}
//else
//{
// disableButton.Text = this.Context.Resources.GetText(Resource.String.Disable);
//}
//disableButton.Click += (sender, args) =>
//{
//};
TextView descriptionText = view.FindViewById<TextView>(Resource.Id.textDescription);
Button buttonAddOrRemove = view.FindViewById<Button>(Resource.Id.buttonAddOrRemove);
headText.Text = mod.Name;
descriptionText.Text = mod.Description;
if (mod.Metadata == null)
{
buttonAddOrRemove.Text = this.Context.Resources.GetText(Resource.String.ModInstall);
headText.SetTextColor(Color.Gray);
}
else
{
buttonAddOrRemove.Text = this.Context.Resources.GetText(Resource.String.ModRemove);
}
buttonAddOrRemove.Click += (sender, args) =>
{
if (mod.Metadata == null)
{
Activity1.Instance.InstallMod(mod);
}
else
{
Activity1.Instance.RemoveMod(mod);
}
};
return view;
}
}

View File

@ -1,5 +1,8 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using Android.App;
using Android.Content;
using Android.OS;
using Android.Widget;
@ -39,18 +42,46 @@ namespace ModLoader.Common
fs.Close();
}
public static void MakeToast(Context context, string message, ToastLength toastLength)
public static void InvokeLooperThread(Action action)
{
new Thread(() =>
{
Looper.Prepare();
new Handler().Post(() =>
{
Toast.MakeText(context, message, toastLength).Show();
});
new Handler().Post(action);
Looper.Loop();
}).Start();
}
public static void MakeToast(Context context, string message, ToastLength toastLength)
{
InvokeLooperThread(() => Toast.MakeText(context, message, toastLength).Show());
}
public static void ShowProgressDialog(Context context, int titleId, string message, Action<AlertDialog> returnCallback)
{
InvokeLooperThread(() =>
{
ProgressDialog dialog = new ProgressDialog(context);
dialog.SetTitle(titleId);
dialog.SetMessage(message);
dialog.SetCancelable(false);
dialog.SetProgressStyle(ProgressDialogStyle.Spinner);
dialog.Show();
returnCallback(dialog);
});
}
public static void ShowConfirmDialog(Context context, int titleId, int messageId, int confirmId, int cancelId, Action onConfirm = null,
Action onCancel = null)
{
InvokeLooperThread(() =>
{
new AlertDialog.Builder(context).SetTitle(titleId).SetMessage(messageId).SetCancelable(true)
.SetPositiveButton(confirmId, (sender, args) => onConfirm?.Invoke())
.SetNegativeButton(cancelId, (sender, args) => onCancel?.Invoke())
.Show();
});
}
}
}

View File

@ -1,6 +1,7 @@
using System;
using StardewValley;
using StardewModdingAPI.Framework;
using StardewModdingAPI.Patches;
using StardewValley.Menus;
namespace SMDroid
@ -24,7 +25,17 @@ namespace SMDroid
{
switch (hookName)
{
case "StardewValley.Object.getDescription":
if (SCore.Instance.HarmonyDetourBridgeFailed && !ObjectErrorPatch.Object_GetDescription_Prefix(__instance as StardewValley.Object, ref __result))
{
return false;
}
return this.core.GameInstance.OnCommonHook_Prefix(hookName, __instance, ref param1, ref param2, ref param3, ref param4, ref __result);
default:
if ((hookName == "StardewValley.Dialogue..ctor") && SCore.Instance.HarmonyDetourBridgeFailed && !DialogueErrorPatch.Prefix(__instance as Dialogue, (string)param1, param2 as NPC))
{
return false;
}
return this.core.GameInstance.OnCommonHook_Prefix(hookName, __instance, ref param1, ref param2, ref param3, ref param4, ref __result);
}
}
@ -51,6 +62,10 @@ namespace SMDroid
}
public override bool OnCommonHook10_Prefix(string hookName, object __instance, ref object param1, ref object param2, ref object param3, ref object param4, ref object param5, ref object param6, ref object param7, ref object param8, ref object param9, ref object __result)
{
if (SCore.Instance.HarmonyDetourBridgeFailed && (hookName == "StardewValley.Menus.IClickableMenu.drawToolTip") && !ObjectErrorPatch.IClickableMenu_DrawTooltip_Prefix(__instance as IClickableMenu, param4 as Item))
{
return false;
}
return this.core.GameInstance.OnCommonHook10_Prefix(hookName, __instance, ref param1, ref param2, ref param3, ref param4, ref param5, ref param6, ref param7, ref param8, ref param9, ref __result);
}
public override void OnCommonStaticHook10_Postfix(string hookName, ref object param1, ref object param2, ref object param3, ref object param4, ref object param5, ref object param6, ref object param7, ref object param8, ref object param9, ref object param10, ref bool __state, ref object __result)

View File

@ -28,6 +28,8 @@
<AndroidSigningStorePass>ZaneYork</AndroidSigningStorePass>
<AndroidSigningKeyAlias>keystore.alias</AndroidSigningKeyAlias>
<AndroidSigningKeyPass>ZaneYork</AndroidSigningKeyPass>
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
@ -55,7 +57,7 @@
<AndroidUseSharedRuntime>false</AndroidUseSharedRuntime>
<AndroidLinkMode>SdkOnly</AndroidLinkMode>
<GenerateSerializationAssemblies>On</GenerateSerializationAssemblies>
<AndroidSupportedAbis>armeabi-v7a;x86;x86_64</AndroidSupportedAbis>
<AndroidSupportedAbis>armeabi-v7a;x86;x86_64;arm64-v8a</AndroidSupportedAbis>
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
<DocumentationFile>
</DocumentationFile>
@ -65,8 +67,9 @@
<AotAssemblies>false</AotAssemblies>
<EnableLLVM>false</EnableLLVM>
<AndroidEnableMultiDex>false</AndroidEnableMultiDex>
<AndroidLinkSkip>mscorlib;System;System.Xml;System.Core;System.Xml.Linq;System.Net.Http;System.Runtime.Serialization;StardewModdingAPI;StardewValley;xTile;BmFont</AndroidLinkSkip>
<AndroidLinkSkip>mscorlib;System;System.Xml;System.Core;System.Xml.Linq;System.Net.Http;System.Runtime.Serialization;MonoGame.Framework;StardewModdingAPI;StardewValley;xTile;BmFont</AndroidLinkSkip>
<LangVersion>7.3</LangVersion>
<AndroidEnableSGenConcurrent>true</AndroidEnableSGenConcurrent>
</PropertyGroup>
<ItemGroup>
<Reference Include="BmFont">
@ -110,6 +113,7 @@
<Private>True</Private>
<HintPath>..\Mods\assemblies\Mono.Android.dll</HintPath>
</Reference>
<Reference Include="Mono.Security" />
<Reference Include="MonoGame.Framework, Version=3.7.1.189, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>.\MonoGame.Framework.dll</HintPath>
@ -123,12 +127,8 @@
<Reference Include="System.Core" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Runtime.Serialization" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Xml" />
<Reference Include="Xamarin.Android.Arch.Core.Common, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\Mods\assemblies\Xamarin.Android.Arch.Core.Common.dll</HintPath>
</Reference>
<Reference Include="System.Xml.Linq" />
<Reference Include="Xamarin.Android.Arch.Lifecycle.Common, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\Mods\assemblies\Xamarin.Android.Arch.Lifecycle.Common.dll</HintPath>
@ -173,6 +173,7 @@
<ItemGroup>
<Compile Include="Activity1.cs" />
<Compile Include="Common\Constants.cs" />
<Compile Include="Common\ModInfo.cs" />
<Compile Include="Common\ModListAdapter.cs" />
<Compile Include="Common\Utils.cs" />
<Compile Include="Common\MethodPatcher.cs" />
@ -1013,6 +1014,7 @@
<Generator>MSBuild:UpdateGeneratedFiles</Generator>
</AndroidResource>
<AndroidResource Include="Resources\Raw\SMDroidFiles.zip" />
<AndroidResource Include="Resources\Raw\ModList.json" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Values\Strings.xml" />

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="5" android:versionName="1.1" android:installLocation="auto" package="com.zane.smdroid" platformBuildVersionCode="28" platformBuildVersionName="9">
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="9" android:versionName="1.3" android:installLocation="auto" package="com.zane.smdroid" platformBuildVersionCode="28" platformBuildVersionName="9">
<uses-sdk android:minSdkVersion="19" android:targetSdkVersion="28" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
@ -8,29 +8,17 @@
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="com.android.vending.CHECK_LICENSE" />
<!-- Required -->
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.GET_TASKS"/>
<application android:label="SMDroid" android:icon="@drawable/icon" android:name=".MainApplication" android:allowBackup="false" android:resizeableActivity="false"
android:usesCleartextTraffic="true">
<uses-permission android:name="com.android.vending.CHECK_LICENSE" />
<!-- Required -->
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.GET_TASKS" />
<application android:label="SMDroid" android:icon="@drawable/icon" android:name=".MainApplication" android:allowBackup="false" android:resizeableActivity="false" android:usesCleartextTraffic="true" android:theme="@style/Theme.Splash">
<meta-data android:name="android.max_aspect" android:value="2.1" />
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.zane.smdroid.fileProvider"
android:grantUriPermissions="true"
android:exported="false">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
<provider
android:name="com.pgyersdk.PgyerProvider"
android:authorities="com.zane.smdroid.com.pgyer.provider"
android:exported="false"/>
</application>
<provider android:name="android.support.v4.content.FileProvider" android:authorities="com.zane.smdroid.fileProvider" android:grantUriPermissions="true" android:exported="false">
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" />
</provider>
<provider android:name="com.pgyersdk.PgyerProvider" android:authorities="com.zane.smdroid.com.pgyer.provider" android:exported="false" />
</application>
</manifest>

View File

@ -19,21 +19,25 @@
android:text="@string/Extract"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minWidth="96sp"
android:id="@+id/buttonExtract" />
<Button
android:text="@string/Generate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minWidth="96sp"
android:id="@+id/buttonGenerate" />
<Button
android:text="@string/Launch"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minWidth="96sp"
android:id="@+id/buttonLaunch" />
<Button
android:text="@string/Wiki"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minWidth="96sp"
android:id="@+id/buttonWiki" />
</LinearLayout>
<LinearLayout

View File

@ -1,21 +1,27 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:id="@+id/ll_view"
android:gravity="center"
android:layout_margin="10dp"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_marginLeft="20dp"
android:layout_weight="1"
android:text="test1"
android:textColor="@android:color/black"
android:id="@+id/textModName"
android:layout_width="0dp"
android:layout_height="wrap_content" />
</LinearLayout>
</LinearLayout>
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/ll_view"
android:layout_height="fill_parent"
android:layout_width="fill_parent" >
<Button
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:text="@android:string/cancel"
android:id="@+id/buttonAddOrRemove"
android:layout_height="wrap_content"
android:layout_width="80sp" />
<TextView
android:id="@+id/textModName"
android:textColor="@android:color/black"
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:paddingRight="88sp"
android:textSize="18sp" />
<TextView
android:id="@+id/textDescription"
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:paddingRight="88sp"
android:layout_below="@+id/textModName" />
</RelativeLayout>

View File

@ -249,26 +249,26 @@ namespace ModLoader
public partial class Id
{
// aapt resource value: 0x7f0c0017
public const int action0 = 2131492887;
// aapt resource value: 0x7f0c0014
public const int action_container = 2131492884;
// aapt resource value: 0x7f0c001b
public const int action_divider = 2131492891;
// aapt resource value: 0x7f0c0015
public const int action_image = 2131492885;
// aapt resource value: 0x7f0c0019
public const int action0 = 2131492889;
// aapt resource value: 0x7f0c0016
public const int action_text = 2131492886;
public const int action_container = 2131492886;
// aapt resource value: 0x7f0c0025
public const int actions = 2131492901;
// aapt resource value: 0x7f0c001d
public const int action_divider = 2131492893;
// aapt resource value: 0x7f0c0029
public const int appIcon = 2131492905;
// aapt resource value: 0x7f0c0017
public const int action_image = 2131492887;
// aapt resource value: 0x7f0c0018
public const int action_text = 2131492888;
// aapt resource value: 0x7f0c0027
public const int actions = 2131492903;
// aapt resource value: 0x7f0c002b
public const int appIcon = 2131492907;
// aapt resource value: 0x7f0c0006
public const int async = 2131492870;
@ -276,6 +276,9 @@ namespace ModLoader
// aapt resource value: 0x7f0c0007
public const int blocking = 2131492871;
// aapt resource value: 0x7f0c0013
public const int buttonAddOrRemove = 2131492883;
// aapt resource value: 0x7f0c000d
public const int buttonExtract = 2131492877;
@ -288,17 +291,17 @@ namespace ModLoader
// aapt resource value: 0x7f0c0010
public const int buttonWiki = 2131492880;
// aapt resource value: 0x7f0c0018
public const int cancel_action = 2131492888;
// aapt resource value: 0x7f0c001a
public const int cancel_action = 2131492890;
// aapt resource value: 0x7f0c0020
public const int chronometer = 2131492896;
// aapt resource value: 0x7f0c0022
public const int chronometer = 2131492898;
// aapt resource value: 0x7f0c002e
public const int description = 2131492910;
// aapt resource value: 0x7f0c0030
public const int description = 2131492912;
// aapt resource value: 0x7f0c0027
public const int end_padder = 2131492903;
// aapt resource value: 0x7f0c0029
public const int end_padder = 2131492905;
// aapt resource value: 0x7f0c0008
public const int forever = 2131492872;
@ -306,14 +309,14 @@ namespace ModLoader
// aapt resource value: 0x7f0c000b
public const int gridLayout1 = 2131492875;
// aapt resource value: 0x7f0c0022
public const int icon = 2131492898;
// aapt resource value: 0x7f0c0024
public const int icon = 2131492900;
// aapt resource value: 0x7f0c0026
public const int icon_group = 2131492902;
// aapt resource value: 0x7f0c0028
public const int icon_group = 2131492904;
// aapt resource value: 0x7f0c0021
public const int info = 2131492897;
// aapt resource value: 0x7f0c0023
public const int info = 2131492899;
// aapt resource value: 0x7f0c0009
public const int italic = 2131492873;
@ -333,44 +336,44 @@ namespace ModLoader
// aapt resource value: 0x7f0c0012
public const int ll_view = 2131492882;
// aapt resource value: 0x7f0c001a
public const int media_actions = 2131492890;
// aapt resource value: 0x7f0c001c
public const int media_actions = 2131492892;
// aapt resource value: 0x7f0c000a
public const int normal = 2131492874;
// aapt resource value: 0x7f0c0028
public const int notificationLayout = 2131492904;
// aapt resource value: 0x7f0c0024
public const int notification_background = 2131492900;
// aapt resource value: 0x7f0c001d
public const int notification_main_column = 2131492893;
// aapt resource value: 0x7f0c001c
public const int notification_main_column_container = 2131492892;
// aapt resource value: 0x7f0c002d
public const int progress_bar = 2131492909;
// aapt resource value: 0x7f0c002c
public const int progress_bar_frame = 2131492908;
// aapt resource value: 0x7f0c002a
public const int progress_text = 2131492906;
public const int notificationLayout = 2131492906;
// aapt resource value: 0x7f0c0023
public const int right_icon = 2131492899;
// aapt resource value: 0x7f0c0026
public const int notification_background = 2131492902;
// aapt resource value: 0x7f0c001f
public const int notification_main_column = 2131492895;
// aapt resource value: 0x7f0c001e
public const int right_side = 2131492894;
public const int notification_main_column_container = 2131492894;
// aapt resource value: 0x7f0c002f
public const int spacer = 2131492911;
public const int progress_bar = 2131492911;
// aapt resource value: 0x7f0c0019
public const int status_bar_latest_event_content = 2131492889;
// aapt resource value: 0x7f0c002e
public const int progress_bar_frame = 2131492910;
// aapt resource value: 0x7f0c002c
public const int progress_text = 2131492908;
// aapt resource value: 0x7f0c0025
public const int right_icon = 2131492901;
// aapt resource value: 0x7f0c0020
public const int right_side = 2131492896;
// aapt resource value: 0x7f0c0031
public const int spacer = 2131492913;
// aapt resource value: 0x7f0c001b
public const int status_bar_latest_event_content = 2131492891;
// aapt resource value: 0x7f0c0002
public const int tag_transition_group = 2131492866;
@ -381,14 +384,17 @@ namespace ModLoader
// aapt resource value: 0x7f0c0004
public const int text2 = 2131492868;
// aapt resource value: 0x7f0c0013
public const int textModName = 2131492883;
// aapt resource value: 0x7f0c0015
public const int textDescription = 2131492885;
// aapt resource value: 0x7f0c001f
public const int time = 2131492895;
// aapt resource value: 0x7f0c0014
public const int textModName = 2131492884;
// aapt resource value: 0x7f0c002b
public const int time_remaining = 2131492907;
// aapt resource value: 0x7f0c0021
public const int time = 2131492897;
// aapt resource value: 0x7f0c002d
public const int time_remaining = 2131492909;
// aapt resource value: 0x7f0c0005
public const int title = 2131492869;
@ -493,7 +499,10 @@ namespace ModLoader
{
// aapt resource value: 0x7f050000
public const int SMDroidFiles = 2131034112;
public const int ModList = 2131034112;
// aapt resource value: 0x7f050001
public const int SMDroidFiles = 2131034113;
static Raw()
{
@ -511,6 +520,12 @@ namespace ModLoader
// aapt resource value: 0x7f090018
public const int ApplicationName = 2131296280;
// aapt resource value: 0x7f090026
public const int Cancel = 2131296294;
// aapt resource value: 0x7f090024
public const int Confirm = 2131296292;
// aapt resource value: 0x7f09001c
public const int Disable = 2131296284;
@ -520,44 +535,65 @@ namespace ModLoader
// aapt resource value: 0x7f090019
public const int Extract = 2131296281;
// aapt resource value: 0x7f090023
public const int ExtractedMessage = 2131296291;
// aapt resource value: 0x7f090028
public const int ExtractedMessage = 2131296296;
// aapt resource value: 0x7f090022
public const int ExtractingMessage = 2131296290;
// aapt resource value: 0x7f090027
public const int ExtractingMessage = 2131296295;
// aapt resource value: 0x7f09001a
public const int Generate = 2131296282;
// aapt resource value: 0x7f090025
public const int GeneratedMessage = 2131296293;
// aapt resource value: 0x7f09002a
public const int GeneratedMessage = 2131296298;
// aapt resource value: 0x7f090024
public const int GeneratingMessage = 2131296292;
// aapt resource value: 0x7f090029
public const int GeneratingMessage = 2131296297;
// aapt resource value: 0x7f090021
public const int Ignore = 2131296289;
// aapt resource value: 0x7f090023
public const int Ignore = 2131296291;
// aapt resource value: 0x7f09001b
public const int Launch = 2131296283;
// aapt resource value: 0x7f090027
public const int NotExtractedMessage = 2131296295;
// aapt resource value: 0x7f090028
public const int NotGeneratedMessage = 2131296296;
// aapt resource value: 0x7f090026
public const int NotInstalledMessage = 2131296294;
// aapt resource value: 0x7f090020
public const int Update = 2131296288;
// aapt resource value: 0x7f09001f
public const int UpdateTip = 2131296287;
// aapt resource value: 0x7f09002b
public const int ModDownloadingMessage = 2131296299;
// aapt resource value: 0x7f09001e
public const int Wiki = 2131296286;
public const int ModInstall = 2131296286;
// aapt resource value: 0x7f09002d
public const int ModInstalledMessage = 2131296301;
// aapt resource value: 0x7f09001f
public const int ModRemove = 2131296287;
// aapt resource value: 0x7f09002e
public const int ModRemovedMessage = 2131296302;
// aapt resource value: 0x7f09002c
public const int NetworkErrorMessage = 2131296300;
// aapt resource value: 0x7f090030
public const int NotExtractedMessage = 2131296304;
// aapt resource value: 0x7f090031
public const int NotGeneratedMessage = 2131296305;
// aapt resource value: 0x7f09002f
public const int NotInstalledMessage = 2131296303;
// aapt resource value: 0x7f090025
public const int RemoveConfirmMessage = 2131296293;
// aapt resource value: 0x7f090022
public const int Update = 2131296290;
// aapt resource value: 0x7f090021
public const int UpdateTip = 2131296289;
// aapt resource value: 0x7f090020
public const int Wiki = 2131296288;
// aapt resource value: 0x7f090001
public const int kilobytes_per_second = 2131296257;

View File

@ -6,14 +6,23 @@
<string name="Launch">启动</string>
<string name="Disable">禁用</string>
<string name="Enable">启用</string>
<string name="ModInstall">安装</string>
<string name="ModRemove">移除</string>
<string name="Wiki">Wiki</string>
<string name="UpdateTip">发现新版本</string>
<string name="Update">立即更新</string>
<string name="Ignore">暂时忽略</string>
<string name="Confirm">确认</string>
<string name="RemoveConfirmMessage">确认移除这个插件?</string>
<string name="Cancel">取消</string>
<string name="ExtractingMessage">正在解压游戏资源</string>
<string name="ExtractedMessage">解压完成</string>
<string name="GeneratingMessage">正在生成DLL文件</string>
<string name="GeneratedMessage">生成完成</string>
<string name="ModDownloadingMessage">正在下载Mod</string>
<string name="NetworkErrorMessage">网络错误</string>
<string name="ModInstalledMessage">Mod已安装</string>
<string name="ModRemovedMessage">Mod已卸载</string>
<string name="NotInstalledMessage">请先安装游戏本体</string>
<string name="NotExtractedMessage">请先点击解压按钮</string>
<string name="NotGeneratedMessage">请先点击生成按钮</string>

View File

@ -6,14 +6,23 @@
<string name="Launch">Launch</string>
<string name="Disable">Disable</string>
<string name="Enable">Enable</string>
<string name="ModInstall">Install</string>
<string name="ModRemove">Remove</string>
<string name="Wiki">Wiki</string>
<string name="UpdateTip">Update Available</string>
<string name="Update">Upgrade</string>
<string name="Ignore">Ignore</string>
<string name="Confirm">Confirm</string>
<string name="RemoveConfirmMessage">Are you sure to remove this mod?</string>
<string name="Cancel">Cancel</string>
<string name="ExtractingMessage">Extracting game resources</string>
<string name="ExtractedMessage">Extracted</string>
<string name="GeneratingMessage">Generating Dlls</string>
<string name="GeneratedMessage">Generated</string>
<string name="ModDownloadingMessage">Mod Downloading</string>
<string name="NetworkErrorMessage">Network Error</string>
<string name="ModInstalledMessage">Mod Installed Successfully</string>
<string name="ModRemovedMessage">Mod Removed Successfully</string>
<string name="NotInstalledMessage">Install the Stardew Valley First</string>
<string name="NotExtractedMessage">Press Extract First</string>
<string name="NotGeneratedMessage">Press Generate First</string>

View File

@ -35,10 +35,6 @@ namespace StardewModdingAPI
/// <summary>The path to the game folder.</summary>
public static string ExecutionPath { get; } = Path.Combine(Android.OS.Environment.ExternalStorageDirectory.Path , "SMDroid");
/// <summary>The path to the Assets folder.</summary>
public static string AssetsPath { get; } = Path.Combine(ExecutionPath, "Game/assets/Content");
/// <summary>The directory path containing Stardew Valley's app data.</summary>
public static string DataPath { get; } = Path.Combine(Android.OS.Environment.ExternalStorageDirectory.Path, "SMDroid");
@ -61,10 +57,10 @@ namespace StardewModdingAPI
internal const string HomePageUrl = "https://smapi.io";
/// <summary>The absolute path to the folder containing SMAPI's internal files.</summary>
internal static readonly string InternalFilesPath = Path.Combine(Android.OS.Environment.ExternalStorageDirectory.Path, "SMDroid/smapi-internal");
internal static readonly string InternalFilesPath = Path.Combine(Android.OS.Environment.ExternalStorageDirectory.Path, "SMDroid/smapi-internal".Replace('/', Path.DirectorySeparatorChar));
/// <summary>The absolute path to the folder containing SMAPI's internal files.</summary>
internal static readonly string GameAssembliesPath = Path.Combine(Android.OS.Environment.ExternalStorageDirectory.Path, "SMDroid/Game/assemblies");
internal static readonly string GameAssembliesPath = Path.Combine(Android.OS.Environment.ExternalStorageDirectory.Path, "SMDroid/Game/assemblies".Replace('/', Path.DirectorySeparatorChar));
/// <summary>The file path for the SMAPI configuration file.</summary>
internal static string ApiConfigPath => Path.Combine(Constants.InternalFilesPath, "StardewModdingAPI.config.json");

View File

@ -89,7 +89,9 @@ namespace StardewModdingAPI.Framework
private bool IsDisposed;
public static SCore Instance;
public bool HarmonyDetourBridgeFailed = false;
/// <summary>Regex patterns which match console messages to suppress from the console and log.</summary>
private readonly Regex[] SuppressConsolePatterns =
{
@ -254,7 +256,15 @@ namespace StardewModdingAPI.Framework
// override game
SGame.ConstructorHack = new SGameConstructorHack(this.Monitor, this.Reflection, this.Toolkit.JsonHelper, this.InitialiseBeforeFirstAssetLoaded);
HarmonyDetourBridge.Init();
try
{
HarmonyDetourBridge.Init();
}
catch
{
this.HarmonyDetourBridgeFailed = true;
}
// override game
this.GameInstance = new SGame(
monitor: this.Monitor,
@ -304,31 +314,13 @@ namespace StardewModdingAPI.Framework
this.Monitor.Log($"SMAPI failed to initialise: {ex.GetLogSummary()}", LogLevel.Error);
return;
}
// check update marker
if (File.Exists(Constants.UpdateMarker))
{
string rawUpdateFound = File.ReadAllText(Constants.UpdateMarker);
if (SemanticVersion.TryParse(rawUpdateFound, out ISemanticVersion updateFound))
{
if (Constants.ApiVersion.IsPrerelease() && updateFound.IsNewerThan(Constants.ApiVersion))
{
this.Monitor.Log("A new version of SMAPI was detected last time you played.", LogLevel.Error);
this.Monitor.Log($"You can update to {updateFound}: https://smapi.io.", LogLevel.Error);
this.Monitor.Log("Press any key to continue playing anyway. (This only appears when using a SMAPI beta.)", LogLevel.Info);
Console.ReadKey();
}
}
File.Delete(Constants.UpdateMarker);
}
// show details if game crashed during last session
if (File.Exists(Constants.FatalCrashMarker))
{
this.Monitor.Log("The game crashed last time you played. That can be due to bugs in the game, but if it happens repeatedly you can ask for help here: https://community.playstarbound.com/threads/108375/.", LogLevel.Error);
this.Monitor.Log("If you ask for help, make sure to share your SMAPI log: https://log.smapi.io.", LogLevel.Error);
this.Monitor.Log("Press any key to delete the crash data and continue playing.", LogLevel.Info);
Console.ReadKey();
File.Delete(Constants.FatalCrashLog);
File.Delete(Constants.FatalCrashMarker);
}
@ -420,6 +412,8 @@ namespace StardewModdingAPI.Framework
this.GameInstance.IsSuspended = true;
new Thread(() =>
{
while (!this.GameInstance.IsAfterInitialize)
Thread.Sleep(10);
// load mod data
ModToolkit toolkit = new ModToolkit();
ModDatabase modDatabase = toolkit.GetModDatabase(Constants.ApiMetadataPath);
@ -1009,7 +1003,7 @@ namespace StardewModdingAPI.Framework
Assembly modAssembly;
try
{
modAssembly = assemblyLoader.Load(mod, assemblyPath, true/*assumeCompatible: mod.DataRecord?.Status == ModStatus.AssumeCompatible*/);
modAssembly = assemblyLoader.Load(mod, assemblyPath, assumeCompatible: mod.DataRecord?.Status == ModStatus.AssumeCompatible);
this.ModRegistry.TrackAssemblies(mod, modAssembly);
}
catch (IncompatibleInstructionException) // details already in trace logs

View File

@ -233,7 +233,9 @@ namespace StardewModdingAPI.Framework
public List<IMod> HookReceiver = new List<IMod>();
public bool IsSuspended;
public bool IsAfterInitialize = false;
/*********
** Protected methods
@ -389,6 +391,8 @@ namespace StardewModdingAPI.Framework
{
if (this.IsSuspended)
{
if(!this.IsAfterInitialize)
this.IsAfterInitialize = true;
if (Game1.graphics.GraphicsDevice != null)
{
this.Reflection.GetMethod(Game1.game1, "_updateAudioEngine").Invoke();
@ -1437,6 +1441,7 @@ namespace StardewModdingAPI.Framework
_spriteBatchEnd.Invoke();
Game1.RestoreViewportAndZoom();
}
return;
}
if (Game1.showingEndOfNightStuff)
{
@ -1555,7 +1560,7 @@ namespace StardewModdingAPI.Framework
}
Game1.currentLocation?.drawWater(Game1.spriteBatch);
_farmerShadows.GetValue().Clear();
if (((Game1.currentLocation.currentEvent != null) && !Game1.currentLocation.currentEvent.isFestival) && (Game1.currentLocation.currentEvent.farmerActors.Count > 0))
if (Game1.currentLocation != null && ((Game1.currentLocation.currentEvent != null) && !Game1.currentLocation.currentEvent.isFestival) && (Game1.currentLocation.currentEvent.farmerActors.Count > 0))
{
foreach (Farmer farmer in Game1.currentLocation.currentEvent.farmerActors)
{

View File

@ -50,7 +50,10 @@ namespace StardewModdingAPI.Patches
ConstructorInfo constructor = AccessTools.Constructor(typeof(Dialogue), new[] { typeof(string), typeof(NPC) });
MethodInfo prefix = AccessTools.Method(this.GetType(), nameof(DialogueErrorPatch.Prefix));
harmony.Patch(constructor, new HarmonyMethod(prefix), null);
if (!SCore.Instance.HarmonyDetourBridgeFailed)
{
harmony.Patch(constructor, new HarmonyMethod(prefix), null);
}
}
@ -64,7 +67,7 @@ namespace StardewModdingAPI.Patches
/// <returns>Returns whether to execute the original method.</returns>
/// <remarks>This method must be static for Harmony to work correctly. See the Harmony documentation before renaming arguments.</remarks>
[SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Argument names are defined by Harmony.")]
private static bool Prefix(Dialogue __instance, string masterDialogue, NPC speaker)
public static bool Prefix(Dialogue __instance, string masterDialogue, NPC speaker)
{
// get private members
bool nameArraysTranslated = DialogueErrorPatch.Reflection.GetField<bool>(typeof(Dialogue), "nameArraysTranslated").GetValue();

View File

@ -5,6 +5,7 @@ using System.Collections.Specialized;
using System.Reflection;
using Harmony;
using StardewModdingAPI.Enums;
using StardewModdingAPI.Framework;
using StardewModdingAPI.Framework.Patching;
using StardewModdingAPI.Framework.Reflection;
using StardewValley;
@ -59,7 +60,10 @@ namespace StardewModdingAPI.Patches
MethodInfo prefix = AccessTools.Method(this.GetType(), nameof(LoadForNewGamePatch.Prefix));
MethodInfo postfix = AccessTools.Method(this.GetType(), nameof(LoadForNewGamePatch.Postfix));
harmony.Patch(method, new HarmonyMethod(prefix), new HarmonyMethod(postfix));
if (!SCore.Instance.HarmonyDetourBridgeFailed)
{
harmony.Patch(method, new HarmonyMethod(prefix), new HarmonyMethod(postfix));
}
}

View File

@ -1,5 +1,6 @@
using System.Diagnostics.CodeAnalysis;
using Harmony;
using StardewModdingAPI.Framework;
using StardewModdingAPI.Framework.Patching;
using StardewValley;
using StardewValley.Menus;
@ -24,17 +25,20 @@ namespace StardewModdingAPI.Patches
/// <param name="harmony">The Harmony instance.</param>
public void Apply(HarmonyInstance harmony)
{
// object.getDescription
harmony.Patch(
original: AccessTools.Method(typeof(SObject), nameof(SObject.getDescription)),
prefix: new HarmonyMethod(AccessTools.Method(this.GetType(), nameof(ObjectErrorPatch.Object_GetDescription_Prefix)))
);
if (!SCore.Instance.HarmonyDetourBridgeFailed)
{
// object.getDescription
harmony.Patch(
original: AccessTools.Method(typeof(SObject), nameof(SObject.getDescription)),
prefix: new HarmonyMethod(AccessTools.Method(this.GetType(), nameof(ObjectErrorPatch.Object_GetDescription_Prefix)))
);
// IClickableMenu.drawToolTip
harmony.Patch(
original: AccessTools.Method(typeof(IClickableMenu), nameof(IClickableMenu.drawToolTip)),
prefix: new HarmonyMethod(AccessTools.Method(this.GetType(), nameof(ObjectErrorPatch.IClickableMenu_DrawTooltip_Prefix)))
);
// IClickableMenu.drawToolTip
harmony.Patch(
original: AccessTools.Method(typeof(IClickableMenu), nameof(IClickableMenu.drawToolTip)),
prefix: new HarmonyMethod(AccessTools.Method(this.GetType(), nameof(ObjectErrorPatch.IClickableMenu_DrawTooltip_Prefix)))
);
}
}
@ -47,7 +51,7 @@ namespace StardewModdingAPI.Patches
/// <returns>Returns whether to execute the original method.</returns>
/// <remarks>This method must be static for Harmony to work correctly. See the Harmony documentation before renaming arguments.</remarks>
[SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Argument names are defined by Harmony.")]
private static bool Object_GetDescription_Prefix(SObject __instance, ref object __result)
public static bool Object_GetDescription_Prefix(SObject __instance, ref object __result)
{
// invalid bigcraftables crash instead of showing '???' like invalid non-bigcraftables
if (!__instance.IsRecipe && __instance.bigCraftable.Value && !Game1.bigCraftablesInformation.ContainsKey(__instance.ParentSheetIndex))
@ -65,7 +69,7 @@ namespace StardewModdingAPI.Patches
/// <returns>Returns whether to execute the original method.</returns>
/// <remarks>This method must be static for Harmony to work correctly. See the Harmony documentation before renaming arguments.</remarks>
[SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Argument names are defined by Harmony.")]
private static bool IClickableMenu_DrawTooltip_Prefix(IClickableMenu __instance, Item hoveredItem)
public static bool IClickableMenu_DrawTooltip_Prefix(IClickableMenu __instance, Item hoveredItem)
{
// invalid edible item cause crash when drawing tooltips
if (hoveredItem is SObject obj && obj.Edibility != -300 && !Game1.objectInformation.ContainsKey(obj.ParentSheetIndex))

BIN
ModLoader/SMDroidFiles.zip Normal file

Binary file not shown.

View File

@ -4,7 +4,9 @@ using System.Reflection;
using Android.App;
using Android.Content.PM;
using Android.OS;
using Android.Provider;
using Android.Views;
using Google.Android.Vending.Licensing;
using StardewModdingAPI;
using StardewModdingAPI.Framework;
using StardewValley;
@ -44,7 +46,17 @@ namespace ModLoader
Game1 game1 = StardewValley.Program.gamePtr;
typeof(MainActivity).GetField("_game1", BindingFlags.Instance | BindingFlags.NonPublic)?.SetValue(this, game1);
this.SetContentView((View)game1.Services.GetService(typeof(View)));
typeof(MainActivity).GetMethod("CheckUsingServerManagedPolicy", BindingFlags.Instance | BindingFlags.NonPublic)?.Invoke(this, null);
this.CheckUsingServerManagedPolicy();
}
private void CheckUsingServerManagedPolicy()
{
string packageName = Constants.GamePackageName;
string deviceId = Settings.Secure.GetString(this.ContentResolver, "android_id");
AESObfuscator obfuscator = new AESObfuscator(new byte[] { 0x2e, 0x41, 30, 0x80, 0x67, 0x39, 0x4a, 0x40, 0x33, 0x58, 0x5f, 0x2d, 0x4d, 0x75, 0x24 }, packageName, deviceId);
ServerManagedPolicy policy = new ServerManagedPolicy(this, obfuscator);
LicenseChecker licenseChecker = new LicenseChecker(this, policy, "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAry4fecehDpCohQk4XhiIZX9ylIGUThWZxfN9qwvQyTh53hvnpQl/lCrjfflKoPz6gz5jJn6JI1PTnoBy/iXVx1+kbO99qBgJE2V8PS5pq+Usbeqqmqqzx4lEzhiYQ2um92v4qkldNYZFwbTODYPIMbSbaLm7eK9ZyemaRbg9ssAl4QYs0EVxzDK1DjuXilRk28WxiK3lNJTz4cT38bfs4q6Zvuk1vWUvnMqcxiugox6c/9j4zZS5C4+k+WY6mHjUMuwssjCY3G+aImWDSwnU3w9G41q8EoPvJ1049PIi7GJXErusTYZITmqfonyejmSFLPt8LHtux9AmJgFSrC3UhwIDAQAB");
typeof(MainActivity).GetField("_licenseChecker", BindingFlags.Instance | BindingFlags.NonPublic)?.SetValue(this, licenseChecker);
licenseChecker.CheckAccess(this);
}
}
}