Modloader 1.4.2

1.CustomAttribute rewrite support
2.Update MonoMod to latest code
3.UI change
This commit is contained in:
yangzhi 2019-05-27 14:18:32 +08:00
parent 0822c41fc4
commit e511ad28a8
47 changed files with 1520 additions and 812 deletions

View File

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net.Http; using System.Net.Http;
@ -9,10 +10,12 @@ using Android.Content;
using Android.Content.PM; using Android.Content.PM;
using Android.OS; using Android.OS;
using Android.Support.V4.App; using Android.Support.V4.App;
using Android.Support.V4.Content;
using Android.Views; using Android.Views;
using Android.Widget; using Android.Widget;
using Com.Pgyersdk.Update; using Com.Pgyersdk.Update;
using DllRewrite; using DllRewrite;
using Java.Lang;
using Microsoft.AppCenter; using Microsoft.AppCenter;
using Microsoft.AppCenter.Analytics; using Microsoft.AppCenter.Analytics;
using Microsoft.AppCenter.Crashes; using Microsoft.AppCenter.Crashes;
@ -25,7 +28,11 @@ using StardewModdingAPI.Framework.ModLoading;
using StardewModdingAPI.Toolkit; using StardewModdingAPI.Toolkit;
using StardewModdingAPI.Toolkit.Framework.ModData; using StardewModdingAPI.Toolkit.Framework.ModData;
using StardewModdingAPI.Toolkit.Serialisation; using StardewModdingAPI.Toolkit.Serialisation;
using static ModLoader.Common.Utils;
using Exception = System.Exception;
using File = Java.IO.File; using File = Java.IO.File;
using Object = System.Object;
using Thread = System.Threading.Thread;
using Uri = Android.Net.Uri; using Uri = Android.Net.Uri;
namespace ModLoader namespace ModLoader
@ -36,40 +43,94 @@ namespace ModLoader
, Theme = "@style/Theme.Splash" , Theme = "@style/Theme.Splash"
, AlwaysRetainTaskState = true , AlwaysRetainTaskState = true
, LaunchMode = LaunchMode.SingleInstance , LaunchMode = LaunchMode.SingleInstance
, ScreenOrientation = ScreenOrientation.SensorLandscape
, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.Keyboard | ConfigChanges.KeyboardHidden | ConfigChanges.ScreenSize | ConfigChanges.ScreenLayout)] , ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.Keyboard | ConfigChanges.KeyboardHidden | ConfigChanges.ScreenSize | ConfigChanges.ScreenLayout)]
[SuppressMessage("ReSharper", "ArrangeThisQualifier")]
public class Activity1 : AndroidGameActivity 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[] 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 get
{ {
List<string> list = new List<string>(); List<string> list = new List<string>();
string[] requiredPermissions = this.requiredPermissions; foreach (string permission in this.requiredPermissions)
for (int i = 0; i < requiredPermissions.Length; i++)
{ {
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(); 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() 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); private readonly Mutex _working = new Mutex(false);
public static Activity1 Instance { get; private set; } 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 => private readonly Handler _handler = new Handler(message =>
{ {
if (MessageHandler.ContainsKey(message.What)) if (MessageHandler.ContainsKey(message.What))
@ -102,12 +163,19 @@ namespace ModLoader
this.SetContentView(Resource.Layout.layout_main); this.SetContentView(Resource.Layout.layout_main);
if (!this.HasPermissions) if (!this.HasPermissions)
this.PromptForPermissions(); this.PromptForPermissions();
while (!this.HasPermissions) else
{ this.OnCreatePartTwo();
Thread.Sleep(50); }
}
new PgyUpdateManager.Builder().SetForced(false).SetUserCanRetry(true).SetDeleteHistroyApk(true).Register();
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) => this.FindViewById<Button>(Resource.Id.buttonExtract).Click += (sender, args) =>
{ {
new Thread(() => new Thread(() =>
@ -120,19 +188,30 @@ namespace ModLoader
.FirstOrDefault(package => package.PackageName == Constants.GamePackageName); .FirstOrDefault(package => package.PackageName == Constants.GamePackageName);
if (packageInfo == null) 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; return;
} }
AlertDialog dialog = null; 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) while (dialog == null)
{ {
Thread.Sleep(50); Thread.Sleep(50);
} }
string sourceDir = packageInfo.ApplicationInfo.SourceDir; 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(); dialog.Dismiss();
Utils.MakeToast(this, this.Resources.GetText(Resource.String.ExtractedMessage), MakeToast(this, this.Resources.GetText(Resource.String.ExtractedMessage),
ToastLength.Long); ToastLength.Long);
} }
@ -152,22 +231,28 @@ namespace ModLoader
{ {
if (!new File(Constants.AssemblyPath).Exists()) 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; return;
} }
AlertDialog dialog = null; 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) while (dialog == null)
{ {
Thread.Sleep(50); Thread.Sleep(50);
} }
MethodPatcher mp = new MethodPatcher(); MethodPatcher mp = new MethodPatcher();
AssemblyDefinition StardewValley = mp.InsertModHooks(); AssemblyDefinition stardewValley = mp.InsertModHooks();
StardewValley.Write(Path.Combine(Constants.GamePath, "StardewValley.dll")); FileStream stream = new FileStream(Path.Combine(Constants.GamePath, "StardewValley.dll"), FileMode.Create,
AssemblyDefinition MonoFramework = mp.InsertMonoHooks(); FileAccess.Write, FileShare.Read);
MonoFramework.Write(Path.Combine(Constants.GamePath, "MonoGame.Framework.dll")); 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(); dialog.Dismiss();
Utils.MakeToast(this, this.Resources.GetText(Resource.String.GeneratedMessage), MakeToast(this, this.Resources.GetText(Resource.String.GeneratedMessage),
ToastLength.Long); ToastLength.Long);
} }
finally finally
@ -181,19 +266,30 @@ namespace ModLoader
if (!this._working.WaitOne(10)) if (!this._working.WaitOne(10))
return; return;
this._working.ReleaseMutex(); 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; return;
} }
if (!new File(Path.Combine(Constants.GamePath, "StardewValley.dll")).Exists() || if (!new File(Path.Combine(Constants.GamePath, "StardewValley.dll")).Exists() ||
!new File(Path.Combine(Constants.GamePath, "MonoGame.Framework.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; return;
} }
this.StartActivity(typeof(SMainActivity)); if (!new File(Path.Combine(Constants.GameInternalPath, "StardewModdingAPI.config.json")).Exists() ||
this.Finish(); !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) => this.FindViewById<Button>(Resource.Id.buttonWiki).Click += (sender, args) =>
@ -202,76 +298,65 @@ namespace ModLoader
Intent intent = new Intent(Intent.ActionView, uri); Intent intent = new Intent(Intent.ActionView, uri);
this.StartActivity(intent); 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 () => new Thread(async () =>
{ {
@ -280,7 +365,7 @@ namespace ModLoader
try try
{ {
AlertDialog dialog = null; AlertDialog dialog = null;
Utils.ShowProgressDialog(this, Resource.String.ModInstall, ShowProgressDialog(this, Resource.String.ModInstall,
this.Resources.GetText(Resource.String.ModDownloadingMessage), dlg => { dialog = dlg; }); this.Resources.GetText(Resource.String.ModDownloadingMessage), dlg => { dialog = dlg; });
while (dialog == null) while (dialog == null)
{ {
@ -293,19 +378,19 @@ namespace ModLoader
if (bytes[0] == 80 && bytes[1] == 75) if (bytes[0] == 80 && bytes[1] == 75)
{ {
ZipHelper.UnZip(new MemoryStream(bytes), Constants.ModPath + Path.DirectorySeparatorChar); 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); ToastLength.Long);
this.InvokeActivityThread(0, this.PrepareModList); this.InvokeActivityThread(0, this.PrepareModList);
} }
else else
{ {
Utils.MakeToast(this, this.Resources.GetText(Resource.String.NetworkErrorMessage), MakeToast(this, this.Resources.GetText(Resource.String.NetworkErrorMessage),
ToastLength.Long); ToastLength.Long);
} }
} }
catch (Exception) catch (Exception)
{ {
Utils.MakeToast(this, this.Resources.GetText(Resource.String.NetworkErrorMessage), MakeToast(this, this.Resources.GetText(Resource.String.NetworkErrorMessage),
ToastLength.Long); ToastLength.Long);
} }
finally finally
@ -319,19 +404,32 @@ namespace ModLoader
} }
}).Start(); }).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) if (mod.Metadata?.DirectoryPath != null)
{ {
File file = new File(mod.Metadata.DirectoryPath); File file = new File(mod.Metadata.DirectoryPath);
if (file.Exists() && file.IsDirectory) 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); 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); ToastLength.Long);
this.InvokeActivityThread(0, this.PrepareModList); 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() protected override void OnDestroy()
{ {
base.OnDestroy(); base.OnDestroy();
PgyUpdateManager.UnRegister(); 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";
}
} }
} }

View File

