cue4parse 0.0.4__tar.gz → 0.0.6__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: cue4parse
3
- Version: 0.0.4
3
+ Version: 0.0.6
4
4
  Summary: CUE4Parse Python wrapper and CLI tool to extract packages from Unreal Engine games
5
5
  Project-URL: Homepage, https://github.com/joric/CUE4Parse-Python/wiki
6
6
  Project-URL: Repository, https://github.com/joric/CUE4Parse-Python
@@ -20,11 +20,4 @@ Description-Content-Type: text/markdown
20
20
 
21
21
  CUE4Parse Python wrapper, CLI tool, and scripting engine
22
22
 
23
- ## Usage
24
-
25
- `pip install cue4parse`
26
-
27
- * Make sure executable is added to paths, e.g. `%LOCALAPPDATA%\Python\pythoncore-3.14-64\Scripts`.
28
- * Needs CUE4Parse libraries in `%LOCALAPPDATA%/cue4parse/libs` (downloads and unpacks FModel on Windows).
29
- * To run CLI: `cue4parse`
30
-
23
+ * Documentation: https://github.com/joric/CUE4Parse-Python/wiki
@@ -0,0 +1,5 @@
1
+ # CUE4Parse-Python
2
+
3
+ CUE4Parse Python wrapper, CLI tool, and scripting engine
4
+
5
+ * Documentation: https://github.com/joric/CUE4Parse-Python/wiki
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "cue4parse"
7
- version = "0.0.4"
7
+ version = "0.0.6"
8
8
  description = "CUE4Parse Python wrapper and CLI tool to extract packages from Unreal Engine games"
9
9
  requires-python = ">=3.9"
10
10
  dependencies = ["pythonnet>=3.0"]
@@ -3,7 +3,10 @@ from cue4parse import *
3
3
  import tempfile
4
4
  import fnmatch
5
5
  import time
6
+ import argparse
7
+ import threading
6
8
  from importlib.metadata import version
9
+ from concurrent.futures import ThreadPoolExecutor, as_completed
7
10
 
8
11
  export_directory = ""
9
12
  overwrite_files = False
@@ -33,12 +36,14 @@ class CliOptions:
33
36
  yes: bool
34
37
  verbose: bool
35
38
  mesh_format: str
36
- anim_format: str
37
39
  texture_format: str
38
- material_format: str
39
- lod_format: str
40
+ material_depth: str
41
+ mesh_quality: str
40
42
  socket_format: str
41
- nanite_format: str # kept for parity (not assigned if enum unavailable)
43
+ nanite_format: str
44
+ texture_platform: str
45
+ texture_quality: int
46
+ export_all_mips: bool
42
47
  export_morph_targets: bool
43
48
  export_materials: bool
44
49
  export_hdr_as_hdr: bool
@@ -49,9 +54,7 @@ def norm_path(path: str) -> str:
49
54
  return "."
50
55
  return path.replace("/", os.sep).replace("\\\\", "\\")
51
56
 
52
-
53
57
  def parse_enum(enum_type: Any, value: str, default_name: str | None = None) -> Any:
54
- # pythonnet enum access: getattr(EnumType, "ValueName")
55
58
  if hasattr(enum_type, value):
56
59
  return getattr(enum_type, value)
57
60
  for name in dir(enum_type):
@@ -61,11 +64,9 @@ def parse_enum(enum_type: Any, value: str, default_name: str | None = None) -> A
61
64
  print(f"Warning: Unknown value '{value}' for {enum_type.__name__}, using default '{default_name}'", file=sys.stderr)
62
65
  return getattr(enum_type, default_name)
63
66
  print(f"Warning: Unknown value '{value}' for {enum_type.__name__}, using default", file=sys.stderr)
64
- # best-effort default
65
67
  names = [n for n in dir(enum_type) if not n.startswith("_")]
66
68
  return getattr(enum_type, names[0]) if names else None
67
69
 
68
-
69
70
  def check_file(out_path: Path, silent: bool = False) -> bool:
70
71
  skip = (not overwrite_files) and out_path.exists()
71
72
  if not silent:
@@ -75,11 +76,9 @@ def check_file(out_path: Path, silent: bool = False) -> bool:
75
76
  Log.Information("Writing {Path}", norm_path(str(out_path)))
76
77
  return not skip
77
78
 
78
-
79
79
  def write_to_log(_folder: str, _log_message: str, counter: list[int]) -> None:
80
80
  counter[0] += 1
81
81
 
82
-
83
82
  def write_to_file(folder: str, file_name: str, data: bytes, log_message: str, counter: list[int]) -> None:
