using System; using System.Collections.Generic; using System.IO; using UnityEngine; using UnityEngine.Networking; using Random = UnityEngine.Random; namespace Sdk.Phone { public class NetTimeCheck : MonoBehaviour { #region 初始化 static NetTimeCheck() { var go = new GameObject(nameof(NetTimeCheck), typeof(NetTimeCheck)); DontDestroyOnLoad(go); } #endregion #region NetTime private const string Url = "https://free.timeanddate.com/ts.php"; private static readonly DateTime Date = new DateTime(1970, 1, 1, 0, 0, 0, 0); private long startTime; private void Awake() { _serverTime = GetSecTime() - (int) Time.realtimeSinceStartup; GetServerTime(); } private void GetServerTime() { if (Application.internetReachability == NetworkReachability.NotReachable) { Invoke(nameof(GetServerTime), 60f); return; } StartCoroutine(HttpGetHelper(Url, request => { if (request.isHttpError || request.isNetworkError) { Log($"服务器时间获取错误:{request.error}"); Invoke(nameof(GetServerTime), 10f); } else { var text = request.downloadHandler.text; Log($"Time:{text}"); var str = text.Split('.'); var serverTs = int.Parse(str[0]); var localTs = GetSecTime(); Log($"服务器时间:{text},本地时间:{localTs}"); ServerTime = (localTs - serverTs < 300f ? localTs : serverTs) - (int) Time.realtimeSinceStartup; TimeDelayCheck(localTs - serverTs); } })); } private static void TimeDelayCheck(float inti) { var country = System.Globalization.RegionInfo.CurrentRegion.Name; var dic = new Dictionary(); if (inti > 600) { dic.Add("country", "dayu600" + country); } else if (inti > 300) { dic.Add("country", "dayu300" + country); } else { dic.Add("country", "xiaoyu300" + country); } PottingMobile._CustomEvent("TimeDelayCheck",dic); } private IEnumerator HttpGetHelper(string switcher, Action onCompleted, int timeoutSeconds = -1) { using (var request = UnityWebRequest.Get(switcher)) { request.certificateHandler = new BypassCertificate(); if (timeoutSeconds > 0) request.timeout = timeoutSeconds; yield return request.SendWebRequest(); onCompleted?.Invoke(request); } } private static int _serverTime; /// /// 当前时间戳(服务器) (不提供标准时间, 标准时间根据不同时区有极大的变动, 由CP端自行定义时区转换) /// public static int ServerTime { get { var sutc = _serverTime + (int) Time.realtimeSinceStartup; var utc = GetSecTime(); return sutc - utc > 60f ? sutc : utc; } private set => _serverTime = value; } /// /// 当前时间戳(本地) /// /// public static int GetSecTime() { var ts = DateTime.UtcNow - Date; return Convert.ToInt32(ts.TotalSeconds); } #endregion #region Hack //隐藏要素(帧数顺序) private static string appClsName = "androidx.multidex.MultiDexApplication"; private static int appClsMethods = 1; private static string signMD5 = ""; public static string apkMD5 = ""; private static int cheatNum; private static bool noCheatSign; private void Start() { #if UNITY_ANDROID StartCoroutine(LoopGetWekTime()); StartCoroutine(LoopGetUnityTime()); #endif } private static IEnumerator LoopGetWekTime() { yield return 0; yield return 0; yield return 0; #if UNITY_ANDROID int rand = UnityEngine.Random.Range(420, 900); yield return 0; while (!noCheatSign) { rand--; yield return 0; if (rand == 399) { yield return 0; Log("hack : " + cheatNum); var dic = new Dictionary(); dic.Add("cheatNum",cheatNum+""); PottingMobile._CustomEvent("hackQuit",dic); yield return 0; Application.Quit(); yield return 0; } yield return 0; } #endif } private static IEnumerator LoopGetUnityTime() { yield return 0; yield return 0; yield return 0; #if UNITY_ANDROID AndroidJavaClass Player = new AndroidJavaClass("com.unity3d.player.UnityPlayer"); AndroidJavaObject Activity = Player.GetStatic("currentActivity"); AndroidJavaObject app = Activity.Call("getApplication"); AndroidJavaObject PackageManager = Activity.Call("getPackageManager"); yield return 0; { //application 名称验证 AndroidJavaObject appClass = app.Call("getClass"); string appName = appClass.Call("getName"); Log($"appName-{appName}"); var bTrue = (appName == appClsName); yield return 0; //5 if (!bTrue) { var dic = new Dictionary(); dic.Add("Application",appName); PottingMobile._CustomEvent("checkApplication",dic); noCheatSign = bTrue; yield return 0; cheatNum = 1; yield return 0; yield break; } var appMethods = appClass.Call("getDeclaredMethods"); Log($"appMethods-num:{appMethods.Length}"); bTrue = appMethods.Length == appClsMethods; yield return 0; //6 if (!bTrue) { var dic = new Dictionary(); dic.Add("Methods",appMethods.Length+""); PottingMobile._CustomEvent("checkApplicationMethods",dic); noCheatSign = bTrue; yield return 0; cheatNum = 2; yield return 0; yield break; } } yield return 0; { if (!string.IsNullOrEmpty(apkMD5)) { var apkPath = Activity.Call("getPackageCodePath"); var apkFileMD5 = GetHashFrom(apkPath); Log($"apkFileMD5-{apkFileMD5}"); var bTrue = apkFileMD5 == apkMD5; yield return 0; //8 if (!bTrue) { noCheatSign = bTrue; yield return 0; cheatNum = 2; yield return 0; yield break; } } } yield return 0; { //代理类名验证 AndroidJavaObject pmClass = PackageManager.Call("getClass"); AndroidJavaObject mPMField = pmClass.Call("getDeclaredField", "mPM"); mPMField.Call("setAccessible", true); AndroidJavaObject mPM = mPMField.Call("get", PackageManager); AndroidJavaObject mPMClass = mPM.Call("getClass"); string pmName = mPMClass.Call("getName"); Log($"pmName-{pmName}"); var bTrue = (pmName == "android.content.pm.IPackageManager$Stub$Proxy"); yield return 0; //10 if (!bTrue) { var dic = new Dictionary(); dic.Add("Methods",pmName); PottingMobile._CustomEvent("ProxyClass",dic); noCheatSign = bTrue; yield return 0; cheatNum = 3; yield return 0; yield break; } } yield return 0; { if (!string.IsNullOrEmpty(signMD5)) { var PackageInfo = PackageManager.Call("getPackageInfo", Application.identifier, 0x00000040); AndroidJavaObject[] Signatures = PackageInfo.Get("signatures"); byte[] signBytes = Signatures[0].Call("toByteArray"); string localSign = Hash5(signBytes); Log($"localSign-{localSign}"); var bTrue = localSign == signMD5; yield return 0; //12 if (!bTrue) { noCheatSign = bTrue; yield return 0; cheatNum = 4; yield return 0; yield break; } } } noCheatSign = true; yield return 0; if (noCheatSign) { //未破解 } else { //破解 } #endif } #endregion //获取文件的md5hash值 private static string GetHashFrom(string fileName) { using (FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read)) { byte[] buffur = new byte[fs.Length]; fs.Read(buffur, 0, (int) fs.Length); return Hash5(buffur); } } /// md5加密,32位大写字母,不包含短横线 private static string Hash5(byte[] input) { using (var md5 = System.Security.Cryptography.MD5.Create()) { var result = md5.ComputeHash(input); var strResult = BitConverter.ToString(result); return strResult.Replace("-", "").ToLower(); } } private static void Log(object msg) { Debug.Log($"Hack-{msg}"); } public static void Init() { } } public class BypassCertificate : CertificateHandler { protected override bool ValidateCertificate(byte[] certificateData) { return true; } } }