├── .gitignore ├── Assets ├── Google Play License Verification.meta ├── Google Play License Verification │ ├── LVL_Example.meta │ ├── LVL_Example │ │ ├── CheckLVLButton.cs │ │ ├── CheckLVLButton.cs.meta │ │ ├── LVL_Example.unity │ │ └── LVL_Example.unity.meta │ ├── LicenseVerification.meta │ └── LicenseVerification │ │ ├── JavaSource.meta │ │ ├── JavaSource │ │ ├── AndroidManifest.xml │ │ ├── AndroidManifest.xml.meta │ │ ├── build.xml │ │ ├── build.xml.meta │ │ ├── proguard-project.txt │ │ ├── proguard-project.txt.meta │ │ ├── project.properties │ │ ├── project.properties.meta │ │ ├── src.meta │ │ └── src │ │ │ ├── com.meta │ │ │ └── com │ │ │ ├── unity3d.meta │ │ │ └── unity3d │ │ │ ├── plugin.meta │ │ │ └── plugin │ │ │ ├── lvl.meta │ │ │ └── lvl │ │ │ ├── ServiceBinder.java │ │ │ └── ServiceBinder.java.meta │ │ ├── RSA.cs │ │ ├── RSA.cs.meta │ │ ├── classes_jar.txt │ │ └── classes_jar.txt.meta ├── Plugins.meta └── Plugins │ ├── Android.meta │ └── Android │ └── GooglePlayLicenseVerification │ ├── AndroidManifest.xml │ └── project.properties ├── LICENSE.txt ├── ProjectSettings ├── AudioManager.asset ├── DynamicsManager.asset ├── EditorBuildSettings.asset ├── EditorSettings.asset ├── InputManager.asset ├── NavMeshLayers.asset ├── NetworkManager.asset ├── ProjectSettings.asset ├── QualitySettings.asset ├── TagManager.asset └── TimeManager.asset └── README.txt /.gitignore: -------------------------------------------------------------------------------- 1 | /Library 2 | /Temp 3 | /Assets/Google Play License Verification/LicenseVerification/JavaSource/local.properties -------------------------------------------------------------------------------- /Assets/Google Play License Verification.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 823db3b28a43d4441a75bccdda0716e5 3 | -------------------------------------------------------------------------------- /Assets/Google Play License Verification/LVL_Example.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 18c7a5de2cd86431ea6c80cd73cf494b 3 | -------------------------------------------------------------------------------- /Assets/Google Play License Verification/LVL_Example/CheckLVLButton.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Security.Cryptography; 5 | 6 | public class CheckLVLButton : MonoBehaviour 7 | { 8 | /* 9 | * This is the Java service binder classes.jar 10 | */ 11 | public TextAsset ServiceBinder; 12 | 13 | /* 14 | * Use the public LVL key from the Android Market publishing section here. 15 | */ 16 | private string m_PublicKey_Base64 = ""; 17 | 18 | /* 19 | * Consider storing the public key as RSAParameters.Modulus/.Exponent rather than Base64 to prevent the ASN1 parsing.. 20 | * These are printed to the logcat below. 21 | */ 22 | private string m_PublicKey_Modulus_Base64 = ""; 23 | private string m_PublicKey_Exponent_Base64 = "< .. and here >"; 24 | 25 | void Start() 26 | { 27 | // Either parse the ASN1-formatted public LVL key at runtime (only available when stripping is disabled).. 28 | RSA.SimpleParseASN1(m_PublicKey_Base64, ref m_PublicKey.Modulus, ref m_PublicKey.Exponent); 29 | m_PublicKey_Modulus_Base64 = System.Convert.ToBase64String(m_PublicKey.Modulus); 30 | m_PublicKey_Exponent_Base64 = System.Convert.ToBase64String(m_PublicKey.Exponent); 31 | // .. and check the logcat for these values ... 32 | Debug.Log("private string m_PublicKey_Modulus_Base64 = \"" + m_PublicKey_Modulus_Base64 + "\";"); 33 | Debug.Log("private string m_PublicKey_Exponent_Base64 = \"" + m_PublicKey_Exponent_Base64 + "\";"); 34 | 35 | // .. or use pre-parsed keys (and remove the code above). 36 | m_PublicKey.Modulus = System.Convert.FromBase64String(m_PublicKey_Modulus_Base64); 37 | m_PublicKey.Exponent = System.Convert.FromBase64String(m_PublicKey_Exponent_Base64); 38 | 39 | m_RunningOnAndroid = new AndroidJavaClass("android.os.Build").GetRawClass() != System.IntPtr.Zero; 40 | if (!m_RunningOnAndroid) 41 | return; 42 | 43 | LoadServiceBinder(); 44 | 45 | new SHA1CryptoServiceProvider(); // keep a dummy reference to prevent too aggressive stripping 46 | 47 | m_ButtonMessage = "Check LVL"; 48 | } 49 | 50 | private RSAParameters m_PublicKey = new RSAParameters(); 51 | 52 | /* 53 | * 54 | */ 55 | private void LoadServiceBinder() 56 | { 57 | byte[] classes_jar = ServiceBinder.bytes; 58 | 59 | m_Activity = new AndroidJavaClass("com.unity3d.player.UnityPlayer").GetStatic("currentActivity"); 60 | m_PackageName = m_Activity.Call("getPackageName"); 61 | 62 | string cachePath = System.IO.Path.Combine(m_Activity.Call("getCacheDir").Call("getPath"), m_PackageName); 63 | System.IO.Directory.CreateDirectory(cachePath); 64 | 65 | System.IO.File.WriteAllBytes(cachePath + "/classes.jar", classes_jar); 66 | System.IO.Directory.CreateDirectory(cachePath + "/odex"); 67 | 68 | AndroidJavaObject dcl = new AndroidJavaObject("dalvik.system.DexClassLoader", 69 | cachePath + "/classes.jar", 70 | cachePath + "/odex", 71 | null, 72 | m_Activity.Call("getClassLoader")); 73 | m_LVLCheckType = dcl.Call("findClass", "com.unity3d.plugin.lvl.ServiceBinder"); 74 | 75 | System.IO.Directory.Delete(cachePath, true); 76 | } 77 | 78 | private bool m_RunningOnAndroid = false; 79 | 80 | private AndroidJavaObject m_Activity; 81 | private AndroidJavaObject m_LVLCheckType; 82 | 83 | private AndroidJavaObject m_LVLCheck = null; 84 | 85 | private string m_ButtonMessage = "Invalid LVL key!\nCheck the source..."; 86 | private bool m_ButtonEnabled = true; 87 | 88 | private string m_PackageName; 89 | private int m_Nonce; 90 | 91 | private bool m_LVL_Received = false; 92 | private string m_ResponseCode_Received; 93 | private string m_PackageName_Received; 94 | private int m_Nonce_Received; 95 | private int m_VersionCode_Received; 96 | private string m_UserID_Received; 97 | private string m_Timestamp_Received; 98 | private int m_MaxRetry_Received; 99 | private string m_LicenceValidityTimestamp_Received; 100 | private string m_GracePeriodTimestamp_Received; 101 | private string m_UpdateTimestamp_Received; 102 | private string m_FileURL1_Received = ""; 103 | private string m_FileURL2_Received = ""; 104 | private string m_FileName1_Received; 105 | private string m_FileName2_Received; 106 | private int m_FileSize1_Received; 107 | private int m_FileSize2_Received; 108 | private string m_LicensingURL_Received = ""; 109 | 110 | void OnGUI() 111 | { 112 | if (!m_RunningOnAndroid) 113 | { 114 | GUI.Label(new Rect(10, 10, Screen.width - 10, 20), "Use LVL checks only on the Android device!"); 115 | return; 116 | } 117 | GUI.enabled = m_ButtonEnabled; 118 | if (GUI.Button(new Rect(10, 10, 450, 300), m_ButtonMessage)) 119 | { 120 | m_ButtonMessage = "Checking..."; 121 | m_ButtonEnabled = false; 122 | 123 | m_Nonce = new System.Random().Next(); 124 | 125 | object[] param = new object[] { new AndroidJavaObject[] { m_Activity } }; 126 | AndroidJavaObject[] ctors = m_LVLCheckType.Call("getConstructors"); 127 | m_LVLCheck = ctors[0].Call("newInstance", param); 128 | m_LVLCheck.Call("create", m_Nonce, new AndroidJavaRunnable(Process)); 129 | } 130 | GUI.enabled = true; 131 | 132 | if (m_LVLCheck != null || m_LVL_Received) 133 | { 134 | GUI.Label(new Rect(10, 320, 450, 20), "Requesting LVL response:"); 135 | GUI.Label(new Rect(20, 340, 450, 20), "Package name = " + m_PackageName); 136 | GUI.Label(new Rect(20, 360, 450, 20), "Request nonce = 0x" + m_Nonce.ToString("X")); 137 | } 138 | 139 | if (m_LVLCheck == null && m_LVL_Received) 140 | { 141 | GUI.Label(new Rect(10, 420, 450, 20), "Received LVL response:"); 142 | GUI.Label(new Rect(20, 440, 450, 20), "Response code = " + m_ResponseCode_Received); 143 | GUI.Label(new Rect(20, 460, 450, 20), "Package name = " + m_PackageName_Received); 144 | GUI.Label(new Rect(20, 480, 450, 20), "Received nonce = 0x" + m_Nonce_Received.ToString("X")); 145 | GUI.Label(new Rect(20, 500, 450, 20), "Version code = " + m_VersionCode_Received); 146 | GUI.Label(new Rect(20, 520, 450, 20), "User ID = " + m_UserID_Received); 147 | GUI.Label(new Rect(20, 540, 450, 20), "Timestamp = " + m_Timestamp_Received); 148 | GUI.Label(new Rect(20, 560, 450, 20), "Max Retry = " + m_MaxRetry_Received); 149 | GUI.Label(new Rect(20, 580, 450, 20), "License Validity = " + m_LicenceValidityTimestamp_Received); 150 | GUI.Label(new Rect(20, 600, 450, 20), "Grace Period = " + m_GracePeriodTimestamp_Received); 151 | GUI.Label(new Rect(20, 620, 450, 20), "Update Since = " + m_UpdateTimestamp_Received); 152 | GUI.Label(new Rect(20, 640, 450, 20), "Main OBB URL = " + m_FileURL1_Received.Substring(0, 153 | Mathf.Min(m_FileURL1_Received.Length,50)) + "..."); 154 | GUI.Label(new Rect(20, 660, 450, 20), "Main OBB Name = " + m_FileName1_Received); 155 | GUI.Label(new Rect(20, 680, 450, 20), "Main OBB Size = " + m_FileSize1_Received); 156 | GUI.Label(new Rect(20, 700, 450, 20), "Patch OBB URL = " + m_FileURL2_Received.Substring(0, 157 | Mathf.Min(m_FileURL2_Received.Length,50)) + "..."); 158 | GUI.Label(new Rect(20, 720, 450, 20), "Patch OBB Name = " + m_FileName2_Received); 159 | GUI.Label(new Rect(20, 740, 450, 20), "Patch OBB Size = " + m_FileSize2_Received); 160 | GUI.Label(new Rect(20, 760, 450, 20), "Licensing URL = " + m_LicensingURL_Received.Substring(0, 161 | Mathf.Min(m_LicensingURL_Received.Length,50)) + "..."); 162 | } 163 | } 164 | 165 | internal static Dictionary DecodeExtras(string query) 166 | { 167 | Dictionary result = new Dictionary(); 168 | 169 | if (query.Length == 0) 170 | return result; 171 | 172 | string decoded = query; 173 | int decodedLength = decoded.Length; 174 | int namePos = 0; 175 | bool first = true; 176 | 177 | while (namePos <= decodedLength) 178 | { 179 | int valuePos = -1, valueEnd = -1; 180 | for (int q = namePos; q < decodedLength; q++) 181 | { 182 | if (valuePos == -1 && decoded[q] == '=') 183 | { 184 | valuePos = q + 1; 185 | } 186 | else if (decoded[q] == '&') 187 | { 188 | valueEnd = q; 189 | break; 190 | } 191 | } 192 | 193 | if (first) 194 | { 195 | first = false; 196 | if (decoded[namePos] == '?') 197 | namePos++; 198 | } 199 | 200 | string name, value; 201 | 202 | if (valuePos == -1) 203 | { 204 | 205 | name = null; 206 | valuePos = namePos; 207 | } 208 | else 209 | { 210 | name = WWW.UnEscapeURL(decoded.Substring(namePos, valuePos - namePos - 1)); 211 | } 212 | 213 | if (valueEnd < 0) 214 | { 215 | namePos = -1; 216 | valueEnd = decoded.Length; 217 | } 218 | else 219 | { 220 | namePos = valueEnd + 1; 221 | } 222 | 223 | value = WWW.UnEscapeURL(decoded.Substring(valuePos, valueEnd - valuePos)); 224 | 225 | result.Add(name, value); 226 | if (namePos == -1) 227 | break; 228 | } 229 | return result; 230 | } 231 | 232 | private System.Int64 ConvertEpochSecondsToTicks(System.Int64 secs) 233 | { 234 | System.DateTime epoch = new System.DateTime(1970, 1, 1, 0, 0, 0, System.DateTimeKind.Utc); 235 | System.Int64 seconds_to_100ns_ticks = 10 * 1000; 236 | System.Int64 max_seconds_allowed = (System.DateTime.MaxValue.Ticks - epoch.Ticks) 237 | / seconds_to_100ns_ticks; 238 | if (secs < 0) 239 | secs = 0; 240 | if (secs > max_seconds_allowed) 241 | secs = max_seconds_allowed; 242 | return epoch.Ticks + secs * seconds_to_100ns_ticks; 243 | } 244 | 245 | private void Process() 246 | { 247 | m_LVL_Received = true; 248 | m_ButtonMessage = "Check LVL"; 249 | m_ButtonEnabled = true; 250 | 251 | if (m_LVLCheck == null) 252 | return; 253 | 254 | int responseCode = m_LVLCheck.Get("_arg0"); 255 | string message = m_LVLCheck.Get("_arg1"); 256 | string signature = m_LVLCheck.Get("_arg2"); 257 | 258 | m_LVLCheck = null; 259 | 260 | m_ResponseCode_Received = responseCode.ToString(); 261 | if (responseCode < 0 || string.IsNullOrEmpty(message) || string.IsNullOrEmpty(signature)) 262 | { 263 | m_PackageName_Received = ""; 264 | return; 265 | } 266 | 267 | byte[] message_bytes = System.Text.Encoding.UTF8.GetBytes(message); 268 | byte[] signature_bytes = System.Convert.FromBase64String(signature); 269 | RSACryptoServiceProvider csp = new RSACryptoServiceProvider(); 270 | csp.ImportParameters(m_PublicKey); 271 | SHA1Managed sha1 = new SHA1Managed(); 272 | bool match = csp.VerifyHash(sha1.ComputeHash(message_bytes), CryptoConfig.MapNameToOID("SHA1"), signature_bytes); 273 | 274 | if (!match) 275 | { 276 | m_ResponseCode_Received = ""; 277 | m_PackageName_Received = ""; 278 | return; 279 | } 280 | 281 | int index = message.IndexOf(':'); 282 | string mainData, extraData; 283 | if (-1 == index) 284 | { 285 | mainData = message; 286 | extraData = ""; 287 | } 288 | else 289 | { 290 | mainData = message.Substring(0, index); 291 | extraData = index >= message.Length ? "" : message.Substring(index + 1); 292 | } 293 | 294 | string[] vars = mainData.Split('|'); // response | nonce | package | version | userid | timestamp 295 | 296 | if (vars[0].CompareTo(responseCode.ToString()) != 0) 297 | { 298 | m_ResponseCode_Received = ""; 299 | m_PackageName_Received = ""; 300 | return; 301 | } 302 | 303 | m_ResponseCode_Received = vars[0]; 304 | m_Nonce_Received = System.Convert.ToInt32(vars[1]); 305 | m_PackageName_Received = vars[2]; 306 | m_VersionCode_Received = System.Convert.ToInt32(vars[3]); 307 | m_UserID_Received = vars[4]; 308 | System.Int64 ticks = ConvertEpochSecondsToTicks(System.Convert.ToInt64(vars[5])); 309 | m_Timestamp_Received = new System.DateTime(ticks).ToLocalTime().ToString(); 310 | 311 | if (!string.IsNullOrEmpty(extraData)) 312 | { 313 | Dictionary extrasDecoded = DecodeExtras(extraData); 314 | 315 | if (extrasDecoded.ContainsKey("GR")) 316 | { 317 | m_MaxRetry_Received = System.Convert.ToInt32(extrasDecoded["GR"]); 318 | } 319 | else 320 | { 321 | m_MaxRetry_Received = 0; 322 | } 323 | 324 | if (extrasDecoded.ContainsKey("VT")) 325 | { 326 | ticks = ConvertEpochSecondsToTicks(System.Convert.ToInt64(extrasDecoded["VT"])); 327 | m_LicenceValidityTimestamp_Received = new System.DateTime(ticks).ToLocalTime().ToString(); 328 | } 329 | else 330 | { 331 | m_LicenceValidityTimestamp_Received = null; 332 | } 333 | 334 | if (extrasDecoded.ContainsKey("GT")) 335 | { 336 | ticks = ConvertEpochSecondsToTicks(System.Convert.ToInt64(extrasDecoded["GT"])); 337 | m_GracePeriodTimestamp_Received = new System.DateTime(ticks).ToLocalTime().ToString(); 338 | } 339 | else 340 | { 341 | m_GracePeriodTimestamp_Received = null; 342 | } 343 | 344 | if (extrasDecoded.ContainsKey("UT")) 345 | { 346 | ticks = ConvertEpochSecondsToTicks(System.Convert.ToInt64(extrasDecoded["UT"])); 347 | m_UpdateTimestamp_Received = new System.DateTime(ticks).ToLocalTime().ToString(); 348 | } 349 | else 350 | { 351 | m_UpdateTimestamp_Received = null; 352 | } 353 | 354 | if (extrasDecoded.ContainsKey("FILE_URL1")) 355 | { 356 | m_FileURL1_Received = extrasDecoded["FILE_URL1"]; 357 | } 358 | else 359 | { 360 | m_FileURL1_Received = ""; 361 | } 362 | 363 | if (extrasDecoded.ContainsKey("FILE_URL2")) 364 | { 365 | m_FileURL2_Received = extrasDecoded["FILE_URL2"]; 366 | } 367 | else 368 | { 369 | m_FileURL2_Received = ""; 370 | } 371 | 372 | if (extrasDecoded.ContainsKey("FILE_NAME1")) 373 | { 374 | m_FileName1_Received = extrasDecoded["FILE_NAME1"]; 375 | } 376 | else 377 | { 378 | m_FileName1_Received = null; 379 | } 380 | 381 | if (extrasDecoded.ContainsKey("FILE_NAME2")) 382 | { 383 | m_FileName2_Received = extrasDecoded["FILE_NAME2"]; 384 | } 385 | else 386 | { 387 | m_FileName2_Received = null; 388 | } 389 | 390 | if (extrasDecoded.ContainsKey("FILE_SIZE1")) 391 | { 392 | m_FileSize1_Received = System.Convert.ToInt32(extrasDecoded["FILE_SIZE1"]); 393 | } 394 | else 395 | { 396 | m_FileSize1_Received = 0; 397 | } 398 | 399 | if (extrasDecoded.ContainsKey("FILE_SIZE2")) 400 | { 401 | m_FileSize2_Received = System.Convert.ToInt32(extrasDecoded["FILE_SIZE2"]); 402 | } 403 | else 404 | { 405 | m_FileSize2_Received = 0; 406 | } 407 | 408 | if (extrasDecoded.ContainsKey("LU")) 409 | { 410 | m_LicensingURL_Received = extrasDecoded["LU"]; 411 | } 412 | else 413 | { 414 | m_LicensingURL_Received = ""; 415 | } 416 | } 417 | } 418 | } 419 | -------------------------------------------------------------------------------- /Assets/Google Play License Verification/LVL_Example/CheckLVLButton.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 161df2ecdbab74db98ae98932c237a96 3 | MonoImporter: 4 | serializedVersion: 2 5 | defaultReferences: [] 6 | executionOrder: 0 7 | icon: {instanceID: 0} 8 | -------------------------------------------------------------------------------- /Assets/Google Play License Verification/LVL_Example/LVL_Example.unity: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unity-Technologies/GooglePlayLicenseVerification/910cd6f5a5a776b71a522907925160b50d233592/Assets/Google Play License Verification/LVL_Example/LVL_Example.unity -------------------------------------------------------------------------------- /Assets/Google Play License Verification/LVL_Example/LVL_Example.unity.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 77e46cb014a084478be5ec8022dbbfb4 3 | -------------------------------------------------------------------------------- /Assets/Google Play License Verification/LicenseVerification.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b18472991819c4d46acdb91432d0bee3 3 | -------------------------------------------------------------------------------- /Assets/Google Play License Verification/LicenseVerification/JavaSource.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 44fa479091879407c8b0e8eb8905fc5d 3 | -------------------------------------------------------------------------------- /Assets/Google Play License Verification/LicenseVerification/JavaSource/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 14 | 15 | 18 | 21 | 22 | 23 | 24 | 25 | 26 | 29 | 30 | 33 | 34 | 35 | 36 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /Assets/Google Play License Verification/LicenseVerification/JavaSource/AndroidManifest.xml.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 34f046d68d7e04493b444d96564690b6 3 | -------------------------------------------------------------------------------- /Assets/Google Play License Verification/LicenseVerification/JavaSource/build.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 29 | 30 | 31 | 40 | 41 | 42 | 43 | 47 | 48 | 60 | 61 | 62 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | -include "${proguard.config}" 113 | -injars ${plugin.jar} 114 | -outjars "${obfuscated.jar}" 115 | -libraryjars "${project.target.classpath.value}" 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 130 | 131 | 132 | 133 | 134 | Android Ant Build. Available targets: 135 | help: Displays this help. 136 | clean: Removes output files created by the build target. 137 | build: Builds the classes.jar (classes.txt) library for use with Unity Android. 138 | 139 | 140 | -------------------------------------------------------------------------------- /Assets/Google Play License Verification/LicenseVerification/JavaSource/build.xml.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: c6d9c50cc9c8f456f80d5da6c5655ae4 3 | -------------------------------------------------------------------------------- /Assets/Google Play License Verification/LicenseVerification/JavaSource/proguard-project.txt: -------------------------------------------------------------------------------- 1 | # To enable ProGuard in your project, edit project.properties 2 | # to define the proguard.config property as described in that file. 3 | # 4 | # Add project specific ProGuard rules here. 5 | # By default, the flags in this file are appended to flags specified 6 | # in ${sdk.dir}/tools/proguard/proguard-android.txt 7 | # You can edit the include path and order by changing the ProGuard 8 | # include property in project.properties. 9 | # 10 | # For more details, see 11 | # http://developer.android.com/guide/developing/tools/proguard.html 12 | 13 | # Add any project specific keep options here: 14 | 15 | # If your project uses WebView with JS, uncomment the following 16 | # and specify the fully qualified class name to the JavaScript interface 17 | # class: 18 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 19 | # public *; 20 | #} 21 | -target 1.6 22 | -optimizationpasses 2 23 | -dontusemixedcaseclassnames 24 | -dontskipnonpubliclibraryclasses 25 | -dontpreverify 26 | -keepattributes InnerClasses,EnclosingMethod 27 | 28 | -optimizations !code/simplification/arithmetic 29 | 30 | -keep class * { public ; !private *; } 31 | 32 | -------------------------------------------------------------------------------- /Assets/Google Play License Verification/LicenseVerification/JavaSource/proguard-project.txt.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: a05d7bd2ff1c34808a3005233d97a14e 3 | -------------------------------------------------------------------------------- /Assets/Google Play License Verification/LicenseVerification/JavaSource/project.properties: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by Android Tools. 2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED! 3 | # 4 | # This file must be checked in Version Control Systems. 5 | # 6 | # To customize properties used by the Ant build system edit 7 | # "ant.properties", and override values to adapt the script to your 8 | # project structure. 9 | # 10 | # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): 11 | #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt 12 | 13 | # Project target. 14 | target=android-19 15 | proguard.config=proguard-project.txt 16 | -------------------------------------------------------------------------------- /Assets/Google Play License Verification/LicenseVerification/JavaSource/project.properties.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 7bda734d65a184312907e33866395b22 3 | -------------------------------------------------------------------------------- /Assets/Google Play License Verification/LicenseVerification/JavaSource/src.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 034b9ba0ea9cc48b69598e74adb143de 3 | -------------------------------------------------------------------------------- /Assets/Google Play License Verification/LicenseVerification/JavaSource/src/com.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 5e3fd7a7293094997bc6567427d46820 3 | -------------------------------------------------------------------------------- /Assets/Google Play License Verification/LicenseVerification/JavaSource/src/com/unity3d.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 3d84254d705524f1a8db7c0393d1a1d3 3 | -------------------------------------------------------------------------------- /Assets/Google Play License Verification/LicenseVerification/JavaSource/src/com/unity3d/plugin.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 2c70df17d4feb4911b58393c97d03417 3 | -------------------------------------------------------------------------------- /Assets/Google Play License Verification/LicenseVerification/JavaSource/src/com/unity3d/plugin/lvl.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 7cadddd08054849509c844246a3ad857 3 | -------------------------------------------------------------------------------- /Assets/Google Play License Verification/LicenseVerification/JavaSource/src/com/unity3d/plugin/lvl/ServiceBinder.java: -------------------------------------------------------------------------------- 1 | package com.unity3d.plugin.lvl; 2 | 3 | import android.content.ComponentName; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import android.content.ServiceConnection; 7 | import android.os.IBinder; 8 | import android.os.RemoteException; 9 | 10 | public class ServiceBinder extends android.os.Binder implements ServiceConnection 11 | { 12 | private final Context mContext; 13 | public ServiceBinder(Context context) 14 | { 15 | mContext = context; 16 | } 17 | 18 | private Runnable mDone = null; 19 | private int mNonce; 20 | public void create(int nonce, Runnable done) 21 | { 22 | if (mDone != null) 23 | { 24 | destroy(); 25 | _arg0 = -1; 26 | mDone.run(); 27 | } 28 | mNonce = nonce; 29 | mDone = done; 30 | Intent serviceIntent = new Intent(SERVICE); 31 | serviceIntent.setPackage("com.android.vending"); 32 | if (mContext.bindService(serviceIntent, this, Context.BIND_AUTO_CREATE)) 33 | return; 34 | 35 | mDone.run(); 36 | } 37 | private void destroy() 38 | { 39 | mContext.unbindService(this); 40 | } 41 | 42 | private static final String SERVICE = "com.android.vending.licensing.ILicensingService"; 43 | public void onServiceConnected(ComponentName name, IBinder service) { 44 | android.os.Parcel _data = android.os.Parcel.obtain(); 45 | _data.writeInterfaceToken(SERVICE); 46 | _data.writeLong(mNonce); 47 | _data.writeString(mContext.getPackageName()); 48 | _data.writeStrongBinder(this); 49 | try { 50 | service.transact(1/*Stub.TRANSACTION_checkLicense*/, _data, null, IBinder.FLAG_ONEWAY); 51 | } 52 | catch (Exception ex) 53 | { 54 | ex.printStackTrace(); 55 | } 56 | finally { 57 | _data.recycle(); 58 | } 59 | } 60 | 61 | private static final String LISTENER = "com.android.vending.licensing.ILicenseResultListener"; 62 | public boolean onTransact(int code, android.os.Parcel data, 63 | android.os.Parcel reply, int flags) 64 | throws android.os.RemoteException { 65 | switch (code) { 66 | case INTERFACE_TRANSACTION: { 67 | reply.writeString(LISTENER); 68 | return true; 69 | } 70 | case 1/*TRANSACTION_verifyLicense*/: { 71 | data.enforceInterface(LISTENER); 72 | _arg0 = data.readInt(); 73 | _arg1 = data.readString(); 74 | _arg2 = data.readString(); 75 | mDone.run(); 76 | destroy(); 77 | return true; 78 | } 79 | } 80 | return super.onTransact(code, data, reply, flags); 81 | } 82 | 83 | public void onServiceDisconnected(ComponentName name) { 84 | } 85 | 86 | int _arg0; 87 | String _arg1; 88 | String _arg2; 89 | } -------------------------------------------------------------------------------- /Assets/Google Play License Verification/LicenseVerification/JavaSource/src/com/unity3d/plugin/lvl/ServiceBinder.java.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 49bec002661f24a2991cb359f9facfca 3 | -------------------------------------------------------------------------------- /Assets/Google Play License Verification/LicenseVerification/RSA.cs: -------------------------------------------------------------------------------- 1 | using System.Security.Cryptography; 2 | 3 | class RSA 4 | { 5 | public static void SimpleParseASN1(string publicKey, ref byte[] modulus, ref byte[] exponent) 6 | { 7 | byte[] publicKey64 = System.Convert.FromBase64String(publicKey); 8 | 9 | // The ASN1 structure for the public key looks like this: 10 | // 11 | // SubjectPublicKeyInfo ::= SEQUENCE { 12 | // algorithm AlgorithmIdentifier, 13 | // publicKey BIT STRING } 14 | // 15 | // Where the BIT STRING is a SEQUENCE of 2 INTEGERs (the modulus and the exponent) 16 | 17 | System.Type ASN1 = System.Type.GetType("Mono.Security.ASN1"); 18 | System.Reflection.ConstructorInfo Ctor = ASN1.GetConstructor(new System.Type[] { typeof(byte[]) }); 19 | System.Reflection.PropertyInfo Value = ASN1.GetProperty("Value"); 20 | System.Reflection.PropertyInfo Item = ASN1.GetProperty("Item"); 21 | 22 | object asn = Ctor.Invoke(new object[] { publicKey64 } ); 23 | object bits = Item.GetValue(asn, new object[] { 1 }); 24 | 25 | byte[] value = (byte[])Value.GetValue(bits, null); 26 | 27 | byte[] seq = new byte[value.Length-1]; 28 | System.Array.Copy(value, 1, seq, 0, value.Length-1); 29 | 30 | asn = Ctor.Invoke(new object[] { seq } ); 31 | 32 | object asn0 = Item.GetValue(asn, new object[]{ 0 }); 33 | object asn1 = Item.GetValue(asn, new object[]{ 1 }); 34 | 35 | modulus = (byte[])Value.GetValue(asn0, null); 36 | exponent = (byte[])Value.GetValue(asn1, null); 37 | 38 | // non-reflected version 39 | // Mono.Security.ASN1 asn = new Mono.Security.ASN1(publicKey64); 40 | // Mono.Security.ASN1 bits = asn[1]; 41 | // byte[] seq = new byte[bits.Length-1]; 42 | // System.Array.Copy(bits.Value, 1, seq, 0, bits.Length-1); 43 | // asn = new Mono.Security.ASN1(seq); 44 | // modulus = asn[0].Value; 45 | // exponent = asn[1].Value; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Assets/Google Play License Verification/LicenseVerification/RSA.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: c95cd0c7e82914341a6639ba727fa670 3 | MonoImporter: 4 | serializedVersion: 2 5 | defaultReferences: [] 6 | executionOrder: 0 7 | icon: {instanceID: 0} 8 | -------------------------------------------------------------------------------- /Assets/Google Play License Verification/LicenseVerification/classes_jar.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unity-Technologies/GooglePlayLicenseVerification/910cd6f5a5a776b71a522907925160b50d233592/Assets/Google Play License Verification/LicenseVerification/classes_jar.txt -------------------------------------------------------------------------------- /Assets/Google Play License Verification/LicenseVerification/classes_jar.txt.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: f12e4552e3edb48cf9878d51457decc6 3 | -------------------------------------------------------------------------------- /Assets/Plugins.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: f3e12cfc3251a43718bc59752dac5c44 3 | -------------------------------------------------------------------------------- /Assets/Plugins/Android.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 6a3445966190341288bccfc010d4c3d3 3 | -------------------------------------------------------------------------------- /Assets/Plugins/Android/GooglePlayLicenseVerification/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Assets/Plugins/Android/GooglePlayLicenseVerification/project.properties: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by Android Tools. 2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED! 3 | # 4 | # This file must be checked in Version Control Systems. 5 | # 6 | # To customize properties used by the Ant build system use, 7 | # "ant.properties", and override values to adapt the script to your 8 | # project structure. 9 | android.library=true 10 | 11 | # Project target. 12 | target=android-19 13 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Erik Hemming - Unity Technologies 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /ProjectSettings/AudioManager.asset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unity-Technologies/GooglePlayLicenseVerification/910cd6f5a5a776b71a522907925160b50d233592/ProjectSettings/AudioManager.asset -------------------------------------------------------------------------------- /ProjectSettings/DynamicsManager.asset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unity-Technologies/GooglePlayLicenseVerification/910cd6f5a5a776b71a522907925160b50d233592/ProjectSettings/DynamicsManager.asset -------------------------------------------------------------------------------- /ProjectSettings/EditorBuildSettings.asset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unity-Technologies/GooglePlayLicenseVerification/910cd6f5a5a776b71a522907925160b50d233592/ProjectSettings/EditorBuildSettings.asset -------------------------------------------------------------------------------- /ProjectSettings/EditorSettings.asset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unity-Technologies/GooglePlayLicenseVerification/910cd6f5a5a776b71a522907925160b50d233592/ProjectSettings/EditorSettings.asset -------------------------------------------------------------------------------- /ProjectSettings/InputManager.asset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unity-Technologies/GooglePlayLicenseVerification/910cd6f5a5a776b71a522907925160b50d233592/ProjectSettings/InputManager.asset -------------------------------------------------------------------------------- /ProjectSettings/NavMeshLayers.asset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unity-Technologies/GooglePlayLicenseVerification/910cd6f5a5a776b71a522907925160b50d233592/ProjectSettings/NavMeshLayers.asset -------------------------------------------------------------------------------- /ProjectSettings/NetworkManager.asset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unity-Technologies/GooglePlayLicenseVerification/910cd6f5a5a776b71a522907925160b50d233592/ProjectSettings/NetworkManager.asset -------------------------------------------------------------------------------- /ProjectSettings/ProjectSettings.asset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unity-Technologies/GooglePlayLicenseVerification/910cd6f5a5a776b71a522907925160b50d233592/ProjectSettings/ProjectSettings.asset -------------------------------------------------------------------------------- /ProjectSettings/QualitySettings.asset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unity-Technologies/GooglePlayLicenseVerification/910cd6f5a5a776b71a522907925160b50d233592/ProjectSettings/QualitySettings.asset -------------------------------------------------------------------------------- /ProjectSettings/TagManager.asset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unity-Technologies/GooglePlayLicenseVerification/910cd6f5a5a776b71a522907925160b50d233592/ProjectSettings/TagManager.asset -------------------------------------------------------------------------------- /ProjectSettings/TimeManager.asset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unity-Technologies/GooglePlayLicenseVerification/910cd6f5a5a776b71a522907925160b50d233592/ProjectSettings/TimeManager.asset -------------------------------------------------------------------------------- /README.txt: -------------------------------------------------------------------------------- 1 | Google Play Application License Verification 2 | ---------------------------------------------- 3 | 4 | Author: Erik Hemming / Unity Technologies (twitter: _eriq_) 5 | 6 | About the plugin 7 | ---------------- 8 | 9 | This is an example of how Google Play Application Licensing [1] can be integrated into any Unity application, with a minimum of additional Java code (everything except the Service Binder is written in C#). The plugin also loads the Java code loaded at runtime [2] (i.e. the classes.dex in the .apk does no include any LVL code). 10 | 11 | The code can also be used in conjunction with an online verification mechanism [3], where the result of the LVL check is sent to a server for proper inspection. 12 | 13 | For detailed explanation on Google Play License Verification mechanism, please have at the very informative 'Evading Pirates and Stopping Vampires' [4] presentation from Google I/O 2011. 14 | 15 | [1] http://developer.android.com/guide/market/licensing/index.html 16 | [2] http://android-developers.blogspot.com/2011/07/custom-class-loading-in-dalvik.html 17 | [3] http://code.google.com/p/android-market-license-verification 18 | [4] http://www.google.com/events/io/2011/sessions/evading-pirates-and-stopping-vampires-using-license-verification-library-in-app-billing-and-app-engine.html 19 | 20 | 21 | How to use 22 | ---------- 23 | 24 | The logic of the plugin is centered around a C# script file, CheckLVLButton.cs, which serves as an example how the LVL check could be integrated in an existing application. This single C# file is responsible for loading the Java code, setting up and calling the LVL backend, and finally deciphering the result. 25 | 26 | Please note that the plugin cannot work without the public LVL key, which can be obtained from the publishing account at Google Play. Have a look in CheckLVLButton.cs for the line 27 | m_PublicKey_Base64 = ""; 28 | and replace the string with your personal LVL key. 29 | 30 | 31 | How to (Re-)Compile the Java source 32 | ----------------------------------- 33 | 34 | First, make sure you have the JDK [1] and ANT [2] installed - while OSX usually comes with these pre-installed, Windows users need to download and install them manually. Then, make sure you have a fairly recent Android SDK [3] - at least API-15. 35 | 36 | Before compiling the code the project must be initialized to have the local.properties file updated with path to the (local) Android SDK: 37 | 38 | $ cd /Assets/LicenseVerification/JavaSource 39 | $ cp ../../Plugins/Android/AndroidManifest.xml . 40 | $ android update project -p . 41 | 42 | Now you can use 43 | $ ant help to display the help message. 44 | $ ant clean to remove output files created by the build target. 45 | $ ant build to build the classes.jar (classes.txt) library for use with Unity Android. 46 | 47 | [1] http://www.oracle.com/technetwork/java/javase/downloads/index.html 48 | [2] http://ant.apache.org/ 49 | [3] http://developer.android.com/sdk/index.html 50 | 51 | 52 | Detailed instructions how the plugin was created 53 | ------------------------------------------------ 54 | 55 | 1) Add the patched manifest 56 | 57 | 1.1) Create the Android plugins directory. 58 | 59 | $ mkdir /Assets/Plugins/Android 60 | $ cd /Assets/Plugins/Android 61 | 62 | 1.2) Copy the default AndroidManifest.xml used by Unity. This is the normal path used on OSX - the Windows path starts with C:\Program Files\Unity\Data\ 63 | 64 | $ cp /Applications/Unity/Unity.app/Contents/PlaybackEngines/AndroidPlayer/AndroidManifest.xml . 65 | 66 | 1.3) Edit the AndroidManifest.xml, and add permissions related to LVL. 67 | 68 | $ diff -rupN /Applications/Unity/Unity.app/Contents/PlaybackEngines/AndroidPlayer/AndroidManifest.xml AndroidManifest.xml 69 | --- /Applications/Unity/Unity.app/Contents/PlaybackEngines/AndroidPlayer/AndroidManifest.xml 2012-03-27 22:25:58.000000000 +0200 70 | +++ AndroidManifest.xml 2012-03-30 18:41:32.000000000 +0200 71 | @@ -39,4 +39,9 @@ 72 | android:configChanges="fontScale|keyboard|keyboardHidden|locale|mnc|mcc|navigation|orientation|screenLayout|screenSize|smallestScreenSize|uiMode|touchscreen"> 73 | 74 | 75 | + 76 | + 77 | + 78 | + 79 | + 80 | 81 | 82 | 83 | 2) Create JavaSource project 84 | 85 | 2.1) Create ANT project 86 | 87 | $ mkdir /Assets/LicenseVerification/JavaSource 88 | $ cd /Assets/LicenseVerification/JavaSource 89 | $ android create project -t android-15 -p . -k com.unity3d.plugin.lvl -a dummy_activity 90 | 91 | 2.2) Remove generated files which we don't use. 92 | 93 | $ rm -rf res/ 94 | $ rm src/com/unity3d/plugin/lvl/dummy_activity.java 95 | 96 | 97 | 2.3) Edit build.xml to add support for obfuscated .jar generation 98 | 99 | $ diff -rupN build_dist.xml build.xml 100 | --- build_dist.xml 2012-03-30 18:36:07.000000000 +0200 101 | +++ build.xml 2012-03-31 11:38:00.000000000 +0200 102 | @@ -80,4 +80,60 @@ 103 | 104 | 105 | 106 | + 107 | + 108 | + 109 | + 110 | + 111 | + 112 | + 113 | + 118 | + 119 | + 120 | + 121 | + 122 | + 123 | + 124 | + 125 | + 126 | + 127 | + 128 | + 129 | + 130 | + 131 | + 132 | + 133 | + 134 | + 135 | + -include "${proguard.config}" 136 | + -injars ${plugin.jar} 137 | + -outjars "${obfuscated.jar}" 138 | + -libraryjars "${android.libraryjars}" 139 | + 140 | + 141 | + 142 | + 143 | + 144 | + 145 | + 146 | + 147 | + 153 | + 154 | + 155 | + 156 | + 157 | + Android Ant Build. Available targets: 158 | + help: Displays this help. 159 | + clean: Removes output files created by the build target. 160 | + build: Builds the classes.jar (classes.txt) library for use with Unity Android. 161 | + 162 | 163 | 164 | 2.4) Enable ProGuard obfuscation by adding a line to project.properties : 165 | 166 | proguard.config=proguard-project.txt 167 | 168 | 2.5) Edit the proguard-project.txt file, and add : 169 | 170 | -target 1.6 171 | -optimizationpasses 2 172 | -dontusemixedcaseclassnames 173 | -dontskipnonpubliclibraryclasses 174 | -dontpreverify 175 | -keepattributes InnerClasses,EnclosingMethod 176 | 177 | -optimizations !code/simplification/arithmetic 178 | 179 | -keep class * { public ; !private *; } 180 | 181 | 3) Add some C# code load the Java classes_jar.txt 182 | 183 | byte[] classes_jar = ServiceBinder.bytes; 184 | 185 | System.IO.File.WriteAllBytes(Application.temporaryCachePath + "/classes.jar", classes_jar); 186 | System.IO.Directory.CreateDirectory(Application.temporaryCachePath + "/odex"); 187 | 188 | m_Activity = new AndroidJavaClass("com.unity3d.player.UnityPlayer").GetStatic("currentActivity"); 189 | AndroidJavaObject dcl = new AndroidJavaObject("dalvik.system.DexClassLoader", 190 | Application.temporaryCachePath + "/classes.jar", 191 | Application.temporaryCachePath + "/odex", 192 | null, 193 | m_Activity.Call("getClassLoader")); 194 | m_LVLCheckType = dcl.Call("findClass", "com.unity3d.plugin.lvl.ServiceBinder"); 195 | --------------------------------------------------------------------------------