app/lib/provider/unp4kc.dart

279 lines
9.1 KiB
Dart
Raw Permalink Normal View History

2024-04-14 19:52:42 +08:00
import 'dart:convert';
import 'dart:io';
import 'package:file/memory.dart';
import 'package:flutter/foundation.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
2024-04-27 14:40:01 +08:00
import 'package:path_provider/path_provider.dart';
2024-04-14 19:52:42 +08:00
import 'package:riverpod_annotation/riverpod_annotation.dart';
2024-05-07 21:08:16 +08:00
import 'package:starcitizen_doctor/api/analytics.dart';
2024-04-14 19:52:42 +08:00
import 'package:starcitizen_doctor/common/conf/binary_conf.dart';
2024-04-27 14:40:01 +08:00
import 'package:starcitizen_doctor/common/helper/log_helper.dart';
2024-04-16 22:34:50 +08:00
import 'package:starcitizen_doctor/common/rust/api/rs_process.dart';
2024-04-14 19:52:42 +08:00
import 'package:starcitizen_doctor/common/utils/log.dart';
import 'package:starcitizen_doctor/common/utils/provider.dart';
import 'package:starcitizen_doctor/data/app_unp4k_p4k_item_data.dart';
import 'package:starcitizen_doctor/ui/tools/tools_ui_model.dart';
2024-04-17 21:40:08 +08:00
import 'package:starcitizen_doctor/common/rust/api/rs_process.dart'
as rs_process;
2024-04-14 19:52:42 +08:00
part 'unp4kc.freezed.dart';
part 'unp4kc.g.dart';
@freezed
class Unp4kcState with _$Unp4kcState {
const factory Unp4kcState({
required bool startUp,
Map<String, AppUnp4kP4kItemData>? files,
MemoryFileSystem? fs,
required String curPath,
String? endMessage,
MapEntry<String, String>? tempOpenFile,
2024-05-05 16:47:42 +08:00
@Default("") String errorMessage,
2024-04-14 19:52:42 +08:00
}) = _Unp4kcState;
}
@riverpod
class Unp4kCModel extends _$Unp4kCModel {
2024-04-17 21:40:08 +08:00
int? _rsPid;
2024-04-14 19:52:42 +08:00
@override
Unp4kcState build() {
2024-05-05 16:34:38 +08:00
state = Unp4kcState(
startUp: false,
curPath: '\\',
endMessage: S.current.tools_unp4k_msg_init);
2024-04-14 19:52:42 +08:00
_init();
return state;
}
ToolsUIState get _toolsState => ref.read(toolsUIModelProvider);
String getGamePath() => _toolsState.scInstalledPath;
2024-05-07 21:08:16 +08:00
bool _hasUnp4kRunTimeError = false;
2024-04-14 19:52:42 +08:00
void _init() async {
final execDir = "${appGlobalState.applicationBinaryModuleDir}\\unp4kc";
await BinaryModuleConf.extractModule(
["unp4kc"], appGlobalState.applicationBinaryModuleDir!);
final exec = "$execDir\\unp4kc.exe";
2024-04-16 22:34:50 +08:00
2024-04-17 21:40:08 +08:00
final stream = rs_process.start(
2024-04-16 22:34:50 +08:00
executable: exec, arguments: [], workingDirectory: execDir);
2024-04-17 21:40:08 +08:00
stream.listen((event) async {
2024-04-16 22:34:50 +08:00
switch (event.dataType) {
case RsProcessStreamDataType.output:
2024-04-17 23:09:28 +08:00
_rsPid = event.rsPid;
2024-04-16 22:34:50 +08:00
try {
final eventJson = await compute(json.decode, event.data);
2024-04-17 21:40:08 +08:00
_handleMessage(eventJson, event.rsPid);
2024-04-16 22:34:50 +08:00
} catch (e) {
dPrint("[unp4kc] json error: $e");
}
break;
case RsProcessStreamDataType.error:
dPrint("[unp4kc] stderr: ${event.data}");
2024-05-05 16:47:42 +08:00
if (state.errorMessage.isEmpty) {
state = state.copyWith(errorMessage: event.data);
} else {
state = state.copyWith(
errorMessage: "${state.errorMessage}\n${event.data}");
}
2024-05-07 21:08:16 +08:00
if (!_hasUnp4kRunTimeError) {
if (checkRunTimeError(state.errorMessage)) {
2024-05-07 21:08:16 +08:00
_hasUnp4kRunTimeError = true;
AnalyticsApi.touch("unp4k_no_runtime");
}
}
2024-04-16 22:34:50 +08:00
break;
case RsProcessStreamDataType.exit:
dPrint("[unp4kc] exit: ${event.data}");
break;
2024-04-14 19:52:42 +08:00
}
});
2024-04-16 22:34:50 +08:00
2024-04-14 19:52:42 +08:00
ref.onDispose(() {
2024-04-17 23:09:28 +08:00
state = state.copyWith(fs: null);
2024-04-17 21:40:08 +08:00
if (_rsPid != null) {
Process.killPid(_rsPid!);
2024-04-16 22:34:50 +08:00
dPrint("[unp4kc] kill ...");
}
2024-04-14 19:52:42 +08:00
});
}
2024-04-17 23:09:28 +08:00
DateTime? _loadStartTime;
2024-04-17 21:40:08 +08:00
void _handleMessage(Map<String, dynamic> eventJson, int rsPid) async {
2024-04-14 19:52:42 +08:00
final action = eventJson["action"];
final data = eventJson["data"];
final gamePath = getGamePath();
final gameP4kPath = "$gamePath\\Data.p4k";
switch (action.toString().trim()) {
case "info: startup":
2024-04-17 21:40:08 +08:00
rs_process.write(rsPid: rsPid, data: "$gameP4kPath\n");
2024-04-17 23:09:28 +08:00
break;
case "info: Reading_p4k_file":
_loadStartTime = DateTime.now();
2024-05-05 16:34:38 +08:00
state = state.copyWith(endMessage: S.current.tools_unp4k_msg_reading);
2024-04-14 19:52:42 +08:00
break;
2024-04-17 23:09:28 +08:00
case "info: All Ready":
2024-05-05 16:34:38 +08:00
state = state.copyWith(endMessage: S.current.tools_unp4k_msg_reading2);
2024-04-17 23:09:28 +08:00
break;
2024-04-14 19:52:42 +08:00
case "data: P4K_Files":
final p4kFiles = (data as List<dynamic>);
final files = <String, AppUnp4kP4kItemData>{};
final fs = MemoryFileSystem(style: FileSystemStyle.posix);
2024-04-17 23:09:28 +08:00
var nextAwait = 0;
2024-04-14 19:52:42 +08:00
for (var i = 0; i < p4kFiles.length; i++) {
final item = AppUnp4kP4kItemData.fromJson(p4kFiles[i]);
item.name = "${item.name}";
files["\\${item.name}"] = item;
await fs
.file(item.name?.replaceAll("\\", "/") ?? "")
.create(recursive: true);
2024-04-17 23:09:28 +08:00
if (i == nextAwait) {
state = state.copyWith(
2024-05-05 16:34:38 +08:00
endMessage:
S.current.tools_unp4k_msg_reading3(i, p4kFiles.length));
2024-04-17 23:37:28 +08:00
await Future.delayed(Duration.zero);
2024-04-17 23:09:28 +08:00
nextAwait += 20000;
}
2024-04-14 19:52:42 +08:00
}
2024-04-17 23:09:28 +08:00
final endTime = DateTime.now();
2024-04-14 19:52:42 +08:00
state = state.copyWith(
2024-04-17 23:09:28 +08:00
files: files,
fs: fs,
2024-05-05 16:34:38 +08:00
endMessage: S.current.tools_unp4k_msg_read_completed(files.length,
endTime.difference(_loadStartTime!).inMilliseconds));
2024-04-17 23:09:28 +08:00
_loadStartTime = null;
2024-04-14 19:52:42 +08:00
break;
case "info: Extracted_Open":
final filePath = data.toString();
dPrint("[unp4kc] Extracted_Open file: $filePath");
const textExt = [".txt", ".xml", ".json", ".lua", ".cfg", ".ini"];
const imgExt = [".png"];
String openType = "unknown";
for (var element in textExt) {
2024-05-01 15:28:32 +08:00
if (filePath.toLowerCase().endsWith(element)) {
2024-04-14 19:52:42 +08:00
openType = "text";
}
}
for (var element in imgExt) {
if (filePath.endsWith(element)) {
openType = "image";
}
}
state = state.copyWith(
tempOpenFile: MapEntry(openType, filePath),
2024-05-05 16:34:38 +08:00
endMessage: S.current.tools_unp4k_msg_open_file(filePath));
2024-04-14 19:52:42 +08:00
break;
default:
dPrint("[unp4kc] unknown action: $action");
break;
}
}
List<AppUnp4kP4kItemData>? getFiles() {
final path = state.curPath.replaceAll("\\", "/");
final fs = state.fs;
if (fs == null) return null;
final dir = fs.directory(path);
if (!dir.existsSync()) return [];
final files = dir.listSync(recursive: false, followLinks: false);
files.sort((a, b) {
if (a is Directory && b is File) {
return -1;
} else if (a is File && b is Directory) {
return 1;
} else {
return a.path.compareTo(b.path);
}
});
final result = <AppUnp4kP4kItemData>[];
for (var file in files) {
if (file is File) {
final f = state.files?[file.path.replaceAll("/", "\\")];
if (f != null) {
if (!(f.name?.startsWith("\\") ?? true)) {
f.name = "\\${f.name}";
}
result.add(f);
}
} else {
result.add(AppUnp4kP4kItemData(
name: file.path.replaceAll("/", "\\"), isDirectory: true));
}
}
return result;
}
void changeDir(String name, {bool fullPath = false}) {
if (fullPath) {
state = state.copyWith(curPath: name);
} else {
state = state.copyWith(curPath: "${state.curPath}$name\\");
}
}
openFile(String filePath) async {
2024-04-27 14:40:01 +08:00
final tempDir = await getTemporaryDirectory();
final tempPath =
"${tempDir.absolute.path}\\SCToolbox_unp4kc\\${SCLoggerHelper.getGameChannelID(getGamePath())}\\";
2024-04-14 19:52:42 +08:00
state = state.copyWith(
tempOpenFile: const MapEntry("loading", ""),
2024-05-05 16:34:38 +08:00
endMessage: S.current.tools_unp4k_msg_open_file(filePath));
2024-04-14 19:52:42 +08:00
extractFile(filePath, tempPath, mode: "extract_open");
}
extractFile(String filePath, String outputPath,
{String mode = "extract"}) async {
// remove first \\
if (filePath.startsWith("\\")) {
filePath = filePath.substring(1);
}
outputPath = "$outputPath$filePath";
dPrint("extractFile .... $filePath");
2024-04-17 21:40:08 +08:00
if (_rsPid != null) {
rs_process.write(
2024-04-17 23:09:28 +08:00
rsPid: _rsPid!, data: "$mode<:,:>$filePath<:,:>$outputPath\n");
2024-04-17 21:40:08 +08:00
}
2024-04-14 19:52:42 +08:00
}
2024-04-27 16:23:57 +08:00
static bool checkRunTimeError(String errorMessage) {
if (errorMessage
.contains("You must install .NET to run this application") ||
errorMessage.contains(
"You must install or update .NET to run this application") ||
errorMessage.contains(
"It was not possible to find any compatible framework version")) {
AnalyticsApi.touch("unp4k_no_runtime");
return true;
}
return false;
}
2024-04-27 16:23:57 +08:00
static Future<Uint8List> unp4kTools(
String applicationBinaryModuleDir, List<String> args) async {
await BinaryModuleConf.extractModule(
["unp4kc"], applicationBinaryModuleDir);
final execDir = "$applicationBinaryModuleDir\\unp4kc";
final exec = "$execDir\\unp4kc.exe";
final r = await Process.run(exec, args);
if (r.exitCode != 0) {
2024-05-05 21:28:53 +08:00
Process.killPid(r.pid);
2024-04-27 16:23:57 +08:00
throw Exception(
"error: ${r.exitCode} , info= ${r.stdout} , err= ${r.stderr}");
}
final eventJson = await compute(json.decode, r.stdout.toString());
if (eventJson["action"] == "data: Uint8List") {
final data = eventJson["data"];
return Uint8List.fromList((data as List).cast<int>());
}
throw Exception("error: data error");
}
2024-04-14 19:52:42 +08:00
}