Modloader 1.4.2
1.CustomAttribute rewrite support 2.Update MonoMod to latest code 3.UI change
This commit is contained in:
parent
0822c41fc4
commit
e511ad28a8
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
|
@ -9,10 +10,12 @@ using Android.Content;
|
|||
using Android.Content.PM;
|
||||
using Android.OS;
|
||||
using Android.Support.V4.App;
|
||||
using Android.Support.V4.Content;
|
||||
using Android.Views;
|
||||
using Android.Widget;
|
||||
using Com.Pgyersdk.Update;
|
||||
using DllRewrite;
|
||||
using Java.Lang;
|
||||
using Microsoft.AppCenter;
|
||||
using Microsoft.AppCenter.Analytics;
|
||||
using Microsoft.AppCenter.Crashes;
|
||||
|
@ -25,7 +28,11 @@ using StardewModdingAPI.Framework.ModLoading;
|
|||
using StardewModdingAPI.Toolkit;
|
||||
using StardewModdingAPI.Toolkit.Framework.ModData;
|
||||
using StardewModdingAPI.Toolkit.Serialisation;
|
||||
using static ModLoader.Common.Utils;
|
||||
using Exception = System.Exception;
|
||||
using File = Java.IO.File;
|
||||
using Object = System.Object;
|
||||
using Thread = System.Threading.Thread;
|
||||
using Uri = Android.Net.Uri;
|
||||
|
||||
namespace ModLoader
|
||||
|
@ -36,40 +43,94 @@ namespace ModLoader
|
|||
, Theme = "@style/Theme.Splash"
|
||||
, AlwaysRetainTaskState = true
|
||||
, LaunchMode = LaunchMode.SingleInstance
|
||||
, ScreenOrientation = ScreenOrientation.SensorLandscape
|
||||
, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.Keyboard | ConfigChanges.KeyboardHidden | ConfigChanges.ScreenSize | ConfigChanges.ScreenLayout)]
|
||||
[SuppressMessage("ReSharper", "ArrangeThisQualifier")]
|
||||
public class Activity1 : AndroidGameActivity
|
||||
{
|
||||
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
|
||||
private string[] DeniedPermissionsArray
|
||||
{
|
||||
get
|
||||
{
|
||||
List<string> list = new List<string>();
|
||||
string[] requiredPermissions = this.requiredPermissions;
|
||||
for (int i = 0; i < requiredPermissions.Length; i++)
|
||||
foreach (string permission in this.requiredPermissions)
|
||||
{
|
||||
if (this.PackageManager.CheckPermission(requiredPermissions[i], this.PackageName) != Permission.Granted)
|
||||
if (this.PackageManager.CheckPermission(permission, this.PackageName) != Permission.Granted)
|
||||
{
|
||||
list.Add(requiredPermissions[i]);
|
||||
list.Add(permission);
|
||||
}
|
||||
}
|
||||
return list.ToArray();
|
||||
}
|
||||
}
|
||||
public bool HasPermissions => ((((this.PackageManager.CheckPermission("android.permission.ACCESS_NETWORK_STATE", this.PackageName) == Permission.Granted) && (this.PackageManager.CheckPermission("android.permission.ACCESS_WIFI_STATE", this.PackageName) == Permission.Granted)) && ((this.PackageManager.CheckPermission("android.permission.INTERNET", this.PackageName) == Permission.Granted) && (this.PackageManager.CheckPermission("android.permission.READ_EXTERNAL_STORAGE", this.PackageName) == Permission.Granted))) && (((this.PackageManager.CheckPermission("android.permission.VIBRATE", this.PackageName) == Permission.Granted) && (this.PackageManager.CheckPermission("android.permission.WAKE_LOCK", this.PackageName) == Permission.Granted)) && ((this.PackageManager.CheckPermission("android.permission.WRITE_EXTERNAL_STORAGE", this.PackageName) == Permission.Granted) && (this.PackageManager.CheckPermission("com.android.vending.CHECK_LICENSE", this.PackageName) == Permission.Granted))));
|
||||
public bool HasPermissions => this.PackageManager.CheckPermission("android.permission.ACCESS_NETWORK_STATE", this.PackageName) == Permission.Granted && this.PackageManager.CheckPermission("android.permission.ACCESS_WIFI_STATE", this.PackageName) == Permission.Granted && this.PackageManager.CheckPermission("android.permission.INTERNET", this.PackageName) == Permission.Granted && this.PackageManager.CheckPermission("android.permission.READ_EXTERNAL_STORAGE", this.PackageName) == Permission.Granted && this.PackageManager.CheckPermission("android.permission.VIBRATE", this.PackageName) == Permission.Granted && this.PackageManager.CheckPermission("android.permission.WAKE_LOCK", this.PackageName) == Permission.Granted && this.PackageManager.CheckPermission("android.permission.WRITE_EXTERNAL_STORAGE", this.PackageName) == Permission.Granted && this.PackageManager.CheckPermission("com.android.vending.CHECK_LICENSE", this.PackageName) == Permission.Granted;
|
||||
|
||||
public void PromptForPermissions()
|
||||
{
|
||||
ActivityCompat.RequestPermissions(this, this.deniedPermissionsArray, 0);
|
||||
ActivityCompat.RequestPermissions(this, this.DeniedPermissionsArray, 0);
|
||||
}
|
||||
|
||||
public override void OnRequestPermissionsResult(int requestCode, string[] permissions, Android.Content.PM.Permission[] grantResults)
|
||||
{
|
||||
if (permissions.Length == 0)
|
||||
{
|
||||
}
|
||||
else
|
||||
{
|
||||
string languageCode = Java.Util.Locale.Default.Language.Substring(0, 2);
|
||||
int num = 0;
|
||||
if (requestCode == 0)
|
||||
{
|
||||
for (int index = 0; index < grantResults.Length; ++index)
|
||||
{
|
||||
if (grantResults[index] == Android.Content.PM.Permission.Granted)
|
||||
++num;
|
||||
else if (grantResults[index] == Android.Content.PM.Permission.Denied)
|
||||
{
|
||||
try
|
||||
{
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder((Context)this);
|
||||
if (ActivityCompat.ShouldShowRequestPermissionRationale((Activity)this, permissions[index]))
|
||||
{
|
||||
builder.SetMessage(this.PermissionMessageA(languageCode));
|
||||
builder.SetPositiveButton(this.GetOKString(languageCode), (EventHandler<DialogClickEventArgs>)((senderAlert, args) =>
|
||||
{
|
||||
this.PromptForPermissions();
|
||||
}));
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.SetMessage(this.PermissionMessageB(languageCode));
|
||||
builder.SetPositiveButton(this.GetOKString(languageCode), (EventHandler<DialogClickEventArgs>)((senderAlert, args) => OpenAppSettingsOnPhone(this)));
|
||||
}
|
||||
Dialog dialog = (Dialog)builder.Create();
|
||||
if (this.IsFinishing)
|
||||
return;
|
||||
dialog.Show();
|
||||
return;
|
||||
}
|
||||
catch (IllegalArgumentException ex)
|
||||
{
|
||||
// ISSUE: variable of the null type
|
||||
Microsoft.AppCenter.Crashes.Crashes.TrackError((System.Exception)ex, null);
|
||||
OpenInPlayStore();
|
||||
this.Finish();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (num != permissions.Length)
|
||||
return;
|
||||
this.OnCreatePartTwo();
|
||||
}
|
||||
}
|
||||
private readonly Mutex _working = new Mutex(false);
|
||||
|
||||
public static Activity1 Instance { get; private set; }
|
||||
private HttpClient _httpClient = new HttpClient();
|
||||
private readonly HttpClient _httpClient = new HttpClient();
|
||||
|
||||
private static Dictionary<int, Action> MessageHandler = new Dictionary<int, Action>();
|
||||
private static readonly Dictionary<int, Action> MessageHandler = new Dictionary<int, Action>();
|
||||
private readonly Handler _handler = new Handler(message =>
|
||||
{
|
||||
if (MessageHandler.ContainsKey(message.What))
|
||||
|
@ -102,12 +163,19 @@ namespace ModLoader
|
|||
this.SetContentView(Resource.Layout.layout_main);
|
||||
if (!this.HasPermissions)
|
||||
this.PromptForPermissions();
|
||||
while (!this.HasPermissions)
|
||||
{
|
||||
Thread.Sleep(50);
|
||||
}
|
||||
new PgyUpdateManager.Builder().SetForced(false).SetUserCanRetry(true).SetDeleteHistroyApk(true).Register();
|
||||
else
|
||||
this.OnCreatePartTwo();
|
||||
}
|
||||
|
||||
private void OnCreatePartTwo()
|
||||
{
|
||||
if (Build.VERSION.SdkInt >= Android.OS.BuildVersionCodes.N)
|
||||
{
|
||||
StrictMode.SetVmPolicy(new StrictMode.VmPolicy.Builder().Build());
|
||||
}
|
||||
|
||||
new PgyUpdateManager.Builder().SetForced(false).SetUserCanRetry(true).SetDeleteHistroyApk(true).Register();
|
||||
this.InitEnvironment();
|
||||
this.FindViewById<Button>(Resource.Id.buttonExtract).Click += (sender, args) =>
|
||||
{
|
||||
new Thread(() =>
|
||||
|
@ -120,19 +188,30 @@ namespace ModLoader
|
|||
.FirstOrDefault(package => package.PackageName == Constants.GamePackageName);
|
||||
if (packageInfo == null)
|
||||
{
|
||||
Utils.MakeToast(this, this.Resources.GetText(Resource.String.NotInstalledMessage), ToastLength.Short);
|
||||
MakeToast(this, this.Resources.GetText(Resource.String.NotInstalledMessage), ToastLength.Short);
|
||||
return;
|
||||
}
|
||||
if (!new File(Constants.GamePath).Exists())
|
||||
{
|
||||
Directory.CreateDirectory(Constants.GamePath);
|
||||
}
|
||||
StatFs sf = new StatFs(Constants.GamePath);
|
||||
if (sf.AvailableBytes + GetDirectoryLength(Path.Combine(Constants.GamePath, "Game" + Path.DirectorySeparatorChar)) -
|
||||
160 * 1024 * 1024 < 0)
|
||||
{
|
||||
MakeToast(this, this.Resources.GetText(Resource.String.StorageIsFullMessage), ToastLength.Short);
|
||||
return;
|
||||
}
|
||||
AlertDialog dialog = null;
|
||||
Utils.ShowProgressDialog(this, Resource.String.Extract, this.Resources.GetText(Resource.String.ExtractingMessage), dlg => { dialog = dlg; });
|
||||
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, Path.Combine(Constants.GamePath, "Game/"));
|
||||
ZipHelper.UnZip(sourceDir, Path.Combine(Constants.GamePath, "Game" + Path.DirectorySeparatorChar));
|
||||
dialog.Dismiss();
|
||||
Utils.MakeToast(this, this.Resources.GetText(Resource.String.ExtractedMessage),
|
||||
MakeToast(this, this.Resources.GetText(Resource.String.ExtractedMessage),
|
||||
ToastLength.Long);
|
||||
|
||||
}
|
||||
|
@ -152,22 +231,28 @@ namespace ModLoader
|
|||
{
|
||||
if (!new File(Constants.AssemblyPath).Exists())
|
||||
{
|
||||
Utils.MakeToast(this, this.Resources.GetText(Resource.String.NotExtractedMessage), ToastLength.Short);
|
||||
MakeToast(this, this.Resources.GetText(Resource.String.NotExtractedMessage), ToastLength.Short);
|
||||
return;
|
||||
}
|
||||
AlertDialog dialog = null;
|
||||
Utils.ShowProgressDialog(this, Resource.String.Generate, this.Resources.GetText(Resource.String.GeneratingMessage), dlg => { dialog = dlg; });
|
||||
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(Path.Combine(Constants.GamePath, "StardewValley.dll"));
|
||||
AssemblyDefinition MonoFramework = mp.InsertMonoHooks();
|
||||
MonoFramework.Write(Path.Combine(Constants.GamePath, "MonoGame.Framework.dll"));
|
||||
AssemblyDefinition stardewValley = mp.InsertModHooks();
|
||||
FileStream stream = new FileStream(Path.Combine(Constants.GamePath, "StardewValley.dll"), FileMode.Create,
|
||||
FileAccess.Write, FileShare.Read);
|
||||
stardewValley.Write(stream);
|
||||
stream.Close();
|
||||
AssemblyDefinition monoFramework = mp.InsertMonoHooks();
|
||||
stream = new FileStream(Path.Combine(Constants.GamePath, "MonoGame.Framework.dll"), FileMode.Create,
|
||||
FileAccess.Write, FileShare.Read);
|
||||
monoFramework.Write(stream);
|
||||
stream.Close();
|
||||
dialog.Dismiss();
|
||||
Utils.MakeToast(this, this.Resources.GetText(Resource.String.GeneratedMessage),
|
||||
MakeToast(this, this.Resources.GetText(Resource.String.GeneratedMessage),
|
||||
ToastLength.Long);
|
||||
}
|
||||
finally
|
||||
|
@ -181,19 +266,30 @@ namespace ModLoader
|
|||
if (!this._working.WaitOne(10))
|
||||
return;
|
||||
this._working.ReleaseMutex();
|
||||
if (!new File(Path.Combine(Constants.ContentPath, "XACT/FarmerSounds.xgs".Replace('/',Path.DirectorySeparatorChar))).Exists())
|
||||
if (!new File(Constants.GamePath).Exists())
|
||||
{
|
||||
Utils.MakeToast(this, this.Resources.GetText(Resource.String.NotExtractedMessage), ToastLength.Short);
|
||||
Directory.CreateDirectory(Constants.GamePath);
|
||||
}
|
||||
if (!new File(Path.Combine(Constants.ContentPath, "XACT/FarmerSounds.xgs".Replace('/', Path.DirectorySeparatorChar))).Exists())
|
||||
{
|
||||
MakeToast(this, this.Resources.GetText(Resource.String.NotExtractedMessage), ToastLength.Short);
|
||||
return;
|
||||
}
|
||||
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);
|
||||
MakeToast(this, this.Resources.GetText(Resource.String.NotGeneratedMessage), ToastLength.Short);
|
||||
return;
|
||||
}
|
||||
this.StartActivity(typeof(SMainActivity));
|
||||
this.Finish();
|
||||
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())
|
||||
{
|
||||
Stream stream = this.Resources.OpenRawResource(Resource.Raw.SMDroidFiles);
|
||||
ZipHelper.UnZip(stream, Constants.GamePath);
|
||||
}
|
||||
this.StartActivityForResult(typeof(SMainActivity), 1);
|
||||
};
|
||||
|
||||
this.FindViewById<Button>(Resource.Id.buttonWiki).Click += (sender, args) =>
|
||||
|
@ -202,76 +298,65 @@ namespace ModLoader
|
|||
Intent intent = new Intent(Intent.ActionView, uri);
|
||||
this.StartActivity(intent);
|
||||
};
|
||||
//this.FindViewById<Button>(Resource.Id.buttonModFolder).Click += (sender, args) =>
|
||||
//{
|
||||
|
||||
// Intent intent = new Intent(Intent.ActionGetContent);
|
||||
// intent.AddCategory(Intent.CategoryOpenable);
|
||||
// File modFolder = new File(Constants.ModPath);
|
||||
// if (Build.VERSION.SdkInt >= Android.OS.BuildVersionCodes.N)
|
||||
// {
|
||||
// intent.SetFlags(ActivityFlags.GrantReadUriPermission);
|
||||
// Android.Net.Uri contentUri = FileProvider.GetUriForFile(this, "com.zane.smdroid.fileProvider", modFolder);
|
||||
// intent.SetDataAndType(contentUri, "file/*.json");
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// intent.SetDataAndType(Android.Net.Uri.FromFile(modFolder), "file/*.json");
|
||||
// intent.SetFlags(ActivityFlags.NewTask);
|
||||
// }
|
||||
|
||||
// try
|
||||
// {
|
||||
// this.StartActivity(Intent.CreateChooser(intent, ""));
|
||||
// }
|
||||
// catch (ActivityNotFoundException) { }
|
||||
//};
|
||||
if (!new File(Constants.GamePath).Exists())
|
||||
{
|
||||
Directory.CreateDirectory(Constants.GamePath);
|
||||
}
|
||||
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)
|
||||
|
||||
private void InitEnvironment()
|
||||
{
|
||||
this._working.WaitOne();
|
||||
try
|
||||
{
|
||||
if (!new File(Constants.GamePath).Exists())
|
||||
{
|
||||
Directory.CreateDirectory(Constants.GamePath);
|
||||
}
|
||||
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())
|
||||
{
|
||||
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);
|
||||
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);
|
||||
if (!this.IsFinishing)
|
||||
{
|
||||
this.InvokeActivityThread(0, this.PrepareModList);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}).Start();
|
||||
}
|
||||
finally
|
||||
{
|
||||
this._working.ReleaseMutex();
|
||||
}
|
||||
}
|
||||
|
||||
internal void InstallMod(ModInfo mod)
|
||||
{
|
||||
new Thread(async () =>
|
||||
{
|
||||
|
@ -280,7 +365,7 @@ namespace ModLoader
|
|||
try
|
||||
{
|
||||
AlertDialog dialog = null;
|
||||
Utils.ShowProgressDialog(this, Resource.String.ModInstall,
|
||||
ShowProgressDialog(this, Resource.String.ModInstall,
|
||||
this.Resources.GetText(Resource.String.ModDownloadingMessage), dlg => { dialog = dlg; });
|
||||
while (dialog == null)
|
||||
{
|
||||
|
@ -293,19 +378,19 @@ namespace ModLoader
|
|||
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),
|
||||
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),
|
||||
MakeToast(this, this.Resources.GetText(Resource.String.NetworkErrorMessage),
|
||||
ToastLength.Long);
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
Utils.MakeToast(this, this.Resources.GetText(Resource.String.NetworkErrorMessage),
|
||||
MakeToast(this, this.Resources.GetText(Resource.String.NetworkErrorMessage),
|
||||
ToastLength.Long);
|
||||
}
|
||||
finally
|
||||
|
@ -319,19 +404,32 @@ namespace ModLoader
|
|||
}
|
||||
}).Start();
|
||||
}
|
||||
internal void ConfigMod(string configPath)
|
||||
{
|
||||
Intent intent = new Intent(Intent.ActionView);
|
||||
intent.AddCategory(Intent.CategoryDefault);
|
||||
File configFile = new File(configPath);
|
||||
intent.SetDataAndType(Android.Net.Uri.FromFile(configFile), "text/plain");
|
||||
intent.AddFlags(ActivityFlags.NewTask);
|
||||
try
|
||||
{
|
||||
this.StartActivity(intent);
|
||||
}
|
||||
catch (ActivityNotFoundException) { }
|
||||
}
|
||||
|
||||
public void RemoveMod(ModInfo mod)
|
||||
internal 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,
|
||||
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),
|
||||
MakeToast(this, this.Resources.GetText(Resource.String.ModRemovedMessage),
|
||||
ToastLength.Long);
|
||||
this.InvokeActivityThread(0, this.PrepareModList);
|
||||
});
|
||||
|
@ -395,11 +493,94 @@ namespace ModLoader
|
|||
}
|
||||
}
|
||||
|
||||
protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
|
||||
{
|
||||
if (requestCode == 1)
|
||||
{
|
||||
this.Finish();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnDestroy()
|
||||
{
|
||||
base.OnDestroy();
|
||||
PgyUpdateManager.UnRegister();
|
||||
}
|
||||
|
||||
private string PermissionMessageA(string languageCode)
|
||||
{
|
||||
if (languageCode == "de")
|
||||
return "Du musst die Erlaubnis zum Lesen/Schreiben auf dem externen Speicher geben, um das Spiel zu speichern und Speicherstände auf andere Plattformen übertragen zu können. Bitte gib diese Genehmigung, um spielen zu können.";
|
||||
if (languageCode == "es")
|
||||
return "Para guardar la partida y transferir partidas guardadas a y desde otras plataformas, se necesita permiso para leer/escribir en almacenamiento externo. Concede este permiso para poder jugar.";
|
||||
if (languageCode == "ja")
|
||||
return "外部機器への読み込み/書き出しの許可が、ゲームのセーブデータの保存や他プラットフォームとの双方向のデータ移行実行に必要です。プレイを続けるには許可をしてください。";
|
||||
if (languageCode == "pt")
|
||||
return "Para salvar o jogo e transferir jogos salvos entre plataformas é necessário permissão para ler/gravar em armazenamento externo. Forneça essa permissão para jogar.";
|
||||
if (languageCode == "ru")
|
||||
return "Для сохранения игры и переноса сохранений с/на другие платформы нужно разрешение на чтение-запись на внешнюю память. Дайте разрешение, чтобы начать играть.";
|
||||
if (languageCode == "ko")
|
||||
return "게임을 저장하려면 외부 저장공간에 대한 읽기/쓰기 권한이 필요합니다. 또한 저장 데이터 이전 기능을 허용해 다른 플랫폼에서 게임 진행상황을 가져올 때에도 권한이 필요합니다. 게임을 플레이하려면 권한을 허용해 주십시오.";
|
||||
if (languageCode == "tr")
|
||||
return "Oyunu kaydetmek ve kayıtları platformlardan platformlara taşımak için harici depolamada okuma/yazma izni gereklidir. Lütfen oynayabilmek için izin verin.";
|
||||
if (languageCode == "fr")
|
||||
return "Une autorisation de lecture / écriture sur un stockage externe est requise pour sauvegarder le jeu et vous permettre de transférer des sauvegardes vers et depuis d'autres plateformes. Veuillez donner l'autorisation afin de jouer.";
|
||||
if (languageCode == "hu")
|
||||
return "A játék mentéséhez, és ahhoz, hogy a különböző platformok között hordozhasd a játékmentést, engedélyezned kell a külső tárhely olvasását/írását, Kérjük, a játékhoz engedélyezd ezeket.";
|
||||
if (languageCode == "it")
|
||||
return "È necessaria l'autorizzazione a leggere/scrivere su un dispositivo di memorizzazione esterno per salvare la partita e per consentire di trasferire i salvataggi da e su altre piattaforme. Concedi l'autorizzazione per giocare.";
|
||||
return languageCode == "zh" ? "《星露谷物语》请求获得授权用来保存游戏数据以及访问线上功能。" : "Read/write to external storage permission is required to save the game, and to allow to you transfer saves to and from other platforms. Please give permission in order to play.";
|
||||
}
|
||||
|
||||
private string PermissionMessageB(string languageCode)
|
||||
{
|
||||
if (languageCode == "de")
|
||||
return "Bitte geh in die Handy-Einstellungen > Apps > Stardew Valley > Berechtigungen und aktiviere den Speicher, um das Spiel zu spielen.";
|
||||
if (languageCode == "es")
|
||||
return "En el teléfono, ve a Ajustes > Aplicaciones > Stardew Valley > Permisos y activa Almacenamiento para jugar al juego.";
|
||||
if (languageCode == "ja")
|
||||
return "設定 > アプリ > スターデューバレー > 許可の順に開いていき、ストレージを有効にしてからゲームをプレイしましょう。";
|
||||
if (languageCode == "pt")
|
||||
return "Acesse Configurar > Aplicativos > Stardew Valley > Permissões e ative Armazenamento para jogar.";
|
||||
if (languageCode == "ru")
|
||||
return "Перейдите в меню Настройки > Приложения > Stardew Valley > Разрешения и дайте доступ к памяти, чтобы начать играть.";
|
||||
if (languageCode == "ko")
|
||||
return "휴대전화의 설정 > 어플리케이션 > 스타듀 밸리 > 권한 에서 저장공간을 활성화한 뒤 게임을 플레이해 주십시오.";
|
||||
if (languageCode == "tr")
|
||||
return "Lütfen oyunu oynayabilmek için telefonda Ayarlar > Uygulamalar > Stardew Valley > İzinler ve Depolamayı etkinleştir yapın.";
|
||||
if (languageCode == "fr")
|
||||
return "Veuillez aller dans les Paramètres du téléphone> Applications> Stardew Valley> Autorisations, puis activez Stockage pour jouer.";
|
||||
if (languageCode == "hu")
|
||||
return "Lépje be a telefonodon a Beállítások > Alkalmazások > Stardew Valley > Engedélyek menübe, majd engedélyezd a Tárhelyet a játékhoz.";
|
||||
if (languageCode == "it")
|
||||
return "Nel telefono, vai su Impostazioni > Applicazioni > Stardew Valley > Autorizzazioni e attiva Memoria archiviazione per giocare.";
|
||||
return languageCode == "zh" ? "可在“设置-权限隐私-按应用管理权限-星露谷物语”进行设置,并打开“电话”、“读取位置信息”、“存储”权限。" : "Please go into phone Settings > Apps > Stardew Valley > Permissions, and enable Storage to play the game.";
|
||||
}
|
||||
private string GetOKString(string languageCode)
|
||||
{
|
||||
if (languageCode == "de")
|
||||
return "OK";
|
||||
if (languageCode == "es")
|
||||
return "DE ACUERDO";
|
||||
if (languageCode == "ja")
|
||||
return "OK";
|
||||
if (languageCode == "pt")
|
||||
return "Está bem";
|
||||
if (languageCode == "ru")
|
||||
return "Хорошо";
|
||||
if (languageCode == "ko")
|
||||
return "승인";
|
||||
if (languageCode == "tr")
|
||||
return "tamam";
|
||||
if (languageCode == "fr")
|
||||
return "D'accord";
|
||||
if (languageCode == "hu")
|
||||
return "rendben";
|
||||
if (languageCode == "it")
|
||||
return "ok";
|
||||
return languageCode == "zh" ? "好" : "OK";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -152,9 +152,22 @@ namespace DllRewrite
|
|||
var isRainingField = this.GetFieldReference("isRaining", "StardewValley.RainManager", this.StardewValley);
|
||||
processor.Emit(OpCodes.Ldfld, isRainingField);
|
||||
processor.Emit(OpCodes.Ret);
|
||||
|
||||
propertyDefinition.SetMethod = new MethodDefinition("set_isRaining",
|
||||
MethodAttributes.Public | MethodAttributes.ReuseSlot | MethodAttributes.SpecialName |
|
||||
MethodAttributes.Static | MethodAttributes.HideBySig, this.GetTypeReference("System.Void"));
|
||||
propertyDefinition.SetMethod.Parameters.Add(new ParameterDefinition("value", ParameterAttributes.None, this.GetTypeReference("System.Boolean")));
|
||||
propertyDefinition.SetMethod.SemanticsAttributes = MethodSemanticsAttributes.Setter;
|
||||
processor = propertyDefinition.SetMethod.Body.GetILProcessor();
|
||||
processor.Emit(OpCodes.Callvirt, getMethod);
|
||||
processor.Emit(OpCodes.Ldarg_0);
|
||||
processor.Emit(OpCodes.Stfld, isRainingField);
|
||||
processor.Emit(OpCodes.Ret);
|
||||
typeGame1.Methods.Add(propertyDefinition.GetMethod);
|
||||
typeGame1.Methods.Add(propertyDefinition.SetMethod);
|
||||
typeGame1.Properties.Add(propertyDefinition);
|
||||
|
||||
|
||||
propertyDefinition = new PropertyDefinition("isDebrisWeather", PropertyAttributes.None,
|
||||
this.GetTypeReference("System.Boolean"));
|
||||
propertyDefinition.GetMethod = new MethodDefinition("get_isDebrisWeather",
|
||||
|
@ -169,9 +182,23 @@ namespace DllRewrite
|
|||
this.StardewValley);
|
||||
processor.Emit(OpCodes.Ldfld, isDebrisWeatherField);
|
||||
processor.Emit(OpCodes.Ret);
|
||||
|
||||
propertyDefinition.SetMethod = new MethodDefinition("set_isDebrisWeather",
|
||||
MethodAttributes.Public | MethodAttributes.ReuseSlot | MethodAttributes.SpecialName |
|
||||
MethodAttributes.Static | MethodAttributes.HideBySig, this.GetTypeReference("System.Void"));
|
||||
propertyDefinition.SetMethod.Parameters.Add(new ParameterDefinition("value", ParameterAttributes.None, this.GetTypeReference("System.Boolean")));
|
||||
propertyDefinition.SetMethod.SemanticsAttributes = MethodSemanticsAttributes.Setter;
|
||||
processor = propertyDefinition.SetMethod.Body.GetILProcessor();
|
||||
processor.Emit(OpCodes.Callvirt, getMethod);
|
||||
processor.Emit(OpCodes.Ldarg_0);
|
||||
processor.Emit(OpCodes.Stfld, isDebrisWeatherField);
|
||||
processor.Emit(OpCodes.Ret);
|
||||
typeGame1.Methods.Add(propertyDefinition.GetMethod);
|
||||
typeGame1.Methods.Add(propertyDefinition.SetMethod);
|
||||
typeGame1.Properties.Add(propertyDefinition);
|
||||
|
||||
|
||||
|
||||
//HUDMessage..ctor
|
||||
var typeHUDMessage = this.StardewValley.MainModule.GetType("StardewValley.HUDMessage");
|
||||
var hudConstructor = new MethodDefinition(".ctor",
|
||||
|
|
|
@ -14,8 +14,11 @@ namespace ModLoader.Common
|
|||
internal ModInfo(IModMetadata metadata)
|
||||
{
|
||||
this.Metadata = metadata;
|
||||
if(metadata != null)
|
||||
if (metadata != null)
|
||||
{
|
||||
this.Name = metadata?.DisplayName;
|
||||
this.Description = metadata?.Manifest?.Description;
|
||||
}
|
||||
}
|
||||
|
||||
public string UniqueID { get; set; }
|
||||
|
|
|
@ -26,6 +26,7 @@ namespace ModLoader.Common
|
|||
TextView headText = view.FindViewById<TextView>(Resource.Id.textModName);
|
||||
TextView descriptionText = view.FindViewById<TextView>(Resource.Id.textDescription);
|
||||
Button buttonAddOrRemove = view.FindViewById<Button>(Resource.Id.buttonAddOrRemove);
|
||||
Button buttonConfig = view.FindViewById<Button>(Resource.Id.buttonConfig);
|
||||
headText.Text = mod.Name;
|
||||
descriptionText.Text = mod.Description;
|
||||
if (mod.Metadata == null)
|
||||
|
@ -33,16 +34,46 @@ namespace ModLoader.Common
|
|||
buttonAddOrRemove.Text = this.Context.Resources.GetText(Resource.String.ModInstall);
|
||||
headText.SetTextColor(Color.Gray);
|
||||
}
|
||||
else if (mod.Metadata.HasManifest() && mod.Metadata.Manifest.Version != null && mod.Version != null && mod.Metadata.Manifest.Version.IsOlderThan(mod.Version))
|
||||
{
|
||||
buttonAddOrRemove.Text = this.Context.Resources.GetText(Resource.String.Update);
|
||||
}
|
||||
else
|
||||
{
|
||||
buttonAddOrRemove.Text = this.Context.Resources.GetText(Resource.String.ModRemove);
|
||||
}
|
||||
|
||||
if (mod.Metadata == null || !File.Exists(System.IO.Path.Combine(mod.Metadata.DirectoryPath, "config.json")))
|
||||
{
|
||||
buttonConfig.Visibility = ViewStates.Invisible;
|
||||
if (headText.LayoutParameters is RelativeLayout.LayoutParams layoutParams1)
|
||||
{
|
||||
layoutParams1.RemoveRule(LayoutRules.LeftOf);
|
||||
layoutParams1.AddRule(LayoutRules.LeftOf, Resource.Id.buttonAddOrRemove);
|
||||
}
|
||||
if (descriptionText.LayoutParameters is RelativeLayout.LayoutParams layoutParams2)
|
||||
{
|
||||
layoutParams2.RemoveRule(LayoutRules.LeftOf);
|
||||
layoutParams2.AddRule(LayoutRules.LeftOf, Resource.Id.buttonAddOrRemove);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
buttonConfig.Click += (sender, args) =>
|
||||
{
|
||||
Activity1.Instance.ConfigMod(System.IO.Path.Combine(mod.Metadata.DirectoryPath, "config.json"));
|
||||
};
|
||||
}
|
||||
buttonAddOrRemove.Click += (sender, args) =>
|
||||
{
|
||||
if (mod.Metadata == null)
|
||||
{
|
||||
Activity1.Instance.InstallMod(mod);
|
||||
}
|
||||
else if (mod.Metadata.HasManifest() && mod.Metadata.Manifest.Version != null && mod.Version != null && mod.Metadata.Manifest.Version.IsOlderThan(mod.Version))
|
||||
{
|
||||
Activity1.Instance.InstallMod(mod);
|
||||
}
|
||||
else
|
||||
{
|
||||
Activity1.Instance.RemoveMod(mod);
|
||||
|
|
|
@ -42,6 +42,32 @@ namespace ModLoader.Common
|
|||
fs.Close();
|
||||
}
|
||||
|
||||
public static long GetDirectoryLength(string dirPath)
|
||||
{
|
||||
//判断给定的路径是否存在,如果不存在则退出
|
||||
if (!Directory.Exists(dirPath))
|
||||
return 0;
|
||||
long len = 0;
|
||||
//定义一个DirectoryInfo对象
|
||||
DirectoryInfo di = new DirectoryInfo(dirPath);
|
||||
//通过GetFiles方法,获取di目录中的所有文件的大小
|
||||
foreach (FileInfo fi in di.GetFiles())
|
||||
{
|
||||
len += fi.Length;
|
||||
}
|
||||
//获取di中所有的文件夹,并存到一个新的对象数组中,以进行递归
|
||||
DirectoryInfo[] dis = di.GetDirectories();
|
||||
if (dis.Length > 0)
|
||||
{
|
||||
for (int i = 0; i < dis.Length; i++)
|
||||
{
|
||||
|
||||
len += GetDirectoryLength(dis[i].FullName);
|
||||
}
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
public static void InvokeLooperThread(Action action)
|
||||
{
|
||||
new Thread(() =>
|
||||
|
@ -83,5 +109,42 @@ namespace ModLoader.Common
|
|||
.Show();
|
||||
});
|
||||
}
|
||||
|
||||
public static void ShowAlertDialog(Context context, int titleId, string message, int confirmId, Action onConfirm = null)
|
||||
{
|
||||
InvokeLooperThread(() =>
|
||||
{
|
||||
new AlertDialog.Builder(context).SetTitle(titleId).SetMessage(message).SetCancelable(true)
|
||||
.SetPositiveButton(confirmId, (sender, args) => onConfirm?.Invoke())
|
||||
.Show();
|
||||
});
|
||||
}
|
||||
public static void OpenAppSettingsOnPhone(Context context)
|
||||
{
|
||||
Intent intent = new Intent();
|
||||
intent.SetAction("android.settings.APPLICATION_DETAILS_SETTINGS");
|
||||
Android.Net.Uri data = Android.Net.Uri.FromParts("package", context.PackageName, (string)null);
|
||||
intent.SetData(data);
|
||||
context.StartActivity(intent);
|
||||
}
|
||||
public static void OpenInPlayStore()
|
||||
{
|
||||
try
|
||||
{
|
||||
Intent intent = new Intent("android.intent.action.VIEW", Android.Net.Uri.Parse("market://details?id=" + Constants.GamePackageName));
|
||||
intent.AddFlags(ActivityFlags.NewTask);
|
||||
Application.Context.StartActivity(intent);
|
||||
}
|
||||
catch (ActivityNotFoundException)
|
||||
{
|
||||
Intent intent = new Intent("android.intent.action.VIEW", Android.Net.Uri.Parse("https://play.google.com/store/apps/details?id=" + Constants.GamePackageName));
|
||||
intent.AddFlags(ActivityFlags.NewTask);
|
||||
Application.Context.StartActivity(intent);
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
Microsoft.AppCenter.Crashes.Crashes.TrackError(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,15 +1,5 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
using Android.App;
|
||||
using Android.Content;
|
||||
using Android.OS;
|
||||
using Android.Runtime;
|
||||
using Android.Views;
|
||||
using Android.Widget;
|
||||
using Java.Util.Zip;
|
||||
|
||||
namespace ModLoader.Helper
|
||||
|
@ -43,24 +33,34 @@ namespace ModLoader.Helper
|
|||
}
|
||||
if (fileName != string.Empty)
|
||||
{
|
||||
using (FileStream streamWriter = File.Create(unZipDir + theEntry.Name))
|
||||
FileStream streamWriter = null;
|
||||
try
|
||||
{
|
||||
|
||||
int size;
|
||||
byte[] data = new byte[2048];
|
||||
while (true)
|
||||
using (streamWriter = File.Create(unZipDir + theEntry.Name))
|
||||
{
|
||||
size = s.Read(data, 0, data.Length);
|
||||
if (size > 0)
|
||||
|
||||
int size;
|
||||
byte[] data = new byte[2048];
|
||||
while (true)
|
||||
{
|
||||
streamWriter.Write(data, 0, size);
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
size = s.Read(data, 0, data.Length);
|
||||
if (size > 0)
|
||||
{
|
||||
streamWriter.Write(data, 0, size);
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (IOException) { }
|
||||
finally
|
||||
{
|
||||
streamWriter?.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using StardewModdingAPI.Framework;
|
||||
|
||||
namespace Harmony
|
||||
{
|
||||
|
@ -60,7 +61,9 @@ namespace Harmony
|
|||
|
||||
public List<DynamicMethod> Patch()
|
||||
{
|
||||
lock (locker)
|
||||
if(SCore.Instance.HarmonyDetourBridgeFailed)
|
||||
return new List<DynamicMethod>();
|
||||
lock (locker)
|
||||
{
|
||||
var dynamicMethods = new List<DynamicMethod>();
|
||||
foreach (var original in originals)
|
||||
|
@ -302,4 +305,4 @@ namespace Harmony
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -69,7 +69,7 @@
|
|||
<AndroidEnableMultiDex>false</AndroidEnableMultiDex>
|
||||
<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>
|
||||
<AndroidEnableSGenConcurrent>false</AndroidEnableSGenConcurrent>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="BmFont">
|
||||
|
@ -870,6 +870,7 @@
|
|||
<Compile Include="SMAPI\Framework\ModLoading\Rewriters\FieldToPropertyRewriter.cs" />
|
||||
<Compile Include="SMAPI\Framework\ModLoading\Rewriters\MethodParentRewriter.cs" />
|
||||
<Compile Include="SMAPI\Framework\ModLoading\Rewriters\StaticFieldToConstantRewriter.cs" />
|
||||
<Compile Include="SMAPI\Framework\ModLoading\Rewriters\TypeFieldToTypeFieldRewriter.cs" />
|
||||
<Compile Include="SMAPI\Framework\ModLoading\Rewriters\TypeReferenceRewriter.cs" />
|
||||
<Compile Include="SMAPI\Framework\ModLoading\TypeReferenceComparer.cs" />
|
||||
<Compile Include="SMAPI\Framework\ModRegistry.cs" />
|
||||
|
@ -962,7 +963,8 @@
|
|||
<Compile Include="SMAPI\Metadata\InstructionMetadata.cs" />
|
||||
<Compile Include="SMAPI\Mod.cs" />
|
||||
<Compile Include="SMAPI\Patches\DialogueErrorPatch.cs" />
|
||||
<Compile Include="SMAPI\Patches\LoadForNewGamePatch.cs" />
|
||||
<Compile Include="SMAPI\Patches\EventErrorPatch.cs" />
|
||||
<Compile Include="SMAPI\Patches\LoadContextPatch.cs" />
|
||||
<Compile Include="SMAPI\Patches\ObjectErrorPatch.cs" />
|
||||
<Compile Include="SMAPI\PatchMode.cs" />
|
||||
<Compile Include="SMAPI\Program.cs" />
|
||||
|
@ -1049,16 +1051,22 @@
|
|||
<SubType>Designer</SubType>
|
||||
</AndroidResource>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\Xml\file_paths.xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\JarBinding\JarBinding.csproj">
|
||||
<Project>{a8fbe3c3-bde1-42c5-a4b9-f34ce5a68f46}</Project>
|
||||
<Name>JarBinding</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup />
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\Drawable\splash_screen.xml">
|
||||
<Generator>MSBuild:UpdateGeneratedFiles</Generator>
|
||||
</AndroidResource>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\Drawable\splash_logos_crop.png">
|
||||
<Generator>MSBuild:UpdateGeneratedFiles</Generator>
|
||||
</AndroidResource>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildExtensionsPath)\Novell\Novell.MonoDroid.CSharp.targets" />
|
||||
<Import Project="$(MSBuildExtensionsPath)\MonoGame\v3.0\MonoGame.Content.Builder.targets" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
|
|
|
@ -129,6 +129,7 @@ namespace MonoMod.RuntimeDetour {
|
|||
DetourHelper.Native.MakeWritable(link);
|
||||
DetourHelper.Native.Apply(link);
|
||||
DetourHelper.Native.MakeExecutable(link);
|
||||
DetourHelper.Native.FlushICache(link);
|
||||
DetourHelper.Native.Free(link);
|
||||
|
||||
detours.Add(this);
|
||||
|
@ -289,6 +290,7 @@ namespace MonoMod.RuntimeDetour {
|
|||
DetourHelper.Native.MakeWritable(link);
|
||||
DetourHelper.Native.Apply(link);
|
||||
DetourHelper.Native.MakeExecutable(link);
|
||||
DetourHelper.Native.FlushICache(link);
|
||||
DetourHelper.Native.Free(link);
|
||||
}
|
||||
|
||||
|
@ -299,6 +301,7 @@ namespace MonoMod.RuntimeDetour {
|
|||
DetourHelper.Native.MakeWritable(link);
|
||||
DetourHelper.Native.Apply(link);
|
||||
DetourHelper.Native.MakeExecutable(link);
|
||||
DetourHelper.Native.FlushICache(link);
|
||||
DetourHelper.Native.Free(link);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,29 +43,27 @@ namespace MonoMod.RuntimeDetour {
|
|||
_Native = new DetourNativeX86Platform();
|
||||
}
|
||||
|
||||
if (PlatformHelper.Is(Platform.Windows)) {
|
||||
return _Native = new DetourNativeWindowsPlatform(_Native);
|
||||
}
|
||||
|
||||
if (Type.GetType("Mono.Runtime") != null) {
|
||||
try {
|
||||
// It's prefixed with lib on every platform.
|
||||
_Native = new DetourNativeMonoPlatform(_Native, $"libmonosgen-2.0.{PlatformHelper.LibrarySuffix}");
|
||||
return _Native;
|
||||
return _Native = new DetourNativeMonoPlatform(_Native, $"libmonosgen-2.0.{PlatformHelper.LibrarySuffix}");
|
||||
} catch {
|
||||
// Fall back to another native platform wrapper.
|
||||
}
|
||||
}
|
||||
|
||||
if (PlatformHelper.Is(Platform.Windows)) {
|
||||
_Native = new DetourNativeWindowsPlatform(_Native);
|
||||
} else if (PlatformHelper.Is(Platform.Unix)) {
|
||||
if (Type.GetType("Mono.Runtime") != null) {
|
||||
try {
|
||||
_Native = new DetourNativeMonoPosixPlatform(_Native);
|
||||
} catch {
|
||||
// If you're reading this: Good job, your copy of Mono doesn't ship with MonoPosixHelper.
|
||||
// https://www.youtube.com/watch?v=l60MnDJklnM
|
||||
}
|
||||
} else {
|
||||
// TODO: .NET Core Posix native platform wrapper.
|
||||
// MonoPosixHelper is available outside of Unix as well.
|
||||
try {
|
||||
_Native = new DetourNativeMonoPosixPlatform(_Native);
|
||||
} catch {
|
||||
// Good job, your copy of Mono doesn't ship with MonoPosixHelper.
|
||||
// https://www.youtube.com/watch?v=l60MnDJklnM
|
||||
}
|
||||
} else {
|
||||
// TODO: .NET Core Posix native platform wrapper.
|
||||
}
|
||||
|
||||
return _Native;
|
||||
|
@ -74,6 +72,14 @@ namespace MonoMod.RuntimeDetour {
|
|||
set => _Native = value;
|
||||
}
|
||||
|
||||
#region Interface extension methods
|
||||
|
||||
public static void MakeWritable(this IDetourNativePlatform plat, NativeDetourData detour) => plat.MakeWritable(detour.Method, detour.Size);
|
||||
public static void MakeExecutable(this IDetourNativePlatform plat, NativeDetourData detour) => plat.MakeExecutable(detour.Method, detour.Size);
|
||||
public static void FlushICache(this IDetourNativePlatform plat, NativeDetourData detour) => plat.FlushICache(detour.Method, detour.Size);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Native helpers
|
||||
|
||||
/// <summary>
|
||||
|
@ -160,6 +166,7 @@ namespace MonoMod.RuntimeDetour {
|
|||
Native.MakeWritable(detour);
|
||||
Native.Apply(detour);
|
||||
Native.MakeExecutable(detour);
|
||||
Native.FlushICache(detour);
|
||||
Native.Free(detour);
|
||||
|
||||
return dm;
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
using Mono.Cecil.Cil;
|
||||
using Mono.Cecil;
|
||||
using Mono.Cecil.Cil;
|
||||
using MonoMod.Cil;
|
||||
using MonoMod.Utils;
|
||||
using MonoMod.Utils.Cil;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
@ -20,13 +22,13 @@ namespace MonoMod.RuntimeDetour {
|
|||
public static bool Initialized { get; private set; }
|
||||
private static Type CurrentType;
|
||||
|
||||
private static Assembly _ASM;
|
||||
private static Assembly _HarmonyASM;
|
||||
private static readonly List<IDisposable> _Detours = new List<IDisposable>();
|
||||
|
||||
private static readonly Dictionary<System.Type, MethodInfo> _Emitters = new Dictionary<System.Type, MethodInfo>();
|
||||
|
||||
[ThreadStatic]
|
||||
private static DynamicMethodDefinition _LastWrapperDMD;
|
||||
[ThreadStatic] private static DynamicMethodDefinition _LastWrapperDMD;
|
||||
private static Assembly _SharedStateASM;
|
||||
|
||||
static HarmonyDetourBridge() {
|
||||
System.Type t_OpCode = typeof(System.Reflection.Emit.OpCode);
|
||||
|
@ -51,13 +53,13 @@ namespace MonoMod.RuntimeDetour {
|
|||
}
|
||||
|
||||
public static bool Init(bool forceLoad = true, Type type = Type.Auto) {
|
||||
if (_ASM == null)
|
||||
_ASM = _FindHarmony();
|
||||
if (_ASM == null && forceLoad)
|
||||
_ASM = Assembly.Load(new AssemblyName() {
|
||||
if (_HarmonyASM == null)
|
||||
_HarmonyASM = _FindHarmony();
|
||||
if (_HarmonyASM == null && forceLoad)
|
||||
_HarmonyASM = Assembly.Load(new AssemblyName() {
|
||||
Name = "0Harmony"
|
||||
});
|
||||
if (_ASM == null)
|
||||
if (_HarmonyASM == null)
|
||||
return false;
|
||||
|
||||
if (Initialized)
|
||||
|
@ -71,17 +73,24 @@ namespace MonoMod.RuntimeDetour {
|
|||
|
||||
try {
|
||||
foreach (MethodInfo methodRD in typeof(HarmonyDetourBridge).GetMethods(BindingFlags.NonPublic | BindingFlags.Static)) {
|
||||
bool critical = methodRD.GetCustomAttributes(typeof(CriticalAttribute), false).Any();
|
||||
|
||||
foreach (DetourToRDAttribute info in methodRD.GetCustomAttributes(typeof(DetourToRDAttribute), false))
|
||||
foreach (MethodInfo methodH in GetHarmonyMethod(methodRD, info.Type, info.SkipParams, info.Name))
|
||||
foreach (MethodInfo methodH in GetHarmonyMethod(methodRD, info.Type, info.SkipParams, info.Name)) {
|
||||
critical = false;
|
||||
_Detours.Add(new Hook(methodH, methodRD));
|
||||
}
|
||||
|
||||
foreach (DetourToHAttribute info in methodRD.GetCustomAttributes(typeof(DetourToHAttribute), false))
|
||||
foreach (MethodInfo methodH in GetHarmonyMethod(methodRD, info.Type, info.SkipParams, info.Name))
|
||||
foreach (MethodInfo methodH in GetHarmonyMethod(methodRD, info.Type, info.SkipParams, info.Name)) {
|
||||
critical = false;
|
||||
_Detours.Add(new Detour(methodRD, methodH));
|
||||
}
|
||||
|
||||
foreach (TranspileAttribute info in methodRD.GetCustomAttributes(typeof(TranspileAttribute), false)) {
|
||||
foreach (MethodInfo methodH in GetHarmonyMethod(methodRD, info.Type, -1, info.Name)) {
|
||||
using (DynamicMethodDefinition dmd = new DynamicMethodDefinition(methodH)) {
|
||||
critical = false;
|
||||
ILContext il = new ILContext(dmd.Definition) {
|
||||
ReferenceBag = RuntimeILReferenceBag.Instance
|
||||
};
|
||||
|
@ -96,6 +105,9 @@ namespace MonoMod.RuntimeDetour {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (critical)
|
||||
throw new Exception($"Cannot apply HarmonyDetourBridge rule {methodRD.Name}");
|
||||
}
|
||||
} catch (Exception) when (_EarlyReset()) {
|
||||
throw;
|
||||
|
@ -119,11 +131,16 @@ namespace MonoMod.RuntimeDetour {
|
|||
_EarlyReset();
|
||||
}
|
||||
|
||||
private static System.Type GetHarmonyType(string typeName) {
|
||||
return
|
||||
_HarmonyASM.GetType(typeName) ??
|
||||
_HarmonyASM.GetType("HarmonyLib." + typeName) ??
|
||||
_HarmonyASM.GetType("Harmony." + typeName) ??
|
||||
_HarmonyASM.GetType("Harmony.ILCopying." + typeName);
|
||||
}
|
||||
|
||||
private static IEnumerable<MethodInfo> GetHarmonyMethod(MethodInfo ctx, string typeName, int skipParams, string name) {
|
||||
System.Type type =
|
||||
_ASM.GetType("HarmonyLib." + typeName) ??
|
||||
_ASM.GetType("Harmony." + typeName) ??
|
||||
_ASM.GetType("Harmony.ILCopying." + typeName);
|
||||
System.Type type = GetHarmonyType(typeName);
|
||||
if (type == null)
|
||||
return null;
|
||||
|
||||
|
@ -191,13 +208,13 @@ namespace MonoMod.RuntimeDetour {
|
|||
}
|
||||
}
|
||||
|
||||
[DetourToRD("Memory")]
|
||||
[DetourToRD("Memory"), Critical]
|
||||
private static string WriteJump(long memory, long destination) {
|
||||
_Detours.Add(new NativeDetour((IntPtr) memory, (IntPtr) destination));
|
||||
return null;
|
||||
}
|
||||
|
||||
[DetourToRD("Memory")]
|
||||
[DetourToRD("Memory"), Critical]
|
||||
private static string DetourMethod(MethodBase original, MethodBase replacement) {
|
||||
if (replacement == null) {
|
||||
replacement = _LastWrapperDMD.Generate();
|
||||
|
@ -222,7 +239,7 @@ namespace MonoMod.RuntimeDetour {
|
|||
return null;
|
||||
}
|
||||
|
||||
[DetourToRD("PatchProcessor", 2)]
|
||||
[DetourToRD("PatchProcessor", 2), Critical]
|
||||
private static List<System.Reflection.Emit.DynamicMethod> Patch(Func<object, List<System.Reflection.Emit.DynamicMethod>> orig, object self) {
|
||||
orig(self);
|
||||
|
||||
|
@ -236,7 +253,7 @@ namespace MonoMod.RuntimeDetour {
|
|||
|
||||
// TODO: Does NativeThisPointer.NeedsNativeThisPointerFix need to be patched?
|
||||
|
||||
[Transpile("PatchFunctions")]
|
||||
[Transpile("PatchFunctions"), Critical]
|
||||
private static void UpdateWrapper(ILContext il) {
|
||||
ILCursor c = new ILCursor(il);
|
||||
|
||||
|
@ -246,14 +263,16 @@ namespace MonoMod.RuntimeDetour {
|
|||
c.Next.OpCode = OpCodes.Pop;
|
||||
}
|
||||
|
||||
[Transpile("MethodPatcher")]
|
||||
[Transpile("MethodPatcher"), Critical]
|
||||
private static void CreatePatchedMethod(ILContext il) {
|
||||
ILCursor c = new ILCursor(il);
|
||||
|
||||
// The original method uses System.Reflection.Emit.
|
||||
|
||||
System.Type t_DynamicTools = GetHarmonyType("DynamicTools");
|
||||
|
||||
// Find and replace DynamicTools.CreateDynamicMethod
|
||||
if (!c.TryGotoNext(i => i.MatchCall("Harmony.DynamicTools", "CreateDynamicMethod"))) {
|
||||
if (!c.TryGotoNext(i => i.MatchCall(t_DynamicTools, "CreateDynamicMethod"))) {
|
||||
// Not the right method. Harmony defines two CreatePatchedMethod methods.
|
||||
il.MakeReadOnly();
|
||||
return;
|
||||
|
@ -273,7 +292,7 @@ namespace MonoMod.RuntimeDetour {
|
|||
c.Next.Operand = il.Import(typeof(DynamicMethodDefinition).GetMethod("GetILGenerator", BindingFlags.Public | BindingFlags.Instance));
|
||||
|
||||
// Find and remove DynamicTools.PrepareDynamicMethod
|
||||
c.GotoNext(i => i.MatchCall("Harmony.DynamicTools", "PrepareDynamicMethod"));
|
||||
c.GotoNext(i => i.MatchCall(t_DynamicTools, "PrepareDynamicMethod"));
|
||||
c.Next.OpCode = OpCodes.Pop;
|
||||
c.Next.Operand = null;
|
||||
|
||||
|
@ -288,6 +307,47 @@ namespace MonoMod.RuntimeDetour {
|
|||
});
|
||||
}
|
||||
|
||||
[DetourToRD("HarmonySharedState", 1)]
|
||||
private static Assembly SharedStateAssembly(Func<Assembly> orig) {
|
||||
Assembly asm = orig();
|
||||
if (asm != null)
|
||||
return asm;
|
||||
|
||||
if (_SharedStateASM != null)
|
||||
return _SharedStateASM;
|
||||
|
||||
string name = (string) GetHarmonyType("HarmonySharedState").GetField("name", BindingFlags.NonPublic | BindingFlags.Static).GetValue(null);
|
||||
using (ModuleDefinition module = ModuleDefinition.CreateModule(
|
||||
$"MonoMod.RuntimeDetour.{name}",
|
||||
new ModuleParameters() {
|
||||
Kind = ModuleKind.Dll,
|
||||
ReflectionImporterProvider = MMReflectionImporter.Provider
|
||||
}
|
||||
)) {
|
||||
TypeDefinition type = new TypeDefinition(
|
||||
"", name,
|
||||
Mono.Cecil.TypeAttributes.Public | Mono.Cecil.TypeAttributes.Abstract | Mono.Cecil.TypeAttributes.Sealed | Mono.Cecil.TypeAttributes.Class
|
||||
) {
|
||||
BaseType = module.TypeSystem.Object
|
||||
};
|
||||
module.Types.Add(type);
|
||||
|
||||
type.Fields.Add(new FieldDefinition(
|
||||
"state",
|
||||
Mono.Cecil.FieldAttributes.Public | Mono.Cecil.FieldAttributes.Static,
|
||||
module.ImportReference(typeof(Dictionary<MethodBase, byte[]>))
|
||||
));
|
||||
|
||||
type.Fields.Add(new FieldDefinition(
|
||||
"version",
|
||||
Mono.Cecil.FieldAttributes.Public | Mono.Cecil.FieldAttributes.Static,
|
||||
module.ImportReference(typeof(int))
|
||||
));
|
||||
|
||||
return _SharedStateASM = ReflectionHelper.Load(module);
|
||||
}
|
||||
}
|
||||
|
||||
private class DetourToRDAttribute : Attribute {
|
||||
public string Type { get; }
|
||||
public int SkipParams { get; }
|
||||
|
@ -319,6 +379,9 @@ namespace MonoMod.RuntimeDetour {
|
|||
}
|
||||
}
|
||||
|
||||
private class CriticalAttribute : Attribute {
|
||||
}
|
||||
|
||||
private static Assembly _FindHarmony() {
|
||||
#if !NETSTANDARD1_X
|
||||
foreach (Assembly asm in AppDomain.CurrentDomain.GetAssemblies()) {
|
||||
|
|
|
@ -8,8 +8,9 @@ namespace MonoMod.RuntimeDetour {
|
|||
void Free(NativeDetourData detour);
|
||||
void Apply(NativeDetourData detour);
|
||||
void Copy(IntPtr src, IntPtr dst, byte type);
|
||||
void MakeWritable(NativeDetourData detour);
|
||||
void MakeExecutable(NativeDetourData detour);
|
||||
void MakeWritable(IntPtr src, uint size);
|
||||
void MakeExecutable(IntPtr src, uint size);
|
||||
void FlushICache(IntPtr src, uint size);
|
||||
IntPtr MemAlloc(uint size);
|
||||
void MemFree(IntPtr ptr);
|
||||
}
|
||||
|
|
|
@ -108,6 +108,7 @@ namespace MonoMod.RuntimeDetour {
|
|||
DetourHelper.Native.MakeWritable(Data);
|
||||
DetourHelper.Native.Apply(Data);
|
||||
DetourHelper.Native.MakeExecutable(Data);
|
||||
DetourHelper.Native.FlushICache(Data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using MonoMod.Utils;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace MonoMod.RuntimeDetour.Platforms {
|
||||
|
@ -179,14 +180,33 @@ namespace MonoMod.RuntimeDetour.Platforms {
|
|||
}
|
||||
}
|
||||
|
||||
public void MakeWritable(NativeDetourData detour) {
|
||||
public void MakeWritable(IntPtr src, uint size) {
|
||||
// no-op.
|
||||
}
|
||||
|
||||
public void MakeExecutable(NativeDetourData detour) {
|
||||
public void MakeExecutable(IntPtr src, uint size) {
|
||||
// no-op.
|
||||
}
|
||||
|
||||
public void FlushICache(IntPtr src, uint size) {
|
||||
// On ARM, we must flush the instruction cache.
|
||||
// Sadly, mono_arch_flush_icache isn't reliably exported.
|
||||
// This thus requires running native code to invoke the syscall.
|
||||
|
||||
if (flushicache == null) {
|
||||
// Emit a native delegate once. It lives as long as the application.
|
||||
byte[] code = IntPtr.Size >= 8 ? _FlushCache64 : _FlushCache32;
|
||||
IntPtr flushPtr = Marshal.AllocHGlobal(code.Length);
|
||||
Marshal.Copy(code, 0, flushPtr, code.Length);
|
||||
DetourHelper.Native.MakeExecutable(flushPtr, (uint) code.Length);
|
||||
|
||||
// It'd be ironic if the flush function would need to be flushed itself...
|
||||
flushicache = flushPtr.AsDelegate<d_flushicache>();
|
||||
}
|
||||
|
||||
flushicache(src, (int) size);
|
||||
}
|
||||
|
||||
public IntPtr MemAlloc(uint size) {
|
||||
return Marshal.AllocHGlobal((int) size);
|
||||
}
|
||||
|
@ -194,5 +214,60 @@ namespace MonoMod.RuntimeDetour.Platforms {
|
|||
public void MemFree(IntPtr ptr) {
|
||||
Marshal.FreeHGlobal(ptr);
|
||||
}
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
private delegate int d_flushicache(IntPtr code, int size);
|
||||
private d_flushicache flushicache;
|
||||
|
||||
// The following tools were used to obtain the shellcode.
|
||||
// https://godbolt.org/ ARM(64) gcc 8.2
|
||||
// http://shell-storm.org/online/Online-Assembler-and-Disassembler/
|
||||
// http://alexaltea.github.io/keystone.js/ .map(x => "0x" + x.toString(16).padStart(2, "0")).join(", ")
|
||||
|
||||
/* -O2 -fPIE -march=armv6
|
||||
// On ARM non-64, apparently SVC is the newer "Unified Assembler Language" (UAL) syntax.
|
||||
// In older versions of mono (before they fully switched to __clear_cache), it was only used for Android.
|
||||
// Adapted from mono's instruction flushing code.
|
||||
// https://github.com/mono/mono/blob/d2acc1d780d40f0a418347181c5adab533944d90/mono/mini/mini-arm.c#L1195
|
||||
void flushicache(void* code, int size) {
|
||||
const int syscall = 0xf0002;
|
||||
__asm __volatile (
|
||||
"mov r0, %0\n"
|
||||
"mov r1, %1\n"
|
||||
"mov r7, %2\n"
|
||||
"mov r2, #0x0\n"
|
||||
"svc 0x00000000\n"
|
||||
:
|
||||
: "r" (code), "r" (((long) code) + size), "r" (syscall)
|
||||
: "r0", "r1", "r7", "r2"
|
||||
);
|
||||
}
|
||||
*/
|
||||
private readonly byte[] _FlushCache32 = { 0x00, 0x30, 0xa0, 0xe1, 0x01, 0xc0, 0x80, 0xe0, 0x14, 0xe0, 0x9f, 0xe5, 0x03, 0x00, 0xa0, 0xe1, 0x0c, 0x10, 0xa0, 0xe1, 0x0e, 0x70, 0xa0, 0xe1, 0x00, 0x20, 0xa0, 0xe3, 0x00, 0x00, 0x00, 0xef, 0x80, 0x80, 0xbd, 0xe8, 0x02, 0x00, 0x0f, 0x00 };
|
||||
|
||||
/* -O2 -fPIE -march=armv8-a
|
||||
// Adapted from mono's instruction flushing code.
|
||||
// https://github.com/mono/mono/blob/cd5e14a3ccaa76e6ba6c58b26823863a2d0a0854/mono/mini/mini-arm64.c#L1997
|
||||
void flushicache(void* code, int size) {
|
||||
unsigned long end = (unsigned long) (((unsigned long) code) + size);
|
||||
unsigned long addr;
|
||||
const unsigned int icache_line_size = 4;
|
||||
const unsigned int dcache_line_size = 4;
|
||||
|
||||
addr = (unsigned long) code & ~(unsigned long) (dcache_line_size - 1);
|
||||
for (; addr < end; addr += dcache_line_size)
|
||||
asm volatile("dc civac, %0" : : "r" (addr) : "memory");
|
||||
asm volatile("dsb ish" : : : "memory");
|
||||
|
||||
addr = (unsigned long) code & ~(unsigned long) (icache_line_size - 1);
|
||||
for (; addr < end; addr += icache_line_size)
|
||||
asm volatile("ic ivau, %0" : : "r" (addr) : "memory");
|
||||
|
||||
asm volatile ("dsb ish" : : : "memory");
|
||||
asm volatile ("isb" : : : "memory");
|
||||
}
|
||||
*/
|
||||
private readonly byte[] _FlushCache64 = { 0x00, 0xf4, 0x7e, 0x92, 0x3f, 0x00, 0x00, 0xeb, 0xc9, 0x00, 0x00, 0x54, 0xe2, 0x03, 0x00, 0xaa, 0x22, 0x7e, 0x0b, 0xd5, 0x42, 0x10, 0x00, 0x91, 0x3f, 0x00, 0x02, 0xeb, 0xa8, 0xff, 0xff, 0x54, 0x9f, 0x3b, 0x03, 0xd5, 0x3f, 0x00, 0x00, 0xeb, 0xa9, 0x00, 0x00, 0x54, 0x20, 0x75, 0x0b, 0xd5, 0x00, 0x10, 0x00, 0x91, 0x3f, 0x00, 0x00, 0xeb, 0xa8, 0xff, 0xff, 0x54, 0x9f, 0x3b, 0x03, 0xd5, 0xdf, 0x3f, 0x03, 0xd5, 0xc0, 0x03, 0x5f, 0xd6 };
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,16 +32,19 @@ namespace MonoMod.RuntimeDetour.Platforms {
|
|||
throw new System.ComponentModel.Win32Exception();
|
||||
}
|
||||
|
||||
public void MakeWritable(NativeDetourData detour) {
|
||||
public void MakeWritable(IntPtr src, uint size) {
|
||||
// RWX because old versions of mono always use RWX.
|
||||
SetMemPerms(detour.Method, detour.Size, MmapProts.PROT_READ | MmapProts.PROT_WRITE | MmapProts.PROT_EXEC);
|
||||
Inner.MakeWritable(detour);
|
||||
SetMemPerms(src, size, MmapProts.PROT_READ | MmapProts.PROT_WRITE | MmapProts.PROT_EXEC);
|
||||
}
|
||||
|
||||
public void MakeExecutable(NativeDetourData detour) {
|
||||
public void MakeExecutable(IntPtr src, uint size) {
|
||||
// RWX because old versions of mono always use RWX.
|
||||
SetMemPerms(detour.Method, detour.Size, MmapProts.PROT_READ | MmapProts.PROT_WRITE | MmapProts.PROT_EXEC);
|
||||
Inner.MakeExecutable(detour);
|
||||
SetMemPerms(src, size, MmapProts.PROT_READ | MmapProts.PROT_WRITE | MmapProts.PROT_EXEC);
|
||||
}
|
||||
|
||||
public void FlushICache(IntPtr src, uint size) {
|
||||
// mono_arch_flush_icache isn't reliably exported.
|
||||
Inner.FlushICache(src, size);
|
||||
}
|
||||
|
||||
public NativeDetourData Create(IntPtr from, IntPtr to, byte? type) {
|
||||
|
@ -70,6 +73,7 @@ namespace MonoMod.RuntimeDetour.Platforms {
|
|||
|
||||
#pragma warning disable IDE0044 // Add readonly modifier
|
||||
#pragma warning disable CS0649 // Field is never assigned to, and will always have its default value null
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
private delegate int d_mono_pagesize();
|
||||
[DynDllImport("mono")]
|
||||
|
@ -79,6 +83,7 @@ namespace MonoMod.RuntimeDetour.Platforms {
|
|||
private delegate int d_mono_mprotect(IntPtr addr, IntPtr length, int flags);
|
||||
[DynDllImport("mono")]
|
||||
private d_mono_mprotect mono_mprotect;
|
||||
|
||||
#pragma warning restore IDE0044 // Add readonly modifier
|
||||
#pragma warning restore CS0649 // Field is never assigned to, and will always have its default value null
|
||||
|
||||
|
|
|
@ -34,16 +34,19 @@ namespace MonoMod.RuntimeDetour.Platforms {
|
|||
throw new Exception(GetLastError("mprotect"));
|
||||
}
|
||||
|
||||
public void MakeWritable(NativeDetourData detour) {
|
||||
public void MakeWritable(IntPtr src, uint size) {
|
||||
// RWX because old versions of mono always use RWX.
|
||||
SetMemPerms(detour.Method, detour.Size, MmapProts.PROT_READ | MmapProts.PROT_WRITE | MmapProts.PROT_EXEC);
|
||||
Inner.MakeWritable(detour);
|
||||
SetMemPerms(src, size, MmapProts.PROT_READ | MmapProts.PROT_WRITE | MmapProts.PROT_EXEC);
|
||||
}
|
||||
|
||||
public void MakeExecutable(NativeDetourData detour) {
|
||||
public void MakeExecutable(IntPtr src, uint size) {
|
||||
// RWX because old versions of mono always use RWX.
|
||||
SetMemPerms(detour.Method, detour.Size, MmapProts.PROT_READ | MmapProts.PROT_WRITE | MmapProts.PROT_EXEC);
|
||||
Inner.MakeExecutable(detour);
|
||||
SetMemPerms(src, size, MmapProts.PROT_READ | MmapProts.PROT_WRITE | MmapProts.PROT_EXEC);
|
||||
}
|
||||
|
||||
public void FlushICache(IntPtr src, uint size) {
|
||||
// There is no cache flushing function in MPH.
|
||||
Inner.FlushICache(src, size);
|
||||
}
|
||||
|
||||
public NativeDetourData Create(IntPtr from, IntPtr to, byte? type) {
|
||||
|
|
|
@ -11,19 +11,20 @@ namespace MonoMod.RuntimeDetour.Platforms {
|
|||
Inner = inner;
|
||||
}
|
||||
|
||||
public void MakeWritable(NativeDetourData detour) {
|
||||
public void MakeWritable(IntPtr src, uint size) {
|
||||
// PAGE_READWRITE causes an AccessViolationException / TargetInvocationException.
|
||||
if (!VirtualProtect(detour.Method, (IntPtr) detour.Size, Protection.PAGE_EXECUTE_READWRITE, out _))
|
||||
if (!VirtualProtect(src, (IntPtr) size, Protection.PAGE_EXECUTE_READWRITE, out _))
|
||||
throw new Win32Exception();
|
||||
|
||||
Inner.MakeWritable(detour);
|
||||
}
|
||||
|
||||
public void MakeExecutable(NativeDetourData detour) {
|
||||
if (!VirtualProtect(detour.Method, (IntPtr) detour.Size, Protection.PAGE_EXECUTE_READWRITE, out _))
|
||||
public void MakeExecutable(IntPtr src, uint size) {
|
||||
if (!VirtualProtect(src, (IntPtr) size, Protection.PAGE_EXECUTE_READWRITE, out _))
|
||||
throw new Win32Exception();
|
||||
}
|
||||
|
||||
Inner.MakeExecutable(detour);
|
||||
public void FlushICache(IntPtr src, uint size) {
|
||||
if (!FlushInstructionCache(GetCurrentProcess(), src, (UIntPtr) size))
|
||||
throw new Win32Exception();
|
||||
}
|
||||
|
||||
public NativeDetourData Create(IntPtr from, IntPtr to, byte? type) {
|
||||
|
@ -53,8 +54,14 @@ namespace MonoMod.RuntimeDetour.Platforms {
|
|||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
private static extern bool VirtualProtect(IntPtr lpAddress, IntPtr dwSize, Protection flNewProtect, out Protection lpflOldProtect);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
public static extern IntPtr GetCurrentProcess();
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
private static extern bool FlushInstructionCache(IntPtr hProcess, IntPtr lpBaseAddress, UIntPtr dwSize);
|
||||
|
||||
[Flags]
|
||||
private enum Protection {
|
||||
private enum Protection : uint {
|
||||
PAGE_NOACCESS = 0x01,
|
||||
PAGE_READONLY = 0x02,
|
||||
PAGE_READWRITE = 0x04,
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using MonoMod.Utils;
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace MonoMod.RuntimeDetour.Platforms {
|
||||
|
@ -108,14 +109,19 @@ namespace MonoMod.RuntimeDetour.Platforms {
|
|||
}
|
||||
}
|
||||
|
||||
public void MakeWritable(NativeDetourData detour) {
|
||||
public void MakeWritable(IntPtr src, uint size) {
|
||||
// no-op.
|
||||
}
|
||||
|
||||
public void MakeExecutable(NativeDetourData detour) {
|
||||
public void MakeExecutable(IntPtr src, uint size) {
|
||||
// no-op.
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public void FlushICache(IntPtr src, uint size) {
|
||||
// On X86, apparently a call / ret is enough to flush the entire cache.
|
||||
}
|
||||
|
||||
public IntPtr MemAlloc(uint size) {
|
||||
return Marshal.AllocHGlobal((int) size);
|
||||
}
|
||||
|
|
|
@ -32,6 +32,7 @@ namespace MonoMod.RuntimeDetour.Platforms {
|
|||
DetourHelper.Native.MakeWritable(detour);
|
||||
DetourHelper.Native.Apply(detour);
|
||||
DetourHelper.Native.MakeExecutable(detour);
|
||||
DetourHelper.Native.FlushICache(detour);
|
||||
DetourHelper.Native.Free(detour);
|
||||
// No need to undo the detour.
|
||||
|
||||
|
|
|
@ -429,6 +429,44 @@ namespace MonoMod.Cil {
|
|||
return false;
|
||||
} // OperandType.InlineMethod
|
||||
|
||||
public static bool MatchCallvirt(this Instruction instr, string typeFullName, string name)
|
||||
=> instr.MatchCallvirt(out var v) && v.Is(typeFullName, name);
|
||||
public static bool MatchCallvirt<T>(this Instruction instr, string name)
|
||||
=> instr.MatchCallvirt(out var v) && v.Is(typeof(T), name);
|
||||
public static bool MatchCallvirt(this Instruction instr, Type type, string name)
|
||||
=> instr.MatchCallvirt(out var v) && v.Is(type, name);
|
||||
public static bool MatchCallvirt(this Instruction instr, MethodBase value)
|
||||
=> instr.MatchCallvirt(out var v) && v.Is(value);
|
||||
public static bool MatchCallvirt(this Instruction instr, MethodReference value)
|
||||
=> instr.MatchCallvirt(out var v) && v == value;
|
||||
public static bool MatchCallvirt(this Instruction instr, out MethodReference value) {
|
||||
if (instr.OpCode == OpCodes.Callvirt) {
|
||||
value = instr.Operand as MethodReference;
|
||||
return true;
|
||||
}
|
||||
value = default;
|
||||
return false;
|
||||
} // OperandType.InlineMethod
|
||||
|
||||
public static bool MatchCallOrCallvirt(this Instruction instr, string typeFullName, string name)
|
||||
=> instr.MatchCallOrCallvirt(out var v) && v.Is(typeFullName, name);
|
||||
public static bool MatchCallOrCallvirt<T>(this Instruction instr, string name)
|
||||
=> instr.MatchCallOrCallvirt(out var v) && v.Is(typeof(T), name);
|
||||
public static bool MatchCallOrCallvirt(this Instruction instr, Type type, string name)
|
||||
=> instr.MatchCallOrCallvirt(out var v) && v.Is(type, name);
|
||||
public static bool MatchCallOrCallvirt(this Instruction instr, MethodBase value)
|
||||
=> instr.MatchCallOrCallvirt(out var v) && v.Is(value);
|
||||
public static bool MatchCallOrCallvirt(this Instruction instr, MethodReference value)
|
||||
=> instr.MatchCallOrCallvirt(out var v) && v == value;
|
||||
public static bool MatchCallOrCallvirt(this Instruction instr, out MethodReference value) {
|
||||
if (instr.OpCode == OpCodes.Call || instr.OpCode == OpCodes.Callvirt) {
|
||||
value = instr.Operand as MethodReference;
|
||||
return true;
|
||||
}
|
||||
value = default;
|
||||
return false;
|
||||
} // OperandType.InlineMethod
|
||||
|
||||
public static bool MatchCalli(this Instruction instr, IMethodSignature value)
|
||||
=> instr.MatchCalli(out var v) && v == value;
|
||||
public static bool MatchCalli(this Instruction instr, out IMethodSignature value) {
|
||||
|
@ -1047,25 +1085,6 @@ namespace MonoMod.Cil {
|
|||
return false;
|
||||
} // OperandType.InlineNone
|
||||
|
||||
public static bool MatchCallvirt(this Instruction instr, string typeFullName, string name)
|
||||
=> instr.MatchCallvirt(out var v) && v.Is(typeFullName, name);
|
||||
public static bool MatchCallvirt<T>(this Instruction instr, string name)
|
||||
=> instr.MatchCallvirt(out var v) && v.Is(typeof(T), name);
|
||||
public static bool MatchCallvirt(this Instruction instr, Type type, string name)
|
||||
=> instr.MatchCallvirt(out var v) && v.Is(type, name);
|
||||
public static bool MatchCallvirt(this Instruction instr, MethodBase value)
|
||||
=> instr.MatchCallvirt(out var v) && v.Is(value);
|
||||
public static bool MatchCallvirt(this Instruction instr, MethodReference value)
|
||||
=> instr.MatchCallvirt(out var v) && v == value;
|
||||
public static bool MatchCallvirt(this Instruction instr, out MethodReference value) {
|
||||
if (instr.OpCode == OpCodes.Callvirt) {
|
||||
value = instr.Operand as MethodReference;
|
||||
return true;
|
||||
}
|
||||
value = default;
|
||||
return false;
|
||||
} // OperandType.InlineMethod
|
||||
|
||||
public static bool MatchCpobj(this Instruction instr, string fullName)
|
||||
=> instr.MatchCpobj(out var v) && v.Is(fullName);
|
||||
public static bool MatchCpobj<T>(this Instruction instr)
|
||||
|
|
|
@ -18,16 +18,6 @@ using System.Diagnostics.SymbolStore;
|
|||
namespace MonoMod.Utils {
|
||||
public sealed partial class DynamicMethodDefinition {
|
||||
|
||||
#if NETSTANDARD1_X
|
||||
private static readonly Type t_AssemblyLoadContext =
|
||||
typeof(Assembly).GetTypeInfo().Assembly
|
||||
.GetType("System.Runtime.Loader.AssemblyLoadContext");
|
||||
private static readonly object _AssemblyLoadContext_Default =
|
||||
t_AssemblyLoadContext.GetProperty("Default").GetValue(null);
|
||||
private static readonly MethodInfo _AssemblyLoadContext_LoadFromStream =
|
||||
t_AssemblyLoadContext.GetMethod("LoadFromStream", new Type[] { typeof(Stream) });
|
||||
#endif
|
||||
|
||||
public MethodInfo GenerateViaCecil(TypeDefinition typeDef) {
|
||||
MethodDefinition def = Definition;
|
||||
|
||||
|
@ -158,17 +148,7 @@ namespace MonoMod.Utils {
|
|||
module.Write(fileStream);
|
||||
}
|
||||
|
||||
Assembly asm;
|
||||
using (MemoryStream asmStream = new MemoryStream()) {
|
||||
module.Write(asmStream);
|
||||
asmStream.Seek(0, SeekOrigin.Begin);
|
||||
#if NETSTANDARD1_X
|
||||
// System.Runtime.Loader.AssemblyLoadContext.Default.LoadFromStream(asmStream);
|
||||
asm = (Assembly) _AssemblyLoadContext_LoadFromStream.Invoke(_AssemblyLoadContext_Default, new object[] { asmStream });
|
||||
#else
|
||||
asm = Assembly.Load(asmStream.GetBuffer());
|
||||
#endif
|
||||
}
|
||||
Assembly asm = ReflectionHelper.Load(module);
|
||||
|
||||
_DynModuleCache[module.Assembly.Name.FullName] = module.Assembly;
|
||||
_DynModuleReflCache[asm.GetModules()[0]] = module;
|
||||
|
|
|
@ -8,6 +8,7 @@ using Mono.Cecil;
|
|||
using Mono.Cecil.Cil;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.IO;
|
||||
|
||||
#if NETSTANDARD
|
||||
using TypeOrTypeInfo = System.Reflection.TypeInfo;
|
||||
|
@ -22,6 +23,18 @@ namespace MonoMod.Utils {
|
|||
public static readonly Dictionary<string, Assembly> AssemblyCache = new Dictionary<string, Assembly>();
|
||||
public static readonly Dictionary<MemberReference, MemberInfo> ResolveReflectionCache = new Dictionary<MemberReference, MemberInfo>();
|
||||
|
||||
private const BindingFlags _BindingFlagsAll = (BindingFlags) (-1);
|
||||
|
||||
#if NETSTANDARD1_X
|
||||
private static readonly Type t_AssemblyLoadContext =
|
||||
typeof(Assembly).GetTypeInfo().Assembly
|
||||
.GetType("System.Runtime.Loader.AssemblyLoadContext");
|
||||
private static readonly object _AssemblyLoadContext_Default =
|
||||
t_AssemblyLoadContext.GetProperty("Default").GetValue(null);
|
||||
private static readonly MethodInfo _AssemblyLoadContext_LoadFromStream =
|
||||
t_AssemblyLoadContext.GetMethod("LoadFromStream", new Type[] { typeof(Stream) });
|
||||
#endif
|
||||
|
||||
private static MemberInfo _Cache(MemberReference key, MemberInfo value) {
|
||||
if (key != null && value != null) {
|
||||
lock (ResolveReflectionCache) {
|
||||
|
@ -31,6 +44,51 @@ namespace MonoMod.Utils {
|
|||
return value;
|
||||
}
|
||||
|
||||
public static Assembly Load(ModuleDefinition module) {
|
||||
using (MemoryStream stream = new MemoryStream()) {
|
||||
module.Write(stream);
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
return Load(stream);
|
||||
}
|
||||
}
|
||||
|
||||
public static Assembly Load(Stream stream) {
|
||||
Assembly asm;
|
||||
|
||||
#if NETSTANDARD1_X
|
||||
// System.Runtime.Loader.AssemblyLoadContext.Default.LoadFromStream(asmStream);
|
||||
asm = (Assembly) _AssemblyLoadContext_LoadFromStream.Invoke(_AssemblyLoadContext_Default, new object[] { stream });
|
||||
#else
|
||||
|
||||
if (stream is MemoryStream ms) {
|
||||
asm = Assembly.Load(ms.GetBuffer());
|
||||
} else {
|
||||
using (MemoryStream copy = new MemoryStream()) {
|
||||
|
||||
#if NETFRAMEWORK
|
||||
byte[] buffer = new byte[4096];
|
||||
int read;
|
||||
while (0 < (read = stream.Read(buffer, 0, buffer.Length))) {
|
||||
copy.Write(buffer, 0, read);
|
||||
}
|
||||
#else
|
||||
stream.CopyTo(copy);
|
||||
#endif
|
||||
|
||||
copy.Seek(0, SeekOrigin.Begin);
|
||||
asm = Assembly.Load(copy.GetBuffer());
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if !NETSTANDARD1_X
|
||||
AppDomain.CurrentDomain.AssemblyResolve +=
|
||||
(s, e) => e.Name == asm.FullName ? asm : null;
|
||||
#endif
|
||||
|
||||
return asm;
|
||||
}
|
||||
|
||||
public static Type ResolveReflection(this TypeReference mref)
|
||||
=> (_ResolveReflection(mref, null) as TypeOrTypeInfo).AsType();
|
||||
public static MethodBase ResolveReflection(this MethodReference mref)
|
||||
|
@ -68,7 +126,10 @@ namespace MonoMod.Utils {
|
|||
// ... but all of the methods have the same MetadataToken. We couldn't compare it anyway.
|
||||
|
||||
string methodID = method.GetFindableID(withType: false);
|
||||
MethodInfo found = type.AsType().GetMethods().First(m => m.GetFindableID(withType: false) == methodID);
|
||||
MethodBase found =
|
||||
type.AsType().GetMethods(_BindingFlagsAll).Cast<MethodBase>()
|
||||
.Concat(type.AsType().GetConstructors(_BindingFlagsAll))
|
||||
.FirstOrDefault(m => m.GetFindableID(withType: false) == methodID);
|
||||
if (found != null)
|
||||
return _Cache(mref, found);
|
||||
}
|
||||
|
@ -169,17 +230,17 @@ namespace MonoMod.Utils {
|
|||
} else if (typeless) {
|
||||
if (mref is MethodReference)
|
||||
member = modules
|
||||
.Select(module => module.GetMethods((BindingFlags) (-1)).FirstOrDefault(m => mref.Is(m)))
|
||||
.Select(module => module.GetMethods(_BindingFlagsAll).FirstOrDefault(m => mref.Is(m)))
|
||||
.FirstOrDefault(m => m != null);
|
||||
else if (mref is FieldReference)
|
||||
member = modules
|
||||
.Select(module => module.GetFields((BindingFlags) (-1)).FirstOrDefault(m => mref.Is(m)))
|
||||
.Select(module => module.GetFields(_BindingFlagsAll).FirstOrDefault(m => mref.Is(m)))
|
||||
.FirstOrDefault(m => m != null);
|
||||
else
|
||||
throw new NotSupportedException($"Unsupported <Module> member type {mref.GetType().FullName}");
|
||||
|
||||
} else {
|
||||
member = (_ResolveReflection(mref.DeclaringType, modules) as TypeOrTypeInfo).AsType().GetMembers((BindingFlags) (-1)).FirstOrDefault(m => mref.Is(m));
|
||||
member = (_ResolveReflection(mref.DeclaringType, modules) as TypeOrTypeInfo).AsType().GetMembers(_BindingFlagsAll).FirstOrDefault(m => mref.Is(m));
|
||||
}
|
||||
|
||||
return _Cache(mref, member);
|
||||
|
|
|
@ -12,8 +12,8 @@ using SRE = System.Reflection.Emit;
|
|||
using Mono.Cecil;
|
||||
using OpCodes = Mono.Cecil.Cil.OpCodes;
|
||||
using OpCode = Mono.Cecil.Cil.OpCode;
|
||||
using ExceptionHandler = Mono.Cecil.Cil.ExceptionHandler;
|
||||
using Mono.Collections.Generic;
|
||||
using ExceptionHandler = Mono.Cecil.Cil.ExceptionHandler;
|
||||
|
||||
namespace MonoMod.Utils.Cil {
|
||||
public sealed class CecilILGenerator : ILGeneratorShim {
|
||||
|
|
|
@ -14,16 +14,6 @@ using TypeAttributes = Mono.Cecil.TypeAttributes;
|
|||
namespace MonoMod.Utils.Cil {
|
||||
public partial class ILGeneratorShim {
|
||||
|
||||
#if NETSTANDARD1_X
|
||||
private static readonly Type t_AssemblyLoadContext =
|
||||
typeof(Assembly).GetTypeInfo().Assembly
|
||||
.GetType("System.Runtime.Loader.AssemblyLoadContext");
|
||||
private static readonly object _AssemblyLoadContext_Default =
|
||||
t_AssemblyLoadContext.GetProperty("Default").GetValue(null);
|
||||
private static readonly MethodInfo _AssemblyLoadContext_LoadFromStream =
|
||||
t_AssemblyLoadContext.GetMethod("LoadFromStream", new Type[] { typeof(Stream) });
|
||||
#endif
|
||||
|
||||
public System.Reflection.Emit.ILGenerator GetProxy() {
|
||||
return (System.Reflection.Emit.ILGenerator) ILGeneratorBuilder
|
||||
.GenerateProxy()
|
||||
|
@ -55,7 +45,6 @@ namespace MonoMod.Utils.Cil {
|
|||
FullName,
|
||||
new ModuleParameters() {
|
||||
Kind = ModuleKind.Dll,
|
||||
AssemblyResolver = new DefaultAssemblyResolver(),
|
||||
ReflectionImporterProvider = MMReflectionImporter.Provider
|
||||
}
|
||||
)) {
|
||||
|
@ -122,23 +111,9 @@ namespace MonoMod.Utils.Cil {
|
|||
il.Emit(OpCodes.Ret);
|
||||
}
|
||||
|
||||
using (MemoryStream asmStream = new MemoryStream()) {
|
||||
module.Write(asmStream);
|
||||
asmStream.Seek(0, SeekOrigin.Begin);
|
||||
#if NETSTANDARD1_X
|
||||
// System.Runtime.Loader.AssemblyLoadContext.Default.LoadFromStream(asmStream);
|
||||
asm = (Assembly) _AssemblyLoadContext_LoadFromStream.Invoke(_AssemblyLoadContext_Default, new object[] { asmStream });
|
||||
#else
|
||||
asm = Assembly.Load(asmStream.GetBuffer());
|
||||
#endif
|
||||
}
|
||||
asm = ReflectionHelper.Load(module);
|
||||
}
|
||||
|
||||
#if !NETSTANDARD1_X
|
||||
AppDomain.CurrentDomain.AssemblyResolve +=
|
||||
(s, e) => e.Name == asm.FullName ? asm : null;
|
||||
#endif
|
||||
|
||||
return ProxyType = asm.GetType(FullName);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<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">
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="16" android:versionName="1.4" 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" />
|
||||
|
@ -16,9 +16,6 @@
|
|||
<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>
|
||||
</manifest>
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 14 KiB |
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<color android:color="#ffffff" />
|
||||
</item>
|
||||
<item>
|
||||
<bitmap android:tileMode="disabled" android:gravity="center" android:src="@drawable/splash_logos_crop"/>
|
||||
</item>
|
||||
</layer-list>
|
|
@ -2,26 +2,40 @@
|
|||
<RelativeLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/ll_view"
|
||||
android:background="@android:color/background_light"
|
||||
android:layout_height="fill_parent"
|
||||
android:layout_width="fill_parent" >
|
||||
<Button
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_alignParentRight="true"
|
||||
android:text="@android:string/cancel"
|
||||
android:id="@+id/buttonAddOrRemove"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="80sp" />
|
||||
<Button
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_toLeftOf="@id/buttonAddOrRemove"
|
||||
android:text="@string/Config"
|
||||
android:id="@+id/buttonConfig"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="80sp" />
|
||||
<TextView
|
||||
android:id="@+id/textModName"
|
||||
android:textColor="@android:color/black"
|
||||
android:text="textModName"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="fill_parent"
|
||||
android:paddingRight="88sp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_toLeftOf="@id/buttonConfig"
|
||||
android:paddingLeft="8sp"
|
||||
android:textSize="18sp" />
|
||||
<TextView
|
||||
android:id="@+id/textDescription"
|
||||
android:text="textDescription"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="fill_parent"
|
||||
android:paddingRight="88sp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:paddingLeft="8sp"
|
||||
android:layout_toLeftOf="@id/buttonConfig"
|
||||
android:layout_below="@+id/textModName" />
|
||||
</RelativeLayout>
|
||||
|
|
Binary file not shown.
|
@ -71,8 +71,8 @@ namespace ModLoader
|
|||
public partial class Boolean
|
||||
{
|
||||
|
||||
// aapt resource value: 0x7f0a0000
|
||||
public const int abc_action_bar_embed_tabs = 2131361792;
|
||||
// aapt resource value: 0x7f090000
|
||||
public const int abc_action_bar_embed_tabs = 2131296256;
|
||||
|
||||
static Boolean()
|
||||
{
|
||||
|
@ -87,26 +87,26 @@ namespace ModLoader
|
|||
public partial class Color
|
||||
{
|
||||
|
||||
// aapt resource value: 0x7f070003
|
||||
public const int notification_action_color_filter = 2131165187;
|
||||
// aapt resource value: 0x7f060003
|
||||
public const int notification_action_color_filter = 2131099651;
|
||||
|
||||
// aapt resource value: 0x7f070004
|
||||
public const int notification_icon_bg_color = 2131165188;
|
||||
// aapt resource value: 0x7f060004
|
||||
public const int notification_icon_bg_color = 2131099652;
|
||||
|
||||
// aapt resource value: 0x7f070000
|
||||
public const int notification_material_background_media_default_color = 2131165184;
|
||||
// aapt resource value: 0x7f060000
|
||||
public const int notification_material_background_media_default_color = 2131099648;
|
||||
|
||||
// aapt resource value: 0x7f070001
|
||||
public const int primary_text_default_material_dark = 2131165185;
|
||||
// aapt resource value: 0x7f060001
|
||||
public const int primary_text_default_material_dark = 2131099649;
|
||||
|
||||
// aapt resource value: 0x7f070005
|
||||
public const int ripple_material_light = 2131165189;
|
||||
// aapt resource value: 0x7f060005
|
||||
public const int ripple_material_light = 2131099653;
|
||||
|
||||
// aapt resource value: 0x7f070002
|
||||
public const int secondary_text_default_material_dark = 2131165186;
|
||||
// aapt resource value: 0x7f060002
|
||||
public const int secondary_text_default_material_dark = 2131099650;
|
||||
|
||||
// aapt resource value: 0x7f070006
|
||||
public const int secondary_text_default_material_light = 2131165190;
|
||||
// aapt resource value: 0x7f060006
|
||||
public const int secondary_text_default_material_light = 2131099654;
|
||||
|
||||
static Color()
|
||||
{
|
||||
|
@ -121,65 +121,65 @@ namespace ModLoader
|
|||
public partial class Dimension
|
||||
{
|
||||
|
||||
// aapt resource value: 0x7f0b0004
|
||||
public const int compat_button_inset_horizontal_material = 2131427332;
|
||||
// aapt resource value: 0x7f0a0004
|
||||
public const int compat_button_inset_horizontal_material = 2131361796;
|
||||
|
||||
// aapt resource value: 0x7f0b0005
|
||||
public const int compat_button_inset_vertical_material = 2131427333;
|
||||
// aapt resource value: 0x7f0a0005
|
||||
public const int compat_button_inset_vertical_material = 2131361797;
|
||||
|
||||
// aapt resource value: 0x7f0b0006
|
||||
public const int compat_button_padding_horizontal_material = 2131427334;
|
||||
// aapt resource value: 0x7f0a0006
|
||||
public const int compat_button_padding_horizontal_material = 2131361798;
|
||||
|
||||
// aapt resource value: 0x7f0b0007
|
||||
public const int compat_button_padding_vertical_material = 2131427335;
|
||||
// aapt resource value: 0x7f0a0007
|
||||
public const int compat_button_padding_vertical_material = 2131361799;
|
||||
|
||||
// aapt resource value: 0x7f0b0008
|
||||
public const int compat_control_corner_material = 2131427336;
|
||||
// aapt resource value: 0x7f0a0008
|
||||
public const int compat_control_corner_material = 2131361800;
|
||||
|
||||
// aapt resource value: 0x7f0b0009
|
||||
public const int notification_action_icon_size = 2131427337;
|
||||
// aapt resource value: 0x7f0a0009
|
||||
public const int notification_action_icon_size = 2131361801;
|
||||
|
||||
// aapt resource value: 0x7f0b000a
|
||||
public const int notification_action_text_size = 2131427338;
|
||||
// aapt resource value: 0x7f0a000a
|
||||
public const int notification_action_text_size = 2131361802;
|
||||
|
||||
// aapt resource value: 0x7f0b000b
|
||||
public const int notification_big_circle_margin = 2131427339;
|
||||
// aapt resource value: 0x7f0a000b
|
||||
public const int notification_big_circle_margin = 2131361803;
|
||||
|
||||
// aapt resource value: 0x7f0b0001
|
||||
public const int notification_content_margin_start = 2131427329;
|
||||
// aapt resource value: 0x7f0a0001
|
||||
public const int notification_content_margin_start = 2131361793;
|
||||
|
||||
// aapt resource value: 0x7f0b000c
|
||||
public const int notification_large_icon_height = 2131427340;
|
||||
// aapt resource value: 0x7f0a000c
|
||||
public const int notification_large_icon_height = 2131361804;
|
||||
|
||||
// aapt resource value: 0x7f0b000d
|
||||
public const int notification_large_icon_width = 2131427341;
|
||||
// aapt resource value: 0x7f0a000d
|
||||
public const int notification_large_icon_width = 2131361805;
|
||||
|
||||
// aapt resource value: 0x7f0b0002
|
||||
public const int notification_main_column_padding_top = 2131427330;
|
||||
// aapt resource value: 0x7f0a0002
|
||||
public const int notification_main_column_padding_top = 2131361794;
|
||||
|
||||
// aapt resource value: 0x7f0b0003
|
||||
public const int notification_media_narrow_margin = 2131427331;
|
||||
// aapt resource value: 0x7f0a0003
|
||||
public const int notification_media_narrow_margin = 2131361795;
|
||||
|
||||
// aapt resource value: 0x7f0b000e
|
||||
public const int notification_right_icon_size = 2131427342;
|
||||
// aapt resource value: 0x7f0a000e
|
||||
public const int notification_right_icon_size = 2131361806;
|
||||
|
||||
// aapt resource value: 0x7f0b0000
|
||||
public const int notification_right_side_padding_top = 2131427328;
|
||||
// aapt resource value: 0x7f0a0000
|
||||
public const int notification_right_side_padding_top = 2131361792;
|
||||
|
||||
// aapt resource value: 0x7f0b000f
|
||||
public const int notification_small_icon_background_padding = 2131427343;
|
||||
// aapt resource value: 0x7f0a000f
|
||||
public const int notification_small_icon_background_padding = 2131361807;
|
||||
|
||||
// aapt resource value: 0x7f0b0010
|
||||
public const int notification_small_icon_size_as_large = 2131427344;
|
||||
// aapt resource value: 0x7f0a0010
|
||||
public const int notification_small_icon_size_as_large = 2131361808;
|
||||
|
||||
// aapt resource value: 0x7f0b0011
|
||||
public const int notification_subtext_size = 2131427345;
|
||||
// aapt resource value: 0x7f0a0011
|
||||
public const int notification_subtext_size = 2131361809;
|
||||
|
||||
// aapt resource value: 0x7f0b0012
|
||||
public const int notification_top_pad = 2131427346;
|
||||
// aapt resource value: 0x7f0a0012
|
||||
public const int notification_top_pad = 2131361810;
|
||||
|
||||
// aapt resource value: 0x7f0b0013
|
||||
public const int notification_top_pad_large_text = 2131427347;
|
||||
// aapt resource value: 0x7f0a0013
|
||||
public const int notification_top_pad_large_text = 2131361811;
|
||||
|
||||
static Dimension()
|
||||
{
|
||||
|
@ -221,11 +221,11 @@ namespace ModLoader
|
|||
// aapt resource value: 0x7f020008
|
||||
public const int notification_icon_background = 2130837512;
|
||||
|
||||
// aapt resource value: 0x7f02000c
|
||||
public const int notification_template_icon_bg = 2130837516;
|
||||
// aapt resource value: 0x7f02000e
|
||||
public const int notification_template_icon_bg = 2130837518;
|
||||
|
||||
// aapt resource value: 0x7f02000d
|
||||
public const int notification_template_icon_low_bg = 2130837517;
|
||||
// aapt resource value: 0x7f02000f
|
||||
public const int notification_template_icon_low_bg = 2130837519;
|
||||
|
||||
// aapt resource value: 0x7f020009
|
||||
public const int notification_tile_bg = 2130837513;
|
||||
|
@ -236,6 +236,12 @@ namespace ModLoader
|
|||
// 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;
|
||||
|
||||
static Drawable()
|
||||
{
|
||||
global::Android.Runtime.ResourceIdManager.UpdateIdValues();
|
||||
|
@ -249,155 +255,158 @@ namespace ModLoader
|
|||
public partial class Id
|
||||
{
|
||||
|
||||
// aapt resource value: 0x7f0c0019
|
||||
public const int action0 = 2131492889;
|
||||
// aapt resource value: 0x7f0b001a
|
||||
public const int action0 = 2131427354;
|
||||
|
||||
// aapt resource value: 0x7f0c0016
|
||||
public const int action_container = 2131492886;
|
||||
// aapt resource value: 0x7f0b0017
|
||||
public const int action_container = 2131427351;
|
||||
|
||||
// aapt resource value: 0x7f0c001d
|
||||
public const int action_divider = 2131492893;
|
||||
// aapt resource value: 0x7f0b001e
|
||||
public const int action_divider = 2131427358;
|
||||
|
||||
// aapt resource value: 0x7f0c0017
|
||||
public const int action_image = 2131492887;
|
||||
// aapt resource value: 0x7f0b0018
|
||||
public const int action_image = 2131427352;
|
||||
|
||||
// aapt resource value: 0x7f0c0018
|
||||
public const int action_text = 2131492888;
|
||||
// aapt resource value: 0x7f0b0019
|
||||
public const int action_text = 2131427353;
|
||||
|
||||
// aapt resource value: 0x7f0c0027
|
||||
public const int actions = 2131492903;
|
||||
// aapt resource value: 0x7f0b0028
|
||||
public const int actions = 2131427368;
|
||||
|
||||
// aapt resource value: 0x7f0c002b
|
||||
public const int appIcon = 2131492907;
|
||||
// aapt resource value: 0x7f0b002c
|
||||
public const int appIcon = 2131427372;
|
||||
|
||||
// aapt resource value: 0x7f0c0006
|
||||
public const int async = 2131492870;
|
||||
// aapt resource value: 0x7f0b0006
|
||||
public const int async = 2131427334;
|
||||
|
||||
// aapt resource value: 0x7f0c0007
|
||||
public const int blocking = 2131492871;
|
||||
// aapt resource value: 0x7f0b0007
|
||||
public const int blocking = 2131427335;
|
||||
|
||||
// aapt resource value: 0x7f0c0013
|
||||
public const int buttonAddOrRemove = 2131492883;
|
||||
// aapt resource value: 0x7f0b0013
|
||||
public const int buttonAddOrRemove = 2131427347;
|
||||
|
||||
// aapt resource value: 0x7f0c000d
|
||||
public const int buttonExtract = 2131492877;
|
||||
// aapt resource value: 0x7f0b0014
|
||||
public const int buttonConfig = 2131427348;
|
||||
|
||||
// aapt resource value: 0x7f0c000e
|
||||
public const int buttonGenerate = 2131492878;
|
||||
// aapt resource value: 0x7f0b000d
|
||||
public const int buttonExtract = 2131427341;
|
||||
|
||||
// aapt resource value: 0x7f0c000f
|
||||
public const int buttonLaunch = 2131492879;
|
||||
// aapt resource value: 0x7f0b000e
|
||||
public const int buttonGenerate = 2131427342;
|
||||
|
||||
// aapt resource value: 0x7f0c0010
|
||||
public const int buttonWiki = 2131492880;
|
||||
// aapt resource value: 0x7f0b000f
|
||||
public const int buttonLaunch = 2131427343;
|
||||
|
||||
// aapt resource value: 0x7f0c001a
|
||||
public const int cancel_action = 2131492890;
|
||||
// aapt resource value: 0x7f0b0010
|
||||
public const int buttonWiki = 2131427344;
|
||||
|
||||
// aapt resource value: 0x7f0c0022
|
||||
public const int chronometer = 2131492898;
|
||||
// aapt resource value: 0x7f0b001b
|
||||
public const int cancel_action = 2131427355;
|
||||
|
||||
// aapt resource value: 0x7f0c0030
|
||||
public const int description = 2131492912;
|
||||
// aapt resource value: 0x7f0b0023
|
||||
public const int chronometer = 2131427363;
|
||||
|
||||
// aapt resource value: 0x7f0c0029
|
||||
public const int end_padder = 2131492905;
|
||||
// aapt resource value: 0x7f0b0031
|
||||
public const int description = 2131427377;
|
||||
|
||||
// aapt resource value: 0x7f0c0008
|
||||
public const int forever = 2131492872;
|
||||
// aapt resource value: 0x7f0b002a
|
||||
public const int end_padder = 2131427370;
|
||||
|
||||
// aapt resource value: 0x7f0c000b
|
||||
public const int gridLayout1 = 2131492875;
|
||||
// aapt resource value: 0x7f0b0008
|
||||
public const int forever = 2131427336;
|
||||
|
||||
// aapt resource value: 0x7f0c0024
|
||||
public const int icon = 2131492900;
|
||||
// aapt resource value: 0x7f0b000b
|
||||
public const int gridLayout1 = 2131427339;
|
||||
|
||||
// aapt resource value: 0x7f0c0028
|
||||
public const int icon_group = 2131492904;
|
||||
// aapt resource value: 0x7f0b0025
|
||||
public const int icon = 2131427365;
|
||||
|
||||
// aapt resource value: 0x7f0c0023
|
||||
public const int info = 2131492899;
|
||||
// aapt resource value: 0x7f0b0029
|
||||
public const int icon_group = 2131427369;
|
||||
|
||||
// aapt resource value: 0x7f0c0009
|
||||
public const int italic = 2131492873;
|
||||
// aapt resource value: 0x7f0b0024
|
||||
public const int info = 2131427364;
|
||||
|
||||
// aapt resource value: 0x7f0c0000
|
||||
public const int line1 = 2131492864;
|
||||
// aapt resource value: 0x7f0b0009
|
||||
public const int italic = 2131427337;
|
||||
|
||||
// aapt resource value: 0x7f0c0001
|
||||
public const int line3 = 2131492865;
|
||||
// aapt resource value: 0x7f0b0000
|
||||
public const int line1 = 2131427328;
|
||||
|
||||
// aapt resource value: 0x7f0c000c
|
||||
public const int linearLayout1 = 2131492876;
|
||||
// aapt resource value: 0x7f0b0001
|
||||
public const int line3 = 2131427329;
|
||||
|
||||
// aapt resource value: 0x7f0c0011
|
||||
public const int listView1 = 2131492881;
|
||||
// aapt resource value: 0x7f0b000c
|
||||
public const int linearLayout1 = 2131427340;
|
||||
|
||||
// aapt resource value: 0x7f0c0012
|
||||
public const int ll_view = 2131492882;
|
||||
// aapt resource value: 0x7f0b0011
|
||||
public const int listView1 = 2131427345;
|
||||
|
||||
// aapt resource value: 0x7f0c001c
|
||||
public const int media_actions = 2131492892;
|
||||
// aapt resource value: 0x7f0b0012
|
||||
public const int ll_view = 2131427346;
|
||||
|
||||
// aapt resource value: 0x7f0c000a
|
||||
public const int normal = 2131492874;
|
||||
// aapt resource value: 0x7f0b001d
|
||||
public const int media_actions = 2131427357;
|
||||
|
||||
// aapt resource value: 0x7f0c002a
|
||||
public const int notificationLayout = 2131492906;
|
||||
// aapt resource value: 0x7f0b000a
|
||||
public const int normal = 2131427338;
|
||||
|
||||
// aapt resource value: 0x7f0c0026
|
||||
public const int notification_background = 2131492902;
|
||||
// aapt resource value: 0x7f0b002b
|
||||
public const int notificationLayout = 2131427371;
|
||||
|
||||
// aapt resource value: 0x7f0c001f
|
||||
public const int notification_main_column = 2131492895;
|
||||
// aapt resource value: 0x7f0b0027
|
||||
public const int notification_background = 2131427367;
|
||||
|
||||
// aapt resource value: 0x7f0c001e
|
||||
public const int notification_main_column_container = 2131492894;
|
||||
// aapt resource value: 0x7f0b0020
|
||||
public const int notification_main_column = 2131427360;
|
||||
|
||||
// aapt resource value: 0x7f0c002f
|
||||
public const int progress_bar = 2131492911;
|
||||
// aapt resource value: 0x7f0b001f
|
||||
public const int notification_main_column_container = 2131427359;
|
||||
|
||||
// aapt resource value: 0x7f0c002e
|
||||
public const int progress_bar_frame = 2131492910;
|
||||
// aapt resource value: 0x7f0b0030
|
||||
public const int progress_bar = 2131427376;
|
||||
|
||||
// aapt resource value: 0x7f0c002c
|
||||
public const int progress_text = 2131492908;
|
||||
// aapt resource value: 0x7f0b002f
|
||||
public const int progress_bar_frame = 2131427375;
|
||||
|
||||
// aapt resource value: 0x7f0c0025
|
||||
public const int right_icon = 2131492901;
|
||||
// aapt resource value: 0x7f0b002d
|
||||
public const int progress_text = 2131427373;
|
||||
|
||||
// aapt resource value: 0x7f0c0020
|
||||
public const int right_side = 2131492896;
|
||||
// aapt resource value: 0x7f0b0026
|
||||
public const int right_icon = 2131427366;
|
||||
|
||||
// aapt resource value: 0x7f0c0031
|
||||
public const int spacer = 2131492913;
|
||||
// aapt resource value: 0x7f0b0021
|
||||
public const int right_side = 2131427361;
|
||||
|
||||
// aapt resource value: 0x7f0c001b
|
||||
public const int status_bar_latest_event_content = 2131492891;
|
||||
// aapt resource value: 0x7f0b0032
|
||||
public const int spacer = 2131427378;
|
||||
|
||||
// aapt resource value: 0x7f0c0002
|
||||
public const int tag_transition_group = 2131492866;
|
||||
// aapt resource value: 0x7f0b001c
|
||||
public const int status_bar_latest_event_content = 2131427356;
|
||||
|
||||
// aapt resource value: 0x7f0c0003
|
||||
public const int text = 2131492867;
|
||||
// aapt resource value: 0x7f0b0002
|
||||
public const int tag_transition_group = 2131427330;
|
||||
|
||||
// aapt resource value: 0x7f0c0004
|
||||
public const int text2 = 2131492868;
|
||||
// aapt resource value: 0x7f0b0003
|
||||
public const int text = 2131427331;
|
||||
|
||||
// aapt resource value: 0x7f0c0015
|
||||
public const int textDescription = 2131492885;
|
||||
// aapt resource value: 0x7f0b0004
|
||||
public const int text2 = 2131427332;
|
||||
|
||||
// aapt resource value: 0x7f0c0014
|
||||
public const int textModName = 2131492884;
|
||||
// aapt resource value: 0x7f0b0016
|
||||
public const int textDescription = 2131427350;
|
||||
|
||||
// aapt resource value: 0x7f0c0021
|
||||
public const int time = 2131492897;
|
||||
// aapt resource value: 0x7f0b0015
|
||||
public const int textModName = 2131427349;
|
||||
|
||||
// aapt resource value: 0x7f0c002d
|
||||
public const int time_remaining = 2131492909;
|
||||
// aapt resource value: 0x7f0b0022
|
||||
public const int time = 2131427362;
|
||||
|
||||
// aapt resource value: 0x7f0c0005
|
||||
public const int title = 2131492869;
|
||||
// aapt resource value: 0x7f0b002e
|
||||
public const int time_remaining = 2131427374;
|
||||
|
||||
// aapt resource value: 0x7f0b0005
|
||||
public const int title = 2131427333;
|
||||
|
||||
static Id()
|
||||
{
|
||||
|
@ -412,11 +421,11 @@ namespace ModLoader
|
|||
public partial class Integer
|
||||
{
|
||||
|
||||
// aapt resource value: 0x7f080000
|
||||
public const int cancel_button_image_alpha = 2131230720;
|
||||
// aapt resource value: 0x7f070000
|
||||
public const int cancel_button_image_alpha = 2131165184;
|
||||
|
||||
// aapt resource value: 0x7f080001
|
||||
public const int status_bar_notification_info_maxnum = 2131230721;
|
||||
// aapt resource value: 0x7f070001
|
||||
public const int status_bar_notification_info_maxnum = 2131165185;
|
||||
|
||||
static Integer()
|
||||
{
|
||||
|
@ -498,11 +507,11 @@ namespace ModLoader
|
|||
public partial class Raw
|
||||
{
|
||||
|
||||
// aapt resource value: 0x7f050000
|
||||
public const int ModList = 2131034112;
|
||||
// aapt resource value: 0x7f040000
|
||||
public const int ModList = 2130968576;
|
||||
|
||||
// aapt resource value: 0x7f050001
|
||||
public const int SMDroidFiles = 2131034113;
|
||||
// aapt resource value: 0x7f040001
|
||||
public const int SMDroidFiles = 2130968577;
|
||||
|
||||
static Raw()
|
||||
{
|
||||
|
@ -517,155 +526,164 @@ namespace ModLoader
|
|||
public partial class String
|
||||
{
|
||||
|
||||
// aapt resource value: 0x7f090018
|
||||
public const int ApplicationName = 2131296280;
|
||||
// aapt resource value: 0x7f080018
|
||||
public const int ApplicationName = 2131230744;
|
||||
|
||||
// aapt resource value: 0x7f090026
|
||||
public const int Cancel = 2131296294;
|
||||
// aapt resource value: 0x7f080028
|
||||
public const int Cancel = 2131230760;
|
||||
|
||||
// aapt resource value: 0x7f090024
|
||||
public const int Confirm = 2131296292;
|
||||
// aapt resource value: 0x7f080026
|
||||
public const int Config = 2131230758;
|
||||
|
||||
// aapt resource value: 0x7f09001c
|
||||
public const int Disable = 2131296284;
|
||||
// aapt resource value: 0x7f080024
|
||||
public const int Confirm = 2131230756;
|
||||
|
||||
// aapt resource value: 0x7f09001d
|
||||
public const int Enable = 2131296285;
|
||||
// aapt resource value: 0x7f08001c
|
||||
public const int Disable = 2131230748;
|
||||
|
||||
// aapt resource value: 0x7f090019
|
||||
public const int Extract = 2131296281;
|
||||
// aapt resource value: 0x7f08001d
|
||||
public const int Enable = 2131230749;
|
||||
|
||||
// aapt resource value: 0x7f090028
|
||||
public const int ExtractedMessage = 2131296296;
|
||||
// aapt resource value: 0x7f080025
|
||||
public const int Error = 2131230757;
|
||||
|
||||
// aapt resource value: 0x7f090027
|
||||
public const int ExtractingMessage = 2131296295;
|
||||
// aapt resource value: 0x7f080019
|
||||
public const int Extract = 2131230745;
|
||||
|
||||
// aapt resource value: 0x7f09001a
|
||||
public const int Generate = 2131296282;
|
||||
// aapt resource value: 0x7f08002b
|
||||
public const int ExtractedMessage = 2131230763;
|
||||
|
||||
// aapt resource value: 0x7f09002a
|
||||
public const int GeneratedMessage = 2131296298;
|
||||
// aapt resource value: 0x7f08002a
|
||||
public const int ExtractingMessage = 2131230762;
|
||||
|
||||
// aapt resource value: 0x7f090029
|
||||
public const int GeneratingMessage = 2131296297;
|
||||
// aapt resource value: 0x7f08001a
|
||||
public const int Generate = 2131230746;
|
||||
|
||||
// aapt resource value: 0x7f090023
|
||||
public const int Ignore = 2131296291;
|
||||
// aapt resource value: 0x7f08002d
|
||||
public const int GeneratedMessage = 2131230765;
|
||||
|
||||
// aapt resource value: 0x7f09001b
|
||||
public const int Launch = 2131296283;
|
||||
// aapt resource value: 0x7f08002c
|
||||
public const int GeneratingMessage = 2131230764;
|
||||
|
||||
// aapt resource value: 0x7f09002b
|
||||
public const int ModDownloadingMessage = 2131296299;
|
||||
// aapt resource value: 0x7f080023
|
||||
public const int Ignore = 2131230755;
|
||||
|
||||
// aapt resource value: 0x7f09001e
|
||||
public const int ModInstall = 2131296286;
|
||||
// aapt resource value: 0x7f08001b
|
||||
public const int Launch = 2131230747;
|
||||
|
||||
// aapt resource value: 0x7f09002d
|
||||
public const int ModInstalledMessage = 2131296301;
|
||||
// aapt resource value: 0x7f08002e
|
||||
public const int ModDownloadingMessage = 2131230766;
|
||||
|
||||
// aapt resource value: 0x7f09001f
|
||||
public const int ModRemove = 2131296287;
|
||||
// aapt resource value: 0x7f08001e
|
||||
public const int ModInstall = 2131230750;
|
||||
|
||||
// aapt resource value: 0x7f09002e
|
||||
public const int ModRemovedMessage = 2131296302;
|
||||
// aapt resource value: 0x7f080030
|
||||
public const int ModInstalledMessage = 2131230768;
|
||||
|
||||
// aapt resource value: 0x7f09002c
|
||||
public const int NetworkErrorMessage = 2131296300;
|
||||
// aapt resource value: 0x7f08001f
|
||||
public const int ModRemove = 2131230751;
|
||||
|
||||
// aapt resource value: 0x7f090030
|
||||
public const int NotExtractedMessage = 2131296304;
|
||||
// aapt resource value: 0x7f080031
|
||||
public const int ModRemovedMessage = 2131230769;
|
||||
|
||||
// aapt resource value: 0x7f090031
|
||||
public const int NotGeneratedMessage = 2131296305;
|
||||
// aapt resource value: 0x7f08002f
|
||||
public const int NetworkErrorMessage = 2131230767;
|
||||
|
||||
// aapt resource value: 0x7f09002f
|
||||
public const int NotInstalledMessage = 2131296303;
|
||||
// aapt resource value: 0x7f080033
|
||||
public const int NotExtractedMessage = 2131230771;
|
||||
|
||||
// aapt resource value: 0x7f090025
|
||||
public const int RemoveConfirmMessage = 2131296293;
|
||||
// aapt resource value: 0x7f080034
|
||||
public const int NotGeneratedMessage = 2131230772;
|
||||
|
||||
// aapt resource value: 0x7f090022
|
||||
public const int Update = 2131296290;
|
||||
// aapt resource value: 0x7f080032
|
||||
public const int NotInstalledMessage = 2131230770;
|
||||
|
||||
// aapt resource value: 0x7f090021
|
||||
public const int UpdateTip = 2131296289;
|
||||
// aapt resource value: 0x7f080027
|
||||
public const int RemoveConfirmMessage = 2131230759;
|
||||
|
||||
// aapt resource value: 0x7f090020
|
||||
public const int Wiki = 2131296288;
|
||||
// aapt resource value: 0x7f080029
|
||||
public const int StorageIsFullMessage = 2131230761;
|
||||
|
||||
// aapt resource value: 0x7f090001
|
||||
public const int kilobytes_per_second = 2131296257;
|
||||
// aapt resource value: 0x7f080022
|
||||
public const int Update = 2131230754;
|
||||
|
||||
// aapt resource value: 0x7f090002
|
||||
public const int notification_download_complete = 2131296258;
|
||||
// aapt resource value: 0x7f080021
|
||||
public const int UpdateTip = 2131230753;
|
||||
|
||||
// aapt resource value: 0x7f090003
|
||||
public const int notification_download_failed = 2131296259;
|
||||
// aapt resource value: 0x7f080020
|
||||
public const int Wiki = 2131230752;
|
||||
|
||||
// aapt resource value: 0x7f090004
|
||||
public const int state_completed = 2131296260;
|
||||
// aapt resource value: 0x7f080001
|
||||
public const int kilobytes_per_second = 2131230721;
|
||||
|
||||
// aapt resource value: 0x7f090005
|
||||
public const int state_connecting = 2131296261;
|
||||
// aapt resource value: 0x7f080002
|
||||
public const int notification_download_complete = 2131230722;
|
||||
|
||||
// aapt resource value: 0x7f090006
|
||||
public const int state_downloading = 2131296262;
|
||||
// aapt resource value: 0x7f080003
|
||||
public const int notification_download_failed = 2131230723;
|
||||
|
||||
// aapt resource value: 0x7f090007
|
||||
public const int state_failed = 2131296263;
|
||||
// aapt resource value: 0x7f080004
|
||||
public const int state_completed = 2131230724;
|
||||
|
||||
// aapt resource value: 0x7f090008
|
||||
public const int state_failed_cancelled = 2131296264;
|
||||
// aapt resource value: 0x7f080005
|
||||
public const int state_connecting = 2131230725;
|
||||
|
||||
// aapt resource value: 0x7f090009
|
||||
public const int state_failed_fetching_url = 2131296265;
|
||||
// aapt resource value: 0x7f080006
|
||||
public const int state_downloading = 2131230726;
|
||||
|
||||
// aapt resource value: 0x7f09000a
|
||||
public const int state_failed_sdcard_full = 2131296266;
|
||||
// aapt resource value: 0x7f080007
|
||||
public const int state_failed = 2131230727;
|
||||
|
||||
// aapt resource value: 0x7f09000b
|
||||
public const int state_failed_unlicensed = 2131296267;
|
||||
// aapt resource value: 0x7f080008
|
||||
public const int state_failed_cancelled = 2131230728;
|
||||
|
||||
// aapt resource value: 0x7f09000c
|
||||
public const int state_fetching_url = 2131296268;
|
||||
// aapt resource value: 0x7f080009
|
||||
public const int state_failed_fetching_url = 2131230729;
|
||||
|
||||
// aapt resource value: 0x7f09000d
|
||||
public const int state_idle = 2131296269;
|
||||
// aapt resource value: 0x7f08000a
|
||||
public const int state_failed_sdcard_full = 2131230730;
|
||||
|
||||
// aapt resource value: 0x7f09000e
|
||||
public const int state_paused_by_request = 2131296270;
|
||||
// aapt resource value: 0x7f08000b
|
||||
public const int state_failed_unlicensed = 2131230731;
|
||||
|
||||
// aapt resource value: 0x7f09000f
|
||||
public const int state_paused_network_setup_failure = 2131296271;
|
||||
// aapt resource value: 0x7f08000c
|
||||
public const int state_fetching_url = 2131230732;
|
||||
|
||||
// aapt resource value: 0x7f090010
|
||||
public const int state_paused_network_unavailable = 2131296272;
|
||||
// aapt resource value: 0x7f08000d
|
||||
public const int state_idle = 2131230733;
|
||||
|
||||
// aapt resource value: 0x7f090011
|
||||
public const int state_paused_roaming = 2131296273;
|
||||
// aapt resource value: 0x7f08000e
|
||||
public const int state_paused_by_request = 2131230734;
|
||||
|
||||
// aapt resource value: 0x7f090012
|
||||
public const int state_paused_sdcard_unavailable = 2131296274;
|
||||
// aapt resource value: 0x7f08000f
|
||||
public const int state_paused_network_setup_failure = 2131230735;
|
||||
|
||||
// aapt resource value: 0x7f090013
|
||||
public const int state_paused_wifi_disabled = 2131296275;
|
||||
// aapt resource value: 0x7f080010
|
||||
public const int state_paused_network_unavailable = 2131230736;
|
||||
|
||||
// aapt resource value: 0x7f090014
|
||||
public const int state_paused_wifi_unavailable = 2131296276;
|
||||
// aapt resource value: 0x7f080011
|
||||
public const int state_paused_roaming = 2131230737;
|
||||
|
||||
// aapt resource value: 0x7f090015
|
||||
public const int state_unknown = 2131296277;
|
||||
// aapt resource value: 0x7f080012
|
||||
public const int state_paused_sdcard_unavailable = 2131230738;
|
||||
|
||||
// aapt resource value: 0x7f090000
|
||||
public const int status_bar_notification_info_overflow = 2131296256;
|
||||
// aapt resource value: 0x7f080013
|
||||
public const int state_paused_wifi_disabled = 2131230739;
|
||||
|
||||
// aapt resource value: 0x7f090016
|
||||
public const int time_remaining = 2131296278;
|
||||
// aapt resource value: 0x7f080014
|
||||
public const int state_paused_wifi_unavailable = 2131230740;
|
||||
|
||||
// aapt resource value: 0x7f090017
|
||||
public const int time_remaining_notification = 2131296279;
|
||||
// aapt resource value: 0x7f080015
|
||||
public const int state_unknown = 2131230741;
|
||||
|
||||
// aapt resource value: 0x7f080000
|
||||
public const int status_bar_notification_info_overflow = 2131230720;
|
||||
|
||||
// aapt resource value: 0x7f080016
|
||||
public const int time_remaining = 2131230742;
|
||||
|
||||
// aapt resource value: 0x7f080017
|
||||
public const int time_remaining_notification = 2131230743;
|
||||
|
||||
static String()
|
||||
{
|
||||
|
@ -680,59 +698,59 @@ namespace ModLoader
|
|||
public partial class Style
|
||||
{
|
||||
|
||||
// aapt resource value: 0x7f06000f
|
||||
public const int ButtonBackground = 2131099663;
|
||||
// aapt resource value: 0x7f05000f
|
||||
public const int ButtonBackground = 2131034127;
|
||||
|
||||
// aapt resource value: 0x7f06000d
|
||||
public const int NotificationText = 2131099661;
|
||||
// aapt resource value: 0x7f05000d
|
||||
public const int NotificationText = 2131034125;
|
||||
|
||||
// aapt resource value: 0x7f06000c
|
||||
public const int NotificationTextSecondary = 2131099660;
|
||||
// aapt resource value: 0x7f05000c
|
||||
public const int NotificationTextSecondary = 2131034124;
|
||||
|
||||
// aapt resource value: 0x7f060010
|
||||
public const int NotificationTextShadow = 2131099664;
|
||||
// aapt resource value: 0x7f050010
|
||||
public const int NotificationTextShadow = 2131034128;
|
||||
|
||||
// aapt resource value: 0x7f06000e
|
||||
public const int NotificationTitle = 2131099662;
|
||||
// aapt resource value: 0x7f05000e
|
||||
public const int NotificationTitle = 2131034126;
|
||||
|
||||
// aapt resource value: 0x7f060005
|
||||
public const int TextAppearance_Compat_Notification = 2131099653;
|
||||
// aapt resource value: 0x7f050005
|
||||
public const int TextAppearance_Compat_Notification = 2131034117;
|
||||
|
||||
// aapt resource value: 0x7f060006
|
||||
public const int TextAppearance_Compat_Notification_Info = 2131099654;
|
||||
// aapt resource value: 0x7f050006
|
||||
public const int TextAppearance_Compat_Notification_Info = 2131034118;
|
||||
|
||||
// aapt resource value: 0x7f060000
|
||||
public const int TextAppearance_Compat_Notification_Info_Media = 2131099648;
|
||||
// aapt resource value: 0x7f050000
|
||||
public const int TextAppearance_Compat_Notification_Info_Media = 2131034112;
|
||||
|
||||
// aapt resource value: 0x7f06000b
|
||||
public const int TextAppearance_Compat_Notification_Line2 = 2131099659;
|
||||
// aapt resource value: 0x7f05000b
|
||||
public const int TextAppearance_Compat_Notification_Line2 = 2131034123;
|
||||
|
||||
// aapt resource value: 0x7f060004
|
||||
public const int TextAppearance_Compat_Notification_Line2_Media = 2131099652;
|
||||
// aapt resource value: 0x7f050004
|
||||
public const int TextAppearance_Compat_Notification_Line2_Media = 2131034116;
|
||||
|
||||
// aapt resource value: 0x7f060001
|
||||
public const int TextAppearance_Compat_Notification_Media = 2131099649;
|
||||
// aapt resource value: 0x7f050001
|
||||
public const int TextAppearance_Compat_Notification_Media = 2131034113;
|
||||
|
||||
// aapt resource value: 0x7f060007
|
||||
public const int TextAppearance_Compat_Notification_Time = 2131099655;
|
||||
// aapt resource value: 0x7f050007
|
||||
public const int TextAppearance_Compat_Notification_Time = 2131034119;
|
||||
|
||||
// aapt resource value: 0x7f060002
|
||||
public const int TextAppearance_Compat_Notification_Time_Media = 2131099650;
|
||||
// aapt resource value: 0x7f050002
|
||||
public const int TextAppearance_Compat_Notification_Time_Media = 2131034114;
|
||||
|
||||
// aapt resource value: 0x7f060008
|
||||
public const int TextAppearance_Compat_Notification_Title = 2131099656;
|
||||
// aapt resource value: 0x7f050008
|
||||
public const int TextAppearance_Compat_Notification_Title = 2131034120;
|
||||
|
||||
// aapt resource value: 0x7f060003
|
||||
public const int TextAppearance_Compat_Notification_Title_Media = 2131099651;
|
||||
// aapt resource value: 0x7f050003
|
||||
public const int TextAppearance_Compat_Notification_Title_Media = 2131034115;
|
||||
|
||||
// aapt resource value: 0x7f060011
|
||||
public const int Theme_Splash = 2131099665;
|
||||
// aapt resource value: 0x7f050011
|
||||
public const int Theme_Splash = 2131034129;
|
||||
|
||||
// aapt resource value: 0x7f060009
|
||||
public const int Widget_Compat_NotificationActionContainer = 2131099657;
|
||||
// aapt resource value: 0x7f050009
|
||||
public const int Widget_Compat_NotificationActionContainer = 2131034121;
|
||||
|
||||
// aapt resource value: 0x7f06000a
|
||||
public const int Widget_Compat_NotificationActionText = 2131099658;
|
||||
// aapt resource value: 0x7f05000a
|
||||
public const int Widget_Compat_NotificationActionText = 2131034122;
|
||||
|
||||
static Style()
|
||||
{
|
||||
|
@ -744,22 +762,6 @@ namespace ModLoader
|
|||
}
|
||||
}
|
||||
|
||||
public partial class Xml
|
||||
{
|
||||
|
||||
// aapt resource value: 0x7f040000
|
||||
public const int file_paths = 2130968576;
|
||||
|
||||
static Xml()
|
||||
{
|
||||
global::Android.Runtime.ResourceIdManager.UpdateIdValues();
|
||||
}
|
||||
|
||||
private Xml()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public partial class Styleable
|
||||
{
|
||||
|
||||
|
|
|
@ -10,11 +10,14 @@
|
|||
<string name="ModRemove">移除</string>
|
||||
<string name="Wiki">Wiki</string>
|
||||
<string name="UpdateTip">发现新版本</string>
|
||||
<string name="Update">立即更新</string>
|
||||
<string name="Ignore">暂时忽略</string>
|
||||
<string name="Update">更新</string>
|
||||
<string name="Ignore">忽略</string>
|
||||
<string name="Confirm">确认</string>
|
||||
<string name="Error">错误</string>
|
||||
<string name="Config">配置</string>
|
||||
<string name="RemoveConfirmMessage">确认移除这个插件?</string>
|
||||
<string name="Cancel">取消</string>
|
||||
<string name="StorageIsFullMessage">存储空间不足</string>
|
||||
<string name="ExtractingMessage">正在解压游戏资源</string>
|
||||
<string name="ExtractedMessage">解压完成</string>
|
||||
<string name="GeneratingMessage">正在生成DLL文件</string>
|
||||
|
|
|
@ -13,8 +13,11 @@
|
|||
<string name="Update">Upgrade</string>
|
||||
<string name="Ignore">Ignore</string>
|
||||
<string name="Confirm">Confirm</string>
|
||||
<string name="Error">Error</string>
|
||||
<string name="Config">Config</string>
|
||||
<string name="RemoveConfirmMessage">Are you sure to remove this mod?</string>
|
||||
<string name="Cancel">Cancel</string>
|
||||
<string name="StorageIsFullMessage">Not enough storage free space</string>
|
||||
<string name="ExtractingMessage">Extracting game resources</string>
|
||||
<string name="ExtractedMessage">Extracted</string>
|
||||
<string name="GeneratingMessage">Generating Dlls</string>
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<style name="Theme.Splash" parent="android:Theme">
|
||||
<item name="android:windowBackground">@drawable/splash</item>
|
||||
<item name="android:windowBackground">@drawable/splash_screen</item>
|
||||
<item name="android:windowNoTitle">true</item>
|
||||
<item name="android:windowFullscreen">true</item>
|
||||
</style>
|
||||
</resources>
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<paths>
|
||||
<external-path path="." name="external_storage_root" />
|
||||
</paths>
|
|
@ -22,6 +22,7 @@ namespace StardewModdingAPI
|
|||
****/
|
||||
/// <summary>SMAPI's current semantic version.</summary>
|
||||
public static ISemanticVersion ApiVersion { get; } = new Toolkit.SemanticVersion("3.0.0");
|
||||
public static ISemanticVersion CoreVersion { get; } = new Toolkit.SemanticVersion("1.4.2");
|
||||
|
||||
/// <summary>The minimum supported version of Stardew Valley.</summary>
|
||||
public static ISemanticVersion MinimumGameVersion { get; } = new GameVersion("1.3.36");
|
||||
|
|
|
@ -122,66 +122,6 @@ namespace StardewModdingAPI.Framework.ContentManagers
|
|||
return this.Coordinator.CreateGameContentManager("(temporary)");
|
||||
}
|
||||
|
||||
|
||||
public T ModedLoad<T>(string assetName, LanguageCode language)
|
||||
{
|
||||
if (language != LanguageCode.en)
|
||||
{
|
||||
string key = assetName + "." + this.LanguageCodeString(language);
|
||||
Dictionary<string, bool> _localizedAsset = this.Reflector.GetField<Dictionary<string, bool>>(this, "_localizedAsset").GetValue();
|
||||
if (!_localizedAsset.TryGetValue(key, out bool flag) | flag)
|
||||
{
|
||||
try
|
||||
{
|
||||
_localizedAsset[key] = true;
|
||||
return this.ModedLoad<T>(key);
|
||||
}
|
||||
catch (ContentLoadException)
|
||||
{
|
||||
_localizedAsset[key] = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return this.ModedLoad<T>(assetName);
|
||||
}
|
||||
|
||||
public T ModedLoad<T>(string assetName)
|
||||
{
|
||||
if (string.IsNullOrEmpty(assetName))
|
||||
{
|
||||
throw new ArgumentNullException("assetName");
|
||||
}
|
||||
T local = default(T);
|
||||
string key = assetName.Replace('\\', '/');
|
||||
Dictionary<string, object> loadedAssets = this.Reflector.GetField<Dictionary<string, object>>(this, "loadedAssets").GetValue();
|
||||
if (loadedAssets.TryGetValue(key, out object obj2) && (obj2 is T))
|
||||
{
|
||||
return (T)obj2;
|
||||
}
|
||||
local = this.ReadAsset<T>(assetName, null);
|
||||
loadedAssets[key] = local;
|
||||
return local;
|
||||
}
|
||||
|
||||
protected override Stream OpenStream(string assetName)
|
||||
{
|
||||
Stream stream;
|
||||
try
|
||||
{
|
||||
stream = new FileStream(Path.Combine(Constants.ExecutionPath, "Game/assets", this.RootDirectory, assetName) + ".xnb", FileMode.Open, FileAccess.Read);
|
||||
MemoryStream destination = new MemoryStream();
|
||||
stream.CopyTo(destination);
|
||||
destination.Seek(0L, SeekOrigin.Begin);
|
||||
stream.Close();
|
||||
stream = destination;
|
||||
}
|
||||
catch (Exception exception3)
|
||||
{
|
||||
throw new ContentLoadException("Opening stream error.", exception3);
|
||||
}
|
||||
return stream;
|
||||
}
|
||||
|
||||
/*********
|
||||
** Private methods
|
||||
*********/
|
||||
|
|
|
@ -246,6 +246,44 @@ namespace StardewModdingAPI.Framework.ModLoading
|
|||
/****
|
||||
** Assembly rewriting
|
||||
****/
|
||||
IEnumerable<TypeReference> GetAttributeTypes(IEnumerable<CustomAttribute> attributes)
|
||||
{
|
||||
foreach (var attribute in attributes)
|
||||
{
|
||||
if(!attribute.AttributeType.FullName.StartsWith("System."))
|
||||
yield return attribute.AttributeType;
|
||||
if (!attribute.AttributeType.FullName.StartsWith("System."))
|
||||
yield return attribute.Constructor.ReturnType;
|
||||
if (!attribute.AttributeType.FullName.StartsWith("System."))
|
||||
yield return attribute.Constructor.DeclaringType;
|
||||
foreach (var constructorArgument in attribute.Constructor.Parameters)
|
||||
{
|
||||
if (!attribute.AttributeType.FullName.StartsWith("System."))
|
||||
yield return constructorArgument.ParameterType;
|
||||
}
|
||||
foreach (var constructorArgument in attribute.ConstructorArguments)
|
||||
{
|
||||
if (!attribute.AttributeType.FullName.StartsWith("System."))
|
||||
yield return constructorArgument.Type;
|
||||
if (constructorArgument.Value is TypeReference reference)
|
||||
{
|
||||
if (!attribute.AttributeType.FullName.StartsWith("System."))
|
||||
yield return reference;
|
||||
}
|
||||
}
|
||||
foreach (var property in attribute.Properties)
|
||||
{
|
||||
if (!attribute.AttributeType.FullName.StartsWith("System."))
|
||||
yield return property.Argument.Type;
|
||||
}
|
||||
foreach (var field in attribute.Fields)
|
||||
{
|
||||
if (!attribute.AttributeType.FullName.StartsWith("System."))
|
||||
yield return field.Argument.Type;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Rewrite the types referenced by an assembly.</summary>
|
||||
/// <param name="mod">The mod for which the assembly is being loaded.</param>
|
||||
/// <param name="assembly">The assembly to rewrite.</param>
|
||||
|
@ -279,6 +317,9 @@ namespace StardewModdingAPI.Framework.ModLoading
|
|||
|
||||
// rewrite type scopes to use target assemblies
|
||||
IEnumerable<TypeReference> typeReferences = module.GetTypeReferences().OrderBy(p => p.FullName);
|
||||
typeReferences = typeReferences.Concat(module.Types.SelectMany(t => this.GetAttributeTypes(t.CustomAttributes)));
|
||||
typeReferences = typeReferences.Concat(module.Types.SelectMany(t => this.GetAttributeTypes(t.Properties.SelectMany(p=>p.CustomAttributes))));
|
||||
typeReferences = typeReferences.Concat(module.Types.SelectMany(t => this.GetAttributeTypes(t.Fields.SelectMany(p => p.CustomAttributes))));
|
||||
foreach (TypeReference type in typeReferences)
|
||||
this.ChangeTypeScope(type);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,78 @@
|
|||
using System;
|
||||
using Mono.Cecil;
|
||||
using Mono.Cecil.Cil;
|
||||
using StardewModdingAPI.Framework.ModLoading.Finders;
|
||||
|
||||
namespace StardewModdingAPI.Framework.ModLoading.Rewriters
|
||||
{
|
||||
/// <summary>Rewrites field references into property references.</summary>
|
||||
internal class TypeFieldToTypeFieldRewriter : FieldFinder
|
||||
{
|
||||
/*********
|
||||
** Fields
|
||||
*********/
|
||||
/// <summary>The type whose field to which references should be rewritten.</summary>
|
||||
private readonly Type Type;
|
||||
|
||||
/// <summary>The type whose field to which references should be rewritten to.</summary>
|
||||
private readonly Type ToType;
|
||||
|
||||
/// <summary>The property name.</summary>
|
||||
private readonly string PropertyName;
|
||||
|
||||
private readonly IMonitor Monitor;
|
||||
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Construct an instance.</summary>
|
||||
/// <param name="type">The type whose field to which references should be rewritten.</param>
|
||||
/// <param name="fieldName">The field name to rewrite.</param>
|
||||
/// <param name="propertyName">The property name (if different).</param>
|
||||
public TypeFieldToTypeFieldRewriter(Type type, Type toType, string fieldName, string propertyName, IMonitor monitor)
|
||||
: base(type.FullName, fieldName, InstructionHandleResult.None)
|
||||
{
|
||||
this.Monitor = monitor;
|
||||
this.Type = type;
|
||||
this.ToType = toType;
|
||||
this.PropertyName = propertyName;
|
||||
}
|
||||
|
||||
/// <summary>Construct an instance.</summary>
|
||||
/// <param name="type">The type whose field to which references should be rewritten.</param>
|
||||
/// <param name="fieldName">The field name to rewrite.</param>
|
||||
public TypeFieldToTypeFieldRewriter(Type type, Type toType, string fieldName, IMonitor monitor)
|
||||
: this(type, toType, fieldName, fieldName, monitor) { }
|
||||
|
||||
/// <summary>Perform the predefined logic for an instruction if applicable.</summary>
|
||||
/// <param name="module">The assembly module containing the instruction.</param>
|
||||
/// <param name="cil">The CIL processor.</param>
|
||||
/// <param name="instruction">The instruction to handle.</param>
|
||||
/// <param name="assemblyMap">Metadata for mapping assemblies to the current platform.</param>
|
||||
/// <param name="platformChanged">Whether the mod was compiled on a different platform.</param>
|
||||
public override InstructionHandleResult Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction, PlatformAssemblyMap assemblyMap, bool platformChanged)
|
||||
{
|
||||
if (!this.IsMatch(instruction))
|
||||
return InstructionHandleResult.None;
|
||||
|
||||
//Instruction: IL_0025: ldsfld StardewValley.GameLocation StardewValley.Game1::currentLocation
|
||||
string methodPrefix = instruction.OpCode == OpCodes.Ldsfld || instruction.OpCode == OpCodes.Ldfld ? "get" : "set";
|
||||
try
|
||||
{
|
||||
//MethodReference propertyRef = module.ImportReference(this.ToType.GetMethod($"{methodPrefix}_{this.PropertyName}"));
|
||||
|
||||
MethodReference method = module.ImportReference(this.ToType.GetMethod($"{methodPrefix}_{this.PropertyName}"));
|
||||
this.Monitor.Log("Method Ref: " + method.ToString());
|
||||
|
||||
cil.Replace(instruction, cil.Create(OpCodes.Call, method));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
this.Monitor.Log(e.Message);
|
||||
}
|
||||
|
||||
|
||||
return InstructionHandleResult.Rewritten;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -19,6 +19,7 @@ using MonoMod.RuntimeDetour;
|
|||
using System.Windows.Forms;
|
||||
#endif
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
using SMDroid;
|
||||
using StardewModdingAPI.Events;
|
||||
using StardewModdingAPI.Framework.Events;
|
||||
|
@ -169,7 +170,7 @@ namespace StardewModdingAPI.Framework
|
|||
|
||||
// init logging
|
||||
this.Monitor.Log($"SMAPI {Constants.ApiVersion} with Stardew Valley {Constants.GameVersion} on {EnvironmentUtility.GetFriendlyPlatformName(Constants.Platform)}", LogLevel.Info);
|
||||
this.Monitor.Log($"SMDroid 1.4.0 for Stardew Valley Android release {Application.Context.PackageManager.GetPackageInfo(Application.Context.PackageName, (PackageInfoFlags)0).VersionCode}", LogLevel.Info);
|
||||
this.Monitor.Log($"SMDroid {Constants.CoreVersion.ToString()} for Stardew Valley Android release {Application.Context.PackageManager.GetPackageInfo(Application.Context.PackageName, (PackageInfoFlags)0).VersionCode}", LogLevel.Info);
|
||||
this.Monitor.Log($"Mods go here: {modsPath}", LogLevel.Info);
|
||||
if (modsPath != Constants.DefaultModsPath)
|
||||
this.Monitor.Log("(Using custom --mods-path argument.)", LogLevel.Trace);
|
||||
|
@ -256,14 +257,10 @@ namespace StardewModdingAPI.Framework
|
|||
|
||||
// override game
|
||||
SGame.ConstructorHack = new SGameConstructorHack(this.Monitor, this.Reflection, this.Toolkit.JsonHelper, this.InitialiseBeforeFirstAssetLoaded);
|
||||
try
|
||||
if(!this.HarmonyDetourBridgeFailed)
|
||||
{
|
||||
HarmonyDetourBridge.Init();
|
||||
}
|
||||
catch
|
||||
{
|
||||
this.HarmonyDetourBridgeFailed = true;
|
||||
}
|
||||
|
||||
// override game
|
||||
this.GameInstance = new SGame(
|
||||
|
@ -281,9 +278,10 @@ namespace StardewModdingAPI.Framework
|
|||
StardewValley.Program.gamePtr = Game1.game1;
|
||||
// apply game patches
|
||||
new GamePatcher(this.Monitor).Apply(
|
||||
new EventErrorPatch(this.MonitorForGame),
|
||||
new DialogueErrorPatch(this.MonitorForGame, this.Reflection),
|
||||
new ObjectErrorPatch(),
|
||||
new LoadForNewGamePatch(this.Reflection, this.GameInstance.OnLoadStageChanged)
|
||||
new LoadContextPatch(this.Reflection, this.GameInstance.OnLoadStageChanged)
|
||||
);
|
||||
// add exit handler
|
||||
new Thread(() =>
|
||||
|
@ -318,10 +316,8 @@ namespace StardewModdingAPI.Framework
|
|||
// 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("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://www.reddit.com/r/StardewValley/comments/bcm8w8/smapi_for_android_version/.", 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);
|
||||
File.Delete(Constants.FatalCrashLog);
|
||||
File.Delete(Constants.FatalCrashMarker);
|
||||
}
|
||||
|
||||
|
|
|
@ -818,6 +818,15 @@ namespace StardewModdingAPI.Framework
|
|||
this.Monitor.Log($"Context: menu changed from {was?.GetType().FullName ?? "none"} to {now?.GetType().FullName ?? "none"}.", LogLevel.Trace);
|
||||
|
||||
// raise menu events
|
||||
int forSaleCount = 0;
|
||||
Dictionary<Item, int[]> itemPriceAndStock;
|
||||
List<Item> forSale;
|
||||
if (now is ShopMenu shop && !(was is ShopMenu))
|
||||
{
|
||||
itemPriceAndStock = this.Reflection.GetField<Dictionary<Item, int[]>>(shop, "itemPriceAndStock").GetValue();
|
||||
forSale = this.Reflection.GetField<List<Item>>(shop, "forSale").GetValue();
|
||||
forSaleCount = forSale.Count;
|
||||
}
|
||||
events.MenuChanged.Raise(new MenuChangedEventArgs(was, now));
|
||||
#if !SMAPI_3_0_STRICT
|
||||
if (now != null)
|
||||
|
@ -825,8 +834,7 @@ namespace StardewModdingAPI.Framework
|
|||
else
|
||||
events.Legacy_MenuClosed.Raise(new EventArgsClickableMenuClosed(was));
|
||||
#endif
|
||||
GameMenu gameMenu = now as GameMenu;
|
||||
if (gameMenu != null)
|
||||
if (now is GameMenu gameMenu)
|
||||
{
|
||||
foreach (IClickableMenu menuPage in gameMenu.pages)
|
||||
{
|
||||
|
@ -846,6 +854,15 @@ namespace StardewModdingAPI.Framework
|
|||
}
|
||||
}
|
||||
}
|
||||
else if (now is ShopMenu shopMenu && !(was is ShopMenu))
|
||||
{
|
||||
itemPriceAndStock = this.Reflection.GetField<Dictionary<Item, int[]>>(shopMenu, "itemPriceAndStock").GetValue();
|
||||
forSale = this.Reflection.GetField<List<Item>>(shopMenu, "forSale").GetValue();
|
||||
if (forSaleCount != forSale.Count)
|
||||
{
|
||||
Game1.activeClickableMenu = new ShopMenu(itemPriceAndStock, this.Reflection.GetField<int>(shopMenu, "currency").GetValue(), this.Reflection.GetField<string>(shopMenu, "personName").GetValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*********
|
||||
|
@ -1048,7 +1065,7 @@ namespace StardewModdingAPI.Framework
|
|||
}
|
||||
|
||||
// preloaded
|
||||
if (Context.IsSaveLoaded && Context.LoadStage != LoadStage.Loaded && Context.LoadStage != LoadStage.Ready)
|
||||
if (Context.IsSaveLoaded && Context.LoadStage != LoadStage.Loaded && Context.LoadStage != LoadStage.Ready && Game1.dayOfMonth != 0)
|
||||
this.OnLoadStageChanged(LoadStage.Loaded);
|
||||
|
||||
// update tick
|
||||
|
|
|
@ -50,10 +50,7 @@ namespace StardewModdingAPI.Patches
|
|||
ConstructorInfo constructor = AccessTools.Constructor(typeof(Dialogue), new[] { typeof(string), typeof(NPC) });
|
||||
MethodInfo prefix = AccessTools.Method(this.GetType(), nameof(DialogueErrorPatch.Prefix));
|
||||
|
||||
if (!SCore.Instance.HarmonyDetourBridgeFailed)
|
||||
{
|
||||
harmony.Patch(constructor, new HarmonyMethod(prefix), null);
|
||||
}
|
||||
harmony.Patch(constructor, new HarmonyMethod(prefix), null);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,85 @@
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Reflection;
|
||||
using Harmony;
|
||||
using StardewModdingAPI.Framework;
|
||||
using StardewModdingAPI.Framework.Patching;
|
||||
using StardewValley;
|
||||
|
||||
namespace StardewModdingAPI.Patches
|
||||
{
|
||||
/// <summary>A Harmony patch for the <see cref="Dialogue"/> constructor which intercepts invalid dialogue lines and logs an error instead of crashing.</summary>
|
||||
internal class EventErrorPatch : IHarmonyPatch
|
||||
{
|
||||
/*********
|
||||
** Fields
|
||||
*********/
|
||||
/// <summary>Writes messages to the console and log file on behalf of the game.</summary>
|
||||
private static IMonitor MonitorForGame;
|
||||
|
||||
/// <summary>Whether the method is currently being intercepted.</summary>
|
||||
private static bool IsIntercepted;
|
||||
|
||||
|
||||
/*********
|
||||
** Accessors
|
||||
*********/
|
||||
/// <summary>A unique name for this patch.</summary>
|
||||
public string Name => $"{nameof(EventErrorPatch)}";
|
||||
|
||||
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Construct an instance.</summary>
|
||||
/// <param name="monitorForGame">Writes messages to the console and log file on behalf of the game.</param>
|
||||
public EventErrorPatch(IMonitor monitorForGame)
|
||||
{
|
||||
EventErrorPatch.MonitorForGame = monitorForGame;
|
||||
}
|
||||
|
||||
/// <summary>Apply the Harmony patch.</summary>
|
||||
/// <param name="harmony">The Harmony instance.</param>
|
||||
public void Apply(HarmonyInstance harmony)
|
||||
{
|
||||
harmony.Patch(
|
||||
original: AccessTools.Method(typeof(GameLocation), "checkEventPrecondition"),
|
||||
prefix: new HarmonyMethod(this.GetType(), nameof(EventErrorPatch.Prefix))
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/*********
|
||||
** Private methods
|
||||
*********/
|
||||
/// <summary>The method to call instead of the GameLocation.CheckEventPrecondition.</summary>
|
||||
/// <param name="__instance">The instance being patched.</param>
|
||||
/// <param name="__result">The return value of the original method.</param>
|
||||
/// <param name="precondition">The precondition to be parsed.</param>
|
||||
/// <param name="__originalMethod">The method being wrapped.</param>
|
||||
/// <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(GameLocation __instance, ref int __result, string precondition, MethodInfo __originalMethod)
|
||||
{
|
||||
if (EventErrorPatch.IsIntercepted)
|
||||
return true;
|
||||
|
||||
try
|
||||
{
|
||||
EventErrorPatch.IsIntercepted = true;
|
||||
__result = (int)__originalMethod.Invoke(__instance, new object[] { precondition });
|
||||
return false;
|
||||
}
|
||||
catch (TargetInvocationException ex)
|
||||
{
|
||||
__result = -1;
|
||||
EventErrorPatch.MonitorForGame.Log($"Failed parsing event precondition ({precondition}):\n{ex.InnerException}", LogLevel.Error);
|
||||
return false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
EventErrorPatch.IsIntercepted = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
using System;
|
||||
using Harmony;
|
||||
using StardewModdingAPI.Enums;
|
||||
using StardewModdingAPI.Framework;
|
||||
using StardewModdingAPI.Framework.Patching;
|
||||
using StardewModdingAPI.Framework.Reflection;
|
||||
using StardewValley;
|
||||
using StardewValley.Menus;
|
||||
using StardewValley.Minigames;
|
||||
|
||||
namespace StardewModdingAPI.Patches
|
||||
{
|
||||
/// <summary>Harmony patches which notify SMAPI for save creation load stages.</summary>
|
||||
/// <remarks>This patch hooks into <see cref="Game1.loadForNewGame"/>, checks if <c>TitleMenu.transitioningCharacterCreationMenu</c> is true (which means the player is creating a new save file), then raises <see cref="LoadStage.CreatedBasicInfo"/> after the location list is cleared twice (the second clear happens right before locations are created), and <see cref="LoadStage.CreatedLocations"/> when the method ends.</remarks>
|
||||
internal class LoadContextPatch : IHarmonyPatch
|
||||
{
|
||||
/*********
|
||||
** Fields
|
||||
*********/
|
||||
/// <summary>Simplifies access to private code.</summary>
|
||||
private static Reflector Reflection;
|
||||
|
||||
/// <summary>A callback to invoke when the load stage changes.</summary>
|
||||
private static Action<LoadStage> OnStageChanged;
|
||||
|
||||
|
||||
/*********
|
||||
** Accessors
|
||||
*********/
|
||||
/// <summary>A unique name for this patch.</summary>
|
||||
public string Name => $"{nameof(LoadContextPatch)}";
|
||||
|
||||
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Construct an instance.</summary>
|
||||
/// <param name="reflection">Simplifies access to private code.</param>
|
||||
/// <param name="onStageChanged">A callback to invoke when the load stage changes.</param>
|
||||
public LoadContextPatch(Reflector reflection, Action<LoadStage> onStageChanged)
|
||||
{
|
||||
LoadContextPatch.Reflection = reflection;
|
||||
LoadContextPatch.OnStageChanged = onStageChanged;
|
||||
}
|
||||
|
||||
/// <summary>Apply the Harmony patch.</summary>
|
||||
/// <param name="harmony">The Harmony instance.</param>
|
||||
public void Apply(HarmonyInstance harmony)
|
||||
{
|
||||
// detect CreatedBasicInfo
|
||||
harmony.Patch(
|
||||
original: AccessTools.Method(typeof(TitleMenu), nameof(TitleMenu.createdNewCharacter)),
|
||||
prefix: new HarmonyMethod(this.GetType(), nameof(LoadContextPatch.OnCreatingNewCharacter))
|
||||
);
|
||||
|
||||
// detect CreatedLocations
|
||||
harmony.Patch(
|
||||
original: AccessTools.Method(typeof(Game1), nameof(Game1.loadForNewGame)),
|
||||
postfix: new HarmonyMethod(this.GetType(), nameof(LoadContextPatch.OnLoaded))
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/*********
|
||||
** Private methods
|
||||
*********/
|
||||
/// <summary>Called before <see cref="TitleMenu.createdNewCharacter"/>.</summary>
|
||||
/// <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>
|
||||
private static bool OnCreatingNewCharacter()
|
||||
{
|
||||
LoadContextPatch.OnStageChanged(LoadStage.CreatedBasicInfo);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>Called after <see cref="Game1.loadForNewGame"/>.</summary>
|
||||
/// <remarks>This method must be static for Harmony to work correctly. See the Harmony documentation before renaming arguments.</remarks>
|
||||
private static void OnLoaded()
|
||||
{
|
||||
bool creating =
|
||||
(Game1.currentMinigame is Intro) // creating save with intro
|
||||
|| (Game1.activeClickableMenu is TitleMenu menu && LoadContextPatch.Reflection.GetField<bool>(menu, "transitioningCharacterCreationMenu").GetValue()); // creating save, skipped intro
|
||||
|
||||
if (creating)
|
||||
LoadContextPatch.OnStageChanged(LoadStage.CreatedLocations);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,114 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
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;
|
||||
using StardewValley.Menus;
|
||||
|
||||
namespace StardewModdingAPI.Patches
|
||||
{
|
||||
/// <summary>A Harmony patch for <see cref="Game1.loadForNewGame"/> which notifies SMAPI for save creation load stages.</summary>
|
||||
/// <remarks>This patch hooks into <see cref="Game1.loadForNewGame"/>, checks if <c>TitleMenu.transitioningCharacterCreationMenu</c> is true (which means the player is creating a new save file), then raises <see cref="LoadStage.CreatedBasicInfo"/> after the location list is cleared twice (the second clear happens right before locations are created), and <see cref="LoadStage.CreatedLocations"/> when the method ends.</remarks>
|
||||
internal class LoadForNewGamePatch : IHarmonyPatch
|
||||
{
|
||||
/*********
|
||||
** Accessors
|
||||
*********/
|
||||
/// <summary>Simplifies access to private code.</summary>
|
||||
private static Reflector Reflection;
|
||||
|
||||
/// <summary>A callback to invoke when the load stage changes.</summary>
|
||||
private static Action<LoadStage> OnStageChanged;
|
||||
|
||||
/// <summary>Whether <see cref="Game1.loadForNewGame"/> was called as part of save creation.</summary>
|
||||
private static bool IsCreating;
|
||||
|
||||
/// <summary>The number of times that <see cref="Game1.locations"/> has been cleared since <see cref="Game1.loadForNewGame"/> started.</summary>
|
||||
private static int TimesLocationsCleared = 0;
|
||||
|
||||
|
||||
/*********
|
||||
** Accessors
|
||||
*********/
|
||||
/// <summary>A unique name for this patch.</summary>
|
||||
public string Name => $"{nameof(LoadForNewGamePatch)}";
|
||||
|
||||
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Construct an instance.</summary>
|
||||
/// <param name="reflection">Simplifies access to private code.</param>
|
||||
/// <param name="onStageChanged">A callback to invoke when the load stage changes.</param>
|
||||
public LoadForNewGamePatch(Reflector reflection, Action<LoadStage> onStageChanged)
|
||||
{
|
||||
LoadForNewGamePatch.Reflection = reflection;
|
||||
LoadForNewGamePatch.OnStageChanged = onStageChanged;
|
||||
}
|
||||
|
||||
/// <summary>Apply the Harmony patch.</summary>
|
||||
/// <param name="harmony">The Harmony instance.</param>
|
||||
public void Apply(HarmonyInstance harmony)
|
||||
{
|
||||
MethodInfo method = AccessTools.Method(typeof(Game1), nameof(Game1.loadForNewGame));
|
||||
MethodInfo prefix = AccessTools.Method(this.GetType(), nameof(LoadForNewGamePatch.Prefix));
|
||||
MethodInfo postfix = AccessTools.Method(this.GetType(), nameof(LoadForNewGamePatch.Postfix));
|
||||
|
||||
if (!SCore.Instance.HarmonyDetourBridgeFailed)
|
||||
{
|
||||
harmony.Patch(method, new HarmonyMethod(prefix), new HarmonyMethod(postfix));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*********
|
||||
** Private methods
|
||||
*********/
|
||||
/// <summary>The method to call instead of <see cref="Game1.loadForNewGame"/>.</summary>
|
||||
/// <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>
|
||||
private static bool Prefix()
|
||||
{
|
||||
LoadForNewGamePatch.IsCreating = Game1.activeClickableMenu is TitleMenu menu && LoadForNewGamePatch.Reflection.GetField<bool>(menu, "transitioningCharacterCreationMenu").GetValue();
|
||||
LoadForNewGamePatch.TimesLocationsCleared = 0;
|
||||
if (LoadForNewGamePatch.IsCreating)
|
||||
{
|
||||
// raise CreatedBasicInfo after locations are cleared twice
|
||||
IList<GameLocation> locations = Game1.locations;
|
||||
//locations.CollectionChanged += LoadForNewGamePatch.OnLocationListChanged;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>The method to call instead after <see cref="Game1.loadForNewGame"/>.</summary>
|
||||
/// <remarks>This method must be static for Harmony to work correctly. See the Harmony documentation before renaming arguments.</remarks>
|
||||
private static void Postfix()
|
||||
{
|
||||
if (LoadForNewGamePatch.IsCreating)
|
||||
{
|
||||
// clean up
|
||||
IList<GameLocation> locations = Game1.locations;
|
||||
//locations.CollectionChanged -= LoadForNewGamePatch.OnLocationListChanged;
|
||||
|
||||
// raise stage changed
|
||||
LoadForNewGamePatch.OnStageChanged(LoadStage.CreatedLocations);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Raised when <see cref="Game1.locations"/> changes.</summary>
|
||||
/// <param name="sender">The event sender.</param>
|
||||
/// <param name="e">The event arguments.</param>
|
||||
private static void OnLocationListChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
if (++LoadForNewGamePatch.TimesLocationsCleared == 2)
|
||||
LoadForNewGamePatch.OnStageChanged(LoadStage.CreatedBasicInfo);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -25,20 +25,17 @@ namespace StardewModdingAPI.Patches
|
|||
/// <param name="harmony">The Harmony instance.</param>
|
||||
public void Apply(HarmonyInstance harmony)
|
||||
{
|
||||
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)))
|
||||
);
|
||||
// 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)))
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
|
Binary file not shown.
|
@ -1,12 +1,16 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using Android.App;
|
||||
using Android.Content;
|
||||
using Android.Content.PM;
|
||||
using Android.OS;
|
||||
using Android.Provider;
|
||||
using Android.Views;
|
||||
using Google.Android.Vending.Licensing;
|
||||
using ModLoader.Common;
|
||||
using StardewModdingAPI;
|
||||
using StardewModdingAPI.Framework;
|
||||
using StardewValley;
|
||||
|
@ -15,7 +19,8 @@ using Constants = ModLoader.Common.Constants;
|
|||
namespace ModLoader
|
||||
{
|
||||
[Activity(Label = "Stardew Valley", Icon = "@drawable/icon", Theme = "@style/Theme.Splash", MainLauncher = false, AlwaysRetainTaskState = true, LaunchMode = LaunchMode.SingleInstance, ScreenOrientation = ScreenOrientation.SensorLandscape, ConfigurationChanges = (ConfigChanges.Keyboard | ConfigChanges.KeyboardHidden | ConfigChanges.Orientation | ConfigChanges.ScreenLayout | ConfigChanges.ScreenSize | ConfigChanges.UiMode))]
|
||||
public class SMainActivity : MainActivity
|
||||
[SuppressMessage("ReSharper", "ArrangeThisQualifier")]
|
||||
public class SMainActivity : MainActivity, ILicenseCheckerCallback
|
||||
{
|
||||
|
||||
protected override void OnCreate(Bundle bundle)
|
||||
|
@ -42,6 +47,10 @@ namespace ModLoader
|
|||
this.SetPaddingForMenus();
|
||||
new GameConsole();
|
||||
SCore core = new SCore(Constants.ModPath, false);
|
||||
if (Build.VERSION.SdkInt <= BuildVersionCodes.LollipopMr1)
|
||||
{
|
||||
core.HarmonyDetourBridgeFailed = true;
|
||||
}
|
||||
core.RunInteractively();
|
||||
Game1 game1 = StardewValley.Program.gamePtr;
|
||||
typeof(MainActivity).GetField("_game1", BindingFlags.Instance | BindingFlags.NonPublic)?.SetValue(this, game1);
|
||||
|
@ -58,5 +67,30 @@ namespace ModLoader
|
|||
typeof(MainActivity).GetField("_licenseChecker", BindingFlags.Instance | BindingFlags.NonPublic)?.SetValue(this, licenseChecker);
|
||||
licenseChecker.CheckAccess(this);
|
||||
}
|
||||
public new void Allow(PolicyResponse response)
|
||||
{
|
||||
typeof(MainActivity).GetMethod("CheckToDownloadExpansion", BindingFlags.Instance | BindingFlags.NonPublic)?.Invoke(this, null);
|
||||
}
|
||||
|
||||
public new void DontAllow(PolicyResponse response)
|
||||
{
|
||||
if (response == PolicyResponse.Retry)
|
||||
this.WaitThenCheckForValidLicense();
|
||||
else if (response == PolicyResponse.Licensed)
|
||||
{
|
||||
typeof(MainActivity).GetMethod("CheckToDownloadExpansion", BindingFlags.Instance | BindingFlags.NonPublic)?.Invoke(this, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
Utils.OpenInPlayStore();
|
||||
this.Finish();
|
||||
}
|
||||
}
|
||||
|
||||
private async void WaitThenCheckForValidLicense()
|
||||
{
|
||||
await Task.Delay(TimeSpan.FromSeconds(30.0));
|
||||
this.CheckUsingServerManagedPolicy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue