feat: update unp4k

This commit is contained in:
xkeyC
2025-12-04 16:06:49 +08:00
parent e3c3986379
commit e1ed30b6e6
14 changed files with 362 additions and 411 deletions

View File

@@ -8,20 +8,20 @@ import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart';
import 'package:freezed_annotation/freezed_annotation.dart' hide protected;
part 'unp4k_api.freezed.dart';
/// 打开 P4K 文件
Future<BigInt> p4KOpen({required String p4KPath}) =>
// These functions are ignored because they are not marked as `pub`: `dos_datetime_to_millis`, `ensure_files_loaded`
/// 打开 P4K 文件(仅打开,不读取文件列表)
Future<void> p4KOpen({required String p4KPath}) =>
RustLib.instance.api.crateApiUnp4KApiP4KOpen(p4KPath: p4KPath);
/// 获取文件数量(会触发文件列表加载)
Future<BigInt> p4KGetFileCount() =>
RustLib.instance.api.crateApiUnp4KApiP4KGetFileCount();
/// 获取所有文件列表
Future<List<P4kFileItem>> p4KGetAllFiles() =>
RustLib.instance.api.crateApiUnp4KApiP4KGetAllFiles();
/// 获取指定目录下的文件列表
Future<List<P4kFileItem>> p4KGetFilesInDirectory({required String directory}) =>
RustLib.instance.api.crateApiUnp4KApiP4KGetFilesInDirectory(
directory: directory,
);
/// 提取文件到内存
Future<Uint8List> p4KExtractToMemory({required String filePath}) =>
RustLib.instance.api.crateApiUnp4KApiP4KExtractToMemory(filePath: filePath);
@@ -46,5 +46,6 @@ sealed class P4kFileItem with _$P4kFileItem {
required bool isDirectory,
required BigInt size,
required BigInt compressedSize,
required PlatformInt64 dateModified,
}) = _P4kFileItem;
}

View File

