feat.Hatsune Miku
This commit is contained in:
1 parent
dca69d095a
commit
53e078ff91
5 files changed
+112
-66
No files matched your search
@@ -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
@@ -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
|
||||||
|
|||||||
@@ -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',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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',
|
||||||
|
|||||||
Reference in new issue
Block a user