84
83
  out_path = Path(export_directory) / folder / file_name
85
84
  if not check_file(out_path):
@@ -89,12 +88,10 @@ def write_to_file(folder: str, file_name: str, data: bytes, log_message: str, co
89
88
  write_to_log(folder, log_message, counter)
90
89
 
91
90
  def save_json(folder: str, name: str, exports: Any, counter: list[int]) -> None:
92
- #text = JsonConvert.SerializeObject(exports, Formatting.Indented) # alternative way
93
-
94
- serializer = JsonSerializer();
95
- serializer.Formatting = Formatting.Indented;
91
+ serializer = JsonSerializer()
92
+ serializer.Formatting = Formatting.Indented
96
93
 
97
- output_to_stdout = export_directory==""
94
+ output_to_stdout = export_directory == ""
98
95
  if output_to_stdout:
99
96
  writer = JsonTextWriter(Console.Out)
100
97
  serializer.Serialize(writer, exports)
@@ -108,12 +105,11 @@ def save_json(folder: str, name: str, exports: Any, counter: list[int]) -> None:
108
105
  return
109
106
 
110
107
  out_path.parent.mkdir(parents=True, exist_ok=True)
111
-
112
- stream = StreamWriter(str(out_path), False, Encoding.UTF8);
108
+ stream = StreamWriter(str(out_path), False, Encoding.UTF8)
113
109
  try:
114
- writer = JsonTextWriter(stream);
110
+ writer = JsonTextWriter(stream)
115
111
  try:
116
- serializer.Serialize(writer, exports);
112
+ serializer.Serialize(writer, exports)
117
113
  writer.Flush()
118
114
  finally:
119
115
  writer.Close()
@@ -122,7 +118,6 @@ def save_json(folder: str, name: str, exports: Any, counter: list[int]) -> None:
122
118
 
123
119
  write_to_log(folder, name, counter)
124
120
 
125
-
126
121
  def save_raw(folder: str, package, provider, counter: list[int]) -> None:
127
122
  name = str(package.Name)
128
123
  out_path = Path(export_directory) / folder / name
@@ -132,7 +127,6 @@ def save_raw(folder: str, package, provider, counter: list[int]) -> None:
132
127
  data = bytes(provider.Files[package.Path].Read())
133
128
  write_to_file(folder, name, data, name, counter)
134
129
 
135
-
136
130
  def try_load_locmeta(package) -> tuple[bool, Any]:
137
131
  ok, reader = package.TryCreateReader()
138
132
  if not ok or reader is None:
@@ -142,7 +136,6 @@ def try_load_locmeta(package) -> tuple[bool, Any]:
142
136
  except Exception:
143
137
  return False, None
144
138
 
145
-
146
139
  def try_load_locres(package) -> tuple[bool, Any]:
147
140
  ok, reader = package.TryCreateReader()
148
141
  if not ok or reader is None:
@@ -152,48 +145,18 @@ def try_load_locres(package) -> tuple[bool, Any]:
152
145
  except Exception:
153
146
  return False, None
154
147
 
155
-
156
- def save_texture(folder: str, texture: UTexture, platform: ETexturePlatform, options: ExporterOptions, counter: list[int], name_prefix='') -> None:
157
- out_base = Path(export_directory) / folder / str(texture.Name)
158
- for ext in (".png", ".hdr"):
159
- p = out_base.with_suffix(ext)
160
- if not check_file(p, silent=True):
161
- check_file(p, silent=False)
162
- return
163
-
164
- if isinstance(texture, UTexture2DArray):
165
- bitmaps = list(TextureDecoder.DecodeTextureArray(texture, platform))
166
- else:
167
- bitmap = TextureDecoder.Decode(texture, platform)
168
- if isinstance(texture, UTextureCube):
169
- bitmap = CubemapConverter.ToPanorama(bitmap) if bitmap is not None else None
170
- bitmaps = [bitmap]
171
-
172
- for bitmap in bitmaps or []:
173
- if bitmap is None:
174
- continue
175
- result = TextureEncoder.Encode(bitmap, options.TextureFormat, options.ExportHdrTexturesAsHdr)
176
- if isinstance(result, tuple):
177
- encoded, extension = result
178
- else:
179
- encoded, extension = result, "png"
180
- file_name = f"{name_prefix}{texture.Name}.{extension}"
181
- write_to_file(folder, file_name, bytes(encoded), f"{file_name} ({bitmap.Width}x{bitmap.Height})", counter)
182
-
183
-
184
148
  def filter_paths(paths: Iterable[str], pattern: str) -> list[str]:
185
149
  return [p for p in paths if fnmatch.fnmatch(p, pattern)]
186
150
 
187
-
188
151
  def execute(options: CliOptions) -> int:
189
152
  global export_directory, overwrite_files
190
153
 
191
154
  overwrite_files = options.yes
192
155
 
193
156
  if options.verbose:
194
- Log.Logger = with_console(LoggerConfiguration().MinimumLevel.Verbose()).CreateLogger()
157
+ Log.Logger = LoggerConfiguration().MinimumLevel.Verbose().WriteTo.Console().CreateLogger()
195
158
  else:
196
- Log.Logger = with_console(LoggerConfiguration().MinimumLevel.Fatal()).CreateLogger()
159
+ Log.Logger = LoggerConfiguration().MinimumLevel.Fatal().WriteTo.Console().CreateLogger()
197
160
 
198
161
  try:
199
162
  game_version = parse_enum(EGame, options.game, "GAME_UE5_LATEST")
@@ -204,7 +167,8 @@ def execute(options: CliOptions) -> int:
204
167
  print(f"Mappings not found: {options.mappings}", file=sys.stderr)
205
168
  return 1
206
169
 
207
- version = VersionContainer(game_version, ETexturePlatform.DesktopMobile)
170
+ texture_platform_value = parse_enum(ETexturePlatform, options.texture_platform, "DesktopMobile")
171
+ version = VersionContainer(game_version, texture_platform_value)
208
172
 
209
173
  single_pak_mode = bool(options.pak)
210
174
  if single_pak_mode:
@@ -255,11 +219,6 @@ def execute(options: CliOptions) -> int:
255
219
  print(f"Total assets: {provider.Files.Count}", file=sys.stderr)
256
220
  print(f"Output format: {options.format}", file=sys.stderr)
257
221
 
258
- detex_path = os.path.join(tempfile.gettempdir(), str(DetexHelper.DLL_NAME))
259
- if not Path(detex_path).exists():
260
- DetexHelper.LoadDllAsync(detex_path).GetAwaiter().GetResult()
261
- DetexHelper.Initialize(detex_path)
262
-
263
222
  package_paths: list[str] = []
264
223
  for list_file in options.config:
265
224
  if list_file and Path(list_file).exists():
@@ -293,20 +252,21 @@ def execute(options: CliOptions) -> int:
293
252
  print("No matches, exiting.", file=sys.stderr)
294
253
  return 0
295
254
 
296
- export_type = ExportType.TEXTURE | ExportType.SOUND | ExportType.MESH | ExportType.ANIMATION | ExportType.OTHER
297
-
298
- exp_options = ExporterOptions()
299
- exp_options.LodFormat = parse_enum(ELodFormat, options.lod_format, "AllLods")
300
- exp_options.MeshFormat = parse_enum(EMeshFormat, options.mesh_format, "ActorX")
301
- exp_options.AnimFormat = parse_enum(EAnimFormat, options.anim_format, "ActorX")
302
- exp_options.MaterialFormat = parse_enum(EMaterialFormat, options.material_format, "AllLayersNoRef")
303
- exp_options.TextureFormat = parse_enum(ETextureFormat, options.texture_format, "Png")
304
- exp_options.CompressionFormat = parse_enum(EFileCompressionFormat, options.compression, "None")
305
- exp_options.Platform = version.Platform
306
- exp_options.SocketFormat = parse_enum(ESocketFormat, options.socket_format, "Bone")
307
- exp_options.ExportMorphTargets = options.export_morph_targets
308
- exp_options.ExportMaterials = options.export_materials
309
- exp_options.ExportHdrTexturesAsHdr = options.export_hdr_as_hdr
255
+ exp_options = ExportOptions(
256
+ parse_enum(EMeshFormat, options.mesh_format, "ActorX"),
257
+ parse_enum(ENaniteMeshFormat, options.nanite_format, "NaniteFirst") if 'ENaniteMeshFormat' in globals() else None,
258
+ parse_enum(EMeshQuality, options.mesh_quality, "Highest"),
259
+ texture_platform_value,
260
+ parse_enum(ETextureFormat, options.texture_format, "Png"),
261
+ options.texture_quality,
262
+ options.export_hdr_as_hdr,
263
+ options.export_all_mips,
264
+ parse_enum(EMaterialDepth, options.material_depth, "TopLayerOnly"),
265
+ options.export_materials,
266
+ options.export_morph_targets,
267
+ parse_enum(ESocketFormat, options.socket_format, "Bone"),
268
+ parse_enum(EFileCompressionFormat, options.compression, "None")
269
+ )
310
270
 
311
271
  needs_output_dir = (not options.list_only) and options.format not in ("csv", "json")
312
272
  if not options.output:
@@ -317,13 +277,13 @@ def execute(options: CliOptions) -> int:
317
277
  else:
318
278
  export_directory = norm_path(options.output)
319
279
 
320
- import threading
321
- from concurrent.futures import ThreadPoolExecutor, as_completed
322
280
 
323
281
  counter_lock = threading.Lock()
324
282
  export_count = [0]
325
283
  counter = 0
326
284
  start = time.monotonic()
285
+
286
+ session_objects = []
327
287
 
328
288
  def process_package(package):
329
289
  folder = str(package.Path).rsplit("/", 1)[0] if "/" in str(package.Path) else ""
@@ -347,7 +307,7 @@ def execute(options: CliOptions) -> int:
347
307
  ok, pkg = provider.TryLoadPackage(package)
348
308
  if not ok or pkg is None:
349
309
  if ext in (".uasset", ".umap"):
350
- raise RuntimeError("Could not load standard asset, check game version, mappings or keys.")
310
+ raise RuntimeError(f"Could not load standard asset, check game version, mappings or keys: {package.Name}")
351
311
  if target_format in ("auto", "raw"):
352
312
  target_format = "raw"
353
313
  else:
@@ -364,10 +324,9 @@ def execute(options: CliOptions) -> int:
364
324
  if target_format not in ("auto", "png"):
365
325
  return True
366
326
 
367
- if target_format == "png":
368
- local_export_type = ExportType.TEXTURE
369
- else:
370
- local_export_type = ExportType.TEXTURE | ExportType.SOUND | ExportType.MESH | ExportType.ANIMATION | ExportType.OTHER
327
+ local_export_type = ExportType.TEXTURE if target_format == "png" else (
328
+ ExportType.TEXTURE | ExportType.SOUND | ExportType.MESH | ExportType.ANIMATION | ExportType.OTHER
329
+ )
371
330
 
372
331
  parsed = False
373
332
 
@@ -381,12 +340,8 @@ def execute(options: CliOptions) -> int:
381
340
 
382
341
  value = pointer.Object.Value
383
342
  if isinstance(dummy, UTexture) and (ExportType.TEXTURE in local_export_type) and isinstance(value, UTexture):
384
-
385
- name_prefix = folder if ext != ".umap" else str(package.Path).rsplit(".", 1)[0]+'.'+str(i)+'.'
386
- try:
387
- save_texture(folder, value, exp_options.Platform, exp_options, export_count, name_prefix)
388
- except Exception as exc:
389
- Log.Warning("failed to decode {ValueName}: {Error}", value.Name, exc)
343
+ with counter_lock:
344
+ session_objects.append(value)
390
345
  parsed = True
391
346
 
392
347
  elif isinstance(dummy, (USoundWave, UAkMediaAssetData)) and (ExportType.SOUND in local_export_type):
@@ -401,10 +356,8 @@ def execute(options: CliOptions) -> int:
401
356
  elif isinstance(dummy, (UAnimSequenceBase, USkeletalMesh, UStaticMesh, USkeleton)) and (
402
357
  ExportType.ANIMATION in local_export_type or ExportType.MESH in local_export_type
403
358
  ):
404
- exporter = CUE4Exporter(value, exp_options)
405
- ok_write, _, file_path = exporter.TryWriteToDir(DirectoryInfo(export_directory), None, None)
406
- if ok_write:
407
- write_to_log(folder, os.path.basename(str(file_path)), export_count)
359
+ with counter_lock:
360
+ session_objects.append(value)
408
361
  parsed = True
409
362
 
410
363
  if not parsed and (target_format == 'json' or target_format == 'auto'):
@@ -419,9 +372,8 @@ def execute(options: CliOptions) -> int:
419
372
  else:
420
373
  print(package.Path)
421
374
  else:
422
-
423
375
  max_workers = os.cpu_count() or 4 if not DEBUG else 1
424
- fatal_error = [None] # shared error slot
376
+ fatal_error = [None]
425
377
 
426
378
  print(f"Using parallel processing, {max_workers} threads", file=sys.stderr)
427
379
 
@@ -443,6 +395,17 @@ def execute(options: CliOptions) -> int:
443
395
 
444
396
  if fatal_error[0]:
445
397
  return 1
398
+
399
+ if session_objects:
400
+ print(f"Exporting {len(session_objects)} assets via ExportSession...", file=sys.stderr)
401
+ session = ExportSession()
402
+ session.MaxDegreeOfParallelism = 1 if options.verbose else max_workers
403
+ for obj in session_objects:
404
+ session.Add(obj)
405
+
406
+ task = session.RunAsync(export_directory, exp_options)
407
+ task.GetAwaiter().GetResult()
408
+ export_count[0] += len(session_objects)
446
409
 
447
410
  elapsed = time.monotonic() - start
448
411
 
@@ -452,8 +415,6 @@ def execute(options: CliOptions) -> int:
452
415
  Log.Information(f"Processed {len(packages)} packages in {elapsed:.2f}s")
453
416
  return 0
454
417
 
455
- import argparse
456
-
457
418
  class CustomHelpFormatter(argparse.RawTextHelpFormatter):
458
419
  def __init__(self, prog):
459
420
  super().__init__(prog, max_help_position=60, width=120)
@@ -472,7 +433,7 @@ examples = [
472
433
  ["Export a single package to stdout in json format:", "cue4parse -i MyGame -p Assets/MyAsset.uasset -f json"],
473
434
  ["Export multiple packages matching wildcard patterns to a directory:", "cue4parse -i MyGame -p */Textures* -p */Icons* -o Exports"],
474
435
  ["Export packages from list, overwrite existing files:", "cue4parse -i MyGame -c packages.txt -o Exports -y"],
475
- ["Export with PSK meshes and PSA animations:", "cue4parse -i MyGame -p */SkeletalMeshes/* -o Exports --mesh-format ActorX --anim-format ActorX"],
436
+ ["Export with PSK meshes and PSA animations:", "cue4parse -i MyGame -p */SkeletalMeshes/* -o Exports --mesh-format ActorX"],
476
437
  ["Process a single .pak file:", "cue4parse --pak MyMod.pak -o Exports -m Mappings.usmap -g GAME_UE5_1"],
477
438
  ]
478
439
 
@@ -496,15 +457,17 @@ parser.add_argument("-l", "--list", action="store_true", dest="list_only", help=
496
457
  parser.add_argument("-y", "--yes", action="store_true", help="Overwrite existing files")
497
458
  parser.add_argument("-v", "--verbose", action="store_true", help="Enable verbose output")
498
459
 
499
- # more options
460
+ # Options synced with Program.cs CUE4Parse_Conversion updates
500
461
  parser.add_argument("--pak", help="Single .pak file to process (no directory scanning)")
501
- parser.add_argument("--mesh-format", default="ActorX", help="Mesh format: ActorX (psk), Gltf2 (glb), UEFormat (uemodel), OBJ")
502
- parser.add_argument("--anim-format", default="ActorX", help="Animation format: ActorX (psa), UEFormat (ueanim)")
503
- parser.add_argument("--texture-format", default="Png", help="Texture format: Png, Jpeg, Tga, Dds")
504
- parser.add_argument("--material-format", default="AllLayersNoRef", help="Material format: FirstLayer, AllLayersNoRef, AllLayers")
505
- parser.add_argument("--lod-format", default="AllLods", help="LOD format: FirstLod, AllLods")
462
+ parser.add_argument("--mesh-format", default="ActorX", help="Mesh format: ActorX (psk), Gltf2 (glb), UEFormat (uemodel), USD (usda)")
463
+ parser.add_argument("--texture-format", default="Png", help="Texture format: Png, Jpeg, Tga, Webp")
464
+ parser.add_argument("--material-depth", default="TopLayerOnly", help="Material depth: TopLayerOnly, AllLayersNoRef, AllLayers")
465
+ parser.add_argument("--mesh-quality", default="Highest", help="Mesh quality: Highest, Lowest, All")
506
466
  parser.add_argument("--socket-format", default="Bone", help="Socket format: Bone, Socket, None")
507
- parser.add_argument("--nanite-format", default="AllLayersNaniteFirst", help="Nanite format: OnlyNaniteLOD, OnlyNormalLODs, AllLayersNaniteFirst, AllLayersNaniteLast")
467
+ parser.add_argument("--nanite-format", default="NaniteFirst", help="Nanite format: NaniteOnly, NoNanite, NaniteFirst, NaniteLast")
468
+ parser.add_argument("--texture-platform", default="DesktopMobile", help="Texture platform: DesktopMobile, XboxAndPlaystation4, NintendoSwitch, Playstation5")
469
+ parser.add_argument("--texture-quality", type=int, default=100, help="Texture quality 1-100")
470
+ parser.add_argument("--export-all-mips", action=argparse.BooleanOptionalAction, default=False, help="Export all texture mip levels")
508
471
  parser.add_argument("--export-morph-targets", action=argparse.BooleanOptionalAction, default=True, help="Export morph targets")
509
472
  parser.add_argument("--export-materials", action=argparse.BooleanOptionalAction, default=True, help="Export materials with meshes")
510
473
  parser.add_argument("--export-hdr-as-hdr", action=argparse.BooleanOptionalAction, default=True, help="Export HDR textures as .hdr")
@@ -0,0 +1,110 @@
1
+ from __future__ import annotations
2
+
3
+ import fnmatch
4
+ import json
5
+ import logging
6
+ import os
7
+ import sys
8
+ import time
9
+ from dataclasses import dataclass
10
+ from enum import Flag, auto
11
+ from pathlib import Path
12
+ from typing import Any, Iterable
13
+
14
+ from cue4parse import _DLL_DIR
15
+
16
+ def _find_dotnet_root() -> Path | None:
17
+ candidates = [
18
+ Path.home() / ".dotnet",
19
+ Path("/usr/share/dotnet"),
20
+ Path("/usr/lib/dotnet"),
21
+ ]
22
+ for candidate in candidates:
23
+ if (candidate / "host" / "fxr").exists():
24
+ return candidate
25
+ return None
26
+
27
+ def _find_runtime_config() -> str | None:
28
+ libs_dir = Path(__file__).parent / "libs"
29
+ configs = list(libs_dir.glob("*.runtimeconfig.json"))
30
+ return str(configs[0]) if configs else None
31
+
32
+ dotnet_root = _find_dotnet_root()
33
+ if dotnet_root:
34
+ os.environ["DOTNET_ROOT"] = str(dotnet_root)
35
+
36
+ import pythonnet
37
+
38
+ runtime_config = _find_runtime_config()
39
+ if runtime_config:
40
+ pythonnet.load("coreclr", runtime_config=runtime_config)
41
+ else:
42
+ pythonnet.load("coreclr")
43
+
44
+ import clr
45
+
46
+ libs_dir = _DLL_DIR
47
+ cue4parse_dll_dir = str(libs_dir)
48
+ sys.path.append(cue4parse_dll_dir)
49
+
50
+ clr.AddReference("CUE4Parse")
51
+ clr.AddReference("CUE4Parse-Conversion")
52
+
53
+ clr.AddReference("System")
54
+ clr.AddReference("System.Core")
55
+ clr.AddReference("Newtonsoft.Json")
56
+
57
+ # enable logger
58
+
59
+ clr.AddReference("Serilog")
60
+ clr.AddReference("Serilog.Sinks.Console")
61
+
62
+ import System
63
+ from Serilog import Log, LoggerConfiguration
64
+ from Serilog.Configuration import LoggerSinkConfiguration
65
+ from Serilog.Events import LogEventLevel
66
+
67
+ def patch_serilog_console() -> None:
68
+ flags = System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static
69
+ sink_type = clr.GetClrType(LoggerSinkConfiguration)
70
+ logger_type = clr.GetClrType(LoggerConfiguration)
71
+ assembly = System.Reflection.Assembly.Load("Serilog.Sinks.Console")
72
+
73
+ console_method = next(
74
+ method
75
+ for typ in assembly.GetTypes()
76
+ for method in typ.GetMethods(flags)
77
+ if method.Name == "Console"
78
+ and (params := method.GetParameters())
79
+ and params[0].ParameterType == sink_type
80
+ )
81
+
82
+ parent_field = next(
83
+ field
84
+ for field in sink_type.GetFields(
85
+ System.Reflection.BindingFlags.Public
86
+ | System.Reflection.BindingFlags.NonPublic
87
+ | System.Reflection.BindingFlags.Instance
88
+ )
89
+ if field.FieldType == logger_type
90
+ )
91
+
92
+ def console(self, *args, **kwargs):
93
+ overrides = {"standardErrorFromLevel": LogEventLevel.Verbose, **kwargs}
94
+ positional = (self, *args)
95
+ values = [
96
+ overrides[p.Name]
97
+ if p.Name in overrides
98
+ else positional[i]
99
+ if i < len(positional)
100
+ else System.Type.Missing
101
+ for i, p in enumerate(console_method.GetParameters())
102
+ ]
103
+ console_method.Invoke(None, System.Array[System.Object](values))
104
+ return parent_field.GetValue(self)
105
+
106
+ setattr(LoggerSinkConfiguration, "Console", console)
107
+
108
+ patch_serilog_console()
109
+
110
+ Log.Logger = LoggerConfiguration().MinimumLevel.Verbose().WriteTo.Console().CreateLogger()
@@ -1,4 +1,5 @@
1
- import time
1
+ import time, sys
2
+
2
3
  from cue4parse import *
3
4
 
4
5
  GAME_DIR = r"E:\Games\Subnautica2\Subnautica2\Content\Paks"
@@ -7,7 +8,7 @@ MAPPINGS_FILE = r"E:\Games\Subnautica2\Subnautica2.usmap"
7
8
  def main():
8
9
  start = time.perf_counter()
9
10
 
10
- Log.Logger = with_console(LoggerConfiguration().MinimumLevel.Verbose()).CreateLogger()
11
+ Log.Logger = LoggerConfiguration().MinimumLevel.Verbose().WriteTo.Console().CreateLogger()
11
12
 
12
13
  version = VersionContainer(EGame.GAME_UE5_6, ETexturePlatform.DesktopMobile)
13
14
  provider = DefaultFileProvider(GAME_DIR, SearchOption.TopDirectoryOnly, version, StringComparer.OrdinalIgnoreCase)
@@ -24,15 +25,14 @@ def main():
24
25
 
25
26
  exports = package.GetExports()
26
27
 
27
- name_filter = lambda name: 'Lifepod' in str(name)
28
+ filter = lambda name: 'Lifepod' in str(name)
28
29
 
29
30
  for export in exports:
30
- name = str(export)
31
- if not name_filter(name):
31
+ if not filter(export.ExportType):
32
32
  continue
33
- print(export.ExportType)
33
+ print(export.ExportType, export.Name)
34
34
 
35
- print(f'Finished in {time.perf_counter() - start:.4f}s')
35
+ print(f'Finished in {time.perf_counter() - start:.4f}s', file=sys.stderr)
36
36
 
37
37
  if __name__ == '__main__':
38
38
  raise SystemExit(main())
@@ -6,13 +6,14 @@ from System.Text import Encoding
6
6
  from Newtonsoft.Json import JsonSerializer, Formatting, JsonTextWriter, JsonConvert
7
7
 
8
8
  from System import Guid, StringComparer
9
- from System.IO import SearchOption, DirectoryInfo
9
+ from System.IO import SearchOption
10
10
  from CUE4Parse.Compression import OodleHelper, ZlibHelper
11
11
  from CUE4Parse.Encryption.Aes import FAesKey
12
12
  from CUE4Parse.FileProvider import DefaultFileProvider, ApkFileProvider
13
- from CUE4Parse.MappingsProvider import FileUsmapTypeMappingsProvider
13
+ from CUE4Parse.MappingsProvider.Usmap import FileUsmapTypeMappingsProvider
14
14
  from CUE4Parse.UE4.Assets import AbstractUePackage
15
15
  from CUE4Parse.UE4.Assets.Exports.Animation import UAnimSequenceBase, USkeleton
16
+ from CUE4Parse.UE4.Assets.Exports.Material import EMaterialDepth
16
17
  from CUE4Parse.UE4.Assets.Exports.SkeletalMesh import USkeletalMesh
17
18
  from CUE4Parse.UE4.Assets.Exports.Sound import USoundWave
18
19
  from CUE4Parse.UE4.Assets.Exports.StaticMesh import UStaticMesh
@@ -24,11 +25,9 @@ from CUE4Parse.UE4.Objects.UObject import FPackageIndex
24
25
  from CUE4Parse.UE4.Versions import VersionContainer, EGame, ELanguage
25
26
  from CUE4Parse.UE4.Assets.Exports.Texture import ETexturePlatform
26
27
 
27
- from CUE4Parse_Conversion import Exporter as CUE4Exporter, ExporterOptions
28
- from CUE4Parse_Conversion.Animations import EAnimFormat
29
- from CUE4Parse_Conversion.Meshes import EMeshFormat, ELodFormat, ESocketFormat
30
- from CUE4Parse_Conversion.Textures import ETextureFormat, TextureDecoder, TextureEncoder, CubemapConverter, CTexture
31
- from CUE4Parse_Conversion.Textures.BC import DetexHelper
32
- from CUE4Parse_Conversion.UEFormat.Enums import EFileCompressionFormat
33
- from CUE4Parse.UE4.Assets.Exports.Material import EMaterialFormat
28
+ from CUE4Parse_Conversion import ExportSession
29
+ from CUE4Parse_Conversion.Options import ExportOptions
30
+ from CUE4Parse_Conversion.Options import EMeshFormat, EMeshQuality, ESocketFormat, ENaniteMeshFormat
31
+ from CUE4Parse_Conversion.Options import ETextureFormat
32
+ from CUE4Parse_Conversion.Writers.UEFormat.Enums import EFileCompressionFormat
34
33
 
cue4parse-0.0.4/README.md DELETED
@@ -1,12 +0,0 @@
1
- # CUE4Parse-Python
2
-
3
- CUE4Parse Python wrapper, CLI tool, and scripting engine
4
-
5
- ## Usage
6
-
7
- `pip install cue4parse`
8
-
9
- * Make sure executable is added to paths, e.g. `%LOCALAPPDATA%\Python\pythoncore-3.14-64\Scripts`.
10
- * Needs CUE4Parse libraries in `%LOCALAPPDATA%/cue4parse/libs` (downloads and unpacks FModel on Windows).
11
- * To run CLI: `cue4parse`
12
-
@@ -1,90 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import fnmatch
4
- import json
5
- import logging
6
- import os
7
- import sys
8
- import time
9
- from dataclasses import dataclass
10
- from enum import Flag, auto
11
- from pathlib import Path
12
- from typing import Any, Iterable
13
-
14
- from cue4parse import _DLL_DIR
15
-
16
- def _find_dotnet_root() -> Path | None:
17
- candidates = [
18
- Path.home() / ".dotnet",
19
- Path("/usr/share/dotnet"),
20
- Path("/usr/lib/dotnet"),
21
- ]
22
- for candidate in candidates:
23
- if (candidate / "host" / "fxr").exists():
24
- return candidate
25
- return None
26
-
27
- def _find_runtime_config() -> str | None:
28
- libs_dir = Path(__file__).parent / "libs"
29
- configs = list(libs_dir.glob("*.runtimeconfig.json"))
30
- return str(configs[0]) if configs else None
31
-
32
- dotnet_root = _find_dotnet_root()
33
- if dotnet_root:
34
- os.environ["DOTNET_ROOT"] = str(dotnet_root)
35
-
36
- import pythonnet
37
-
38
- runtime_config = _find_runtime_config()
39
- if runtime_config:
40
- pythonnet.load("coreclr", runtime_config=runtime_config)
41
- else:
42
- pythonnet.load("coreclr")
43
-
44
- import clr
45
-
46
- libs_dir = _DLL_DIR
47
- cue4parse_dll_dir = str(libs_dir)
48
- sys.path.append(cue4parse_dll_dir)
49
-
50
- clr.AddReference("CUE4Parse")
51
- clr.AddReference("CUE4Parse-Conversion")
52
-
53
- clr.AddReference("System")
54
- clr.AddReference("System.Core")
55
- clr.AddReference("Newtonsoft.Json")
56
-
57
- clr.AddReference("Serilog")
58
- clr.AddReference("Serilog.Sinks.Console")
59
-
60
- import System
61
- from Serilog import Log, LoggerConfiguration
62
- from Serilog.Events import LogEventLevel
63
- from Serilog.Configuration import LoggerSinkConfiguration
64
-
65
- def with_console(cfg: LoggerConfiguration) -> LoggerConfiguration:
66
- asm = System.Reflection.Assembly.Load("Serilog.Sinks.Console")
67
- bf = System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static
68
- lsc_t = clr.GetClrType(LoggerSinkConfiguration)
69
- for t in asm.GetTypes():
70
- if not (t.IsSealed and t.IsAbstract):
71
- continue
72
- for m in t.GetMethods(bf):
73
- if m.Name != "Console":
74
- continue
75
- ps = m.GetParameters()
76
- if len(ps) >= 1 and ps[0].ParameterType == lsc_t:
77
- args = [System.Type.Missing] * len(ps)
78
- args[0] = cfg.WriteTo
79
- # Find and set the standardErrorFromLevel parameter so it's stderr, not stdout
80
- for i, p in enumerate(ps):
81
- if p.Name == "standardErrorFromLevel":
82
- lel_t = System.Type.GetType("Serilog.Events.LogEventLevel, Serilog")
83
- args[i] = System.Enum.Parse(lel_t, "Verbose")
84
- break
85
- m.Invoke(None, System.Array[System.Object](args))
86
- return cfg
87
- raise RuntimeError("Serilog console sink extension method not found")
88
-
89
- # you can re-define it in user code later with Verbose() or any other level
90
- Log.Logger = with_console(LoggerConfiguration().MinimumLevel.Fatal()).CreateLogger()
File without changes
File without changes