@@ -14,7 +14,7 @@ T _$identity<T>(T value) => value;
/// @nodoc
mixin _$P4kFileItem {
String get name; bool get isDirectory; BigInt get size; BigInt get compressedSize;
String get name; bool get isDirectory; BigInt get size; BigInt get compressedSize; PlatformInt64 get dateModified;
/// Create a copy of P4kFileItem
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@@ -25,16 +25,16 @@ $P4kFileItemCopyWith<P4kFileItem> get copyWith => _$P4kFileItemCopyWithImpl<P4kF
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is P4kFileItem&&(identical(other.name, name) || other.name == name)&&(identical(other.isDirectory, isDirectory) || other.isDirectory == isDirectory)&&(identical(other.size, size) || other.size == size)&&(identical(other.compressedSize, compressedSize) || other.compressedSize == compressedSize));
return identical(this, other) || (other.runtimeType == runtimeType&&other is P4kFileItem&&(identical(other.name, name) || other.name == name)&&(identical(other.isDirectory, isDirectory) || other.isDirectory == isDirectory)&&(identical(other.size, size) || other.size == size)&&(identical(other.compressedSize, compressedSize) || other.compressedSize == compressedSize)&&(identical(other.dateModified, dateModified) || other.dateModified == dateModified));
}
@override
int get hashCode => Object.hash(runtimeType,name,isDirectory,size,compressedSize);
int get hashCode => Object.hash(runtimeType,name,isDirectory,size,compressedSize,dateModified);
@override
String toString() {
return 'P4kFileItem(name: $name, isDirectory: $isDirectory, size: $size, compressedSize: $compressedSize)';
return 'P4kFileItem(name: $name, isDirectory: $isDirectory, size: $size, compressedSize: $compressedSize, dateModified: $dateModified)';
}
@@ -45,7 +45,7 @@ abstract mixin class $P4kFileItemCopyWith<$Res> {
factory $P4kFileItemCopyWith(P4kFileItem value, $Res Function(P4kFileItem) _then) = _$P4kFileItemCopyWithImpl;
@useResult
$Res call({
String name, bool isDirectory, BigInt size, BigInt compressedSize
String name, bool isDirectory, BigInt size, BigInt compressedSize, PlatformInt64 dateModified
});
@@ -62,13 +62,14 @@ class _$P4kFileItemCopyWithImpl<$Res>
/// Create a copy of P4kFileItem
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? name = null,Object? isDirectory = null,Object? size = null,Object? compressedSize = null,}) {
@pragma('vm:prefer-inline') @override $Res call({Object? name = null,Object? isDirectory = null,Object? size = null,Object? compressedSize = null,Object? dateModified = null,}) {
return _then(_self.copyWith(
name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
as String,isDirectory: null == isDirectory ? _self.isDirectory : isDirectory // ignore: cast_nullable_to_non_nullable
as bool,size: null == size ? _self.size : size // ignore: cast_nullable_to_non_nullable
as BigInt,compressedSize: null == compressedSize ? _self.compressedSize : compressedSize // ignore: cast_nullable_to_non_nullable
as BigInt,
as BigInt,dateModified: null == dateModified ? _self.dateModified : dateModified // ignore: cast_nullable_to_non_nullable
as PlatformInt64,
));
}
@@ -150,10 +151,10 @@ return $default(_that);case _:
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String name, bool isDirectory, BigInt size, BigInt compressedSize)? $default,{required TResult orElse(),}) {final _that = this;
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String name, bool isDirectory, BigInt size, BigInt compressedSize, PlatformInt64 dateModified)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _P4kFileItem() when $default != null:
return $default(_that.name,_that.isDirectory,_that.size,_that.compressedSize);case _:
return $default(_that.name,_that.isDirectory,_that.size,_that.compressedSize,_that.dateModified);case _:
return orElse();
}
@@ -171,10 +172,10 @@ return $default(_that.name,_that.isDirectory,_that.size,_that.compressedSize);ca
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String name, bool isDirectory, BigInt size, BigInt compressedSize) $default,) {final _that = this;
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String name, bool isDirectory, BigInt size, BigInt compressedSize, PlatformInt64 dateModified) $default,) {final _that = this;
switch (_that) {
case _P4kFileItem():
return $default(_that.name,_that.isDirectory,_that.size,_that.compressedSize);}
return $default(_that.name,_that.isDirectory,_that.size,_that.compressedSize,_that.dateModified);}
}
/// A variant of `when` that fallback to returning `null`
///
@@ -188,10 +189,10 @@ return $default(_that.name,_that.isDirectory,_that.size,_that.compressedSize);}
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String name, bool isDirectory, BigInt size, BigInt compressedSize)? $default,) {final _that = this;
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String name, bool isDirectory, BigInt size, BigInt compressedSize, PlatformInt64 dateModified)? $default,) {final _that = this;
switch (_that) {
case _P4kFileItem() when $default != null:
return $default(_that.name,_that.isDirectory,_that.size,_that.compressedSize);case _:
return $default(_that.name,_that.isDirectory,_that.size,_that.compressedSize,_that.dateModified);case _:
return null;
}
@@ -203,13 +204,14 @@ return $default(_that.name,_that.isDirectory,_that.size,_that.compressedSize);ca
class _P4kFileItem implements P4kFileItem {
const _P4kFileItem({required this.name, required this.isDirectory, required this.size, required this.compressedSize});
const _P4kFileItem({required this.name, required this.isDirectory, required this.size, required this.compressedSize, required this.dateModified});
@override final String name;
@override final bool isDirectory;
@override final BigInt size;
@override final BigInt compressedSize;
@override final PlatformInt64 dateModified;
/// Create a copy of P4kFileItem
/// with the given fields replaced by the non-null parameter values.
@@ -221,16 +223,16 @@ _$P4kFileItemCopyWith<_P4kFileItem> get copyWith => __$P4kFileItemCopyWithImpl<_
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _P4kFileItem&&(identical(other.name, name) || other.name == name)&&(identical(other.isDirectory, isDirectory) || other.isDirectory == isDirectory)&&(identical(other.size, size) || other.size == size)&&(identical(other.compressedSize, compressedSize) || other.compressedSize == compressedSize));
return identical(this, other) || (other.runtimeType == runtimeType&&other is _P4kFileItem&&(identical(other.name, name) || other.name == name)&&(identical(other.isDirectory, isDirectory) || other.isDirectory == isDirectory)&&(identical(other.size, size) || other.size == size)&&(identical(other.compressedSize, compressedSize) || other.compressedSize == compressedSize)&&(identical(other.dateModified, dateModified) || other.dateModified == dateModified));
}
@override
int get hashCode => Object.hash(runtimeType,name,isDirectory,size,compressedSize);
int get hashCode => Object.hash(runtimeType,name,isDirectory,size,compressedSize,dateModified);
@override
String toString() {
return 'P4kFileItem(name: $name, isDirectory: $isDirectory, size: $size, compressedSize: $compressedSize)';
return 'P4kFileItem(name: $name, isDirectory: $isDirectory, size: $size, compressedSize: $compressedSize, dateModified: $dateModified)';
}
@@ -241,7 +243,7 @@ abstract mixin class _$P4kFileItemCopyWith<$Res> implements $P4kFileItemCopyWith
factory _$P4kFileItemCopyWith(_P4kFileItem value, $Res Function(_P4kFileItem) _then) = __$P4kFileItemCopyWithImpl;
@override @useResult
$Res call({
String name, bool isDirectory, BigInt size, BigInt compressedSize
String name, bool isDirectory, BigInt size, BigInt compressedSize, PlatformInt64 dateModified
});
@@ -258,13 +260,14 @@ class __$P4kFileItemCopyWithImpl<$Res>
/// Create a copy of P4kFileItem
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? name = null,Object? isDirectory = null,Object? size = null,Object? compressedSize = null,}) {
@override @pragma('vm:prefer-inline') $Res call({Object? name = null,Object? isDirectory = null,Object? size = null,Object? compressedSize = null,Object? dateModified = null,}) {
return _then(_P4kFileItem(
name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
as String,isDirectory: null == isDirectory ? _self.isDirectory : isDirectory // ignore: cast_nullable_to_non_nullable
as bool,size: null == size ? _self.size : size // ignore: cast_nullable_to_non_nullable
as BigInt,compressedSize: null == compressedSize ? _self.compressedSize : compressedSize // ignore: cast_nullable_to_non_nullable
as BigInt,
as BigInt,dateModified: null == dateModified ? _self.dateModified : dateModified // ignore: cast_nullable_to_non_nullable
as PlatformInt64,
));
}

View File

@@ -70,7 +70,7 @@ class RustLib extends BaseEntrypoint<RustLibApi, RustLibApiImpl, RustLibWire> {
String get codegenVersion => '2.11.1';
@override
int get rustContentHash => -737964996;
int get rustContentHash => 1801517256;
static const kDefaultExternalLibraryLoaderConfig =
ExternalLibraryLoaderConfig(
@@ -133,11 +133,9 @@ abstract class RustLibApi extends BaseApi {
Future<List<P4kFileItem>> crateApiUnp4KApiP4KGetAllFiles();
Future<List<P4kFileItem>> crateApiUnp4KApiP4KGetFilesInDirectory({
required String directory,
});
Future<BigInt> crateApiUnp4KApiP4KGetFileCount();
Future<BigInt> crateApiUnp4KApiP4KOpen({required String p4KPath});
Future<void> crateApiUnp4KApiP4KOpen({required String p4KPath});
Future<void> crateApiAsarApiRsiLauncherAsarDataWriteMainJs({
required RsiLauncherAsarData that,
@@ -582,37 +580,28 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
const TaskConstMeta(debugName: "p4k_get_all_files", argNames: []);
@override
Future<List<P4kFileItem>> crateApiUnp4KApiP4KGetFilesInDirectory({
required String directory,
}) {
Future<BigInt> crateApiUnp4KApiP4KGetFileCount() {
return handler.executeNormal(
NormalTask(
callFfi: (port_) {
var arg0 = cst_encode_String(directory);
return wire.wire__crate__api__unp4k_api__p4k_get_files_in_directory(
port_,
arg0,
);
return wire.wire__crate__api__unp4k_api__p4k_get_file_count(port_);
},
codec: DcoCodec(
decodeSuccessData: dco_decode_list_p_4_k_file_item,
decodeSuccessData: dco_decode_usize,
decodeErrorData: dco_decode_AnyhowException,
),
constMeta: kCrateApiUnp4KApiP4KGetFilesInDirectoryConstMeta,
argValues: [directory],
constMeta: kCrateApiUnp4KApiP4KGetFileCountConstMeta,
argValues: [],
apiImpl: this,
),
);
}
TaskConstMeta get kCrateApiUnp4KApiP4KGetFilesInDirectoryConstMeta =>
const TaskConstMeta(
debugName: "p4k_get_files_in_directory",
argNames: ["directory"],
);
TaskConstMeta get kCrateApiUnp4KApiP4KGetFileCountConstMeta =>
const TaskConstMeta(debugName: "p4k_get_file_count", argNames: []);
@override
Future<BigInt> crateApiUnp4KApiP4KOpen({required String p4KPath}) {
Future<void> crateApiUnp4KApiP4KOpen({required String p4KPath}) {
return handler.executeNormal(
NormalTask(
callFfi: (port_) {
@@ -620,7 +609,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
return wire.wire__crate__api__unp4k_api__p4k_open(port_, arg0);
},
codec: DcoCodec(
decodeSuccessData: dco_decode_usize,
decodeSuccessData: dco_decode_unit,
decodeErrorData: dco_decode_AnyhowException,
),
constMeta: kCrateApiUnp4KApiP4KOpenConstMeta,
@@ -991,6 +980,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
return raw as int;
}
@protected
PlatformInt64 dco_decode_i_64(dynamic raw) {
// Codec=Dco (DartCObject based), see doc to use other codecs
return dcoDecodeI64(raw);
}
@protected
List<String> dco_decode_list_String(dynamic raw) {
// Codec=Dco (DartCObject based), see doc to use other codecs
@@ -1073,13 +1068,14 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
P4kFileItem dco_decode_p_4_k_file_item(dynamic raw) {
// Codec=Dco (DartCObject based), see doc to use other codecs
final arr = raw as List<dynamic>;
if (arr.length != 4)
throw Exception('unexpected arr length: expect 4 but see ${arr.length}');
if (arr.length != 5)
throw Exception('unexpected arr length: expect 5 but see ${arr.length}');
return P4kFileItem(
name: dco_decode_String(arr[0]),
isDirectory: dco_decode_bool(arr[1]),
size: dco_decode_u_64(arr[2]),
compressedSize: dco_decode_u_64(arr[3]),
dateModified: dco_decode_i_64(arr[4]),
);
}
@@ -1255,6 +1251,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
return deserializer.buffer.getInt32();
}
@protected
PlatformInt64 sse_decode_i_64(SseDeserializer deserializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
return deserializer.buffer.getPlatformInt64();
}
@protected
List<String> sse_decode_list_String(SseDeserializer deserializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
@@ -1399,11 +1401,13 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
var var_isDirectory = sse_decode_bool(deserializer);
var var_size = sse_decode_u_64(deserializer);
var var_compressedSize = sse_decode_u_64(deserializer);
var var_dateModified = sse_decode_i_64(deserializer);
return P4kFileItem(
name: var_name,
isDirectory: var_isDirectory,
size: var_size,
compressedSize: var_compressedSize,
dateModified: var_dateModified,
);
}
@@ -1652,6 +1656,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
serializer.buffer.putInt32(self);
}
@protected
void sse_encode_i_64(PlatformInt64 self, SseSerializer serializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
serializer.buffer.putPlatformInt64(self);
}
@protected
void sse_encode_list_String(List<String> self, SseSerializer serializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
@@ -1797,6 +1807,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
sse_encode_bool(self.isDirectory, serializer);
sse_encode_u_64(self.size, serializer);
sse_encode_u_64(self.compressedSize, serializer);
sse_encode_i_64(self.dateModified, serializer);
}
@protected

View File

@@ -54,6 +54,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected
int dco_decode_i_32(dynamic raw);
@protected
PlatformInt64 dco_decode_i_64(dynamic raw);
@protected
List<String> dco_decode_list_String(dynamic raw);
@@ -166,6 +169,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected
int sse_decode_i_32(SseDeserializer deserializer);
@protected
PlatformInt64 sse_decode_i_64(SseDeserializer deserializer);
@protected
List<String> sse_decode_list_String(SseDeserializer deserializer);
@@ -318,6 +324,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
return wire.cst_new_box_autoadd_u_64(cst_encode_u_64(raw));
}
@protected
int cst_encode_i_64(PlatformInt64 raw) {
// Codec=Cst (C-struct based), see doc to use other codecs
return raw.toInt();
}
@protected
ffi.Pointer<wire_cst_list_String> cst_encode_list_String(List<String> raw) {
// Codec=Cst (C-struct based), see doc to use other codecs
@@ -446,6 +458,7 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
wireObj.is_directory = cst_encode_bool(apiObj.isDirectory);
wireObj.size = cst_encode_u_64(apiObj.size);
wireObj.compressed_size = cst_encode_u_64(apiObj.compressedSize);
wireObj.date_modified = cst_encode_i_64(apiObj.dateModified);
}
@protected
@@ -571,6 +584,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected
void sse_encode_i_32(int self, SseSerializer serializer);
@protected
void sse_encode_i_64(PlatformInt64 self, SseSerializer serializer);
@protected
void sse_encode_list_String(List<String> self, SseSerializer serializer);
@@ -1064,32 +1080,17 @@ class RustLibWire implements BaseWire {
_wire__crate__api__unp4k_api__p4k_get_all_filesPtr
.asFunction<void Function(int)>();
void wire__crate__api__unp4k_api__p4k_get_files_in_directory(
int port_,
ffi.Pointer<wire_cst_list_prim_u_8_strict> directory,
) {
return _wire__crate__api__unp4k_api__p4k_get_files_in_directory(
port_,
directory,
);
void wire__crate__api__unp4k_api__p4k_get_file_count(int port_) {
return _wire__crate__api__unp4k_api__p4k_get_file_count(port_);
}
late final _wire__crate__api__unp4k_api__p4k_get_files_in_directoryPtr =
_lookup<
ffi.NativeFunction<
ffi.Void Function(
ffi.Int64,
ffi.Pointer<wire_cst_list_prim_u_8_strict>,
)
>
>(
'frbgen_starcitizen_doctor_wire__crate__api__unp4k_api__p4k_get_files_in_directory',
late final _wire__crate__api__unp4k_api__p4k_get_file_countPtr =
_lookup<ffi.NativeFunction<ffi.Void Function(ffi.Int64)>>(
'frbgen_starcitizen_doctor_wire__crate__api__unp4k_api__p4k_get_file_count',
);
late final _wire__crate__api__unp4k_api__p4k_get_files_in_directory =
_wire__crate__api__unp4k_api__p4k_get_files_in_directoryPtr
.asFunction<
void Function(int, ffi.Pointer<wire_cst_list_prim_u_8_strict>)
>();
late final _wire__crate__api__unp4k_api__p4k_get_file_count =
_wire__crate__api__unp4k_api__p4k_get_file_countPtr
.asFunction<void Function(int)>();
void wire__crate__api__unp4k_api__p4k_open(
int port_,
@@ -1592,6 +1593,9 @@ final class wire_cst_p_4_k_file_item extends ffi.Struct {
@ffi.Uint64()
external int compressed_size;
@ffi.Int64()
external int date_modified;
}
final class wire_cst_list_p_4_k_file_item extends ffi.Struct {

View File

@@ -2,46 +2,25 @@
/// size : 524288
/// compressedSize : 169812
/// isDirectory : false
/// isFile : true
/// isEncrypted : false
/// isUnicodeText : false
/// dateTime : "2019-12-16T15:11:18"
/// version : 45
class AppUnp4kP4kItemData {
AppUnp4kP4kItemData({
this.name,
this.size,
this.compressedSize,
this.isDirectory,
this.isFile,
this.isEncrypted,
this.isUnicodeText,
this.dateTime,
this.version,
});
AppUnp4kP4kItemData({this.name, this.size, this.compressedSize, this.isDirectory, this.dateModified});
AppUnp4kP4kItemData.fromJson(dynamic json) {
name = json['name'];
size = json['size'];
compressedSize = json['compressedSize'];
isDirectory = json['isDirectory'];
isFile = json['isFile'];
isEncrypted = json['isEncrypted'];
isUnicodeText = json['isUnicodeText'];
dateTime = json['dateTime'];
version = json['version'];
dateModified = json['dateModified'];
}
String? name;
num? size;
num? compressedSize;
bool? isDirectory;
bool? isFile;
bool? isEncrypted;
bool? isUnicodeText;
String? dateTime;
num? version;
/// 文件修改时间(毫秒时间戳)
int? dateModified;
List<AppUnp4kP4kItemData> children = [];
Map<String, dynamic> toJson() {
@@ -50,11 +29,7 @@ class AppUnp4kP4kItemData {
map['size'] = size;
map['compressedSize'] = compressedSize;
map['isDirectory'] = isDirectory;
map['isFile'] = isFile;
map['isEncrypted'] = isEncrypted;
map['isUnicodeText'] = isUnicodeText;
map['dateTime'] = dateTime;
map['version'] = version;
map['dateModified'] = dateModified;
return map;
}
}

View File

@@ -51,12 +51,12 @@ class Unp4kCModel extends _$Unp4kCModel {
final loadStartTime = DateTime.now();
// 使用 Rust API 打开 P4K 文件(异步
// 使用 Rust API 打开 P4K 文件(仅打开,不读取文件列表
await unp4k_api.p4KOpen(p4KPath: gameP4kPath);
state = state.copyWith(endMessage: S.current.tools_unp4k_msg_reading2);
// 获取所有文件列表(异步
// 获取所有文件列表(会触发文件列表加载
final p4kFiles = await unp4k_api.p4KGetAllFiles();
final files = <String, AppUnp4kP4kItemData>{};
@@ -70,6 +70,7 @@ class Unp4kCModel extends _$Unp4kCModel {
isDirectory: item.isDirectory,
size: item.size.toInt(),
compressedSize: item.compressedSize.toInt(),
dateModified: item.dateModified,
);
files[item.name] = fileData;
@@ -158,7 +159,7 @@ class Unp4kCModel extends _$Unp4kCModel {
tempOpenFile: const MapEntry("loading", ""),
endMessage: S.current.tools_unp4k_msg_open_file(filePath),
);
extractFile(filePath, tempPath, mode: "extract_open");
await extractFile(filePath, tempPath, mode: "extract_open");
}
Future<void> extractFile(String filePath, String outputPath, {String mode = "extract"}) async {
@@ -171,6 +172,7 @@ class Unp4kCModel extends _$Unp4kCModel {
final fullOutputPath = "$outputPath$filePath";
dPrint("extractFile .... $filePath -> $fullOutputPath");
// 使用 Rust API 提取到磁盘
await unp4k_api.p4KExtractToDisk(filePath: filePath, outputPath: fullOutputPath);
if (mode == "extract_open") {
@@ -198,11 +200,6 @@ class Unp4kCModel extends _$Unp4kCModel {
}
}
static bool checkRunTimeError(String errorMessage) {
// Rust 实现不再需要 .NET runtime这个方法保留以兼容现有代码
return false;
}
/// 从 P4K 文件中提取指定文件到内存
/// [p4kPath] P4K 文件路径
/// [filePath] 要提取的文件路径P4K 内部路径)

View File

@@ -41,7 +41,7 @@ final class Unp4kCModelProvider
}
}
String _$unp4kCModelHash() => r'fe88d52b11464fdbded606bacbd833c1e908b738';
String _$unp4kCModelHash() => r'a296a499158e78848a698c3fda92c4c88ff039be';
abstract class _$Unp4kCModel extends $Notifier<Unp4kcState> {
Unp4kcState build();

View File

@@ -229,9 +229,7 @@ class AdvancedLocalizationUIModel extends _$AdvancedLocalizationUIModel {
return iniData;
} catch (e) {
final errorMessage = e.toString();
if (Unp4kCModel.checkRunTimeError(errorMessage)) {
AnalyticsApi.touch("advanced_localization_no_runtime");
}
// Rust 实现不再需要 .NET runtime 检查
state = state.copyWith(errorMessage: errorMessage);
// rethrow;
}

View File

@@ -47,7 +47,7 @@ final class AdvancedLocalizationUIModelProvider
}
String _$advancedLocalizationUIModelHash() =>
r'c7cca8935ac7df2281e83297b11b6b82d94f7a59';
r'5ff4d8156fbae4dcf69cb3fbcabfb9abda69ffbb';
abstract class _$AdvancedLocalizationUIModel
extends $Notifier<AdvancedLocalizationUIState> {

View File

@@ -11,7 +11,6 @@ import 'package:starcitizen_doctor/data/app_unp4k_p4k_item_data.dart';
import 'package:starcitizen_doctor/provider/unp4kc.dart';
import 'package:starcitizen_doctor/widgets/widgets.dart';
import 'package:super_sliver_list/super_sliver_list.dart';
import 'package:url_launcher/url_launcher_string.dart';
class UnP4kcUI extends HookConsumerWidget {
const UnP4kcUI({super.key});
@@ -28,14 +27,21 @@ class UnP4kcUI extends HookConsumerWidget {
return null;
}, const []);
return makeDefaultPage(context,
title: S.current.tools_unp4k_title(model.getGamePath()),
useBodyContainer: false,
content: makeBody(context, state, model, files, paths));
return makeDefaultPage(
context,
title: S.current.tools_unp4k_title(model.getGamePath()),
useBodyContainer: false,
content: makeBody(context, state, model, files, paths),
);
}
Widget makeBody(BuildContext context, Unp4kcState state, Unp4kCModel model,
List<AppUnp4kP4kItemData>? files, List<String> paths) {
Widget makeBody(
BuildContext context,
Unp4kcState state,
Unp4kCModel model,
List<AppUnp4kP4kItemData>? files,
List<String> paths,
) {
if (state.errorMessage.isNotEmpty) {
return UnP4kErrorWidget(errorMessage: state.errorMessage);
}
@@ -47,10 +53,7 @@ class UnP4kcUI extends HookConsumerWidget {
if (state.endMessage != null)
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
"${state.endMessage}",
style: const TextStyle(fontSize: 12),
),
child: Text("${state.endMessage}", style: const TextStyle(fontSize: 12)),
),
],
)
@@ -58,10 +61,7 @@ class UnP4kcUI extends HookConsumerWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
decoration: BoxDecoration(
color: FluentTheme.of(context)
.cardColor
.withValues(alpha: .06)),
decoration: BoxDecoration(color: FluentTheme.of(context).cardColor.withValues(alpha: .06)),
height: 36,
padding: const EdgeInsets.only(left: 12, right: 12),
child: SuperListView.builder(
@@ -72,8 +72,7 @@ class UnP4kcUI extends HookConsumerWidget {
if (path.isEmpty) {
path = "\\";
}
final fullPath =
"${paths.sublist(0, index + 1).join("\\")}\\";
final fullPath = "${paths.sublist(0, index + 1).join("\\")}\\";
return Row(
children: [
IconButton(
@@ -82,181 +81,153 @@ class UnP4kcUI extends HookConsumerWidget {
model.changeDir(fullPath, fullPath: true);
},
),
const Icon(
FluentIcons.chevron_right,
size: 12,
),
const Icon(FluentIcons.chevron_right, size: 12),
],
);
},
),
),
Expanded(
child: Row(
children: [
Container(
width: MediaQuery.of(context).size.width * .3,
decoration: BoxDecoration(
color: FluentTheme.of(context)
.cardColor
.withValues(alpha: .01),
),
child: SuperListView.builder(
padding: const EdgeInsets.only(
top: 6, bottom: 6, left: 3, right: 12),
itemBuilder: (BuildContext context, int index) {
final item = files![index];
final fileName =
item.name?.replaceAll(state.curPath.trim(), "") ??
"?";
return Container(
margin: const EdgeInsets.only(top: 4, bottom: 4),
decoration: BoxDecoration(
color: FluentTheme.of(context)
.cardColor
.withValues(alpha: .05),
),
child: IconButton(
onPressed: () {
if (item.isDirectory ?? false) {
model.changeDir(fileName);
} else {
model.openFile(item.name ?? "");
}
},
icon: Padding(
padding: const EdgeInsets.only(left: 4, right: 4),
child: Row(
children: [
if (item.isDirectory ?? false)
const Icon(
FluentIcons.folder_fill,
color: Color.fromRGBO(255, 224, 138, 1),
)
else if (fileName.endsWith(".xml"))
const Icon(
FluentIcons.file_code,
)
else
const Icon(
FluentIcons.open_file,
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Row(
children: [
Expanded(
child: Text(
fileName,
style: const TextStyle(
fontSize: 13),
textAlign: TextAlign.start,
),
),
],
),
if (!(item.isDirectory ?? true)) ...[
const SizedBox(height: 1),
child: Row(
children: [
Container(
width: MediaQuery.of(context).size.width * .3,
decoration: BoxDecoration(color: FluentTheme.of(context).cardColor.withValues(alpha: .01)),
child: SuperListView.builder(
padding: const EdgeInsets.only(top: 6, bottom: 6, left: 3, right: 12),
itemBuilder: (BuildContext context, int index) {
final item = files![index];
final fileName = item.name?.replaceAll(state.curPath.trim(), "") ?? "?";
return Container(
margin: const EdgeInsets.only(top: 4, bottom: 4),
decoration: BoxDecoration(color: FluentTheme.of(context).cardColor.withValues(alpha: .05)),
child: IconButton(
onPressed: () {
if (item.isDirectory ?? false) {
model.changeDir(fileName);
} else {
model.openFile(item.name ?? "");
}
},
icon: Padding(
padding: const EdgeInsets.only(left: 4, right: 4),
child: Row(
children: [
if (item.isDirectory ?? false)
const Icon(FluentIcons.folder_fill, color: Color.fromRGBO(255, 224, 138, 1))
else if (fileName.endsWith(".xml"))
const Icon(FluentIcons.file_code)
else
const Icon(FluentIcons.open_file),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(
FileSize.getSize(item.size),
style: TextStyle(
fontSize: 10,
color: Colors.white
.withValues(alpha: .6)),
),
const SizedBox(width: 12),
Text(
"${item.dateTime}",
style: TextStyle(
fontSize: 10,
color: Colors.white
.withValues(alpha: .6)),
Expanded(
child: Text(
fileName,
style: const TextStyle(fontSize: 13),
textAlign: TextAlign.start,
),
),
],
),
if (!(item.isDirectory ?? true)) ...[
const SizedBox(height: 1),
Row(
children: [
Text(
FileSize.getSize(item.size),
style: TextStyle(
fontSize: 10,
color: Colors.white.withValues(alpha: .6),
),
),
const SizedBox(width: 12),
Text(
item.dateModified != null
? DateTime.fromMillisecondsSinceEpoch(
item.dateModified!,
).toString().substring(0, 19)
: "",
style: TextStyle(
fontSize: 10,
color: Colors.white.withValues(alpha: .6),
),
),
],
),
],
],
],
),
),
),
const SizedBox(width: 3),
Icon(
FluentIcons.chevron_right,
size: 14,
color: Colors.white.withValues(alpha: .6),
)
],
const SizedBox(width: 3),
Icon(
FluentIcons.chevron_right,
size: 14,
color: Colors.white.withValues(alpha: .6),
),
],
),
),
),
),
);
},
itemCount: files?.length ?? 0,
);
},
itemCount: files?.length ?? 0,
),
),
),
Expanded(
Expanded(
child: Container(
child: state.tempOpenFile == null
? Center(
child: Text(S.current.tools_unp4k_view_file),
)
: state.tempOpenFile?.key == "loading"
child: state.tempOpenFile == null
? Center(child: Text(S.current.tools_unp4k_view_file))
: state.tempOpenFile?.key == "loading"
? makeLoading(context)
: Padding(
padding: const EdgeInsets.all(12),
child: Column(
children: [
if (state.tempOpenFile?.key == "text")
Expanded(
child: _TextTempWidget(
state.tempOpenFile?.value ?? ""))
Expanded(child: _TextTempWidget(state.tempOpenFile?.value ?? ""))
else
Expanded(
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(S.current
.tools_unp4k_msg_unknown_file_type(
state.tempOpenFile
?.value ??
"")),
Text(
S.current.tools_unp4k_msg_unknown_file_type(
state.tempOpenFile?.value ?? "",
),
),
const SizedBox(height: 32),
FilledButton(
child: Padding(
padding:
const EdgeInsets.all(4),
child: Text(S.current
.action_open_folder),
),
onPressed: () {
SystemHelper.openDir(state
.tempOpenFile
?.value ??
"");
})
child: Padding(
padding: const EdgeInsets.all(4),
child: Text(S.current.action_open_folder),
),
onPressed: () {
SystemHelper.openDir(state.tempOpenFile?.value ?? "");
},
),
],
),
),
)
),
],
),
),
))
],
)),
),
),
],
),
),
if (state.endMessage != null)
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
"${state.endMessage}",
style: const TextStyle(fontSize: 12),
),
child: Text("${state.endMessage}", style: const TextStyle(fontSize: 12)),
),
],
);
@@ -273,20 +244,20 @@ class _TextTempWidget extends HookConsumerWidget {
final textData = useState<String?>(null);
useEffect(() {
File(filePath).readAsString().then((value) {
textData.value = value;
}).catchError((err) {
textData.value = "Error: $err";
});
File(filePath)
.readAsString()
.then((value) {
textData.value = value;
})
.catchError((err) {
textData.value = "Error: $err";
});
return null;
}, const []);
if (textData.value == null) return makeLoading(context);
return CodeEditor(
controller: CodeLineEditingController.fromText('${textData.value}'),
readOnly: true,
);
return CodeEditor(controller: CodeLineEditingController.fromText('${textData.value}'), readOnly: true);
}
}
@@ -295,39 +266,12 @@ class UnP4kErrorWidget extends StatelessWidget {
const UnP4kErrorWidget({super.key, required this.errorMessage});
static const _downloadUrl =
"https://aka.ms/dotnet-core-applaunch?missing_runtime=true&arch=x64&rid=win-x64&os=win10&apphost_version=8.0.0";
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(24),
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
if (Unp4kCModel.checkRunTimeError(errorMessage)) ...[
Text(
S.current.tools_unp4k_missing_runtime,
style: const TextStyle(fontSize: 16),
),
const SizedBox(height: 6),
Text(S.current.tools_unp4k_missing_runtime_info),
const SizedBox(height: 16),
FilledButton(
child: Padding(
padding:
const EdgeInsets.symmetric(horizontal: 12, vertical: 3),
child: Text(
S.current.tools_unp4k_missing_runtime_action_install),
),
onPressed: () {
launchUrlString(_downloadUrl);
}),
] else
Text(errorMessage),
],
),
child: Column(mainAxisSize: MainAxisSize.min, children: [Text(errorMessage)]),
),
);
}