first commit
This commit is contained in:
107
lib/utils/aes_decrypt.dart
Normal file
107
lib/utils/aes_decrypt.dart
Normal file
@@ -0,0 +1,107 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:pointycastle/export.dart';
|
||||
|
||||
class AesDecrypt {
|
||||
static const String _cn = 'i0.a';
|
||||
|
||||
static const List<int> _obf = <int>[
|
||||
0xED,
|
||||
0x7A,
|
||||
0x7F,
|
||||
0x3F,
|
||||
0x9E,
|
||||
0x6E,
|
||||
0xE7,
|
||||
0xF9,
|
||||
0x06,
|
||||
0xA0,
|
||||
0xA8,
|
||||
0xE4,
|
||||
0x16,
|
||||
0xE5,
|
||||
0x69,
|
||||
0x70,
|
||||
];
|
||||
|
||||
/// Java String.hashCode() 等价实现(32-bit signed)
|
||||
static int javaStringHashCode(String str) {
|
||||
int h = 0;
|
||||
for (int i = 0; i < str.length; i++) {
|
||||
h = _toSigned32(h * 31 + str.codeUnitAt(i));
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
static int _toSigned32(int value) {
|
||||
value &= 0xFFFFFFFF;
|
||||
if ((value & 0x80000000) != 0) {
|
||||
return value - 0x100000000;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
static int _unsignedRightShift32(int value, int shift) {
|
||||
return (value & 0xFFFFFFFF) >> shift;
|
||||
}
|
||||
|
||||
static Uint8List deriveKeyBytes16() {
|
||||
final int seed = _toSigned32(javaStringHashCode(_cn) ^ 0x5f3759df);
|
||||
|
||||
final Uint8List keyBytes = Uint8List(16);
|
||||
|
||||
for (int i = 0; i < 16; i++) {
|
||||
final int shift = (i & 3) * 8;
|
||||
final int shifted = _unsignedRightShift32(seed, shift);
|
||||
final int mask = (((shifted & 0xff) ^ ((i * 17 + 31) & 0xff)) & 0xff);
|
||||
keyBytes[i] = (_obf[i] ^ mask) & 0xff;
|
||||
}
|
||||
|
||||
return keyBytes;
|
||||
}
|
||||
|
||||
static Uint8List _aesEcbPkcs7(bool forEncryption, Uint8List input) {
|
||||
final key = deriveKeyBytes16();
|
||||
|
||||
final cipher = PaddedBlockCipherImpl(
|
||||
PKCS7Padding(),
|
||||
ECBBlockCipher(AESEngine()),
|
||||
);
|
||||
|
||||
cipher.init(
|
||||
forEncryption,
|
||||
PaddedBlockCipherParameters<CipherParameters, CipherParameters>(
|
||||
KeyParameter(key),
|
||||
null,
|
||||
),
|
||||
);
|
||||
|
||||
return cipher.process(input);
|
||||
}
|
||||
|
||||
/// 对应 JS / Java encrypt(String) -> Base64.NO_WRAP
|
||||
static String encrypt(String plainText) {
|
||||
try {
|
||||
final Uint8List input = Uint8List.fromList(utf8.encode(plainText));
|
||||
final Uint8List encrypted = _aesEcbPkcs7(true, input);
|
||||
return base64.encode(encrypted);
|
||||
} catch (_) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/// 对应 JS / Java decrypt(String base64Encrypted) -> UTF-8
|
||||
static String? decrypt(String base64Encrypted) {
|
||||
try {
|
||||
final Uint8List encrypted = Uint8List.fromList(
|
||||
base64.decode(base64Encrypted),
|
||||
);
|
||||
final Uint8List decrypted = _aesEcbPkcs7(false, encrypted);
|
||||
final String text = utf8.decode(decrypted, allowMalformed: false);
|
||||
return text.isNotEmpty ? text : null;
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
124
lib/utils/device_info.dart
Normal file
124
lib/utils/device_info.dart
Normal file
@@ -0,0 +1,124 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:adjust_sdk/adjust.dart';
|
||||
import 'package:device_info_plus/device_info_plus.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
import 'package:play_install_referrer/play_install_referrer.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
class DeviceInfo {
|
||||
static const String _spInstallReferrer = "installReferrer";
|
||||
static const String _spAdjustAttribution = "adjustAttribution";
|
||||
DeviceInfo._();
|
||||
static final DeviceInfo I = DeviceInfo._();
|
||||
Future<Map<String, dynamic>> deviceInfo() async {
|
||||
final Map<String, dynamic> json = {};
|
||||
|
||||
if (!Platform.isAndroid) {
|
||||
return json;
|
||||
}
|
||||
|
||||
final packageInfo = await PackageInfo.fromPlatform();
|
||||
final devicePlugin = DeviceInfoPlugin();
|
||||
final androidInfo = await devicePlugin.androidInfo;
|
||||
|
||||
json["packageName"] = packageInfo.packageName;
|
||||
json["versionName"] = packageInfo.version;
|
||||
json["versionCode"] = packageInfo.buildNumber;
|
||||
json["osVersion"] = androidInfo.version.release;
|
||||
json["sdkInt"] = androidInfo.version.sdkInt.toString();
|
||||
json["brand"] = androidInfo.brand;
|
||||
json["model"] = androidInfo.model;
|
||||
json["device"] = androidInfo.device;
|
||||
|
||||
try {
|
||||
final results = await Future.wait<String?>([
|
||||
getInstallReferrer(),
|
||||
getAdjustAdId(),
|
||||
getAdjustAttribution(),
|
||||
]);
|
||||
|
||||
final installReferrer = results[0];
|
||||
final adid = results[1];
|
||||
final adjustAttributionJson = results[2];
|
||||
|
||||
if (installReferrer != null && installReferrer.isNotEmpty) {
|
||||
json["installReferrer"] = installReferrer;
|
||||
}
|
||||
|
||||
if (adid != null && adid.isNotEmpty) {
|
||||
json["adjustAdId"] = adid;
|
||||
}
|
||||
|
||||
if (adjustAttributionJson != null && adjustAttributionJson.isNotEmpty) {
|
||||
json["adjustInstall"] = adjustAttributionJson;
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint("[DeviceInfo] load adjust/referrer failed: $e");
|
||||
}
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
Future<String?> getInstallReferrer() async {
|
||||
try {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final cachedReferrer = prefs.getString(_spInstallReferrer);
|
||||
if (cachedReferrer != null && cachedReferrer.isNotEmpty) {
|
||||
return cachedReferrer;
|
||||
}
|
||||
|
||||
final referrerDetails = await PlayInstallReferrer.installReferrer;
|
||||
final referrer = referrerDetails.installReferrer;
|
||||
|
||||
if (referrer != null && referrer.isNotEmpty) {
|
||||
await prefs.setString(_spInstallReferrer, referrer);
|
||||
}
|
||||
return referrer;
|
||||
} catch (e) {
|
||||
debugPrint("[DeviceInfo] get install referrer failed: $e");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Future<String?> getAdjustAdId() async {
|
||||
try {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final cached = prefs.getString("adjustAdId");
|
||||
if (cached != null && cached.isNotEmpty) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
final adid = await Adjust.getAdidWithTimeout(2000);
|
||||
if (adid != null && adid.isNotEmpty) {
|
||||
await prefs.setString("adjustAdId", adid);
|
||||
}
|
||||
return adid;
|
||||
} catch (e) {
|
||||
debugPrint("[DeviceInfo] get adjust adid failed: $e");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Future<String?> getAdjustAttribution() async {
|
||||
try {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final cachedAttribution = prefs.getString(_spAdjustAttribution);
|
||||
if (cachedAttribution != null && cachedAttribution.isNotEmpty) {
|
||||
return cachedAttribution;
|
||||
}
|
||||
|
||||
final attribution = await Adjust.getAttributionWithTimeout(2000);
|
||||
final attributionJson = attribution?.jsonResponse;
|
||||
|
||||
if (attributionJson != null && attributionJson.isNotEmpty) {
|
||||
await prefs.setString(_spAdjustAttribution, attributionJson);
|
||||
}
|
||||
return attributionJson;
|
||||
} catch (e) {
|
||||
debugPrint("[DeviceInfo] get adjust attribution failed: $e");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
57
lib/utils/http_util.dart
Normal file
57
lib/utils/http_util.dart
Normal file
@@ -0,0 +1,57 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:http/http.dart' as http;
|
||||
|
||||
import 'aes_decrypt.dart';
|
||||
|
||||
Future<dynamic> remoteInfo(String info) async {
|
||||
const String urlInfo =
|
||||
'ueytXddgndQ0JKZhjGTnMxSgQxnO4xT+dZc9PgCEw4lhZ28TUxqzoupSMIewnKOSJSgmlDi1Xj084F7/wMUUWg==';
|
||||
// const String urlInfo =
|
||||
// "Pq2OF021zqiIoq2ViKE3FDuoTvR4owYUiN+/7wU9ZpfV+JogViY3cv6lGw3/2aEQcULb+mXEzKPU78+bTrLwSA==";
|
||||
final String? url = AesDecrypt.decrypt(urlInfo);
|
||||
final String request = AesDecrypt.encrypt(info);
|
||||
final Map<String, dynamic> requestInfo = <String, dynamic>{
|
||||
'request': request,
|
||||
};
|
||||
|
||||
if (url == null || url.isEmpty) {
|
||||
throw Exception('HTTP error url');
|
||||
}
|
||||
|
||||
return postJson(url, requestInfo);
|
||||
}
|
||||
|
||||
Future<dynamic> postJson(
|
||||
String url,
|
||||
Map<String, dynamic> data, {
|
||||
int timeout = 8000,
|
||||
}) async {
|
||||
http.Response res;
|
||||
|
||||
try {
|
||||
res = await http
|
||||
.post(
|
||||
Uri.parse(url),
|
||||
headers: <String, String>{'Content-Type': 'application/json'},
|
||||
body: jsonEncode(data),
|
||||
)
|
||||
.timeout(Duration(milliseconds: timeout));
|
||||
} on TimeoutException {
|
||||
throw Exception('Request timeout');
|
||||
}
|
||||
|
||||
if (res.statusCode < 200 || res.statusCode >= 300) {
|
||||
throw Exception('HTTP ${res.statusCode}');
|
||||
}
|
||||
|
||||
final String rs = res.body;
|
||||
final String? jsonStr = AesDecrypt.decrypt(rs);
|
||||
|
||||
if (jsonStr == null || jsonStr.isEmpty) {
|
||||
throw Exception('Decrypt failed');
|
||||
}
|
||||
|
||||
return jsonDecode(jsonStr);
|
||||
}
|
||||
125
lib/utils/redirect_url_resolver.dart
Normal file
125
lib/utils/redirect_url_resolver.dart
Normal file
@@ -0,0 +1,125 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
const SP_ORIGIN = "redirect_cache_origin";
|
||||
const SP_RESOLVED = "redirect_cache_resolved";
|
||||
|
||||
Future<void> sleep(int ms) async {
|
||||
await Future.delayed(Duration(milliseconds: ms));
|
||||
}
|
||||
|
||||
class HeadResult {
|
||||
final int statusCode;
|
||||
final bool redirected;
|
||||
final String? location;
|
||||
|
||||
HeadResult(this.statusCode, this.redirected, this.location);
|
||||
}
|
||||
|
||||
Future<HeadResult?> headRequest(String url) async {
|
||||
try {
|
||||
final client = HttpClient();
|
||||
final request = await client.openUrl("HEAD", Uri.parse(url));
|
||||
|
||||
request.followRedirects = false;
|
||||
request.headers.set("Cache-Control", "no-cache");
|
||||
request.headers.set("Pragma", "no-cache");
|
||||
|
||||
final response = await request.close();
|
||||
|
||||
final location = response.headers.value(HttpHeaders.locationHeader);
|
||||
|
||||
final redirected = response.statusCode >= 300 && response.statusCode < 400;
|
||||
|
||||
return HeadResult(response.statusCode, redirected, location);
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> canConnect(String url) async {
|
||||
try {
|
||||
final res = await headRequest(url);
|
||||
return res != null;
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<String> getRedirect(String url) async {
|
||||
try {
|
||||
final res = await headRequest(url);
|
||||
|
||||
if (res == null) {
|
||||
return url;
|
||||
}
|
||||
|
||||
debugPrint(
|
||||
"[RedirectResolver] head $url "
|
||||
"status=${res.statusCode} "
|
||||
"redirected=${res.redirected} "
|
||||
"location=${res.location}",
|
||||
);
|
||||
|
||||
if (res.redirected && res.location != null) {
|
||||
try {
|
||||
return Uri.parse(url).resolve(res.location!).toString();
|
||||
} catch (_) {
|
||||
return res.location!;
|
||||
}
|
||||
}
|
||||
|
||||
return url;
|
||||
} catch (_) {
|
||||
return url;
|
||||
}
|
||||
}
|
||||
|
||||
Future<String?> resolveWithRetry(String originUrl) async {
|
||||
for (int i = 0; i < 3; i++) {
|
||||
final redirect = await getRedirect(originUrl);
|
||||
|
||||
if (redirect == originUrl) {
|
||||
return originUrl;
|
||||
}
|
||||
|
||||
if (await canConnect(redirect)) {
|
||||
return redirect;
|
||||
}
|
||||
|
||||
if (i < 2) {
|
||||
await sleep(100);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<String> resolveRedirectUrl(String originUrl) async {
|
||||
try {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
|
||||
final cacheOrigin = prefs.getString(SP_ORIGIN);
|
||||
final cacheResolved = prefs.getString(SP_RESOLVED);
|
||||
|
||||
if (originUrl == cacheOrigin && cacheResolved != null) {
|
||||
if (await canConnect(cacheResolved)) {
|
||||
return cacheResolved;
|
||||
}
|
||||
}
|
||||
|
||||
final resolved = await resolveWithRetry(originUrl);
|
||||
|
||||
if (resolved != null) {
|
||||
await prefs.setString(SP_ORIGIN, originUrl);
|
||||
await prefs.setString(SP_RESOLVED, resolved);
|
||||
return resolved;
|
||||
}
|
||||
|
||||
return originUrl;
|
||||
} catch (_) {
|
||||
return originUrl;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user