Monorepo for Aesthetic.Computer aesthetic.computer
at main 118 lines 4.0 kB view raw
1#!/usr/bin/env python3 2""" 3Parse UE5 uncooked .uasset files to extract widget/blueprint references. 4Works outside of Unreal Editor. 5""" 6import struct 7import sys 8import json 9from pathlib import Path 10 11def parse_uasset(filepath): 12 """Parse uncooked .uasset file and extract useful references""" 13 with open(filepath, 'rb') as f: 14 data = f.read() 15 16 result = { 17 'file': str(filepath), 18 'size': len(data), 19 'game_paths': [], 20 'script_refs': [], 21 'widget_refs': [], 22 'button_bindings': [], 23 'fonts': [], 24 'textures': [] 25 } 26 27 # Find /Game/ paths (widget, blueprint, asset references) 28 game_pattern = b'/Game/' 29 idx = 0 30 while True: 31 idx = data.find(game_pattern, idx) 32 if idx == -1: 33 break 34 end = idx 35 while end < len(data) and data[end] != 0 and 32 <= data[end] < 127: 36 end += 1 37 path = data[idx:end].decode('utf-8', errors='replace') 38 if path and path not in result['game_paths']: 39 result['game_paths'].append(path) 40 # Categorize 41 if 'WBP_' in path or 'Widget' in path: 42 result['widget_refs'].append(path) 43 if 'Font' in path: 44 result['fonts'].append(path) 45 if 'Texture' in path or '.uasset' in path.lower(): 46 result['textures'].append(path) 47 idx += 1 48 49 # Find /Script/ references (engine classes) 50 script_pattern = b'/Script/' 51 idx = 0 52 while True: 53 idx = data.find(script_pattern, idx) 54 if idx == -1: 55 break 56 end = idx 57 while end < len(data) and data[end] != 0 and 32 <= data[end] < 127: 58 end += 1 59 path = data[idx:end].decode('utf-8', errors='replace') 60 if path and path not in result['script_refs']: 61 result['script_refs'].append(path) 62 idx += 1 63 64 # Find button event bindings (BndEvt__ pattern) 65 evt_pattern = b'BndEvt__' 66 idx = 0 67 while True: 68 idx = data.find(evt_pattern, idx) 69 if idx == -1: 70 break 71 end = idx 72 while end < len(data) and data[end] != 0 and 32 <= data[end] < 127: 73 end += 1 74 binding = data[idx:end].decode('utf-8', errors='replace') 75 if binding and binding not in result['button_bindings']: 76 result['button_bindings'].append(binding) 77 idx += 1 78 79 return result 80 81def compare_widgets(file1, file2): 82 """Compare two widget assets""" 83 r1 = parse_uasset(file1) 84 r2 = parse_uasset(file2) 85 86 comparison = { 87 'file1': str(file1), 88 'file2': str(file2), 89 'widget_refs': { 90 'only_in_file1': [x for x in r1['widget_refs'] if x not in r2['widget_refs']], 91 'only_in_file2': [x for x in r2['widget_refs'] if x not in r1['widget_refs']], 92 'common': [x for x in r1['widget_refs'] if x in r2['widget_refs']] 93 }, 94 'button_bindings': { 95 'only_in_file1': [x for x in r1['button_bindings'] if x not in r2['button_bindings']], 96 'only_in_file2': [x for x in r2['button_bindings'] if x not in r1['button_bindings']], 97 'common': [x for x in r1['button_bindings'] if x in r2['button_bindings']] 98 }, 99 'game_paths': { 100 'only_in_file1': [x for x in r1['game_paths'] if x not in r2['game_paths']], 101 'only_in_file2': [x for x in r2['game_paths'] if x not in r1['game_paths']] 102 } 103 } 104 return comparison 105 106if __name__ == '__main__': 107 if len(sys.argv) < 2: 108 print("Usage:") 109 print(" python parse_uasset.py <file.uasset> # Parse single file") 110 print(" python parse_uasset.py <file1> <file2> # Compare two files") 111 sys.exit(1) 112 113 if len(sys.argv) == 2: 114 result = parse_uasset(sys.argv[1]) 115 print(json.dumps(result, indent=2)) 116 else: 117 comparison = compare_widgets(sys.argv[1], sys.argv[2]) 118 print(json.dumps(comparison, indent=2))