@ -152,9 +152,22 @@ namespace DllRewrite
var isRainingField = this.GetFieldReference("isRaining", "StardewValley.RainManager", this.StardewValley); var isRainingField = this.GetFieldReference("isRaining", "StardewValley.RainManager", this.StardewValley);
processor.Emit(OpCodes.Ldfld, isRainingField); processor.Emit(OpCodes.Ldfld, isRainingField);
processor.Emit(OpCodes.Ret); 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.GetMethod);
typeGame1.Methods.Add(propertyDefinition.SetMethod);
typeGame1.Properties.Add(propertyDefinition); typeGame1.Properties.Add(propertyDefinition);
propertyDefinition = new PropertyDefinition("isDebrisWeather", PropertyAttributes.None, propertyDefinition = new PropertyDefinition("isDebrisWeather", PropertyAttributes.None,
this.GetTypeReference("System.Boolean")); this.GetTypeReference("System.Boolean"));
propertyDefinition.GetMethod = new MethodDefinition("get_isDebrisWeather", propertyDefinition.GetMethod = new MethodDefinition("get_isDebrisWeather",
@ -169,9 +182,23 @@ namespace DllRewrite
this.StardewValley); this.StardewValley);
processor.Emit(OpCodes.Ldfld, isDebrisWeatherField); processor.Emit(OpCodes.Ldfld, isDebrisWeatherField);
processor.Emit(OpCodes.Ret); 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.GetMethod);
typeGame1.Methods.Add(propertyDefinition.SetMethod);
typeGame1.Properties.Add(propertyDefinition); typeGame1.Properties.Add(propertyDefinition);
//HUDMessage..ctor //HUDMessage..ctor
var typeHUDMessage = this.StardewValley.MainModule.GetType("StardewValley.HUDMessage"); var typeHUDMessage = this.StardewValley.MainModule.GetType("StardewValley.HUDMessage");
var hudConstructor = new MethodDefinition(".ctor", var hudConstructor = new MethodDefinition(".ctor",

View File

@ -14,8 +14,11 @@ namespace ModLoader.Common
internal ModInfo(IModMetadata metadata) internal ModInfo(IModMetadata metadata)
{ {
this.Metadata = metadata; this.Metadata = metadata;
if(metadata != null) if (metadata != null)
{
this.Name = metadata?.DisplayName; this.Name = metadata?.DisplayName;
this.Description = metadata?.Manifest?.Description;
}
} }
public string UniqueID { get; set; } public string UniqueID { get; set; }

View File

@ -26,6 +26,7 @@ namespace ModLoader.Common
TextView headText = view.FindViewById<TextView>(Resource.Id.textModName); TextView headText = view.FindViewById<TextView>(Resource.Id.textModName);
TextView descriptionText = view.FindViewById<TextView>(Resource.Id.textDescription); TextView descriptionText = view.FindViewById<TextView>(Resource.Id.textDescription);
Button buttonAddOrRemove = view.FindViewById<Button>(Resource.Id.buttonAddOrRemove); Button buttonAddOrRemove = view.FindViewById<Button>(Resource.Id.buttonAddOrRemove);
Button buttonConfig = view.FindViewById<Button>(Resource.Id.buttonConfig);
headText.Text = mod.Name; headText.Text = mod.Name;
descriptionText.Text = mod.Description; descriptionText.Text = mod.Description;
if (mod.Metadata == null) if (mod.Metadata == null)
@ -33,16 +34,46 @@ namespace ModLoader.Common
buttonAddOrRemove.Text = this.Context.Resources.GetText(Resource.String.ModInstall); buttonAddOrRemove.Text = this.Context.Resources.GetText(Resource.String.ModInstall);
headText.SetTextColor(Color.Gray); 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 else
{ {
buttonAddOrRemove.Text = this.Context.Resources.GetText(Resource.String.ModRemove); 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) => buttonAddOrRemove.Click += (sender, args) =>
{ {
if (mod.Metadata == null) if (mod.Metadata == null)
{ {
Activity1.Instance.InstallMod(mod); 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 else
{ {
Activity1.Instance.RemoveMod(mod); Activity1.Instance.RemoveMod(mod);

View File

@ -42,6 +42,32 @@ namespace ModLoader.Common
fs.Close(); 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) public static void InvokeLooperThread(Action action)
{ {
new Thread(() => new Thread(() =>
@ -83,5 +109,42 @@ namespace ModLoader.Common
.Show(); .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);
}
}
} }
} }

View File

@ -1,15 +1,5 @@
using System; using System;
using System.Collections.Generic;
using System.IO; 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; using Java.Util.Zip;
namespace ModLoader.Helper namespace ModLoader.Helper
@ -43,24 +33,34 @@ namespace ModLoader.Helper
} }
if (fileName != string.Empty) if (fileName != string.Empty)
{ {
using (FileStream streamWriter = File.Create(unZipDir + theEntry.Name)) FileStream streamWriter = null;
try
{ {
int size; using (streamWriter = File.Create(unZipDir + theEntry.Name))
byte[] data = new byte[2048];
while (true)
{ {
size = s.Read(data, 0, data.Length);
if (size > 0) int size;
byte[] data = new byte[2048];
while (true)
{ {
streamWriter.Write(data, 0, size); size = s.Read(data, 0, data.Length);
} if (size > 0)
else {
{ streamWriter.Write(data, 0, size);
break; }
else
{
break;
}
} }
} }
} }
catch (IOException) { }
finally
{
streamWriter?.Close();
}
} }
} }
} }

View File

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Reflection.Emit; using System.Reflection.Emit;
using StardewModdingAPI.Framework;
namespace Harmony namespace Harmony
{ {
@ -60,7 +61,9 @@ namespace Harmony
public List<DynamicMethod> Patch() public List<DynamicMethod> Patch()
{ {
lock (locker) if(SCore.Instance.HarmonyDetourBridgeFailed)
return new List<DynamicMethod>();
lock (locker)
{ {
var dynamicMethods = new List<DynamicMethod>(); var dynamicMethods = new List<DynamicMethod>();
foreach (var original in originals) foreach (var original in originals)
@ -302,4 +305,4 @@ namespace Harmony
} }
} }
} }
} }

View File

@ -69,7 +69,7 @@
<AndroidEnableMultiDex>false</AndroidEnableMultiDex> <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> <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> <LangVersion>7.3</LangVersion>
<AndroidEnableSGenConcurrent>true</AndroidEnableSGenConcurrent> <AndroidEnableSGenConcurrent>false</AndroidEnableSGenConcurrent>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="BmFont"> <Reference Include="BmFont">
@ -870,6 +870,7 @@
<Compile Include="SMAPI\Framework\ModLoading\Rewriters\FieldToPropertyRewriter.cs" /> <Compile Include="SMAPI\Framework\ModLoading\Rewriters\FieldToPropertyRewriter.cs" />
<Compile Include="SMAPI\Framework\ModLoading\Rewriters\MethodParentRewriter.cs" /> <Compile Include="SMAPI\Framework\ModLoading\Rewriters\MethodParentRewriter.cs" />
<Compile Include="SMAPI\Framework\ModLoading\Rewriters\StaticFieldToConstantRewriter.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\Rewriters\TypeReferenceRewriter.cs" />
<Compile Include="SMAPI\Framework\ModLoading\TypeReferenceComparer.cs" /> <Compile Include="SMAPI\Framework\ModLoading\TypeReferenceComparer.cs" />
<Compile Include="SMAPI\Framework\ModRegistry.cs" /> <Compile Include="SMAPI\Framework\ModRegistry.cs" />
@ -962,7 +963,8 @@
<Compile Include="SMAPI\Metadata\InstructionMetadata.cs" /> <Compile Include="SMAPI\Metadata\InstructionMetadata.cs" />
<Compile Include="SMAPI\Mod.cs" /> <Compile Include="SMAPI\Mod.cs" />
<Compile Include="SMAPI\Patches\DialogueErrorPatch.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\Patches\ObjectErrorPatch.cs" />
<Compile Include="SMAPI\PatchMode.cs" /> <Compile Include="SMAPI\PatchMode.cs" />
<Compile Include="SMAPI\Program.cs" /> <Compile Include="SMAPI\Program.cs" />
@ -1049,16 +1051,22 @@
<SubType>Designer</SubType> <SubType>Designer</SubType>
</AndroidResource> </AndroidResource>
</ItemGroup> </ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Xml\file_paths.xml" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\JarBinding\JarBinding.csproj"> <ProjectReference Include="..\JarBinding\JarBinding.csproj">
<Project>{a8fbe3c3-bde1-42c5-a4b9-f34ce5a68f46}</Project> <Project>{a8fbe3c3-bde1-42c5-a4b9-f34ce5a68f46}</Project>
<Name>JarBinding</Name> <Name>JarBinding</Name>
</ProjectReference> </ProjectReference>
</ItemGroup> </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)\Novell\Novell.MonoDroid.CSharp.targets" />
<Import Project="$(MSBuildExtensionsPath)\MonoGame\v3.0\MonoGame.Content.Builder.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. <!-- To modify your build process, add your task inside one of the targets below and uncomment it.

View File

@ -129,6 +129,7 @@ namespace MonoMod.RuntimeDetour {
DetourHelper.Native.MakeWritable(link); DetourHelper.Native.MakeWritable(link);
DetourHelper.Native.Apply(link); DetourHelper.Native.Apply(link);
DetourHelper.Native.MakeExecutable(link); DetourHelper.Native.MakeExecutable(link);
DetourHelper.Native.FlushICache(link);
DetourHelper.Native.Free(link); DetourHelper.Native.Free(link);
detours.Add(this); detours.Add(this);
@ -289,6 +290,7 @@ namespace MonoMod.RuntimeDetour {
DetourHelper.Native.MakeWritable(link); DetourHelper.Native.MakeWritable(link);
DetourHelper.Native.Apply(link); DetourHelper.Native.Apply(link);
DetourHelper.Native.MakeExecutable(link); DetourHelper.Native.MakeExecutable(link);
DetourHelper.Native.FlushICache(link);
DetourHelper.Native.Free(link); DetourHelper.Native.Free(link);
} }
@ -299,6 +301,7 @@ namespace MonoMod.RuntimeDetour {
DetourHelper.Native.MakeWritable(link); DetourHelper.Native.MakeWritable(link);
DetourHelper.Native.Apply(link); DetourHelper.Native.Apply(link);
DetourHelper.Native.MakeExecutable(link); DetourHelper.Native.MakeExecutable(link);
DetourHelper.Native.FlushICache(link);
DetourHelper.Native.Free(link); DetourHelper.Native.Free(link);
} }
} }

View File

