diff --git a/android/gradle.properties b/android/gradle.properties index e96108c..4c2086c 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -4,3 +4,5 @@ android.useAndroidX=true android.newDsl=false # This builtInKotlin flag was added by the Flutter template android.builtInKotlin=false +kotlin.incremental=false +kotlin.daemon.jvm.options=-Xmx2G diff --git a/lib/config/strings.dart b/lib/config/strings.dart index c0e5280..32283c3 100644 --- a/lib/config/strings.dart +++ b/lib/config/strings.dart @@ -119,17 +119,18 @@ class AppStrings { // Settings static const titleServerSettings = 'Title Server 设置'; - static const required = '必填'; - static const optional = '可选 (有默认值)'; + static const titleServerSection = 'Title Server'; + static const authServerSettings = 'Auth Server'; + static const machineSettings = '机器信息'; static const save = '保存'; static const labelTitleServerUrl = 'Title Server URL'; - static const hintTitleServerUrl = 'http://maimai-gm.wahlap.com:42081'; + static const hintTitleServerUrl = 'https://maimai-gm.wahlap.com:42081/Maimai2Servlet/'; static const labelAesKey = 'AES Key'; static const hintAesKey = 'your aes key string'; static const labelAesIv = 'AES IV'; static const hintAesIv = 'your aes iv string'; static const labelClientId = 'Client ID'; - static const hintClientId = 'your client id'; + static const hintClientId = 'A63E01C2805'; static const labelRegionId = 'Region ID'; static const hintRegionId = '1'; static const labelPlaceId = 'Place ID'; @@ -138,6 +139,16 @@ class AppStrings { static const hintObfuscateParam = 'LatuAa81'; static const labelApiVersion = 'API Version (Mai-Encoding)'; static const hintApiVersion = '1.53'; + static const labelRegionName = 'Region 名称'; + static const hintRegionName = '北京'; + static const labelPlaceName = 'Place 名称'; + static const hintPlaceName = '插电师北京王府井银泰店'; + static const labelKeychipId = 'Keychip ID'; + static const hintKeychipId = 'A63E-01C28055905'; + static const labelAimeUrl = 'Aime URL'; + static const hintAimeUrl = 'http://ai.sys-allnet.cn/wc_aime/api/get_data'; + static const labelAimeSalt = 'Aime Salt'; + static const hintAimeSalt = 'API Key 派生用 Salt'; static String fieldRequired(String label) => '$label 不能为空'; // About diff --git a/lib/config/title_server_config.dart b/lib/config/title_server_config.dart index 21c66c8..e378f80 100644 --- a/lib/config/title_server_config.dart +++ b/lib/config/title_server_config.dart @@ -11,6 +11,12 @@ class TitleServerConfig { final String clientId; final int regionId; final int placeId; + final String regionName; + final String placeName; + final String keychipId; + final String aimeUrl; + final String aimeSalt; + final String openGameID; const TitleServerConfig({ required this.titleServerUrl, @@ -21,7 +27,13 @@ class TitleServerConfig { required this.clientId, this.regionId = 1, this.placeId = 1403, - }); + this.regionName = '', + this.placeName = '', + this.keychipId = '', + this.aimeUrl = '', + this.aimeSalt = '', + String? openGameID, + }) : openGameID = openGameID ?? 'MAID'; List get aesKeyBytes => utf8.encode(aesKey); List get aesIvBytes => utf8.encode(aesIv); @@ -35,6 +47,12 @@ class TitleServerConfig { 'clientId': clientId, 'regionId': regionId, 'placeId': placeId, + 'regionName': regionName, + 'placeName': placeName, + 'keychipId': keychipId, + 'aimeUrl': aimeUrl, + 'aimeSalt': aimeSalt, + 'openGameID': openGameID, }; factory TitleServerConfig.fromJson(Map json) { @@ -47,6 +65,12 @@ class TitleServerConfig { clientId: json['clientId'] as String? ?? '', regionId: json['regionId'] as int? ?? 1, placeId: json['placeId'] as int? ?? 1403, + regionName: json['regionName'] as String? ?? '', + placeName: json['placeName'] as String? ?? '', + keychipId: json['keychipId'] as String? ?? '', + aimeUrl: json['aimeUrl'] as String? ?? '', + aimeSalt: json['aimeSalt'] as String? ?? '', + openGameID: json['openGameID'] as String? ?? 'MAID', ); } } diff --git a/lib/pages/settings_page.dart b/lib/pages/settings_page.dart index 9a23273..7b8f3fe 100644 --- a/lib/pages/settings_page.dart +++ b/lib/pages/settings_page.dart @@ -22,6 +22,11 @@ class _SettingsPageState extends State { final _placeIdController = TextEditingController(text: '1403'); final _obfuscateController = TextEditingController(text: 'LatuAa81'); final _apiVersionController = TextEditingController(text: '1.53'); + final _regionNameController = TextEditingController(); + final _placeNameController = TextEditingController(); + final _keychipIdController = TextEditingController(); + final _aimeUrlController = TextEditingController(); + final _aimeSaltController = TextEditingController(); @override void initState() { @@ -36,6 +41,11 @@ class _SettingsPageState extends State { _placeIdController.text = '${config.placeId}'; _obfuscateController.text = config.obfuscateParam; _apiVersionController.text = config.apiVersion; + _regionNameController.text = config.regionName; + _placeNameController.text = config.placeName; + _keychipIdController.text = config.keychipId; + _aimeUrlController.text = config.aimeUrl; + _aimeSaltController.text = config.aimeSalt; } } @@ -49,6 +59,11 @@ class _SettingsPageState extends State { _placeIdController.dispose(); _obfuscateController.dispose(); _apiVersionController.dispose(); + _regionNameController.dispose(); + _placeNameController.dispose(); + _keychipIdController.dispose(); + _aimeUrlController.dispose(); + _aimeSaltController.dispose(); super.dispose(); } @@ -64,6 +79,11 @@ class _SettingsPageState extends State { placeId: int.tryParse(_placeIdController.text.trim()) ?? 1403, obfuscateParam: _obfuscateController.text.trim(), apiVersion: _apiVersionController.text.trim(), + regionName: _regionNameController.text.trim(), + placeName: _placeNameController.text.trim(), + keychipId: _keychipIdController.text.trim(), + aimeUrl: _aimeUrlController.text.trim(), + aimeSalt: _aimeSaltController.text.trim(), )); if (mounted) Navigator.of(context).pop(); @@ -88,64 +108,36 @@ class _SettingsPageState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - _buildSection(theme, AppStrings.required, [ - _buildField( - controller: _urlController, - label: AppStrings.labelTitleServerUrl, - hint: AppStrings.hintTitleServerUrl, - required: true, - ), + _buildSection(theme, AppStrings.titleServerSection, [ + _buildField(controller: _urlController, label: AppStrings.labelTitleServerUrl, hint: AppStrings.hintTitleServerUrl), const SizedBox(height: 14), - _buildField( - controller: _aesKeyController, - label: AppStrings.labelAesKey, - hint: AppStrings.hintAesKey, - required: true, - ), + _buildField(controller: _aesKeyController, label: AppStrings.labelAesKey, hint: AppStrings.hintAesKey), const SizedBox(height: 14), - _buildField( - controller: _aesIvController, - label: AppStrings.labelAesIv, - hint: AppStrings.hintAesIv, - required: true, - ), + _buildField(controller: _aesIvController, label: AppStrings.labelAesIv, hint: AppStrings.hintAesIv), const SizedBox(height: 14), - _buildField( - controller: _clientIdController, - label: AppStrings.labelClientId, - hint: AppStrings.hintClientId, - required: true, - ), + _buildField(controller: _clientIdController, label: AppStrings.labelClientId, hint: AppStrings.hintClientId), + const SizedBox(height: 14), + _buildField(controller: _obfuscateController, label: AppStrings.labelObfuscateParam, hint: AppStrings.hintObfuscateParam), + const SizedBox(height: 14), + _buildField(controller: _apiVersionController, label: AppStrings.labelApiVersion, hint: AppStrings.hintApiVersion), ]), const SizedBox(height: 24), - _buildSection(theme, AppStrings.optional, [ - _buildField( - controller: _regionIdController, - label: AppStrings.labelRegionId, - hint: AppStrings.hintRegionId, - required: false, - ), + _buildSection(theme, AppStrings.machineSettings, [ + _buildField(controller: _regionIdController, label: AppStrings.labelRegionId, hint: AppStrings.hintRegionId), const SizedBox(height: 14), - _buildField( - controller: _placeIdController, - label: AppStrings.labelPlaceId, - hint: AppStrings.hintPlaceId, - required: false, - ), + _buildField(controller: _regionNameController, label: AppStrings.labelRegionName, hint: AppStrings.hintRegionName), const SizedBox(height: 14), - _buildField( - controller: _obfuscateController, - label: AppStrings.labelObfuscateParam, - hint: AppStrings.hintObfuscateParam, - required: false, - ), + _buildField(controller: _placeIdController, label: AppStrings.labelPlaceId, hint: AppStrings.hintPlaceId), const SizedBox(height: 14), - _buildField( - controller: _apiVersionController, - label: AppStrings.labelApiVersion, - hint: AppStrings.hintApiVersion, - required: false, - ), + _buildField(controller: _placeNameController, label: AppStrings.labelPlaceName, hint: AppStrings.hintPlaceName), + ]), + const SizedBox(height: 24), + _buildSection(theme, AppStrings.authServerSettings, [ + _buildField(controller: _keychipIdController, label: AppStrings.labelKeychipId, hint: AppStrings.hintKeychipId), + const SizedBox(height: 14), + _buildField(controller: _aimeUrlController, label: AppStrings.labelAimeUrl, hint: AppStrings.hintAimeUrl), + const SizedBox(height: 14), + _buildField(controller: _aimeSaltController, label: AppStrings.labelAimeSalt, hint: AppStrings.hintAimeSalt), ]), const SizedBox(height: 32), FilledButton.icon( @@ -200,7 +192,6 @@ class _SettingsPageState extends State { required TextEditingController controller, required String label, required String hint, - required bool required, }) { return TextFormField( controller: controller, @@ -212,9 +203,7 @@ class _SettingsPageState extends State { ), contentPadding: const EdgeInsets.all(14), ), - validator: required - ? (v) => (v == null || v.trim().isEmpty) ? AppStrings.fieldRequired(label) : null - : null, + validator: (v) => (v == null || v.trim().isEmpty) ? AppStrings.fieldRequired(label) : null, ); } } diff --git a/lib/services/api_service.dart b/lib/services/api_service.dart index 54476b6..368a13c 100644 --- a/lib/services/api_service.dart +++ b/lib/services/api_service.dart @@ -3,6 +3,8 @@ import 'dart:convert'; import 'package:crypto/crypto.dart'; import 'package:http/http.dart' as http; +import '../config/title_server_config.dart'; + class LoginResult { final bool success; final int errorId; @@ -20,12 +22,24 @@ class LoginResult { } class ApiService { - static const String chimeSalt = 'XcW5FW4cPArBXEk4vzKz3CIrMuA5EVVW'; - static const String baseUrl = 'http://ai.sys-allnet.cn'; + final TitleServerConfig? _config; - String chipId; + ApiService([this._config]); - ApiService({this.chipId = 'A63E-01C28055905'}); + TitleServerConfig? get _cfg => _config ?? TitleServerConfigHolder().config; + String get _chipId { + final v = _cfg?.keychipId; + return (v != null && v.isNotEmpty) ? v : 'A63E-01C28055905'; + } + String get _aimeSalt { + final v = _cfg?.aimeSalt; + return (v != null && v.isNotEmpty) ? v : 'XcW5FW4cPArBXEk4vzKz3CIrMuA5EVVW'; + } + String get _aimeUrl { + final v = _cfg?.aimeUrl; + return (v != null && v.isNotEmpty) ? v : 'http://ai.sys-allnet.cn/wc_aime/api/get_data'; + } + String get _openGameID => _cfg?.openGameID ?? 'MAID'; String _formatTimestamp() { final tokyo = DateTime.now().toUtc().add(const Duration(hours: 9)); @@ -53,20 +67,26 @@ class ApiService { Future login(String qrCodeToken) async { final timestamp = _formatTimestamp(); final qrCode = extractQRCode(qrCodeToken); - final rawKey = chipId + timestamp + chimeSalt; + final chipId = _chipId; + final rawKey = chipId + timestamp + _aimeSalt; final key = _sha256(rawKey).toUpperCase(); final body = jsonEncode({ 'chipID': chipId, - 'openGameID': 'MAID', + 'openGameID': _openGameID, 'key': key, 'qrCode': qrCode, 'timestamp': timestamp, }); + // ignore: avoid_print + print('[login] POST $_aimeUrl'); + // ignore: avoid_print + print('[login] chipId=$chipId openGameID=$_openGameID'); + final response = await http .post( - Uri.parse('$baseUrl/wc_aime/api/get_data'), + Uri.parse(_aimeUrl), headers: { 'Content-Type': 'application/json', 'User-Agent': 'WC_AIME_LIB',