feat.Hatsune Miku

This commit is contained in:
Cookiues committed 2026-05-29 16:08:32 +08:00
1 parent dca69d095a
commit 53e078ff91
5 files changed
+112 -66

No files matched your search

+2
View File
@@ -4,3 +4,5 @@ android.useAndroidX=true
android.newDsl=false android.newDsl=false
# This builtInKotlin flag was added by the Flutter template # This builtInKotlin flag was added by the Flutter template
android.builtInKotlin=false android.builtInKotlin=false
kotlin.incremental=false
kotlin.daemon.jvm.options=-Xmx2G
+15 -4
View File
@@ -119,17 +119,18 @@ class AppStrings {
// Settings // Settings
static const titleServerSettings = 'Title Server 设置'; static const titleServerSettings = 'Title Server 设置';
static const required = '必填'; static const titleServerSection = 'Title Server';
static const optional = '可选 (有默认值)'; static const authServerSettings = 'Auth Server';
static const machineSettings = '机器信息';
static const save = '保存'; static const save = '保存';
static const labelTitleServerUrl = 'Title Server URL'; 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 labelAesKey = 'AES Key';
static const hintAesKey = 'your aes key string'; static const hintAesKey = 'your aes key string';
static const labelAesIv = 'AES IV'; static const labelAesIv = 'AES IV';
static const hintAesIv = 'your aes iv string'; static const hintAesIv = 'your aes iv string';
static const labelClientId = 'Client ID'; static const labelClientId = 'Client ID';
static const hintClientId = 'your client id'; static const hintClientId = 'A63E01C2805';
static const labelRegionId = 'Region ID'; static const labelRegionId = 'Region ID';
static const hintRegionId = '1'; static const hintRegionId = '1';
static const labelPlaceId = 'Place ID'; static const labelPlaceId = 'Place ID';
@@ -138,6 +139,16 @@ class AppStrings {
static const hintObfuscateParam = 'LatuAa81'; static const hintObfuscateParam = 'LatuAa81';
static const labelApiVersion = 'API Version (Mai-Encoding)'; static const labelApiVersion = 'API Version (Mai-Encoding)';
static const hintApiVersion = '1.53'; 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 不能为空'; static String fieldRequired(String label) => '$label 不能为空';
// About // About
+25 -1
View File
@@ -11,6 +11,12 @@ class TitleServerConfig {
final String clientId; final String clientId;
final int regionId; final int regionId;
final int placeId; final int placeId;
final String regionName;
final String placeName;
final String keychipId;
final String aimeUrl;
final String aimeSalt;
final String openGameID;
const TitleServerConfig({ const TitleServerConfig({
required this.titleServerUrl, required this.titleServerUrl,
@@ -21,7 +27,13 @@ class TitleServerConfig {
required this.clientId, required this.clientId,
this.regionId = 1, this.regionId = 1,
this.placeId = 1403, this.placeId = 1403,
}); this.regionName = '',
this.placeName = '',
this.keychipId = '',
this.aimeUrl = '',
this.aimeSalt = '',
String? openGameID,
}) : openGameID = openGameID ?? 'MAID';
List<int> get aesKeyBytes => utf8.encode(aesKey); List<int> get aesKeyBytes => utf8.encode(aesKey);
List<int> get aesIvBytes => utf8.encode(aesIv); List<int> get aesIvBytes => utf8.encode(aesIv);
@@ -35,6 +47,12 @@ class TitleServerConfig {
'clientId': clientId, 'clientId': clientId,
'regionId': regionId, 'regionId': regionId,
'placeId': placeId, 'placeId': placeId,
'regionName': regionName,
'placeName': placeName,
'keychipId': keychipId,
'aimeUrl': aimeUrl,
'aimeSalt': aimeSalt,
'openGameID': openGameID,
}; };
factory TitleServerConfig.fromJson(Map<String, dynamic> json) { factory TitleServerConfig.fromJson(Map<String, dynamic> json) {
@@ -47,6 +65,12 @@ class TitleServerConfig {
clientId: json['clientId'] as String? ?? '', clientId: json['clientId'] as String? ?? '',
regionId: json['regionId'] as int? ?? 1, regionId: json['regionId'] as int? ?? 1,
placeId: json['placeId'] as int? ?? 1403, 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',
); );
} }
} }
+43 -54
View File
@@ -22,6 +22,11 @@ class _SettingsPageState extends State<SettingsPage> {
final _placeIdController = TextEditingController(text: '1403'); final _placeIdController = TextEditingController(text: '1403');
final _obfuscateController = TextEditingController(text: 'LatuAa81'); final _obfuscateController = TextEditingController(text: 'LatuAa81');
final _apiVersionController = TextEditingController(text: '1.53'); final _apiVersionController = TextEditingController(text: '1.53');
final _regionNameController = TextEditingController();
final _placeNameController = TextEditingController();
final _keychipIdController = TextEditingController();
final _aimeUrlController = TextEditingController();
final _aimeSaltController = TextEditingController();
@override @override
void initState() { void initState() {
@@ -36,6 +41,11 @@ class _SettingsPageState extends State<SettingsPage> {
_placeIdController.text = '${config.placeId}'; _placeIdController.text = '${config.placeId}';
_obfuscateController.text = config.obfuscateParam; _obfuscateController.text = config.obfuscateParam;
_apiVersionController.text = config.apiVersion; _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<SettingsPage> {
_placeIdController.dispose(); _placeIdController.dispose();
_obfuscateController.dispose(); _obfuscateController.dispose();
_apiVersionController.dispose(); _apiVersionController.dispose();
_regionNameController.dispose();
_placeNameController.dispose();
_keychipIdController.dispose();
_aimeUrlController.dispose();
_aimeSaltController.dispose();
super.dispose(); super.dispose();
} }
@@ -64,6 +79,11 @@ class _SettingsPageState extends State<SettingsPage> {
placeId: int.tryParse(_placeIdController.text.trim()) ?? 1403, placeId: int.tryParse(_placeIdController.text.trim()) ?? 1403,
obfuscateParam: _obfuscateController.text.trim(), obfuscateParam: _obfuscateController.text.trim(),
apiVersion: _apiVersionController.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(); if (mounted) Navigator.of(context).pop();
@@ -88,64 +108,36 @@ class _SettingsPageState extends State<SettingsPage> {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: [ children: [
_buildSection(theme, AppStrings.required, [ _buildSection(theme, AppStrings.titleServerSection, [
_buildField( _buildField(controller: _urlController, label: AppStrings.labelTitleServerUrl, hint: AppStrings.hintTitleServerUrl),
controller: _urlController,
label: AppStrings.labelTitleServerUrl,
hint: AppStrings.hintTitleServerUrl,
required: true,
),
const SizedBox(height: 14), const SizedBox(height: 14),
_buildField( _buildField(controller: _aesKeyController, label: AppStrings.labelAesKey, hint: AppStrings.hintAesKey),
controller: _aesKeyController,
label: AppStrings.labelAesKey,
hint: AppStrings.hintAesKey,
required: true,
),
const SizedBox(height: 14), const SizedBox(height: 14),
_buildField( _buildField(controller: _aesIvController, label: AppStrings.labelAesIv, hint: AppStrings.hintAesIv),
controller: _aesIvController,
label: AppStrings.labelAesIv,
hint: AppStrings.hintAesIv,
required: true,
),
const SizedBox(height: 14), const SizedBox(height: 14),
_buildField( _buildField(controller: _clientIdController, label: AppStrings.labelClientId, hint: AppStrings.hintClientId),
controller: _clientIdController, const SizedBox(height: 14),
label: AppStrings.labelClientId, _buildField(controller: _obfuscateController, label: AppStrings.labelObfuscateParam, hint: AppStrings.hintObfuscateParam),
hint: AppStrings.hintClientId, const SizedBox(height: 14),
required: true, _buildField(controller: _apiVersionController, label: AppStrings.labelApiVersion, hint: AppStrings.hintApiVersion),
),
]), ]),
const SizedBox(height: 24), const SizedBox(height: 24),
_buildSection(theme, AppStrings.optional, [ _buildSection(theme, AppStrings.machineSettings, [
_buildField( _buildField(controller: _regionIdController, label: AppStrings.labelRegionId, hint: AppStrings.hintRegionId),
controller: _regionIdController,
label: AppStrings.labelRegionId,
hint: AppStrings.hintRegionId,
required: false,
),
const SizedBox(height: 14), const SizedBox(height: 14),
_buildField( _buildField(controller: _regionNameController, label: AppStrings.labelRegionName, hint: AppStrings.hintRegionName),
controller: _placeIdController,
label: AppStrings.labelPlaceId,
hint: AppStrings.hintPlaceId,
required: false,
),
const SizedBox(height: 14), const SizedBox(height: 14),
_buildField( _buildField(controller: _placeIdController, label: AppStrings.labelPlaceId, hint: AppStrings.hintPlaceId),
controller: _obfuscateController,
label: AppStrings.labelObfuscateParam,
hint: AppStrings.hintObfuscateParam,
required: false,
),
const SizedBox(height: 14), const SizedBox(height: 14),
_buildField( _buildField(controller: _placeNameController, label: AppStrings.labelPlaceName, hint: AppStrings.hintPlaceName),
controller: _apiVersionController, ]),
label: AppStrings.labelApiVersion, const SizedBox(height: 24),
hint: AppStrings.hintApiVersion, _buildSection(theme, AppStrings.authServerSettings, [
required: false, _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), const SizedBox(height: 32),
FilledButton.icon( FilledButton.icon(
@@ -200,7 +192,6 @@ class _SettingsPageState extends State<SettingsPage> {
required TextEditingController controller, required TextEditingController controller,
required String label, required String label,
required String hint, required String hint,
required bool required,
}) { }) {
return TextFormField( return TextFormField(
controller: controller, controller: controller,
@@ -212,9 +203,7 @@ class _SettingsPageState extends State<SettingsPage> {
), ),
contentPadding: const EdgeInsets.all(14), contentPadding: const EdgeInsets.all(14),
), ),
validator: required validator: (v) => (v == null || v.trim().isEmpty) ? AppStrings.fieldRequired(label) : null,
? (v) => (v == null || v.trim().isEmpty) ? AppStrings.fieldRequired(label) : null
: null,
); );
} }
} }
+27 -7
View File
@@ -3,6 +3,8 @@ import 'dart:convert';
import 'package:crypto/crypto.dart'; import 'package:crypto/crypto.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import '../config/title_server_config.dart';
class LoginResult { class LoginResult {
final bool success; final bool success;
final int errorId; final int errorId;
@@ -20,12 +22,24 @@ class LoginResult {
} }
class ApiService { class ApiService {
static const String chimeSalt = 'XcW5FW4cPArBXEk4vzKz3CIrMuA5EVVW'; final TitleServerConfig? _config;
static const String baseUrl = 'http://ai.sys-allnet.cn';
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() { String _formatTimestamp() {
final tokyo = DateTime.now().toUtc().add(const Duration(hours: 9)); final tokyo = DateTime.now().toUtc().add(const Duration(hours: 9));
@@ -53,20 +67,26 @@ class ApiService {
Future<LoginResult> login(String qrCodeToken) async { Future<LoginResult> login(String qrCodeToken) async {
final timestamp = _formatTimestamp(); final timestamp = _formatTimestamp();
final qrCode = extractQRCode(qrCodeToken); final qrCode = extractQRCode(qrCodeToken);
final rawKey = chipId + timestamp + chimeSalt; final chipId = _chipId;
final rawKey = chipId + timestamp + _aimeSalt;
final key = _sha256(rawKey).toUpperCase(); final key = _sha256(rawKey).toUpperCase();
final body = jsonEncode({ final body = jsonEncode({
'chipID': chipId, 'chipID': chipId,
'openGameID': 'MAID', 'openGameID': _openGameID,
'key': key, 'key': key,
'qrCode': qrCode, 'qrCode': qrCode,
'timestamp': timestamp, 'timestamp': timestamp,
}); });
// ignore: avoid_print
print('[login] POST $_aimeUrl');
// ignore: avoid_print
print('[login] chipId=$chipId openGameID=$_openGameID');
final response = await http final response = await http
.post( .post(
Uri.parse('$baseUrl/wc_aime/api/get_data'), Uri.parse(_aimeUrl),
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'User-Agent': 'WC_AIME_LIB', 'User-Agent': 'WC_AIME_LIB',