@ -43,29 +43,27 @@ namespace MonoMod.RuntimeDetour {
_Native = new DetourNativeX86Platform(); _Native = new DetourNativeX86Platform();
} }
if (PlatformHelper.Is(Platform.Windows)) {
return _Native = new DetourNativeWindowsPlatform(_Native);
}
if (Type.GetType("Mono.Runtime") != null) { if (Type.GetType("Mono.Runtime") != null) {
try { try {
// It's prefixed with lib on every platform. // It's prefixed with lib on every platform.
_Native = new DetourNativeMonoPlatform(_Native, $"libmonosgen-2.0.{PlatformHelper.LibrarySuffix}"); return _Native = new DetourNativeMonoPlatform(_Native, $"libmonosgen-2.0.{PlatformHelper.LibrarySuffix}");
return _Native;
} catch { } catch {
// Fall back to another native platform wrapper. // Fall back to another native platform wrapper.
} }
}
if (PlatformHelper.Is(Platform.Windows)) { // MonoPosixHelper is available outside of Unix as well.
_Native = new DetourNativeWindowsPlatform(_Native); try {
} else if (PlatformHelper.Is(Platform.Unix)) { _Native = new DetourNativeMonoPosixPlatform(_Native);
if (Type.GetType("Mono.Runtime") != null) { } catch {
try { // Good job, your copy of Mono doesn't ship with MonoPosixHelper.
_Native = new DetourNativeMonoPosixPlatform(_Native); // https://www.youtube.com/watch?v=l60MnDJklnM
} 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.
} }
} else {
// TODO: .NET Core Posix native platform wrapper.
} }
return _Native; return _Native;
@ -74,6 +72,14 @@ namespace MonoMod.RuntimeDetour {
set => _Native = value; 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 #region Native helpers
/// <summary> /// <summary>
@ -160,6 +166,7 @@ namespace MonoMod.RuntimeDetour {
Native.MakeWritable(detour); Native.MakeWritable(detour);
Native.Apply(detour); Native.Apply(detour);
Native.MakeExecutable(detour); Native.MakeExecutable(detour);
Native.FlushICache(detour);
Native.Free(detour); Native.Free(detour);
return dm; return dm;

View File

@ -1,9 +1,11 @@
using Mono.Cecil.Cil; using Mono.Cecil;
using Mono.Cecil.Cil;
using MonoMod.Cil; using MonoMod.Cil;
using MonoMod.Utils; using MonoMod.Utils;
using MonoMod.Utils.Cil; using MonoMod.Utils.Cil;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
@ -20,13 +22,13 @@ namespace MonoMod.RuntimeDetour {
public static bool Initialized { get; private set; } public static bool Initialized { get; private set; }
private static Type CurrentType; private static Type CurrentType;
private static Assembly _ASM; private static Assembly _HarmonyASM;
private static readonly List<IDisposable> _Detours = new List<IDisposable>(); private static readonly List<IDisposable> _Detours = new List<IDisposable>();
private static readonly Dictionary<System.Type, MethodInfo> _Emitters = new Dictionary<System.Type, MethodInfo>(); private static readonly Dictionary<System.Type, MethodInfo> _Emitters = new Dictionary<System.Type, MethodInfo>();
[ThreadStatic] [ThreadStatic] private static DynamicMethodDefinition _LastWrapperDMD;
private static DynamicMethodDefinition _LastWrapperDMD; private static Assembly _SharedStateASM;
static HarmonyDetourBridge() { static HarmonyDetourBridge() {
System.Type t_OpCode = typeof(System.Reflection.Emit.OpCode); 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) { public static bool Init(bool forceLoad = true, Type type = Type.Auto) {
if (_ASM == null) if (_HarmonyASM == null)
_ASM = _FindHarmony(); _HarmonyASM = _FindHarmony();
if (_ASM == null && forceLoad) if (_HarmonyASM == null && forceLoad)
_ASM = Assembly.Load(new AssemblyName() { _HarmonyASM = Assembly.Load(new AssemblyName() {
Name = "0Harmony" Name = "0Harmony"
}); });
if (_ASM == null) if (_HarmonyASM == null)
return false; return false;
if (Initialized) if (Initialized)
@ -71,17 +73,24 @@ namespace MonoMod.RuntimeDetour {
try { try {
foreach (MethodInfo methodRD in typeof(HarmonyDetourBridge).GetMethods(BindingFlags.NonPublic | BindingFlags.Static)) { 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 (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)); _Detours.Add(new Hook(methodH, methodRD));
}
foreach (DetourToHAttribute info in methodRD.GetCustomAttributes(typeof(DetourToHAttribute), false)) 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)); _Detours.Add(new Detour(methodRD, methodH));
}
foreach (TranspileAttribute info in methodRD.GetCustomAttributes(typeof(TranspileAttribute), false)) { foreach (TranspileAttribute info in methodRD.GetCustomAttributes(typeof(TranspileAttribute), false)) {
foreach (MethodInfo methodH in GetHarmonyMethod(methodRD, info.Type, -1, info.Name)) { foreach (MethodInfo methodH in GetHarmonyMethod(methodRD, info.Type, -1, info.Name)) {
using (DynamicMethodDefinition dmd = new DynamicMethodDefinition(methodH)) { using (DynamicMethodDefinition dmd = new DynamicMethodDefinition(methodH)) {
critical = false;
ILContext il = new ILContext(dmd.Definition) { ILContext il = new ILContext(dmd.Definition) {
ReferenceBag = RuntimeILReferenceBag.Instance 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()) { } catch (Exception) when (_EarlyReset()) {
throw; throw;
@ -119,11 +131,16 @@ namespace MonoMod.RuntimeDetour {
_EarlyReset(); _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) { private static IEnumerable<MethodInfo> GetHarmonyMethod(MethodInfo ctx, string typeName, int skipParams, string name) {
System.Type type = System.Type type = GetHarmonyType(typeName);
_ASM.GetType("HarmonyLib." + typeName) ??
_ASM.GetType("Harmony." + typeName) ??
_ASM.GetType("Harmony.ILCopying." + typeName);
if (type == null) if (type == null)
return null; return null;
@ -191,13 +208,13 @@ namespace MonoMod.RuntimeDetour {
} }
} }
[DetourToRD("Memory")] [DetourToRD("Memory"), Critical]
private static string WriteJump(long memory, long destination) { private static string WriteJump(long memory, long destination) {
_Detours.Add(new NativeDetour((IntPtr) memory, (IntPtr) destination)); _Detours.Add(new NativeDetour((IntPtr) memory, (IntPtr) destination));
return null; return null;
} }
[DetourToRD("Memory")] [DetourToRD("Memory"), Critical]
private static string DetourMethod(MethodBase original, MethodBase replacement) { private static string DetourMethod(MethodBase original, MethodBase replacement) {
if (replacement == null) { if (replacement == null) {
replacement = _LastWrapperDMD.Generate(); replacement = _LastWrapperDMD.Generate();
@ -222,7 +239,7 @@ namespace MonoMod.RuntimeDetour {
return null; 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) { private static List<System.Reflection.Emit.DynamicMethod> Patch(Func<object, List<System.Reflection.Emit.DynamicMethod>> orig, object self) {
orig(self); orig(self);
@ -236,7 +253,7 @@ namespace MonoMod.RuntimeDetour {
// TODO: Does NativeThisPointer.NeedsNativeThisPointerFix need to be patched? // TODO: Does NativeThisPointer.NeedsNativeThisPointerFix need to be patched?
[Transpile("PatchFunctions")] [Transpile("PatchFunctions"), Critical]
private static void UpdateWrapper(ILContext il) { private static void UpdateWrapper(ILContext il) {
ILCursor c = new ILCursor(il); ILCursor c = new ILCursor(il);
@ -246,14 +263,16 @@ namespace MonoMod.RuntimeDetour {
c.Next.OpCode = OpCodes.Pop; c.Next.OpCode = OpCodes.Pop;
} }
[Transpile("MethodPatcher")] [Transpile("MethodPatcher"), Critical]
private static void CreatePatchedMethod(ILContext il) { private static void CreatePatchedMethod(ILContext il) {
ILCursor c = new ILCursor(il); ILCursor c = new ILCursor(il);
// The original method uses System.Reflection.Emit. // The original method uses System.Reflection.Emit.
System.Type t_DynamicTools = GetHarmonyType("DynamicTools");
// Find and replace DynamicTools.CreateDynamicMethod // 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. // Not the right method. Harmony defines two CreatePatchedMethod methods.
il.MakeReadOnly(); il.MakeReadOnly();
return; return;
@ -273,7 +292,7 @@ namespace MonoMod.RuntimeDetour {
c.Next.Operand = il.Import(typeof(DynamicMethodDefinition).GetMethod("GetILGenerator", BindingFlags.Public | BindingFlags.Instance)); c.Next.Operand = il.Import(typeof(DynamicMethodDefinition).GetMethod("GetILGenerator", BindingFlags.Public | BindingFlags.Instance));
// Find and remove DynamicTools.PrepareDynamicMethod // 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.OpCode = OpCodes.Pop;
c.Next.Operand = null; 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 { private class DetourToRDAttribute : Attribute {
public string Type { get; } public string Type { get; }
public int SkipParams { get; } public int SkipParams { get; }
@ -319,6 +379,9 @@ namespace MonoMod.RuntimeDetour {
} }
} }
private class CriticalAttribute : Attribute {
}
private static Assembly _FindHarmony() { private static Assembly _FindHarmony() {
#if !NETSTANDARD1_X #if !NETSTANDARD1_X
foreach (Assembly asm in AppDomain.CurrentDomain.GetAssemblies()) { foreach (Assembly asm in AppDomain.CurrentDomain.GetAssemblies()) {

View File

@ -8,8 +8,9 @@ namespace MonoMod.RuntimeDetour {
void Free(NativeDetourData detour); void Free(NativeDetourData detour);
void Apply(NativeDetourData detour); void Apply(NativeDetourData detour);
void Copy(IntPtr src, IntPtr dst, byte type); void Copy(IntPtr src, IntPtr dst, byte type);
void MakeWritable(NativeDetourData detour); void MakeWritable(IntPtr src, uint size);
void MakeExecutable(NativeDetourData detour); void MakeExecutable(IntPtr src, uint size);
void FlushICache(IntPtr src, uint size);
IntPtr MemAlloc(uint size); IntPtr MemAlloc(uint size);
void MemFree(IntPtr ptr); void MemFree(IntPtr ptr);
} }

View File

@ -108,6 +108,7 @@ namespace MonoMod.RuntimeDetour {
DetourHelper.Native.MakeWritable(Data); DetourHelper.Native.MakeWritable(Data);
DetourHelper.Native.Apply(Data); DetourHelper.Native.Apply(Data);
DetourHelper.Native.MakeExecutable(Data); DetourHelper.Native.MakeExecutable(Data);
DetourHelper.Native.FlushICache(Data);
} }
/// <summary> /// <summary>

View File

@ -1,5 +1,6 @@
using MonoMod.Utils; using MonoMod.Utils;
using System; using System;
using System.Linq;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
namespace MonoMod.RuntimeDetour.Platforms { 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. // no-op.
} }
public void MakeExecutable(NativeDetourData detour) { public void MakeExecutable(IntPtr src, uint size) {
// no-op. // 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) { public IntPtr MemAlloc(uint size) {
return Marshal.AllocHGlobal((int) size); return Marshal.AllocHGlobal((int) size);
} }
@ -194,5 +214,60 @@ namespace MonoMod.RuntimeDetour.Platforms {
public void MemFree(IntPtr ptr) { public void MemFree(IntPtr ptr) {
Marshal.FreeHGlobal(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 };
} }
} }

View File

@ -32,16 +32,19 @@ namespace MonoMod.RuntimeDetour.Platforms {
throw new System.ComponentModel.Win32Exception(); 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. // RWX because old versions of mono always use RWX.
SetMemPerms(detour.Method, detour.Size, MmapProts.PROT_READ | MmapProts.PROT_WRITE | MmapProts.PROT_EXEC); SetMemPerms(src, size, MmapProts.PROT_READ | MmapProts.PROT_WRITE | MmapProts.PROT_EXEC);
Inner.MakeWritable(detour);
} }
public void MakeExecutable(NativeDetourData detour) { public void MakeExecutable(IntPtr src, uint size) {
// RWX because old versions of mono always use RWX. // RWX because old versions of mono always use RWX.
SetMemPerms(detour.Method, detour.Size, MmapProts.PROT_READ | MmapProts.PROT_WRITE | MmapProts.PROT_EXEC); SetMemPerms(src, size, MmapProts.PROT_READ | MmapProts.PROT_WRITE | MmapProts.PROT_EXEC);
Inner.MakeExecutable(detour); }
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) { 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 IDE0044 // Add readonly modifier
#pragma warning disable CS0649 // Field is never assigned to, and will always have its default value null #pragma warning disable CS0649 // Field is never assigned to, and will always have its default value null
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate int d_mono_pagesize(); private delegate int d_mono_pagesize();
[DynDllImport("mono")] [DynDllImport("mono")]
@ -79,6 +83,7 @@ namespace MonoMod.RuntimeDetour.Platforms {
private delegate int d_mono_mprotect(IntPtr addr, IntPtr length, int flags); private delegate int d_mono_mprotect(IntPtr addr, IntPtr length, int flags);
[DynDllImport("mono")] [DynDllImport("mono")]
private d_mono_mprotect mono_mprotect; private d_mono_mprotect mono_mprotect;
#pragma warning restore IDE0044 // Add readonly modifier #pragma warning restore IDE0044 // Add readonly modifier
#pragma warning restore CS0649 // Field is never assigned to, and will always have its default value null #pragma warning restore CS0649 // Field is never assigned to, and will always have its default value null

View File

@ -34,16 +34,19 @@ namespace MonoMod.RuntimeDetour.Platforms {
throw new Exception(GetLastError("mprotect")); 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. // RWX because old versions of mono always use RWX.
SetMemPerms(detour.Method, detour.Size, MmapProts.PROT_READ | MmapProts.PROT_WRITE | MmapProts.PROT_EXEC); SetMemPerms(src, size, MmapProts.PROT_READ | MmapProts.PROT_WRITE | MmapProts.PROT_EXEC);
Inner.MakeWritable(detour);
} }
public void MakeExecutable(NativeDetourData detour) { public void MakeExecutable(IntPtr src, uint size) {
// RWX because old versions of mono always use RWX. // RWX because old versions of mono always use RWX.
SetMemPerms(detour.Method, detour.Size, MmapProts.PROT_READ | MmapProts.PROT_WRITE | MmapProts.PROT_EXEC); SetMemPerms(src, size, MmapProts.PROT_READ | MmapProts.PROT_WRITE | MmapProts.PROT_EXEC);
Inner.MakeExecutable(detour); }
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) { public NativeDetourData Create(IntPtr from, IntPtr to, byte? type) {

View File

@ -11,19 +11,20 @@ namespace MonoMod.RuntimeDetour.Platforms {
Inner = inner; Inner = inner;
} }
public void MakeWritable(NativeDetourData detour) { public void MakeWritable(IntPtr src, uint size) {
// PAGE_READWRITE causes an AccessViolationException / TargetInvocationException. // 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(); throw new Win32Exception();
Inner.MakeWritable(detour);
} }
public void MakeExecutable(NativeDetourData detour) { public void MakeExecutable(IntPtr src, uint size) {
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(); 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) { public NativeDetourData Create(IntPtr from, IntPtr to, byte? type) {
@ -53,8 +54,14 @@ namespace MonoMod.RuntimeDetour.Platforms {
[DllImport("kernel32.dll", SetLastError = true)] [DllImport("kernel32.dll", SetLastError = true)]
private static extern bool VirtualProtect(IntPtr lpAddress, IntPtr dwSize, Protection flNewProtect, out Protection lpflOldProtect); 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] [Flags]
private enum Protection { private enum Protection : uint {
PAGE_NOACCESS = 0x01, PAGE_NOACCESS = 0x01,
PAGE_READONLY = 0x02, PAGE_READONLY = 0x02,
PAGE_READWRITE = 0x04, PAGE_READWRITE = 0x04,

View File

@ -1,5 +1,6 @@
using MonoMod.Utils; using MonoMod.Utils;
using System; using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
namespace MonoMod.RuntimeDetour.Platforms { 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. // no-op.
} }
public void MakeExecutable(NativeDetourData detour) { public void MakeExecutable(IntPtr src, uint size) {
// no-op. // 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) { public IntPtr MemAlloc(uint size) {
return Marshal.AllocHGlobal((int) size); return Marshal.AllocHGlobal((int) size);
} }

View File

@ -32,6 +32,7 @@ namespace MonoMod.RuntimeDetour.Platforms {
DetourHelper.Native.MakeWritable(detour); DetourHelper.Native.MakeWritable(detour);
DetourHelper.Native.Apply(detour); DetourHelper.Native.Apply(detour);
DetourHelper.Native.MakeExecutable(detour); DetourHelper.Native.MakeExecutable(detour);
DetourHelper.Native.FlushICache(detour);
DetourHelper.Native.Free(detour); DetourHelper.Native.Free(detour);
// No need to undo the detour. // No need to undo the detour.

View File

@ -429,6 +429,44 @@ namespace MonoMod.Cil {
return false; return false;
} // OperandType.InlineMethod } // 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) public static bool MatchCalli(this Instruction instr, IMethodSignature value)
=> instr.MatchCalli(out var v) && v == value; => instr.MatchCalli(out var v) && v == value;
public static bool MatchCalli(this Instruction instr, out IMethodSignature value) { public static bool MatchCalli(this Instruction instr, out IMethodSignature value) {
@ -1047,25 +1085,6 @@ namespace MonoMod.Cil {
return false; return false;
} // OperandType.InlineNone } // 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) public static bool MatchCpobj(this Instruction instr, string fullName)
=> instr.MatchCpobj(out var v) && v.Is(fullName); => instr.MatchCpobj(out var v) && v.Is(fullName);
public static bool MatchCpobj<T>(this Instruction instr) public static bool MatchCpobj<T>(this Instruction instr)

View File

@ -18,16 +18,6 @@ using System.Diagnostics.SymbolStore;
namespace MonoMod.Utils { namespace MonoMod.Utils {
public sealed partial class DynamicMethodDefinition { 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) { public MethodInfo GenerateViaCecil(TypeDefinition typeDef) {
MethodDefinition def = Definition; MethodDefinition def = Definition;
@ -158,17 +148,7 @@ namespace MonoMod.Utils {
module.Write(fileStream); module.Write(fileStream);
} }
Assembly asm; Assembly asm = ReflectionHelper.Load(module);
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
}
_DynModuleCache[module.Assembly.Name.FullName] = module.Assembly; _DynModuleCache[module.Assembly.Name.FullName] = module.Assembly;
_DynModuleReflCache[asm.GetModules()[0]] = module; _DynModuleReflCache[asm.GetModules()[0]] = module;

View File

@ -8,6 +8,7 @@ using Mono.Cecil;
using Mono.Cecil.Cil; using Mono.Cecil.Cil;
using System.Linq; using System.Linq;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.IO;
#if NETSTANDARD #if NETSTANDARD
using TypeOrTypeInfo = System.Reflection.TypeInfo; 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<string, Assembly> AssemblyCache = new Dictionary<string, Assembly>();
public static readonly Dictionary<MemberReference, MemberInfo> ResolveReflectionCache = new Dictionary<MemberReference, MemberInfo>(); 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) { private static MemberInfo _Cache(MemberReference key, MemberInfo value) {
if (key != null && value != null) { if (key != null && value != null) {
lock (ResolveReflectionCache) { lock (ResolveReflectionCache) {
@ -31,6 +44,51 @@ namespace MonoMod.Utils {
return value; 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) public static Type ResolveReflection(this TypeReference mref)
=> (_ResolveReflection(mref, null) as TypeOrTypeInfo).AsType(); => (_ResolveReflection(mref, null) as TypeOrTypeInfo).AsType();
public static MethodBase ResolveReflection(this MethodReference mref) 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. // ... but all of the methods have the same MetadataToken. We couldn't compare it anyway.
string methodID = method.GetFindableID(withType: false); 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) if (found != null)
return _Cache(mref, found); return _Cache(mref, found);
} }
@ -169,17 +230,17 @@ namespace MonoMod.Utils {
} else if (typeless) { } else if (typeless) {
if (mref is MethodReference) if (mref is MethodReference)
member = modules 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); .FirstOrDefault(m => m != null);
else if (mref is FieldReference) else if (mref is FieldReference)
member = modules 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); .FirstOrDefault(m => m != null);
else else
throw new NotSupportedException($"Unsupported <Module> member type {mref.GetType().FullName}"); throw new NotSupportedException($"Unsupported <Module> member type {mref.GetType().FullName}");
} else { } 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); return _Cache(mref, member);

View File

@ -12,8 +12,8 @@ using SRE = System.Reflection.Emit;
using Mono.Cecil; using Mono.Cecil;
using OpCodes = Mono.Cecil.Cil.OpCodes; using OpCodes = Mono.Cecil.Cil.OpCodes;
using OpCode = Mono.Cecil.Cil.OpCode; using OpCode = Mono.Cecil.Cil.OpCode;
using ExceptionHandler = Mono.Cecil.Cil.ExceptionHandler;
using Mono.Collections.Generic; using Mono.Collections.Generic;
using ExceptionHandler = Mono.Cecil.Cil.ExceptionHandler;
namespace MonoMod.Utils.Cil { namespace MonoMod.Utils.Cil {
public sealed class CecilILGenerator : ILGeneratorShim { public sealed class CecilILGenerator : ILGeneratorShim {

View File

@ -14,16 +14,6 @@ using TypeAttributes = Mono.Cecil.TypeAttributes;
namespace MonoMod.Utils.Cil { namespace MonoMod.Utils.Cil {
public partial class ILGeneratorShim { 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() { public System.Reflection.Emit.ILGenerator GetProxy() {
return (System.Reflection.Emit.ILGenerator) ILGeneratorBuilder return (System.Reflection.Emit.ILGenerator) ILGeneratorBuilder
.GenerateProxy() .GenerateProxy()
@ -55,7 +45,6 @@ namespace MonoMod.Utils.Cil {
FullName, FullName,
new ModuleParameters() { new ModuleParameters() {
Kind = ModuleKind.Dll, Kind = ModuleKind.Dll,
AssemblyResolver = new DefaultAssemblyResolver(),
ReflectionImporterProvider = MMReflectionImporter.Provider ReflectionImporterProvider = MMReflectionImporter.Provider
} }
)) { )) {
@ -122,23 +111,9 @@ namespace MonoMod.Utils.Cil {
il.Emit(OpCodes.Ret); il.Emit(OpCodes.Ret);
} }
using (MemoryStream asmStream = new MemoryStream()) { asm = ReflectionHelper.Load(module);
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
}
} }
#if !NETSTANDARD1_X
AppDomain.CurrentDomain.AssemblyResolve +=
(s, e) => e.Name == asm.FullName ? asm : null;
#endif
return ProxyType = asm.GetType(FullName); return ProxyType = asm.GetType(FullName);
} }

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?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-sdk android:minSdkVersion="19" android:targetSdkVersion="28" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
@ -16,9 +16,6 @@
<uses-permission android:name="android.permission.GET_TASKS" /> <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"> <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" /> <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" /> <provider android:name="com.pgyersdk.PgyerProvider" android:authorities="com.zane.smdroid.com.pgyer.provider" android:exported="false" />
</application> </application>
</manifest> </manifest>

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -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>

View File

@ -2,26 +2,40 @@
<RelativeLayout <RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/ll_view" android:id="@+id/ll_view"
android:background="@android:color/background_light"
android:layout_height="fill_parent" android:layout_height="fill_parent"
android:layout_width="fill_parent" > android:layout_width="fill_parent" >
<Button <Button
android:layout_alignParentRight="true"
android:layout_centerVertical="true" android:layout_centerVertical="true"
android:layout_alignParentRight="true"
android:text="@android:string/cancel" android:text="@android:string/cancel"
android:id="@+id/buttonAddOrRemove" android:id="@+id/buttonAddOrRemove"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_width="80sp" /> 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 <TextView
android:id="@+id/textModName" android:id="@+id/textModName"
android:textColor="@android:color/black" android:textColor="@android:color/black"
android:text="textModName"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_width="fill_parent" android:layout_width="wrap_content"
android:paddingRight="88sp" android:layout_alignParentLeft="true"
android:layout_toLeftOf="@id/buttonConfig"
android:paddingLeft="8sp"
android:textSize="18sp" /> android:textSize="18sp" />
<TextView <TextView
android:id="@+id/textDescription" android:id="@+id/textDescription"
android:text="textDescription"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_width="fill_parent" android:layout_width="wrap_content"
android:paddingRight="88sp" android:layout_alignParentLeft="true"
android:paddingLeft="8sp"
android:layout_toLeftOf="@id/buttonConfig"
android:layout_below="@+id/textModName" /> android:layout_below="@+id/textModName" />
</RelativeLayout> </RelativeLayout>

View File

@ -71,8 +71,8 @@ namespace ModLoader
public partial class Boolean public partial class Boolean
{ {
// aapt resource value: 0x7f0a0000 // aapt resource value: 0x7f090000
public const int abc_action_bar_embed_tabs = 2131361792; public const int abc_action_bar_embed_tabs = 2131296256;
static Boolean() static Boolean()
{ {
@ -87,26 +87,26 @@ namespace ModLoader
public partial class Color public partial class Color
{ {
// aapt resource value: 0x7f070003 // aapt resource value: 0x7f060003
public const int notification_action_color_filter = 2131165187; public const int notification_action_color_filter = 2131099651;
// aapt resource value: 0x7f070004 // aapt resource value: 0x7f060004
public const int notification_icon_bg_color = 2131165188; public const int notification_icon_bg_color = 2131099652;
// aapt resource value: 0x7f070000 // aapt resource value: 0x7f060000
public const int notification_material_background_media_default_color = 2131165184; public const int notification_material_background_media_default_color = 2131099648;
// aapt resource value: 0x7f070001 // aapt resource value: 0x7f060001
public const int primary_text_default_material_dark = 2131165185; public const int primary_text_default_material_dark = 2131099649;
// aapt resource value: 0x7f070005 // aapt resource value: 0x7f060005
public const int ripple_material_light = 2131165189; public const int ripple_material_light = 2131099653;
// aapt resource value: 0x7f070002 // aapt resource value: 0x7f060002
public const int secondary_text_default_material_dark = 2131165186; public const int secondary_text_default_material_dark = 2131099650;
// aapt resource value: 0x7f070006 // aapt resource value: 0x7f060006
public const int secondary_text_default_material_light = 2131165190; public const int secondary_text_default_material_light = 2131099654;
static Color() static Color()
{ {
@ -121,65 +121,65 @@ namespace ModLoader
public partial class Dimension public partial class Dimension
{ {
// aapt resource value: 0x7f0b0004 // aapt resource value: 0x7f0a0004
public const int compat_button_inset_horizontal_material = 2131427332; public const int compat_button_inset_horizontal_material = 2131361796;
// aapt resource value: 0x7f0b0005 // aapt resource value: 0x7f0a0005
public const int compat_button_inset_vertical_material = 2131427333; public const int compat_button_inset_vertical_material = 2131361797;
// aapt resource value: 0x7f0b0006 // aapt resource value: 0x7f0a0006
public const int compat_button_padding_horizontal_material = 2131427334; public const int compat_button_padding_horizontal_material = 2131361798;
// aapt resource value: 0x7f0b0007 // aapt resource value: 0x7f0a0007
public const int compat_button_padding_vertical_material = 2131427335; public const int compat_button_padding_vertical_material = 2131361799;
// aapt resource value: 0x7f0b0008 // aapt resource value: 0x7f0a0008
public const int compat_control_corner_material = 2131427336; public const int compat_control_corner_material = 2131361800;
// aapt resource value: 0x7f0b0009 // aapt resource value: 0x7f0a0009
public const int notification_action_icon_size = 2131427337; public const int notification_action_icon_size = 2131361801;
// aapt resource value: 0x7f0b000a // aapt resource value: 0x7f0a000a
public const int notification_action_text_size = 2131427338; public const int notification_action_text_size = 2131361802;
// aapt resource value: 0x7f0b000b // aapt resource value: 0x7f0a000b
public const int notification_big_circle_margin = 2131427339; public const int notification_big_circle_margin = 2131361803;
// aapt resource value: 0x7f0b0001 // aapt resource value: 0x7f0a0001
public const int notification_content_margin_start = 2131427329; public const int notification_content_margin_start = 2131361793;
// aapt resource value: 0x7f0b000c // aapt resource value: 0x7f0a000c
public const int notification_large_icon_height = 2131427340; public const int notification_large_icon_height = 2131361804;
// aapt resource value: 0x7f0b000d // aapt resource value: 0x7f0a000d
public const int notification_large_icon_width = 2131427341; public const int notification_large_icon_width = 2131361805;
// aapt resource value: 0x7f0b0002 // aapt resource value: 0x7f0a0002
public const int notification_main_column_padding_top = 2131427330; public const int notification_main_column_padding_top = 2131361794;
// aapt resource value: 0x7f0b0003 // aapt resource value: 0x7f0a0003
public const int notification_media_narrow_margin = 2131427331; public const int notification_media_narrow_margin = 2131361795;
// aapt resource value: 0x7f0b000e // aapt resource value: 0x7f0a000e
public const int notification_right_icon_size = 2131427342; public const int notification_right_icon_size = 2131361806;
// aapt resource value: 0x7f0b0000 // aapt resource value: 0x7f0a0000
public const int notification_right_side_padding_top = 2131427328; public const int notification_right_side_padding_top = 2131361792;
// aapt resource value: 0x7f0b000f // aapt resource value: 0x7f0a000f
public const int notification_small_icon_background_padding = 2131427343; public const int notification_small_icon_background_padding = 2131361807;
// aapt resource value: 0x7f0b0010 // aapt resource value: 0x7f0a0010
public const int notification_small_icon_size_as_large = 2131427344; public const int notification_small_icon_size_as_large = 2131361808;
// aapt resource value: 0x7f0b0011 // aapt resource value: 0x7f0a0011
public const int notification_subtext_size = 2131427345; public const int notification_subtext_size = 2131361809;
// aapt resource value: 0x7f0b0012 // aapt resource value: 0x7f0a0012
public const int notification_top_pad = 2131427346; public const int notification_top_pad = 2131361810;
// aapt resource value: 0x7f0b0013 // aapt resource value: 0x7f0a0013
public const int notification_top_pad_large_text = 2131427347; public const int notification_top_pad_large_text = 2131361811;
static Dimension() static Dimension()
{ {
@ -221,11 +221,11 @@ namespace ModLoader
// aapt resource value: 0x7f020008 // aapt resource value: 0x7f020008
public const int notification_icon_background = 2130837512; public const int notification_icon_background = 2130837512;
// aapt resource value: 0x7f02000c // aapt resource value: 0x7f02000e
public const int notification_template_icon_bg = 2130837516; public const int notification_template_icon_bg = 2130837518;
// aapt resource value: 0x7f02000d // aapt resource value: 0x7f02000f
public const int notification_template_icon_low_bg = 2130837517; public const int notification_template_icon_low_bg = 2130837519;
// aapt resource value: 0x7f020009 // aapt resource value: 0x7f020009
public const int notification_tile_bg = 2130837513; public const int notification_tile_bg = 2130837513;
@ -236,6 +236,12 @@ namespace ModLoader
// aapt resource value: 0x7f02000b // aapt resource value: 0x7f02000b
public const int Splash = 2130837515; 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() static Drawable()
{ {
global::Android.Runtime.ResourceIdManager.UpdateIdValues(); global::Android.Runtime.ResourceIdManager.UpdateIdValues();
@ -249,155 +255,158 @@ namespace ModLoader
public partial class Id public partial class Id
{ {
// aapt resource value: 0x7f0c0019 // aapt resource value: 0x7f0b001a
public const int action0 = 2131492889; public const int action0 = 2131427354;
// aapt resource value: 0x7f0c0016 // aapt resource value: 0x7f0b0017
public const int action_container = 2131492886; public const int action_container = 2131427351;
// aapt resource value: 0x7f0c001d // aapt resource value: 0x7f0b001e
public const int action_divider = 2131492893; public const int action_divider = 2131427358;
// aapt resource value: 0x7f0c0017 // aapt resource value: 0x7f0b0018
public const int action_image = 2131492887; public const int action_image = 2131427352;
// aapt resource value: 0x7f0c0018 // aapt resource value: 0x7f0b0019
public const int action_text = 2131492888; public const int action_text = 2131427353;
// aapt resource value: 0x7f0c0027 // aapt resource value: 0x7f0b0028
public const int actions = 2131492903; public const int actions = 2131427368;
// aapt resource value: 0x7f0c002b // aapt resource value: 0x7f0b002c
public const int appIcon = 2131492907; public const int appIcon = 2131427372;
// aapt resource value: 0x7f0c0006 // aapt resource value: 0x7f0b0006
public const int async = 2131492870; public const int async = 2131427334;
// aapt resource value: 0x7f0c0007 // aapt resource value: 0x7f0b0007
public const int blocking = 2131492871; public const int blocking = 2131427335;
// aapt resource value: 0x7f0c0013 // aapt resource value: 0x7f0b0013
public const int buttonAddOrRemove = 2131492883; public const int buttonAddOrRemove = 2131427347;
// aapt resource value: 0x7f0c000d // aapt resource value: 0x7f0b0014
public const int buttonExtract = 2131492877; public const int buttonConfig = 2131427348;
// aapt resource value: 0x7f0c000e // aapt resource value: 0x7f0b000d
public const int buttonGenerate = 2131492878; public const int buttonExtract = 2131427341;
// aapt resource value: 0x7f0c000f // aapt resource value: 0x7f0b000e
public const int buttonLaunch = 2131492879; public const int buttonGenerate = 2131427342;
// aapt resource value: 0x7f0c0010 // aapt resource value: 0x7f0b000f
public const int buttonWiki = 2131492880; public const int buttonLaunch = 2131427343;
// aapt resource value: 0x7f0c001a // aapt resource value: 0x7f0b0010
public const int cancel_action = 2131492890; public const int buttonWiki = 2131427344;
// aapt resource value: 0x7f0c0022 // aapt resource value: 0x7f0b001b
public const int chronometer = 2131492898; public const int cancel_action = 2131427355;
// aapt resource value: 0x7f0c0030 // aapt resource value: 0x7f0b0023
public const int description = 2131492912; public const int chronometer = 2131427363;
// aapt resource value: 0x7f0c0029 // aapt resource value: 0x7f0b0031
public const int end_padder = 2131492905; public const int description = 2131427377;
// aapt resource value: 0x7f0c0008 // aapt resource value: 0x7f0b002a
public const int forever = 2131492872; public const int end_padder = 2131427370;
// aapt resource value: 0x7f0c000b // aapt resource value: 0x7f0b0008
public const int gridLayout1 = 2131492875; public const int forever = 2131427336;
// aapt resource value: 0x7f0c0024 // aapt resource value: 0x7f0b000b
public const int icon = 2131492900; public const int gridLayout1 = 2131427339;
// aapt resource value: 0x7f0c0028 // aapt resource value: 0x7f0b0025
public const int icon_group = 2131492904; public const int icon = 2131427365;
// aapt resource value: 0x7f0c0023 // aapt resource value: 0x7f0b0029
public const int info = 2131492899; public const int icon_group = 2131427369;
// aapt resource value: 0x7f0c0009 // aapt resource value: 0x7f0b0024
public const int italic = 2131492873; public const int info = 2131427364;
// aapt resource value: 0x7f0c0000 // aapt resource value: 0x7f0b0009
public const int line1 = 2131492864; public const int italic = 2131427337;
// aapt resource value: 0x7f0c0001 // aapt resource value: 0x7f0b0000
public const int line3 = 2131492865; public const int line1 = 2131427328;
// aapt resource value: 0x7f0c000c // aapt resource value: 0x7f0b0001
public const int linearLayout1 = 2131492876; public const int line3 = 2131427329;
// aapt resource value: 0x7f0c0011 // aapt resource value: 0x7f0b000c
public const int listView1 = 2131492881; public const int linearLayout1 = 2131427340;
// aapt resource value: 0x7f0c0012 // aapt resource value: 0x7f0b0011
public const int ll_view = 2131492882; public const int listView1 = 2131427345;
// aapt resource value: 0x7f0c001c // aapt resource value: 0x7f0b0012
public const int media_actions = 2131492892; public const int ll_view = 2131427346;
// aapt resource value: 0x7f0c000a // aapt resource value: 0x7f0b001d
public const int normal = 2131492874; public const int media_actions = 2131427357;
// aapt resource value: 0x7f0c002a // aapt resource value: 0x7f0b000a
public const int notificationLayout = 2131492906; public const int normal = 2131427338;
// aapt resource value: 0x7f0c0026 // aapt resource value: 0x7f0b002b
public const int notification_background = 2131492902; public const int notificationLayout = 2131427371;
// aapt resource value: 0x7f0c001f // aapt resource value: 0x7f0b0027
public const int notification_main_column = 2131492895; public const int notification_background = 2131427367;
// aapt resource value: 0x7f0c001e // aapt resource value: 0x7f0b0020
public const int notification_main_column_container = 2131492894; public const int notification_main_column = 2131427360;
// aapt resource value: 0x7f0c002f // aapt resource value: 0x7f0b001f
public const int progress_bar = 2131492911; public const int notification_main_column_container = 2131427359;
// aapt resource value: 0x7f0c002e // aapt resource value: 0x7f0b0030
public const int progress_bar_frame = 2131492910; public const int progress_bar = 2131427376;
// aapt resource value: 0x7f0c002c // aapt resource value: 0x7f0b002f
public const int progress_text = 2131492908; public const int progress_bar_frame = 2131427375;
// aapt resource value: 0x7f0c0025 // aapt resource value: 0x7f0b002d
public const int right_icon = 2131492901; public const int progress_text = 2131427373;
// aapt resource value: 0x7f0c0020 // aapt resource value: 0x7f0b0026
public const int right_side = 2131492896; public const int right_icon = 2131427366;
// aapt resource value: 0x7f0c0031 // aapt resource value: 0x7f0b0021
public const int spacer = 2131492913; public const int right_side = 2131427361;
// aapt resource value: 0x7f0c001b // aapt resource value: 0x7f0b0032
public const int status_bar_latest_event_content = 2131492891; public const int spacer = 2131427378;
// aapt resource value: 0x7f0c0002 // aapt resource value: 0x7f0b001c
public const int tag_transition_group = 2131492866; public const int status_bar_latest_event_content = 2131427356;
// aapt resource value: 0x7f0c0003 // aapt resource value: 0x7f0b0002
public const int text = 2131492867; public const int tag_transition_group = 2131427330;
// aapt resource value: 0x7f0c0004 // aapt resource value: 0x7f0b0003
public const int text2 = 2131492868; public const int text = 2131427331;
// aapt resource value: 0x7f0c0015 // aapt resource value: 0x7f0b0004
public const int textDescription = 2131492885; public const int text2 = 2131427332;
// aapt resource value: 0x7f0c0014 // aapt resource value: 0x7f0b0016
public const int textModName = 2131492884; public const int textDescription = 2131427350;
// aapt resource value: 0x7f0c0021 // aapt resource value: 0x7f0b0015
public const int time = 2131492897; public const int textModName = 2131427349;
// aapt resource value: 0x7f0c002d // aapt resource value: 0x7f0b0022
public const int time_remaining = 2131492909; public const int time = 2131427362;
// aapt resource value: 0x7f0c0005 // aapt resource value: 0x7f0b002e
public const int title = 2131492869; public const int time_remaining = 2131427374;
// aapt resource value: 0x7f0b0005
public const int title = 2131427333;
static Id() static Id()
{ {
@ -412,11 +421,11 @@ namespace ModLoader
public partial class Integer public partial class Integer
{ {
// aapt resource value: 0x7f080000 // aapt resource value: 0x7f070000
public const int cancel_button_image_alpha = 2131230720; public const int cancel_button_image_alpha = 2131165184;
// aapt resource value: 0x7f080001 // aapt resource value: 0x7f070001
public const int status_bar_notification_info_maxnum = 2131230721; public const int status_bar_notification_info_maxnum = 2131165185;
static Integer() static Integer()
{ {
@ -498,11 +507,11 @@ namespace ModLoader
public partial class Raw public partial class Raw
{ {
// aapt resource value: 0x7f050000 // aapt resource value: 0x7f040000
public const int ModList = 2131034112; public const int ModList = 2130968576;
// aapt resource value: 0x7f050001 // aapt resource value: 0x7f040001
public const int SMDroidFiles = 2131034113; public const int SMDroidFiles = 2130968577;
static Raw() static Raw()
{ {
@ -517,155 +526,164 @@ namespace ModLoader
public partial class String public partial class String
{ {
// aapt resource value: 0x7f090018 // aapt resource value: 0x7f080018
public const int ApplicationName = 2131296280; public const int ApplicationName = 2131230744;
// aapt resource value: 0x7f090026 // aapt resource value: 0x7f080028
public const int Cancel = 2131296294; public const int Cancel = 2131230760;
// aapt resource value: 0x7f090024 // aapt resource value: 0x7f080026
public const int Confirm = 2131296292; public const int Config = 2131230758;
// aapt resource value: 0x7f09001c // aapt resource value: 0x7f080024
public const int Disable = 2131296284; public const int Confirm = 2131230756;
// aapt resource value: 0x7f09001d // aapt resource value: 0x7f08001c
public const int Enable = 2131296285; public const int Disable = 2131230748;
// aapt resource value: 0x7f090019 // aapt resource value: 0x7f08001d
public const int Extract = 2131296281; public const int Enable = 2131230749;
// aapt resource value: 0x7f090028 // aapt resource value: 0x7f080025
public const int ExtractedMessage = 2131296296; public const int Error = 2131230757;
// aapt resource value: 0x7f090027 // aapt resource value: 0x7f080019
public const int ExtractingMessage = 2131296295; public const int Extract = 2131230745;
// aapt resource value: 0x7f09001a // aapt resource value: 0x7f08002b
public const int Generate = 2131296282; public const int ExtractedMessage = 2131230763;
// aapt resource value: 0x7f09002a // aapt resource value: 0x7f08002a
public const int GeneratedMessage = 2131296298; public const int ExtractingMessage = 2131230762;
// aapt resource value: 0x7f090029 // aapt resource value: 0x7f08001a
public const int GeneratingMessage = 2131296297; public const int Generate = 2131230746;
// aapt resource value: 0x7f090023 // aapt resource value: 0x7f08002d
public const int Ignore = 2131296291; public const int GeneratedMessage = 2131230765;
// aapt resource value: 0x7f09001b // aapt resource value: 0x7f08002c
public const int Launch = 2131296283; public const int GeneratingMessage = 2131230764;
// aapt resource value: 0x7f09002b // aapt resource value: 0x7f080023
public const int ModDownloadingMessage = 2131296299; public const int Ignore = 2131230755;
// aapt resource value: 0x7f09001e // aapt resource value: 0x7f08001b
public const int ModInstall = 2131296286; public const int Launch = 2131230747;
// aapt resource value: 0x7f09002d // aapt resource value: 0x7f08002e
public const int ModInstalledMessage = 2131296301; public const int ModDownloadingMessage = 2131230766;
// aapt resource value: 0x7f09001f // aapt resource value: 0x7f08001e
public const int ModRemove = 2131296287; public const int ModInstall = 2131230750;
// aapt resource value: 0x7f09002e // aapt resource value: 0x7f080030
public const int ModRemovedMessage = 2131296302; public const int ModInstalledMessage = 2131230768;
// aapt resource value: 0x7f09002c // aapt resource value: 0x7f08001f
public const int NetworkErrorMessage = 2131296300; public const int ModRemove = 2131230751;
// aapt resource value: 0x7f090030 // aapt resource value: 0x7f080031
public const int NotExtractedMessage = 2131296304; public const int ModRemovedMessage = 2131230769;
// aapt resource value: 0x7f090031 // aapt resource value: 0x7f08002f
public const int NotGeneratedMessage = 2131296305; public const int NetworkErrorMessage = 2131230767;
// aapt resource value: 0x7f09002f // aapt resource value: 0x7f080033
public const int NotInstalledMessage = 2131296303; public const int NotExtractedMessage = 2131230771;
// aapt resource value: 0x7f090025 // aapt resource value: 0x7f080034
public const int RemoveConfirmMessage = 2131296293; public const int NotGeneratedMessage = 2131230772;
// aapt resource value: 0x7f090022 // aapt resource value: 0x7f080032
public const int Update = 2131296290; public const int NotInstalledMessage = 2131230770;
// aapt resource value: 0x7f090021 // aapt resource value: 0x7f080027
public const int UpdateTip = 2131296289; public const int RemoveConfirmMessage = 2131230759;
// aapt resource value: 0x7f090020 // aapt resource value: 0x7f080029
public const int Wiki = 2131296288; public const int StorageIsFullMessage = 2131230761;
// aapt resource value: 0x7f090001 // aapt resource value: 0x7f080022
public const int kilobytes_per_second = 2131296257; public const int Update = 2131230754;
// aapt resource value: 0x7f090002 // aapt resource value: 0x7f080021
public const int notification_download_complete = 2131296258; public const int UpdateTip = 2131230753;
// aapt resource value: 0x7f090003 // aapt resource value: 0x7f080020
public const int notification_download_failed = 2131296259; public const int Wiki = 2131230752;
// aapt resource value: 0x7f090004 // aapt resource value: 0x7f080001
public const int state_completed = 2131296260; public const int kilobytes_per_second = 2131230721;
// aapt resource value: 0x7f090005 // aapt resource value: 0x7f080002
public const int state_connecting = 2131296261; public const int notification_download_complete = 2131230722;
// aapt resource value: 0x7f090006 // aapt resource value: 0x7f080003
public const int state_downloading = 2131296262; public const int notification_download_failed = 2131230723;
// aapt resource value: 0x7f090007 // aapt resource value: 0x7f080004
public const int state_failed = 2131296263; public const int state_completed = 2131230724;
// aapt resource value: 0x7f090008 // aapt resource value: 0x7f080005
public const int state_failed_cancelled = 2131296264; public const int state_connecting = 2131230725;
// aapt resource value: 0x7f090009 // aapt resource value: 0x7f080006
public const int state_failed_fetching_url = 2131296265; public const int state_downloading = 2131230726;
// aapt resource value: 0x7f09000a // aapt resource value: 0x7f080007
public const int state_failed_sdcard_full = 2131296266; public const int state_failed = 2131230727;
// aapt resource value: 0x7f09000b // aapt resource value: 0x7f080008
public const int state_failed_unlicensed = 2131296267; public const int state_failed_cancelled = 2131230728;
// aapt resource value: 0x7f09000c // aapt resource value: 0x7f080009
public const int state_fetching_url = 2131296268; public const int state_failed_fetching_url = 2131230729;
// aapt resource value: 0x7f09000d // aapt resource value: 0x7f08000a
public const int state_idle = 2131296269; public const int state_failed_sdcard_full = 2131230730;
// aapt resource value: 0x7f09000e // aapt resource value: 0x7f08000b
public const int state_paused_by_request = 2131296270; public const int state_failed_unlicensed = 2131230731;
// aapt resource value: 0x7f09000f // aapt resource value: 0x7f08000c
public const int state_paused_network_setup_failure = 2131296271; public const int state_fetching_url = 2131230732;
// aapt resource value: 0x7f090010 // aapt resource value: 0x7f08000d
public const int state_paused_network_unavailable = 2131296272; public const int state_idle = 2131230733;
// aapt resource value: 0x7f090011 // aapt resource value: 0x7f08000e
public const int state_paused_roaming = 2131296273; public const int state_paused_by_request = 2131230734;
// aapt resource value: 0x7f090012 // aapt resource value: 0x7f08000f
public const int state_paused_sdcard_unavailable = 2131296274; public const int state_paused_network_setup_failure = 2131230735;
// aapt resource value: 0x7f090013 // aapt resource value: 0x7f080010
public const int state_paused_wifi_disabled = 2131296275; public const int state_paused_network_unavailable = 2131230736;
// aapt resource value: 0x7f090014 // aapt resource value: 0x7f080011
public const int state_paused_wifi_unavailable = 2131296276; public const int state_paused_roaming = 2131230737;
// aapt resource value: 0x7f090015 // aapt resource value: 0x7f080012
public const int state_unknown = 2131296277; public const int state_paused_sdcard_unavailable = 2131230738;
// aapt resource value: 0x7f090000 // aapt resource value: 0x7f080013
public const int status_bar_notification_info_overflow = 2131296256; public const int state_paused_wifi_disabled = 2131230739;
// aapt resource value: 0x7f090016 // aapt resource value: 0x7f080014
public const int time_remaining = 2131296278; public const int state_paused_wifi_unavailable = 2131230740;
// aapt resource value: 0x7f090017 // aapt resource value: 0x7f080015
public const int time_remaining_notification = 2131296279; 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() static String()
{ {
@ -680,59 +698,59 @@ namespace ModLoader
public partial class Style public partial class Style
{ {
// aapt resource value: 0x7f06000f // aapt resource value: 0x7f05000f
public const int ButtonBackground = 2131099663; public const int ButtonBackground = 2131034127;
// aapt resource value: 0x7f06000d // aapt resource value: 0x7f05000d
public const int NotificationText = 2131099661; public const int NotificationText = 2131034125;
// aapt resource value: 0x7f06000c // aapt resource value: 0x7f05000c
public const int NotificationTextSecondary = 2131099660; public const int NotificationTextSecondary = 2131034124;
// aapt resource value: 0x7f060010 // aapt resource value: 0x7f050010
public const int NotificationTextShadow = 2131099664; public const int NotificationTextShadow = 2131034128;
// aapt resource value: 0x7f06000e // aapt resource value: 0x7f05000e
public const int NotificationTitle = 2131099662; public const int NotificationTitle = 2131034126;
// aapt resource value: 0x7f060005 // aapt resource value: 0x7f050005
public const int TextAppearance_Compat_Notification = 2131099653; public const int TextAppearance_Compat_Notification = 2131034117;
// aapt resource value: 0x7f060006 // aapt resource value: 0x7f050006
public const int TextAppearance_Compat_Notification_Info = 2131099654; public const int TextAppearance_Compat_Notification_Info = 2131034118;
// aapt resource value: 0x7f060000 // aapt resource value: 0x7f050000
public const int TextAppearance_Compat_Notification_Info_Media = 2131099648; public const int TextAppearance_Compat_Notification_Info_Media = 2131034112;
// aapt resource value: 0x7f06000b // aapt resource value: 0x7f05000b
public const int TextAppearance_Compat_Notification_Line2 = 2131099659; public const int TextAppearance_Compat_Notification_Line2 = 2131034123;
// aapt resource value: 0x7f060004 // aapt resource value: 0x7f050004
public const int TextAppearance_Compat_Notification_Line2_Media = 2131099652; public const int TextAppearance_Compat_Notification_Line2_Media = 2131034116;
// aapt resource value: 0x7f060001 // aapt resource value: 0x7f050001
public const int TextAppearance_Compat_Notification_Media = 2131099649; public const int TextAppearance_Compat_Notification_Media = 2131034113;
// aapt resource value: 0x7f060007 // aapt resource value: 0x7f050007
public const int TextAppearance_Compat_Notification_Time = 2131099655; public const int TextAppearance_Compat_Notification_Time = 2131034119;
// aapt resource value: 0x7f060002 // aapt resource value: 0x7f050002
public const int TextAppearance_Compat_Notification_Time_Media = 2131099650; public const int TextAppearance_Compat_Notification_Time_Media = 2131034114;
// aapt resource value: 0x7f060008 // aapt resource value: 0x7f050008
public const int TextAppearance_Compat_Notification_Title = 2131099656; public const int TextAppearance_Compat_Notification_Title = 2131034120;
// aapt resource value: 0x7f060003 // aapt resource value: 0x7f050003
public const int TextAppearance_Compat_Notification_Title_Media = 2131099651; public const int TextAppearance_Compat_Notification_Title_Media = 2131034115;
// aapt resource value: 0x7f060011 // aapt resource value: 0x7f050011
public const int Theme_Splash = 2131099665; public const int Theme_Splash = 2131034129;
// aapt resource value: 0x7f060009 // aapt resource value: 0x7f050009
public const int Widget_Compat_NotificationActionContainer = 2131099657; public const int Widget_Compat_NotificationActionContainer = 2131034121;
// aapt resource value: 0x7f06000a // aapt resource value: 0x7f05000a
public const int Widget_Compat_NotificationActionText = 2131099658; public const int Widget_Compat_NotificationActionText = 2131034122;
static Style() 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 public partial class Styleable
{ {

View File

@ -10,11 +10,14 @@
<string name="ModRemove">移除</string> <string name="ModRemove">移除</string>
<string name="Wiki">Wiki</string> <string name="Wiki">Wiki</string>
<string name="UpdateTip">发现新版本</string> <string name="UpdateTip">发现新版本</string>
<string name="Update">立即更新</string> <string name="Update">更新</string>
<string name="Ignore">暂时忽略</string> <string name="Ignore">忽略</string>
<string name="Confirm">确认</string> <string name="Confirm">确认</string>
<string name="Error">错误</string>
<string name="Config">配置</string>
<string name="RemoveConfirmMessage">确认移除这个插件?</string> <string name="RemoveConfirmMessage">确认移除这个插件?</string>
<string name="Cancel">取消</string> <string name="Cancel">取消</string>
<string name="StorageIsFullMessage">存储空间不足</string>
<string name="ExtractingMessage">正在解压游戏资源</string> <string name="ExtractingMessage">正在解压游戏资源</string>
<string name="ExtractedMessage">解压完成</string> <string name="ExtractedMessage">解压完成</string>
<string name="GeneratingMessage">正在生成DLL文件</string> <string name="GeneratingMessage">正在生成DLL文件</string>

View File

@ -13,8 +13,11 @@
<string name="Update">Upgrade</string> <string name="Update">Upgrade</string>
<string name="Ignore">Ignore</string> <string name="Ignore">Ignore</string>
<string name="Confirm">Confirm</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="RemoveConfirmMessage">Are you sure to remove this mod?</string>
<string name="Cancel">Cancel</string> <string name="Cancel">Cancel</string>
<string name="StorageIsFullMessage">Not enough storage free space</string>
<string name="ExtractingMessage">Extracting game resources</string> <string name="ExtractingMessage">Extracting game resources</string>
<string name="ExtractedMessage">Extracted</string> <string name="ExtractedMessage">Extracted</string>
<string name="GeneratingMessage">Generating Dlls</string> <string name="GeneratingMessage">Generating Dlls</string>

View File

@ -1,7 +1,8 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<style name="Theme.Splash" parent="android:Theme"> <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:windowNoTitle">true</item>
<item name="android:windowFullscreen">true</item>
</style> </style>
</resources> </resources>

View File

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-path path="." name="external_storage_root" />
</paths>

View File

@ -22,6 +22,7 @@ namespace StardewModdingAPI
****/ ****/
/// <summary>SMAPI's current semantic version.</summary> /// <summary>SMAPI's current semantic version.</summary>
public static ISemanticVersion ApiVersion { get; } = new Toolkit.SemanticVersion("3.0.0"); 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> /// <summary>The minimum supported version of Stardew Valley.</summary>
public static ISemanticVersion MinimumGameVersion { get; } = new GameVersion("1.3.36"); public static ISemanticVersion MinimumGameVersion { get; } = new GameVersion("1.3.36");

View File

@ -122,66 +122,6 @@ namespace StardewModdingAPI.Framework.ContentManagers
return this.Coordinator.CreateGameContentManager("(temporary)"); 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 ** Private methods
*********/ *********/

View File

@ -246,6 +246,44 @@ namespace StardewModdingAPI.Framework.ModLoading
/**** /****
** Assembly rewriting ** 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> /// <summary>Rewrite the types referenced by an assembly.</summary>
/// <param name="mod">The mod for which the assembly is being loaded.</param> /// <param name="mod">The mod for which the assembly is being loaded.</param>
/// <param name="assembly">The assembly to rewrite.</param> /// <param name="assembly">The assembly to rewrite.</param>
@ -279,6 +317,9 @@ namespace StardewModdingAPI.Framework.ModLoading
// rewrite type scopes to use target assemblies // rewrite type scopes to use target assemblies
IEnumerable<TypeReference> typeReferences = module.GetTypeReferences().OrderBy(p => p.FullName); 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) foreach (TypeReference type in typeReferences)
this.ChangeTypeScope(type); this.ChangeTypeScope(type);
} }

View File

@ -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;
}
}
}

View File

@ -19,6 +19,7 @@ using MonoMod.RuntimeDetour;
using System.Windows.Forms; using System.Windows.Forms;
#endif #endif
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using SMDroid; using SMDroid;
using StardewModdingAPI.Events; using StardewModdingAPI.Events;
using StardewModdingAPI.Framework.Events; using StardewModdingAPI.Framework.Events;
@ -169,7 +170,7 @@ namespace StardewModdingAPI.Framework
// init logging // init logging
this.Monitor.Log($"SMAPI {Constants.ApiVersion} with Stardew Valley {Constants.GameVersion} on {EnvironmentUtility.GetFriendlyPlatformName(Constants.Platform)}", LogLevel.Info); 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); this.Monitor.Log($"Mods go here: {modsPath}", LogLevel.Info);
if (modsPath != Constants.DefaultModsPath) if (modsPath != Constants.DefaultModsPath)
this.Monitor.Log("(Using custom --mods-path argument.)", LogLevel.Trace); this.Monitor.Log("(Using custom --mods-path argument.)", LogLevel.Trace);
@ -256,14 +257,10 @@ namespace StardewModdingAPI.Framework
// override game // override game
SGame.ConstructorHack = new SGameConstructorHack(this.Monitor, this.Reflection, this.Toolkit.JsonHelper, this.InitialiseBeforeFirstAssetLoaded); SGame.ConstructorHack = new SGameConstructorHack(this.Monitor, this.Reflection, this.Toolkit.JsonHelper, this.InitialiseBeforeFirstAssetLoaded);
try if(!this.HarmonyDetourBridgeFailed)
{ {
HarmonyDetourBridge.Init(); HarmonyDetourBridge.Init();
} }
catch
{
this.HarmonyDetourBridgeFailed = true;
}
// override game // override game
this.GameInstance = new SGame( this.GameInstance = new SGame(
@ -281,9 +278,10 @@ namespace StardewModdingAPI.Framework
StardewValley.Program.gamePtr = Game1.game1; StardewValley.Program.gamePtr = Game1.game1;
// apply game patches // apply game patches
new GamePatcher(this.Monitor).Apply( new GamePatcher(this.Monitor).Apply(
new EventErrorPatch(this.MonitorForGame),
new DialogueErrorPatch(this.MonitorForGame, this.Reflection), new DialogueErrorPatch(this.MonitorForGame, this.Reflection),
new ObjectErrorPatch(), new ObjectErrorPatch(),
new LoadForNewGamePatch(this.Reflection, this.GameInstance.OnLoadStageChanged) new LoadContextPatch(this.Reflection, this.GameInstance.OnLoadStageChanged)
); );
// add exit handler // add exit handler
new Thread(() => new Thread(() =>
@ -318,10 +316,8 @@ namespace StardewModdingAPI.Framework
// show details if game crashed during last session // show details if game crashed during last session
if (File.Exists(Constants.FatalCrashMarker)) 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("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); File.Delete(Constants.FatalCrashMarker);
} }

View File

@ -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); this.Monitor.Log($"Context: menu changed from {was?.GetType().FullName ?? "none"} to {now?.GetType().FullName ?? "none"}.", LogLevel.Trace);
// raise menu events // 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)); events.MenuChanged.Raise(new MenuChangedEventArgs(was, now));
#if !SMAPI_3_0_STRICT #if !SMAPI_3_0_STRICT
if (now != null) if (now != null)
@ -825,8 +834,7 @@ namespace StardewModdingAPI.Framework
else else
events.Legacy_MenuClosed.Raise(new EventArgsClickableMenuClosed(was)); events.Legacy_MenuClosed.Raise(new EventArgsClickableMenuClosed(was));
#endif #endif
GameMenu gameMenu = now as GameMenu; if (now is GameMenu gameMenu)
if (gameMenu != null)
{ {
foreach (IClickableMenu menuPage in gameMenu.pages) 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 // 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); this.OnLoadStageChanged(LoadStage.Loaded);
// update tick // update tick

View File

@ -50,10 +50,7 @@ namespace StardewModdingAPI.Patches
ConstructorInfo constructor = AccessTools.Constructor(typeof(Dialogue), new[] { typeof(string), typeof(NPC) }); ConstructorInfo constructor = AccessTools.Constructor(typeof(Dialogue), new[] { typeof(string), typeof(NPC) });
MethodInfo prefix = AccessTools.Method(this.GetType(), nameof(DialogueErrorPatch.Prefix)); 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);
}
} }

View File

@ -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;
}
}
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -25,20 +25,17 @@ namespace StardewModdingAPI.Patches
/// <param name="harmony">The Harmony instance.</param> /// <param name="harmony">The Harmony instance.</param>
public void Apply(HarmonyInstance harmony) public void Apply(HarmonyInstance harmony)
{ {
if (!SCore.Instance.HarmonyDetourBridgeFailed) // object.getDescription
{ harmony.Patch(
// object.getDescription original: AccessTools.Method(typeof(SObject), nameof(SObject.getDescription)),
harmony.Patch( prefix: new HarmonyMethod(AccessTools.Method(this.GetType(), nameof(ObjectErrorPatch.Object_GetDescription_Prefix)))
original: AccessTools.Method(typeof(SObject), nameof(SObject.getDescription)), );
prefix: new HarmonyMethod(AccessTools.Method(this.GetType(), nameof(ObjectErrorPatch.Object_GetDescription_Prefix)))
);
// IClickableMenu.drawToolTip // IClickableMenu.drawToolTip
harmony.Patch( harmony.Patch(
original: AccessTools.Method(typeof(IClickableMenu), nameof(IClickableMenu.drawToolTip)), original: AccessTools.Method(typeof(IClickableMenu), nameof(IClickableMenu.drawToolTip)),
prefix: new HarmonyMethod(AccessTools.Method(this.GetType(), nameof(ObjectErrorPatch.IClickableMenu_DrawTooltip_Prefix))) prefix: new HarmonyMethod(AccessTools.Method(this.GetType(), nameof(ObjectErrorPatch.IClickableMenu_DrawTooltip_Prefix)))
); );
}
} }

Binary file not shown.

View File

@ -1,12 +1,16 @@
using System; using System;
using System.IO; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Reflection; using System.Reflection;
using System.Threading.Tasks;
using Android.App; using Android.App;
using Android.Content;
using Android.Content.PM; using Android.Content.PM;
using Android.OS; using Android.OS;
using Android.Provider; using Android.Provider;
using Android.Views; using Android.Views;
using Google.Android.Vending.Licensing; using Google.Android.Vending.Licensing;
using ModLoader.Common;
using StardewModdingAPI; using StardewModdingAPI;
using StardewModdingAPI.Framework; using StardewModdingAPI.Framework;
using StardewValley; using StardewValley;
@ -15,7 +19,8 @@ using Constants = ModLoader.Common.Constants;
namespace ModLoader 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))] [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) protected override void OnCreate(Bundle bundle)
@ -42,6 +47,10 @@ namespace ModLoader
this.SetPaddingForMenus(); this.SetPaddingForMenus();
new GameConsole(); new GameConsole();
SCore core = new SCore(Constants.ModPath, false); SCore core = new SCore(Constants.ModPath, false);
if (Build.VERSION.SdkInt <= BuildVersionCodes.LollipopMr1)
{
core.HarmonyDetourBridgeFailed = true;
}
core.RunInteractively(); core.RunInteractively();
Game1 game1 = StardewValley.Program.gamePtr; Game1 game1 = StardewValley.Program.gamePtr;
typeof(MainActivity).GetField("_game1", BindingFlags.Instance | BindingFlags.NonPublic)?.SetValue(this, game1); 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); typeof(MainActivity).GetField("_licenseChecker", BindingFlags.Instance | BindingFlags.NonPublic)?.SetValue(this, licenseChecker);
licenseChecker.CheckAccess(this); 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();
}
} }
} }