unity-agentic-tools 0.4.1 → 0.4.3
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.
- package/README.md +20 -2
- package/dist/cli.js +2109 -657
- package/dist/index.js +1211 -188
- package/native/unity-file-tools.darwin-arm64.node +0 -0
- package/native/unity-file-tools.darwin-x64.node +0 -0
- package/native/unity-file-tools.linux-x64-gnu.node +0 -0
- package/native/unity-file-tools.win32-x64-msvc.node +0 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -205,7 +205,7 @@ var require_package = __commonJS((exports2, module2) => {
|
|
|
205
205
|
module2.exports = {
|
|
206
206
|
name: "unity-agentic-tools",
|
|
207
207
|
packageManager: "bun@latest",
|
|
208
|
-
version: "0.4.
|
|
208
|
+
version: "0.4.3",
|
|
209
209
|
description: "Fast, token-efficient Unity YAML parser for AI agents",
|
|
210
210
|
exports: {
|
|
211
211
|
".": {
|
|
@@ -1848,6 +1848,19 @@ function build_guid_cache(project_path, raw) {
|
|
|
1848
1848
|
}
|
|
1849
1849
|
}
|
|
1850
1850
|
return substringMatch;
|
|
1851
|
+
},
|
|
1852
|
+
find_all_by_name_exact(name, extension) {
|
|
1853
|
+
const nameLower = name.toLowerCase().replace(/\.[^.]+$/, "");
|
|
1854
|
+
const matches = [];
|
|
1855
|
+
for (const [guid, assetPath] of Object.entries(raw)) {
|
|
1856
|
+
if (extension && !assetPath.endsWith(extension))
|
|
1857
|
+
continue;
|
|
1858
|
+
const fileName = import_path5.basename(assetPath, import_path5.extname(assetPath)).toLowerCase();
|
|
1859
|
+
if (fileName === nameLower) {
|
|
1860
|
+
matches.push({ guid, path: assetPath });
|
|
1861
|
+
}
|
|
1862
|
+
}
|
|
1863
|
+
return matches;
|
|
1851
1864
|
}
|
|
1852
1865
|
};
|
|
1853
1866
|
}
|
|
@@ -1946,15 +1959,14 @@ var UNITY_CLASS_IDS = {
|
|
|
1946
1959
|
114: "MonoBehaviour",
|
|
1947
1960
|
115: "MonoScript",
|
|
1948
1961
|
117: "Texture3D",
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
126: "
|
|
1957
|
-
127: "NavMeshProjectSettings",
|
|
1962
|
+
118: "NewAnimationTrack",
|
|
1963
|
+
119: "Projector",
|
|
1964
|
+
120: "LineRenderer",
|
|
1965
|
+
121: "Flare",
|
|
1966
|
+
122: "Halo",
|
|
1967
|
+
123: "LensFlare",
|
|
1968
|
+
124: "FlareLayer",
|
|
1969
|
+
126: "NavMeshProjectSettings",
|
|
1958
1970
|
128: "Font",
|
|
1959
1971
|
129: "PlayerSettings",
|
|
1960
1972
|
130: "NamedObject",
|
|
@@ -1981,6 +1993,7 @@ var UNITY_CLASS_IDS = {
|
|
|
1981
1993
|
164: "AudioReverbFilter",
|
|
1982
1994
|
165: "AudioHighPassFilter",
|
|
1983
1995
|
166: "AudioChorusFilter",
|
|
1996
|
+
1660057539: "SceneRoots",
|
|
1984
1997
|
167: "AudioReverbZone",
|
|
1985
1998
|
168: "AudioEchoFilter",
|
|
1986
1999
|
169: "AudioLowPassFilter",
|
|
@@ -2051,29 +2064,41 @@ var UNITY_CLASS_IDS = {
|
|
|
2051
2064
|
257: "TargetJoint2D",
|
|
2052
2065
|
258: "LightProbes",
|
|
2053
2066
|
259: "LightProbeProxyVolume",
|
|
2054
|
-
260: "
|
|
2055
|
-
261: "
|
|
2056
|
-
262: "
|
|
2067
|
+
260: "SampleClipLegacy",
|
|
2068
|
+
261: "AudioMixerSnapshotLegacy",
|
|
2069
|
+
262: "AudioMixerGroupLegacy",
|
|
2057
2070
|
265: "NScreenBridge",
|
|
2058
|
-
271: "
|
|
2071
|
+
271: "SampleClip",
|
|
2059
2072
|
272: "UnityAdsManager",
|
|
2060
|
-
273: "
|
|
2061
|
-
280: "
|
|
2062
|
-
281: "
|
|
2063
|
-
290: "
|
|
2064
|
-
292: "
|
|
2065
|
-
293: "
|
|
2066
|
-
294: "
|
|
2067
|
-
298: "
|
|
2073
|
+
273: "AudioMixerGroup",
|
|
2074
|
+
280: "UnityConnectSettingsLegacy",
|
|
2075
|
+
281: "AvatarMaskLegacy",
|
|
2076
|
+
290: "AssetBundleManifest",
|
|
2077
|
+
292: "VideoPlayerLegacy",
|
|
2078
|
+
293: "VideoClipLegacy",
|
|
2079
|
+
294: "ParticleSystemForceFieldLegacy",
|
|
2080
|
+
298: "SpriteMaskLegacy",
|
|
2068
2081
|
300: "WorldAnchor",
|
|
2069
|
-
301: "
|
|
2070
|
-
310: "
|
|
2071
|
-
|
|
2072
|
-
|
|
2082
|
+
301: "OcclusionCullingDataLegacy",
|
|
2083
|
+
310: "UnityConnectSettings",
|
|
2084
|
+
1001: "PrefabInstance",
|
|
2085
|
+
319: "AvatarMask",
|
|
2086
|
+
320: "PlayableDirector",
|
|
2087
|
+
328: "VideoPlayer",
|
|
2088
|
+
329: "VideoClip",
|
|
2089
|
+
330: "ParticleSystemForceField",
|
|
2090
|
+
331: "SpriteMask",
|
|
2091
|
+
363: "OcclusionCullingData",
|
|
2092
|
+
1006: "TextureImporter",
|
|
2093
|
+
181963792: "Preset",
|
|
2073
2094
|
687078895: "SpriteAtlas",
|
|
2095
|
+
156049354: "Grid",
|
|
2096
|
+
1742807556: "GridLayout",
|
|
2074
2097
|
1839735485: "Tilemap",
|
|
2075
|
-
|
|
2076
|
-
|
|
2098
|
+
19719996: "TilemapCollider2D",
|
|
2099
|
+
483693784: "TilemapRenderer",
|
|
2100
|
+
1839735486: "TilemapCollider2DLegacy",
|
|
2101
|
+
1839735487: "TilemapRendererLegacy"
|
|
2077
2102
|
};
|
|
2078
2103
|
var UNITY_CLASS_NAMES = Object.fromEntries(Object.entries(UNITY_CLASS_IDS).map(([id, name]) => [name, parseInt(id, 10)]));
|
|
2079
2104
|
function get_class_id_name(class_id) {
|
|
@@ -2147,8 +2172,12 @@ function resolve_source_prefab(doc, file_path, project_path) {
|
|
|
2147
2172
|
prefab_instance_block: pi_block
|
|
2148
2173
|
};
|
|
2149
2174
|
}
|
|
2150
|
-
function resolveScriptGuid(script, projectPath) {
|
|
2175
|
+
function resolveScriptGuid(script, projectPath, options) {
|
|
2176
|
+
const strictExactName = options?.strict_exact_name === true;
|
|
2151
2177
|
if (/^[a-f0-9]{32}$/i.test(script)) {
|
|
2178
|
+
if (/^0{32}$/i.test(script)) {
|
|
2179
|
+
throw new Error(`Invalid script GUID "${script}": all-zero GUID is not allowed. Provide a real script GUID from a .meta file.`);
|
|
2180
|
+
}
|
|
2152
2181
|
return { guid: script.toLowerCase(), path: null };
|
|
2153
2182
|
}
|
|
2154
2183
|
if (script.endsWith(".cs")) {
|
|
@@ -2173,9 +2202,20 @@ function resolveScriptGuid(script, projectPath) {
|
|
|
2173
2202
|
if (projectPath) {
|
|
2174
2203
|
const guidCache = load_guid_cache(projectPath);
|
|
2175
2204
|
if (guidCache) {
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2205
|
+
if (strictExactName) {
|
|
2206
|
+
const exactMatches = guidCache.find_all_by_name_exact(script, ".cs");
|
|
2207
|
+
if (exactMatches.length === 1) {
|
|
2208
|
+
return exactMatches[0];
|
|
2209
|
+
}
|
|
2210
|
+
if (exactMatches.length > 1) {
|
|
2211
|
+
const paths = exactMatches.map((m) => m.path).join(", ");
|
|
2212
|
+
throw new Error(`Ambiguous type "${script}": found ${exactMatches.length} exact script name matches (${paths}). ` + 'Use a qualified name (e.g., "Namespace.TypeName") or provide the full script path.');
|
|
2213
|
+
}
|
|
2214
|
+
} else {
|
|
2215
|
+
const result = guidCache.find_by_name(script, ".cs");
|
|
2216
|
+
if (result)
|
|
2217
|
+
return result;
|
|
2218
|
+
}
|
|
2179
2219
|
}
|
|
2180
2220
|
const registryPath = path3.join(projectPath, ".unity-agentic", "type-registry.json");
|
|
2181
2221
|
if (import_fs8.existsSync(registryPath)) {
|
|
@@ -2326,16 +2366,49 @@ function resolveScriptGuid(script, projectPath) {
|
|
|
2326
2366
|
continue;
|
|
2327
2367
|
try {
|
|
2328
2368
|
const cache = JSON.parse(import_fs8.readFileSync(cachePath, "utf-8"));
|
|
2369
|
+
const exactMatches = [];
|
|
2329
2370
|
for (const [guid, assetPath] of Object.entries(cache)) {
|
|
2330
2371
|
if (!assetPath.endsWith(".cs"))
|
|
2331
2372
|
continue;
|
|
2332
2373
|
const fileName = path3.basename(assetPath, ".cs").toLowerCase();
|
|
2333
2374
|
if (fileName === scriptNameLower || classNameOnly && fileName === classNameOnly) {
|
|
2334
|
-
|
|
2375
|
+
exactMatches.push({ guid, path: assetPath });
|
|
2335
2376
|
}
|
|
2336
2377
|
}
|
|
2337
|
-
|
|
2378
|
+
if (exactMatches.length === 1) {
|
|
2379
|
+
return exactMatches[0];
|
|
2380
|
+
}
|
|
2381
|
+
if (exactMatches.length > 1) {
|
|
2382
|
+
const paths = exactMatches.map((m) => m.path).join(", ");
|
|
2383
|
+
throw new Error(`Ambiguous type "${script}": found ${exactMatches.length} exact script name matches (${paths}). ` + 'Use a qualified name (e.g., "Namespace.TypeName") or provide the full script path.');
|
|
2384
|
+
}
|
|
2385
|
+
} catch (err) {
|
|
2386
|
+
if (err instanceof Error && err.message.startsWith("Ambiguous type")) {
|
|
2387
|
+
throw err;
|
|
2388
|
+
}
|
|
2389
|
+
}
|
|
2390
|
+
}
|
|
2391
|
+
if (strictExactName) {
|
|
2392
|
+
return null;
|
|
2338
2393
|
}
|
|
2394
|
+
try {
|
|
2395
|
+
const assetsDir = path3.join(projectPath, "Assets");
|
|
2396
|
+
if (import_fs8.existsSync(assetsDir)) {
|
|
2397
|
+
const entries = import_fs8.readdirSync(assetsDir, { recursive: true, withFileTypes: false });
|
|
2398
|
+
for (const entry of entries) {
|
|
2399
|
+
if (!entry.endsWith(".cs"))
|
|
2400
|
+
continue;
|
|
2401
|
+
const fileName = path3.basename(entry, ".cs").toLowerCase();
|
|
2402
|
+
if (fileName === scriptNameLower || classNameOnly && fileName === classNameOnly) {
|
|
2403
|
+
const fullPath = path3.join(assetsDir, entry);
|
|
2404
|
+
const guid = extractGuidFromMeta(fullPath + ".meta");
|
|
2405
|
+
if (guid) {
|
|
2406
|
+
return { guid, path: path3.join("Assets", entry) };
|
|
2407
|
+
}
|
|
2408
|
+
}
|
|
2409
|
+
}
|
|
2410
|
+
}
|
|
2411
|
+
} catch {}
|
|
2339
2412
|
}
|
|
2340
2413
|
return null;
|
|
2341
2414
|
}
|
|
@@ -2387,19 +2460,38 @@ function resolveAssetPathToPPtr(value, file_path, project_path) {
|
|
|
2387
2460
|
return null;
|
|
2388
2461
|
return `{fileID: ${mapping.fileID}, guid: ${guid}, type: ${mapping.type}}`;
|
|
2389
2462
|
}
|
|
2390
|
-
function
|
|
2391
|
-
|
|
2463
|
+
function detect_abstract_class_in_source(file_path, class_name) {
|
|
2464
|
+
try {
|
|
2465
|
+
const source = import_fs8.readFileSync(file_path, "utf-8");
|
|
2466
|
+
const escaped_name = class_name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2467
|
+
const pattern = new RegExp(`(^|[^\\w])abstract\\s+class\\s+${escaped_name}(?=$|[^\\w])`, "m");
|
|
2468
|
+
return pattern.test(source);
|
|
2469
|
+
} catch {
|
|
2470
|
+
return false;
|
|
2471
|
+
}
|
|
2472
|
+
}
|
|
2473
|
+
function resolve_script_with_fields(script, project_path, options) {
|
|
2474
|
+
const resolved = resolveScriptGuid(script, project_path, options);
|
|
2392
2475
|
if (!resolved)
|
|
2393
2476
|
return null;
|
|
2394
2477
|
const result = {
|
|
2395
2478
|
guid: resolved.guid,
|
|
2396
2479
|
path: resolved.path
|
|
2397
2480
|
};
|
|
2481
|
+
if (resolved.path && resolved.path.endsWith(".cs")) {
|
|
2482
|
+
const full_path = path3.isAbsolute(resolved.path) ? resolved.path : project_path ? path3.join(project_path, resolved.path) : resolved.path;
|
|
2483
|
+
if (import_fs8.existsSync(full_path)) {
|
|
2484
|
+
const class_name = script.includes(".") ? script.split(".").pop() : script;
|
|
2485
|
+
if (class_name.length > 0) {
|
|
2486
|
+
result.is_abstract = detect_abstract_class_in_source(full_path, class_name);
|
|
2487
|
+
}
|
|
2488
|
+
}
|
|
2489
|
+
}
|
|
2398
2490
|
if (!project_path)
|
|
2399
2491
|
return result;
|
|
2400
2492
|
if (resolved.path) {
|
|
2401
2493
|
try {
|
|
2402
|
-
const full_path = resolved.path
|
|
2494
|
+
const full_path = path3.isAbsolute(resolved.path) ? resolved.path : path3.join(project_path, resolved.path);
|
|
2403
2495
|
if (!import_fs8.existsSync(full_path)) {
|
|
2404
2496
|
result.extraction_error = `Script file not found at resolved path: ${full_path}`;
|
|
2405
2497
|
} else if (resolved.path.endsWith(".cs")) {
|
|
@@ -2413,6 +2505,10 @@ function resolve_script_with_fields(script, project_path) {
|
|
|
2413
2505
|
result.fields = chosen.fields;
|
|
2414
2506
|
result.base_class = chosen.baseClass ?? undefined;
|
|
2415
2507
|
result.kind = chosen.kind;
|
|
2508
|
+
result.class_name = chosen.name;
|
|
2509
|
+
if (chosen.name) {
|
|
2510
|
+
result.is_abstract = detect_abstract_class_in_source(full_path, chosen.name);
|
|
2511
|
+
}
|
|
2416
2512
|
}
|
|
2417
2513
|
} else {
|
|
2418
2514
|
result.extraction_error = "Native extractSerializedFields function not available";
|
|
@@ -2490,8 +2586,8 @@ function build_type_lookup(project_path) {
|
|
|
2490
2586
|
const assetsDir = path3.join(project_path, "Assets");
|
|
2491
2587
|
const csIndex = new Map;
|
|
2492
2588
|
try {
|
|
2493
|
-
const { readdirSync:
|
|
2494
|
-
const entries =
|
|
2589
|
+
const { readdirSync: readdirSync5 } = require("fs");
|
|
2590
|
+
const entries = readdirSync5(assetsDir, { recursive: true, withFileTypes: false });
|
|
2495
2591
|
for (const entry of entries) {
|
|
2496
2592
|
if (typeof entry === "string" && entry.endsWith(".cs")) {
|
|
2497
2593
|
const basename4 = entry.substring(entry.lastIndexOf("/") + 1).replace(/\.cs$/, "");
|
|
@@ -3512,7 +3608,21 @@ class UnityDocument {
|
|
|
3512
3608
|
}
|
|
3513
3609
|
}
|
|
3514
3610
|
}
|
|
3515
|
-
|
|
3611
|
+
const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3612
|
+
const name_pattern = new RegExp(`propertyPath: m_Name\\s*\\n\\s*value:\\s*${escaped}\\s*$`, "m");
|
|
3613
|
+
for (const block of this._blocks) {
|
|
3614
|
+
if ((block.class_id === 1001 || block.class_id === 310) && name_pattern.test(block.raw)) {
|
|
3615
|
+
const pi_id = block.file_id;
|
|
3616
|
+
const pi_ref_pattern = new RegExp(`m_PrefabInstance:[ \\t]*\\{fileID:[ \\t]*${pi_id}\\}`);
|
|
3617
|
+
const stripped_transform = this._blocks.find((b) => (b.class_id === 4 || b.class_id === 224) && b.is_stripped && pi_ref_pattern.test(b.raw));
|
|
3618
|
+
if (stripped_transform) {
|
|
3619
|
+
transform_ids.push(stripped_transform.file_id);
|
|
3620
|
+
} else {
|
|
3621
|
+
transform_ids.push(pi_id);
|
|
3622
|
+
}
|
|
3623
|
+
}
|
|
3624
|
+
}
|
|
3625
|
+
return Array.from(new Set(transform_ids));
|
|
3516
3626
|
}
|
|
3517
3627
|
require_unique_game_object(name_or_id) {
|
|
3518
3628
|
if (/^-?\d+$/.test(name_or_id)) {
|
|
@@ -3541,10 +3651,7 @@ class UnityDocument {
|
|
|
3541
3651
|
if (!block) {
|
|
3542
3652
|
return { error: `Block with fileID ${name_or_id} not found` };
|
|
3543
3653
|
}
|
|
3544
|
-
if (block.class_id === 4) {
|
|
3545
|
-
return block;
|
|
3546
|
-
}
|
|
3547
|
-
if (block.class_id === 224) {
|
|
3654
|
+
if (block.class_id === 4 || block.class_id === 224 || block.class_id === 1001 || block.class_id === 310) {
|
|
3548
3655
|
return block;
|
|
3549
3656
|
}
|
|
3550
3657
|
if (block.class_id === 1) {
|
|
@@ -3557,15 +3664,40 @@ class UnityDocument {
|
|
|
3557
3664
|
}
|
|
3558
3665
|
return { error: `Transform for GameObject fileID ${name_or_id} not found` };
|
|
3559
3666
|
}
|
|
3560
|
-
return { error: `fileID ${name_or_id} is not a GameObject or
|
|
3667
|
+
return { error: `fileID ${name_or_id} is not a GameObject, Transform, or PrefabInstance (class ${block.class_id})` };
|
|
3561
3668
|
}
|
|
3562
3669
|
const transform_ids = this.find_transforms_by_name(name_or_id);
|
|
3563
3670
|
if (transform_ids.length === 0) {
|
|
3564
|
-
return { error: `
|
|
3671
|
+
return { error: `Object "${name_or_id}" not found` };
|
|
3565
3672
|
}
|
|
3566
3673
|
if (transform_ids.length > 1) {
|
|
3567
|
-
const
|
|
3568
|
-
|
|
3674
|
+
const entity_ids = [];
|
|
3675
|
+
for (const tid of transform_ids) {
|
|
3676
|
+
const block = this.find_by_file_id(tid);
|
|
3677
|
+
if (block) {
|
|
3678
|
+
if (block.class_id === 4 || block.class_id === 224) {
|
|
3679
|
+
const go_match = block.raw.match(/m_GameObject:\s*\{fileID:\s*(-?\d+)\}/);
|
|
3680
|
+
if (go_match) {
|
|
3681
|
+
entity_ids.push(go_match[1]);
|
|
3682
|
+
} else {
|
|
3683
|
+
entity_ids.push(tid);
|
|
3684
|
+
}
|
|
3685
|
+
} else if (block.class_id === 1001 || block.class_id === 310) {
|
|
3686
|
+
entity_ids.push(tid);
|
|
3687
|
+
} else if (block.is_stripped) {
|
|
3688
|
+
const pi_match = block.raw.match(/m_PrefabInstance:\s*\{fileID:\s*(-?\d+)\}/);
|
|
3689
|
+
if (pi_match) {
|
|
3690
|
+
entity_ids.push(pi_match[1]);
|
|
3691
|
+
} else {
|
|
3692
|
+
entity_ids.push(tid);
|
|
3693
|
+
}
|
|
3694
|
+
} else {
|
|
3695
|
+
entity_ids.push(tid);
|
|
3696
|
+
}
|
|
3697
|
+
}
|
|
3698
|
+
}
|
|
3699
|
+
const ids_str = Array.from(new Set(entity_ids)).join(", ");
|
|
3700
|
+
return { error: `Multiple GameObjects/PrefabInstances named "${name_or_id}" found (fileIDs: ${ids_str}). Use numeric fileID to specify which one.` };
|
|
3569
3701
|
}
|
|
3570
3702
|
const transform = this.find_by_file_id(transform_ids[0]);
|
|
3571
3703
|
if (!transform) {
|
|
@@ -3593,7 +3725,7 @@ class UnityDocument {
|
|
|
3593
3725
|
if (t_block.class_id === 4 && t_block.is_stripped) {
|
|
3594
3726
|
let name = "Variant";
|
|
3595
3727
|
for (const pi_block of this._blocks) {
|
|
3596
|
-
if (pi_block.class_id === 1001) {
|
|
3728
|
+
if (pi_block.class_id === 1001 || pi_block.class_id === 310) {
|
|
3597
3729
|
const name_mod = pi_block.raw.match(/propertyPath: m_Name\s*\n\s*value:\s*(.+)/);
|
|
3598
3730
|
if (name_mod) {
|
|
3599
3731
|
name = name_mod[1].trim();
|
|
@@ -3746,7 +3878,7 @@ class UnityDocument {
|
|
|
3746
3878
|
}
|
|
3747
3879
|
add_child_to_parent(parent_id, child_id) {
|
|
3748
3880
|
const parent = this.find_by_file_id(parent_id);
|
|
3749
|
-
if (!parent || parent.class_id !== 4)
|
|
3881
|
+
if (!parent || parent.class_id !== 4 && parent.class_id !== 224)
|
|
3750
3882
|
return false;
|
|
3751
3883
|
let raw = parent.raw;
|
|
3752
3884
|
const inline_empty = /m_Children:\s*\[\]/;
|
|
@@ -3782,7 +3914,7 @@ class UnityDocument {
|
|
|
3782
3914
|
}
|
|
3783
3915
|
remove_child_from_parent(parent_id, child_id) {
|
|
3784
3916
|
const parent = this.find_by_file_id(parent_id);
|
|
3785
|
-
if (!parent || parent.class_id !== 4)
|
|
3917
|
+
if (!parent || parent.class_id !== 4 && parent.class_id !== 224)
|
|
3786
3918
|
return false;
|
|
3787
3919
|
let raw = parent.raw;
|
|
3788
3920
|
const child_line = new RegExp(`\\n[ \\t]*- \\{fileID: ${child_id}\\}`);
|
|
@@ -3803,7 +3935,49 @@ class UnityDocument {
|
|
|
3803
3935
|
}
|
|
3804
3936
|
_collect_hierarchy_recursive(transform_id, result) {
|
|
3805
3937
|
const transform = this.find_by_file_id(transform_id);
|
|
3806
|
-
if (!transform
|
|
3938
|
+
if (!transform)
|
|
3939
|
+
return;
|
|
3940
|
+
if (transform.class_id === 1001 || transform.class_id === 310) {
|
|
3941
|
+
const pi_id = transform.file_id;
|
|
3942
|
+
const pi_ref_pattern = new RegExp(`m_PrefabInstance:[ \\t]*\\{fileID:[ \\t]*${pi_id}\\}`);
|
|
3943
|
+
for (const block of this._blocks) {
|
|
3944
|
+
if (block.is_stripped && pi_ref_pattern.test(block.raw)) {
|
|
3945
|
+
result.add(block.file_id);
|
|
3946
|
+
if (block.class_id === 4 || block.class_id === 224) {
|
|
3947
|
+
this._collect_hierarchy_recursive(block.file_id, result);
|
|
3948
|
+
}
|
|
3949
|
+
}
|
|
3950
|
+
}
|
|
3951
|
+
const added_go_matches = transform.raw.matchAll(/addedObject:[ \t]*\{[^}]*fileID:[ \t]*(-?\d+)\}/g);
|
|
3952
|
+
for (const match of added_go_matches) {
|
|
3953
|
+
const added_id = match[1];
|
|
3954
|
+
if (added_id !== "0" && !result.has(added_id)) {
|
|
3955
|
+
result.add(added_id);
|
|
3956
|
+
const added_block = this.find_by_file_id(added_id);
|
|
3957
|
+
if (added_block && added_block.class_id === 1) {
|
|
3958
|
+
const comp_matches = added_block.raw.matchAll(/component:[ \t]*\{fileID:[ \t]*(-?\d+)\}/g);
|
|
3959
|
+
for (const cm of comp_matches) {
|
|
3960
|
+
const comp_block = this.find_by_file_id(cm[1]);
|
|
3961
|
+
if (comp_block && (comp_block.class_id === 4 || comp_block.class_id === 224)) {
|
|
3962
|
+
if (!result.has(cm[1])) {
|
|
3963
|
+
result.add(cm[1]);
|
|
3964
|
+
this._collect_hierarchy_recursive(cm[1], result);
|
|
3965
|
+
}
|
|
3966
|
+
break;
|
|
3967
|
+
}
|
|
3968
|
+
}
|
|
3969
|
+
}
|
|
3970
|
+
}
|
|
3971
|
+
}
|
|
3972
|
+
const added_comp_matches = transform.raw.matchAll(/addedObject:[ \t]*\{[^}]*fileID:[ \t]*(-?\d+)\}/g);
|
|
3973
|
+
for (const match of added_comp_matches) {
|
|
3974
|
+
const added_id = match[1];
|
|
3975
|
+
if (added_id !== "0")
|
|
3976
|
+
result.add(added_id);
|
|
3977
|
+
}
|
|
3978
|
+
return;
|
|
3979
|
+
}
|
|
3980
|
+
if (transform.class_id !== 4 && transform.class_id !== 224)
|
|
3807
3981
|
return;
|
|
3808
3982
|
const children_section = transform.raw.match(/m_Children:[\s\S]*?(?=\s*m_Father:)/);
|
|
3809
3983
|
if (!children_section)
|
|
@@ -3838,14 +4012,21 @@ class UnityDocument {
|
|
|
3838
4012
|
if (parent_id === "0") {
|
|
3839
4013
|
let count = 0;
|
|
3840
4014
|
for (const block of this._blocks) {
|
|
3841
|
-
if (block.class_id === 4 && /m_Father:\s*\{fileID:\s*0\}/.test(block.raw)) {
|
|
4015
|
+
if ((block.class_id === 4 || block.class_id === 224) && /m_Father:\s*\{fileID:\s*0\}/.test(block.raw)) {
|
|
3842
4016
|
count++;
|
|
4017
|
+
} else if ((block.class_id === 1001 || block.class_id === 310) && /m_TransformParent:\s*\{fileID:\s*0\}/.test(block.raw)) {
|
|
4018
|
+
const pi_id = block.file_id;
|
|
4019
|
+
const pi_ref_pattern = new RegExp(`m_PrefabInstance:[ \\t]*\\{fileID:[ \\t]*${pi_id}\\}`);
|
|
4020
|
+
const has_stripped = this._blocks.some((b) => (b.class_id === 4 || b.class_id === 224) && b.is_stripped && pi_ref_pattern.test(b.raw));
|
|
4021
|
+
if (!has_stripped) {
|
|
4022
|
+
count++;
|
|
4023
|
+
}
|
|
3843
4024
|
}
|
|
3844
4025
|
}
|
|
3845
4026
|
return count;
|
|
3846
4027
|
}
|
|
3847
4028
|
const parent = this.find_by_file_id(parent_id);
|
|
3848
|
-
if (!parent || parent.class_id !== 4)
|
|
4029
|
+
if (!parent || parent.class_id !== 4 && parent.class_id !== 224)
|
|
3849
4030
|
return 0;
|
|
3850
4031
|
const children_match = parent.raw.match(/m_Children:[\s\S]*?(?=\s*m_Father:)/);
|
|
3851
4032
|
if (children_match) {
|
|
@@ -3878,19 +4059,97 @@ ${new_children}`);
|
|
|
3878
4059
|
}
|
|
3879
4060
|
return false;
|
|
3880
4061
|
}
|
|
4062
|
+
add_root_to_scene_roots(root_id) {
|
|
4063
|
+
const pi_block = this.find_by_file_id(root_id);
|
|
4064
|
+
if (pi_block && (pi_block.class_id === 1001 || pi_block.class_id === 310)) {
|
|
4065
|
+
const pi_ref_pattern = new RegExp(`m_PrefabInstance:[ \\t]*\\{fileID:[ \\t]*${root_id}\\}`);
|
|
4066
|
+
const stripped_root = this._blocks.find((b) => (b.class_id === 4 || b.class_id === 224) && b.is_stripped && pi_ref_pattern.test(b.raw) && /m_Father:\s*\{fileID:\s*0\}/.test(b.raw));
|
|
4067
|
+
if (stripped_root) {
|
|
4068
|
+
root_id = stripped_root.file_id;
|
|
4069
|
+
}
|
|
4070
|
+
}
|
|
4071
|
+
const sr_blocks = [...this.find_by_class_id(166), ...this.find_by_class_id(1660057539)];
|
|
4072
|
+
if (sr_blocks.length === 0)
|
|
4073
|
+
return false;
|
|
4074
|
+
const sr_block = sr_blocks[0];
|
|
4075
|
+
let raw = sr_block.raw;
|
|
4076
|
+
const roots_pattern = /(m_Roots:\s*\n(?:\s*-\s*\{fileID:\s*-?\d+\}\s*\n)*)/;
|
|
4077
|
+
if (roots_pattern.test(raw)) {
|
|
4078
|
+
raw = raw.replace(roots_pattern, `$1 - {fileID: ${root_id}}
|
|
4079
|
+
`);
|
|
4080
|
+
sr_block.replace_raw(raw);
|
|
4081
|
+
return true;
|
|
4082
|
+
}
|
|
4083
|
+
const empty_roots = /m_Roots:\s*\[\]/;
|
|
4084
|
+
if (empty_roots.test(raw)) {
|
|
4085
|
+
raw = raw.replace(empty_roots, `m_Roots:
|
|
4086
|
+
- {fileID: ${root_id}}`);
|
|
4087
|
+
sr_block.replace_raw(raw);
|
|
4088
|
+
return true;
|
|
4089
|
+
}
|
|
4090
|
+
return false;
|
|
4091
|
+
}
|
|
4092
|
+
remove_root_from_scene_roots(root_id) {
|
|
4093
|
+
const pi_block = this.find_by_file_id(root_id);
|
|
4094
|
+
if (pi_block && (pi_block.class_id === 1001 || pi_block.class_id === 310)) {
|
|
4095
|
+
const pi_ref_pattern = new RegExp(`m_PrefabInstance:[ \\t]*\\{fileID:[ \\t]*${root_id}\\}`);
|
|
4096
|
+
const stripped_root = this._blocks.find((b) => (b.class_id === 4 || b.class_id === 224) && b.is_stripped && pi_ref_pattern.test(b.raw));
|
|
4097
|
+
if (stripped_root) {
|
|
4098
|
+
this._remove_id_from_scene_roots(stripped_root.file_id);
|
|
4099
|
+
}
|
|
4100
|
+
}
|
|
4101
|
+
return this._remove_id_from_scene_roots(root_id);
|
|
4102
|
+
}
|
|
4103
|
+
_remove_id_from_scene_roots(id) {
|
|
4104
|
+
const sr_blocks = [...this.find_by_class_id(166), ...this.find_by_class_id(1660057539)];
|
|
4105
|
+
if (sr_blocks.length === 0)
|
|
4106
|
+
return false;
|
|
4107
|
+
const sr_block = sr_blocks[0];
|
|
4108
|
+
let raw = sr_block.raw;
|
|
4109
|
+
const root_line = new RegExp(`\\n[ \\t]*- \\{fileID: ${id}\\}`);
|
|
4110
|
+
if (!root_line.test(raw))
|
|
4111
|
+
return false;
|
|
4112
|
+
raw = raw.replace(root_line, "");
|
|
4113
|
+
sr_block.replace_raw(raw);
|
|
4114
|
+
return true;
|
|
4115
|
+
}
|
|
3881
4116
|
reorder_entities(ordered_transform_ids) {
|
|
3882
4117
|
const entity_groups = [];
|
|
3883
4118
|
for (const transform_id of ordered_transform_ids) {
|
|
3884
|
-
const
|
|
3885
|
-
if (!
|
|
4119
|
+
const block = this.find_by_file_id(transform_id);
|
|
4120
|
+
if (!block)
|
|
3886
4121
|
continue;
|
|
3887
|
-
|
|
3888
|
-
if (
|
|
4122
|
+
let pi_block = null;
|
|
4123
|
+
if (block.class_id === 1001 || block.class_id === 310) {
|
|
4124
|
+
pi_block = block;
|
|
4125
|
+
} else if (block.is_stripped) {
|
|
4126
|
+
const pi_match = block.raw.match(/m_PrefabInstance:\s*\{fileID:\s*(-?\d+)\}/);
|
|
4127
|
+
if (pi_match) {
|
|
4128
|
+
const found = this.find_by_file_id(pi_match[1]);
|
|
4129
|
+
if (found && (found.class_id === 1001 || found.class_id === 310)) {
|
|
4130
|
+
pi_block = found;
|
|
4131
|
+
}
|
|
4132
|
+
}
|
|
4133
|
+
}
|
|
4134
|
+
if (pi_block) {
|
|
4135
|
+
const group = [pi_block.file_id];
|
|
4136
|
+
const hierarchy = this.collect_hierarchy(pi_block.file_id);
|
|
4137
|
+
for (const id of hierarchy)
|
|
4138
|
+
group.push(id);
|
|
4139
|
+
entity_groups.push(group);
|
|
3889
4140
|
continue;
|
|
4141
|
+
}
|
|
4142
|
+
const go_match = block.raw.match(/m_GameObject:\s*\{fileID:\s*(-?\d+)\}/);
|
|
4143
|
+
if (!go_match) {
|
|
4144
|
+
entity_groups.push([transform_id]);
|
|
4145
|
+
continue;
|
|
4146
|
+
}
|
|
3890
4147
|
const go_id = go_match[1];
|
|
3891
4148
|
const go = this.find_by_file_id(go_id);
|
|
3892
|
-
if (!go)
|
|
4149
|
+
if (!go) {
|
|
4150
|
+
entity_groups.push([transform_id]);
|
|
3893
4151
|
continue;
|
|
4152
|
+
}
|
|
3894
4153
|
const comp_ids = [];
|
|
3895
4154
|
const comp_matches = go.raw.matchAll(/component:\s*\{fileID:\s*(-?\d+)\}/g);
|
|
3896
4155
|
for (const m of comp_matches) {
|
|
@@ -4252,48 +4511,320 @@ function find_game_object_in_variant(doc, name, file_path, project_path) {
|
|
|
4252
4511
|
}
|
|
4253
4512
|
return null;
|
|
4254
4513
|
}
|
|
4255
|
-
function
|
|
4256
|
-
const
|
|
4257
|
-
if (!
|
|
4258
|
-
return;
|
|
4259
|
-
const
|
|
4260
|
-
|
|
4261
|
-
|
|
4262
|
-
|
|
4263
|
-
|
|
4264
|
-
|
|
4265
|
-
|
|
4266
|
-
|
|
4267
|
-
|
|
4268
|
-
|
|
4514
|
+
function parsePrefabRef(refText) {
|
|
4515
|
+
const fileIdMatch = refText.match(/fileID:[ \t]*(-?\d+)/i);
|
|
4516
|
+
if (!fileIdMatch)
|
|
4517
|
+
return null;
|
|
4518
|
+
const guidMatch = refText.match(/guid:[ \t]*([a-f0-9]{32})/i);
|
|
4519
|
+
const typeMatch = refText.match(/type:[ \t]*(-?\d+)/i);
|
|
4520
|
+
return {
|
|
4521
|
+
file_id: fileIdMatch[1],
|
|
4522
|
+
guid: guidMatch ? guidMatch[1].toLowerCase() : undefined,
|
|
4523
|
+
type: typeMatch ? typeMatch[1] : undefined
|
|
4524
|
+
};
|
|
4525
|
+
}
|
|
4526
|
+
function extractPrefabSourceGuid(prefabInstanceRaw) {
|
|
4527
|
+
const sourceMatch = prefabInstanceRaw.match(/m_SourcePrefab:[ \t]*\{[^}]*guid:[ \t]*([a-f0-9]{32})/i);
|
|
4528
|
+
return sourceMatch ? sourceMatch[1].toLowerCase() : null;
|
|
4529
|
+
}
|
|
4530
|
+
function normalizePrefabRef(refText, sourceGuid) {
|
|
4531
|
+
const parsed = parsePrefabRef(refText);
|
|
4532
|
+
if (!parsed) {
|
|
4533
|
+
return { error: `Invalid reference "${refText}". Expected format: {fileID: N, guid: ..., type: T}` };
|
|
4534
|
+
}
|
|
4535
|
+
const guid = parsed.guid ?? sourceGuid;
|
|
4536
|
+
if (!guid) {
|
|
4537
|
+
return { error: `Reference "${refText}" is missing guid and source prefab guid could not be inferred.` };
|
|
4269
4538
|
}
|
|
4270
|
-
|
|
4271
|
-
|
|
4272
|
-
raw = raw.replace(existingPattern, `$1${entry}`);
|
|
4273
|
-
piBlock.replace_raw(raw);
|
|
4539
|
+
if (!/^[a-f0-9]{32}$/i.test(guid)) {
|
|
4540
|
+
return { error: `Invalid guid in reference "${refText}". Expected 32-character hex GUID.` };
|
|
4274
4541
|
}
|
|
4542
|
+
const type = parsed.type ?? "3";
|
|
4543
|
+
if (!/^-?\d+$/.test(type)) {
|
|
4544
|
+
return { error: `Invalid type in reference "${refText}". Expected an integer type value.` };
|
|
4545
|
+
}
|
|
4546
|
+
return {
|
|
4547
|
+
ref: {
|
|
4548
|
+
file_id: parsed.file_id,
|
|
4549
|
+
guid,
|
|
4550
|
+
type
|
|
4551
|
+
}
|
|
4552
|
+
};
|
|
4275
4553
|
}
|
|
4276
|
-
function
|
|
4277
|
-
|
|
4278
|
-
|
|
4279
|
-
|
|
4280
|
-
const
|
|
4281
|
-
|
|
4282
|
-
|
|
4283
|
-
|
|
4284
|
-
|
|
4285
|
-
|
|
4286
|
-
|
|
4287
|
-
|
|
4288
|
-
|
|
4289
|
-
|
|
4554
|
+
function serializePrefabRef(ref) {
|
|
4555
|
+
return `{fileID: ${ref.file_id}, guid: ${ref.guid}, type: ${ref.type}}`;
|
|
4556
|
+
}
|
|
4557
|
+
function ensurePrefabModificationSerializedVersion(prefabInstanceBlock) {
|
|
4558
|
+
const raw = prefabInstanceBlock.raw;
|
|
4559
|
+
const updated = raw.replace(/(^([ \t]*)m_Modification:[ \t]*\n)(?!\2[ \t]+serializedVersion:[ \t]*\d+)/m, (_match, prefix, indent) => `${prefix}${indent} serializedVersion: 3
|
|
4560
|
+
`);
|
|
4561
|
+
if (updated === raw) {
|
|
4562
|
+
return false;
|
|
4563
|
+
}
|
|
4564
|
+
prefabInstanceBlock.replace_raw(updated);
|
|
4565
|
+
return true;
|
|
4566
|
+
}
|
|
4567
|
+
function addedOverrideEntryKey(entry) {
|
|
4568
|
+
return `${entry.target.file_id}|${entry.target.guid}|${entry.target.type}|${entry.insert_index}|${entry.added_object_file_id}`;
|
|
4569
|
+
}
|
|
4570
|
+
function splitPrefabArraySection(rawText, key) {
|
|
4571
|
+
const lines = rawText.split(`
|
|
4572
|
+
`);
|
|
4573
|
+
const keyPattern = new RegExp(`^([ ]*)${key}:[ ]*(.*)$`);
|
|
4574
|
+
let keyIndex = -1;
|
|
4575
|
+
let indent = "";
|
|
4576
|
+
for (let i = 0;i < lines.length; i++) {
|
|
4577
|
+
const match = lines[i].match(keyPattern);
|
|
4578
|
+
if (match) {
|
|
4579
|
+
keyIndex = i;
|
|
4580
|
+
indent = match[1];
|
|
4581
|
+
break;
|
|
4582
|
+
}
|
|
4583
|
+
}
|
|
4584
|
+
if (keyIndex === -1) {
|
|
4585
|
+
return null;
|
|
4586
|
+
}
|
|
4587
|
+
const keyIndentLen = indent.length;
|
|
4588
|
+
let sectionEnd = keyIndex + 1;
|
|
4589
|
+
for (let i = keyIndex + 1;i < lines.length; i++) {
|
|
4590
|
+
const line = lines[i];
|
|
4591
|
+
const trimmed = line.trim();
|
|
4592
|
+
if (trimmed.length === 0) {
|
|
4593
|
+
sectionEnd = i + 1;
|
|
4594
|
+
continue;
|
|
4595
|
+
}
|
|
4596
|
+
const indentLen = (line.match(/^[ \t]*/) || [""])[0].length;
|
|
4597
|
+
if (indentLen < keyIndentLen) {
|
|
4598
|
+
break;
|
|
4599
|
+
}
|
|
4600
|
+
if (indentLen === keyIndentLen && !trimmed.startsWith("-") && /^[A-Za-z_][A-Za-z0-9_]*:/.test(trimmed)) {
|
|
4601
|
+
break;
|
|
4602
|
+
}
|
|
4603
|
+
sectionEnd = i + 1;
|
|
4604
|
+
}
|
|
4605
|
+
return {
|
|
4606
|
+
lines,
|
|
4607
|
+
key_index: keyIndex,
|
|
4608
|
+
section_end: sectionEnd,
|
|
4609
|
+
indent
|
|
4610
|
+
};
|
|
4611
|
+
}
|
|
4612
|
+
function collectAddedOverrideEntries(lines, key_index, section_end, sourceGuid) {
|
|
4613
|
+
const sectionLines = lines.slice(key_index + 1, section_end);
|
|
4614
|
+
const dedup = new Map;
|
|
4615
|
+
let i = 0;
|
|
4616
|
+
while (i < sectionLines.length) {
|
|
4617
|
+
const line = sectionLines[i];
|
|
4618
|
+
const trimmed = line.trimStart();
|
|
4619
|
+
if (!trimmed.startsWith("-")) {
|
|
4620
|
+
i++;
|
|
4621
|
+
continue;
|
|
4622
|
+
}
|
|
4623
|
+
const entryIndent = (line.match(/^[ \t]*/) || [""])[0].length;
|
|
4624
|
+
const entryLines = [line];
|
|
4625
|
+
i++;
|
|
4626
|
+
while (i < sectionLines.length) {
|
|
4627
|
+
const nextLine = sectionLines[i];
|
|
4628
|
+
const nextTrimmed = nextLine.trim();
|
|
4629
|
+
const nextIndent = (nextLine.match(/^[ \t]*/) || [""])[0].length;
|
|
4630
|
+
if (nextTrimmed.length > 0 && nextIndent === entryIndent && nextTrimmed.startsWith("-")) {
|
|
4631
|
+
break;
|
|
4632
|
+
}
|
|
4633
|
+
entryLines.push(nextLine);
|
|
4634
|
+
i++;
|
|
4635
|
+
}
|
|
4636
|
+
const entryText = entryLines.join(`
|
|
4637
|
+
`);
|
|
4638
|
+
const targetMatch = entryText.match(/targetCorrespondingSourceObject:[ \t]*(\{[^}]+\})/);
|
|
4639
|
+
const insertMatch = entryText.match(/insertIndex:[ \t]*(-?\d+)/);
|
|
4640
|
+
const addedObjectMatch = entryText.match(/addedObject:[ \t]*\{[^}]*fileID:[ \t]*(-?\d+)/);
|
|
4641
|
+
if (!targetMatch || !addedObjectMatch) {
|
|
4642
|
+
continue;
|
|
4643
|
+
}
|
|
4644
|
+
const normalizedTarget = normalizePrefabRef(targetMatch[1], sourceGuid).ref;
|
|
4645
|
+
if (!normalizedTarget) {
|
|
4646
|
+
continue;
|
|
4647
|
+
}
|
|
4648
|
+
const insertIndex = insertMatch ? Number.parseInt(insertMatch[1], 10) : -1;
|
|
4649
|
+
const entry = {
|
|
4650
|
+
target: normalizedTarget,
|
|
4651
|
+
insert_index: Number.isNaN(insertIndex) ? -1 : insertIndex,
|
|
4652
|
+
added_object_file_id: addedObjectMatch[1]
|
|
4653
|
+
};
|
|
4654
|
+
const dedupKey = addedOverrideEntryKey(entry);
|
|
4655
|
+
if (!dedup.has(dedupKey)) {
|
|
4656
|
+
dedup.set(dedupKey, entry);
|
|
4657
|
+
}
|
|
4290
4658
|
}
|
|
4291
|
-
|
|
4292
|
-
|
|
4293
|
-
|
|
4294
|
-
|
|
4659
|
+
return [...dedup.values()];
|
|
4660
|
+
}
|
|
4661
|
+
function appendToAddedOverrideArray(doc, piId, key, targetSourceRef, newObjectId) {
|
|
4662
|
+
const piBlock = doc.find_by_file_id(piId);
|
|
4663
|
+
if (!piBlock) {
|
|
4664
|
+
return { success: false, error: `PrefabInstance with fileID ${piId} not found` };
|
|
4665
|
+
}
|
|
4666
|
+
ensurePrefabModificationSerializedVersion(piBlock);
|
|
4667
|
+
const split = splitPrefabArraySection(piBlock.raw, key);
|
|
4668
|
+
if (!split) {
|
|
4669
|
+
return { success: false, error: `${key} property not found in PrefabInstance ${piId}` };
|
|
4670
|
+
}
|
|
4671
|
+
const sourceGuid = extractPrefabSourceGuid(piBlock.raw);
|
|
4672
|
+
const normalizedTarget = normalizePrefabRef(targetSourceRef, sourceGuid);
|
|
4673
|
+
if (!normalizedTarget.ref) {
|
|
4674
|
+
return { success: false, error: normalizedTarget.error };
|
|
4675
|
+
}
|
|
4676
|
+
const candidate = {
|
|
4677
|
+
target: normalizedTarget.ref,
|
|
4678
|
+
insert_index: -1,
|
|
4679
|
+
added_object_file_id: newObjectId
|
|
4680
|
+
};
|
|
4681
|
+
const candidateKey = addedOverrideEntryKey(candidate);
|
|
4682
|
+
const existing = collectAddedOverrideEntries(split.lines, split.key_index, split.section_end, sourceGuid);
|
|
4683
|
+
if (existing.some((entry) => addedOverrideEntryKey(entry) === candidateKey)) {
|
|
4684
|
+
return { success: true, changed: false };
|
|
4685
|
+
}
|
|
4686
|
+
const entryLines = [
|
|
4687
|
+
`${split.indent}- targetCorrespondingSourceObject: ${serializePrefabRef(candidate.target)}`,
|
|
4688
|
+
`${split.indent} insertIndex: ${candidate.insert_index}`,
|
|
4689
|
+
`${split.indent} addedObject: {fileID: ${candidate.added_object_file_id}}`
|
|
4690
|
+
];
|
|
4691
|
+
const keyLine = split.lines[split.key_index].trim();
|
|
4692
|
+
let insertIndex = split.section_end;
|
|
4693
|
+
while (insertIndex > split.key_index + 1 && split.lines[insertIndex - 1].trim().length === 0) {
|
|
4694
|
+
insertIndex--;
|
|
4695
|
+
}
|
|
4696
|
+
let updatedLines;
|
|
4697
|
+
if (keyLine === `${key}: []`) {
|
|
4698
|
+
const replacement = [`${split.indent}${key}:`, ...entryLines];
|
|
4699
|
+
updatedLines = [
|
|
4700
|
+
...split.lines.slice(0, split.key_index),
|
|
4701
|
+
...replacement,
|
|
4702
|
+
...split.lines.slice(split.section_end)
|
|
4703
|
+
];
|
|
4704
|
+
} else {
|
|
4705
|
+
updatedLines = [
|
|
4706
|
+
...split.lines.slice(0, insertIndex),
|
|
4707
|
+
...entryLines,
|
|
4708
|
+
...split.lines.slice(insertIndex)
|
|
4709
|
+
];
|
|
4295
4710
|
}
|
|
4711
|
+
piBlock.replace_raw(updatedLines.join(`
|
|
4712
|
+
`));
|
|
4713
|
+
return { success: true, changed: true };
|
|
4714
|
+
}
|
|
4715
|
+
function appendToAddedGameObjects(doc, piId, targetSourceRef, newGoId) {
|
|
4716
|
+
return appendToAddedOverrideArray(doc, piId, "m_AddedGameObjects", targetSourceRef, newGoId);
|
|
4296
4717
|
}
|
|
4718
|
+
function appendToAddedComponents(doc, piId, targetSourceRef, newComponentId) {
|
|
4719
|
+
return appendToAddedOverrideArray(doc, piId, "m_AddedComponents", targetSourceRef, newComponentId);
|
|
4720
|
+
}
|
|
4721
|
+
var COMPONENT_FULL_TEMPLATES = {
|
|
4722
|
+
65: (gameObjectId) => ` m_GameObject: {fileID: ${gameObjectId}}
|
|
4723
|
+
m_Material: {fileID: 0}
|
|
4724
|
+
m_IncludeLayers:
|
|
4725
|
+
serializedVersion: 2
|
|
4726
|
+
m_Bits: 0
|
|
4727
|
+
m_ExcludeLayers:
|
|
4728
|
+
serializedVersion: 2
|
|
4729
|
+
m_Bits: 0
|
|
4730
|
+
m_LayerOverridePriority: 0
|
|
4731
|
+
m_IsTrigger: 0
|
|
4732
|
+
m_ProvidesContacts: 0
|
|
4733
|
+
m_Enabled: 1
|
|
4734
|
+
serializedVersion: 3
|
|
4735
|
+
m_Size: {x: 1, y: 1, z: 1}
|
|
4736
|
+
m_Center: {x: 0, y: 0, z: 0}`,
|
|
4737
|
+
82: (gameObjectId) => ` m_GameObject: {fileID: ${gameObjectId}}
|
|
4738
|
+
m_Enabled: 1
|
|
4739
|
+
serializedVersion: 4
|
|
4740
|
+
OutputAudioMixerGroup: {fileID: 0}
|
|
4741
|
+
m_audioClip: {fileID: 0}
|
|
4742
|
+
m_PlayOnAwake: 1
|
|
4743
|
+
m_Volume: 1
|
|
4744
|
+
m_Pitch: 1
|
|
4745
|
+
Loop: 0
|
|
4746
|
+
Mute: 0
|
|
4747
|
+
Spatialize: 0
|
|
4748
|
+
SpatializePostEffects: 0
|
|
4749
|
+
Priority: 128
|
|
4750
|
+
DopplerLevel: 1
|
|
4751
|
+
MinDistance: 1
|
|
4752
|
+
MaxDistance: 500
|
|
4753
|
+
Pan2D: 0
|
|
4754
|
+
rolloffMode: 0
|
|
4755
|
+
BypassEffects: 0
|
|
4756
|
+
BypassListenerEffects: 0
|
|
4757
|
+
BypassReverbZones: 0
|
|
4758
|
+
rolloffCustomCurve:
|
|
4759
|
+
serializedVersion: 2
|
|
4760
|
+
m_Curve:
|
|
4761
|
+
- serializedVersion: 3
|
|
4762
|
+
time: 0
|
|
4763
|
+
value: 1
|
|
4764
|
+
inSlope: 0
|
|
4765
|
+
outSlope: 0
|
|
4766
|
+
tangentMode: 0
|
|
4767
|
+
weightedMode: 0
|
|
4768
|
+
inWeight: 0.33333334
|
|
4769
|
+
outWeight: 0.33333334
|
|
4770
|
+
- serializedVersion: 3
|
|
4771
|
+
time: 1
|
|
4772
|
+
value: 0
|
|
4773
|
+
inSlope: 0
|
|
4774
|
+
outSlope: 0
|
|
4775
|
+
tangentMode: 0
|
|
4776
|
+
weightedMode: 0
|
|
4777
|
+
inWeight: 0.33333334
|
|
4778
|
+
outWeight: 0.33333334
|
|
4779
|
+
m_PreInfinity: 2
|
|
4780
|
+
m_PostInfinity: 2
|
|
4781
|
+
m_RotationOrder: 4
|
|
4782
|
+
panLevelCustomCurve:
|
|
4783
|
+
serializedVersion: 2
|
|
4784
|
+
m_Curve:
|
|
4785
|
+
- serializedVersion: 3
|
|
4786
|
+
time: 0
|
|
4787
|
+
value: 0
|
|
4788
|
+
inSlope: 0
|
|
4789
|
+
outSlope: 0
|
|
4790
|
+
tangentMode: 0
|
|
4791
|
+
weightedMode: 0
|
|
4792
|
+
inWeight: 0.33333334
|
|
4793
|
+
outWeight: 0.33333334
|
|
4794
|
+
m_PreInfinity: 2
|
|
4795
|
+
m_PostInfinity: 2
|
|
4796
|
+
m_RotationOrder: 4
|
|
4797
|
+
spreadCustomCurve:
|
|
4798
|
+
serializedVersion: 2
|
|
4799
|
+
m_Curve:
|
|
4800
|
+
- serializedVersion: 3
|
|
4801
|
+
time: 0
|
|
4802
|
+
value: 0
|
|
4803
|
+
inSlope: 0
|
|
4804
|
+
outSlope: 0
|
|
4805
|
+
tangentMode: 0
|
|
4806
|
+
weightedMode: 0
|
|
4807
|
+
inWeight: 0.33333334
|
|
4808
|
+
outWeight: 0.33333334
|
|
4809
|
+
m_PreInfinity: 2
|
|
4810
|
+
m_PostInfinity: 2
|
|
4811
|
+
m_RotationOrder: 4
|
|
4812
|
+
reverbZoneMixCustomCurve:
|
|
4813
|
+
serializedVersion: 2
|
|
4814
|
+
m_Curve:
|
|
4815
|
+
- serializedVersion: 3
|
|
4816
|
+
time: 0
|
|
4817
|
+
value: 1
|
|
4818
|
+
inSlope: 0
|
|
4819
|
+
outSlope: 0
|
|
4820
|
+
tangentMode: 0
|
|
4821
|
+
weightedMode: 0
|
|
4822
|
+
inWeight: 0.33333334
|
|
4823
|
+
outWeight: 0.33333334
|
|
4824
|
+
m_PreInfinity: 2
|
|
4825
|
+
m_PostInfinity: 2
|
|
4826
|
+
m_RotationOrder: 4`
|
|
4827
|
+
};
|
|
4297
4828
|
var COMPONENT_DEFAULTS = {
|
|
4298
4829
|
20: ` serializedVersion: 2
|
|
4299
4830
|
m_ClearFlags: 1
|
|
@@ -4420,6 +4951,17 @@ var COMPONENT_DEFAULTS = {
|
|
|
4420
4951
|
m_IgnoreParentGroups: 0`
|
|
4421
4952
|
};
|
|
4422
4953
|
function createGenericComponentYAML(componentName, classId, componentId, gameObjectId) {
|
|
4954
|
+
const fullTemplate = COMPONENT_FULL_TEMPLATES[classId];
|
|
4955
|
+
if (fullTemplate) {
|
|
4956
|
+
return `--- !u!${classId} &${componentId}
|
|
4957
|
+
${componentName}:
|
|
4958
|
+
m_ObjectHideFlags: 0
|
|
4959
|
+
m_CorrespondingSourceObject: {fileID: 0}
|
|
4960
|
+
m_PrefabInstance: {fileID: 0}
|
|
4961
|
+
m_PrefabAsset: {fileID: 0}
|
|
4962
|
+
${fullTemplate(gameObjectId)}
|
|
4963
|
+
`;
|
|
4964
|
+
}
|
|
4423
4965
|
const defaults = COMPONENT_DEFAULTS[classId] ? `
|
|
4424
4966
|
` + COMPONENT_DEFAULTS[classId] + `
|
|
4425
4967
|
` : "";
|
|
@@ -4572,7 +5114,14 @@ function createGameObject(options) {
|
|
|
4572
5114
|
doc.add_child_to_parent(parentTransformIdStr, transformIdStr);
|
|
4573
5115
|
}
|
|
4574
5116
|
if (variantPiId && strippedSourceRef) {
|
|
4575
|
-
appendToAddedGameObjects(doc, variantPiId, strippedSourceRef, gameObjectIdStr);
|
|
5117
|
+
const appendResult = appendToAddedGameObjects(doc, variantPiId, strippedSourceRef, gameObjectIdStr);
|
|
5118
|
+
if (!appendResult.success) {
|
|
5119
|
+
return {
|
|
5120
|
+
success: false,
|
|
5121
|
+
file_path,
|
|
5122
|
+
error: appendResult.error || `Failed to update m_AddedGameObjects for PrefabInstance ${variantPiId}`
|
|
5123
|
+
};
|
|
5124
|
+
}
|
|
4576
5125
|
}
|
|
4577
5126
|
const saveResult = doc.save();
|
|
4578
5127
|
if (!saveResult.success) {
|
|
@@ -5055,6 +5604,7 @@ PrefabInstance:
|
|
|
5055
5604
|
m_ObjectHideFlags: 0
|
|
5056
5605
|
serializedVersion: 2
|
|
5057
5606
|
m_Modification:
|
|
5607
|
+
serializedVersion: 3
|
|
5058
5608
|
m_TransformParent: {fileID: 0}
|
|
5059
5609
|
m_Modifications:
|
|
5060
5610
|
- target: {fileID: ${rootInfo.game_object.file_id}, guid: ${sourceGuid}, type: 3}
|
|
@@ -5141,6 +5691,9 @@ function createScriptableObject(options) {
|
|
|
5141
5691
|
if (resolved.kind === "enum" || resolved.kind === "interface") {
|
|
5142
5692
|
return { success: false, output_path, error: `"${script}" is ${resolved.kind === "enum" ? "an enum" : "an interface"}, not a ScriptableObject.` };
|
|
5143
5693
|
}
|
|
5694
|
+
if (resolved.is_abstract) {
|
|
5695
|
+
return { success: false, output_path, error: `"${script}" is abstract and cannot be instantiated as a ScriptableObject asset.` };
|
|
5696
|
+
}
|
|
5144
5697
|
if (resolved.base_class && resolved.base_class !== "ScriptableObject") {
|
|
5145
5698
|
return { success: false, output_path, error: `"${script}" extends ${resolved.base_class}, not ScriptableObject. Cannot create as a ScriptableObject asset.` };
|
|
5146
5699
|
}
|
|
@@ -5341,6 +5894,8 @@ function is_valid_component_base(base_class, project_path, depth = 0) {
|
|
|
5341
5894
|
}
|
|
5342
5895
|
if (!fullPath.endsWith(".cs") || !import_fs10.existsSync(fullPath)) {
|
|
5343
5896
|
if (match.kind === "class") {
|
|
5897
|
+
if (fullPath.includes("Assembly-CSharp.dll"))
|
|
5898
|
+
return true;
|
|
5344
5899
|
return is_likely_component_base_name(base_class);
|
|
5345
5900
|
}
|
|
5346
5901
|
}
|
|
@@ -5422,6 +5977,13 @@ function addComponent(options) {
|
|
|
5422
5977
|
}
|
|
5423
5978
|
const gameObjectId = gameObjectIdStr;
|
|
5424
5979
|
const classId = get_class_id(component_type);
|
|
5980
|
+
if (classId === 114 && component_type.toLowerCase() === "monobehaviour") {
|
|
5981
|
+
return {
|
|
5982
|
+
success: false,
|
|
5983
|
+
file_path,
|
|
5984
|
+
error: '"MonoBehaviour" is a base class, not a concrete component script. Provide a script type/path/GUID (for example, "PlayerController", "Assets/Scripts/PlayerController.cs", or a 32-char GUID).'
|
|
5985
|
+
};
|
|
5986
|
+
}
|
|
5425
5987
|
let duplicateWarning;
|
|
5426
5988
|
const goBlock = doc.find_by_file_id(gameObjectIdStr);
|
|
5427
5989
|
if (goBlock && classId !== null) {
|
|
@@ -5446,7 +6008,7 @@ function addComponent(options) {
|
|
|
5446
6008
|
} else {
|
|
5447
6009
|
let resolved;
|
|
5448
6010
|
try {
|
|
5449
|
-
resolved = resolve_script_with_fields(component_type, project_path);
|
|
6011
|
+
resolved = resolve_script_with_fields(component_type, project_path, { strict_exact_name: true });
|
|
5450
6012
|
} catch (e) {
|
|
5451
6013
|
return {
|
|
5452
6014
|
success: false,
|
|
@@ -5484,13 +6046,20 @@ function addComponent(options) {
|
|
|
5484
6046
|
error: `"${component_type}" is ${resolved.kind === "enum" ? "an enum" : "an interface"}, not a MonoBehaviour. Cannot add as a component.`
|
|
5485
6047
|
};
|
|
5486
6048
|
}
|
|
5487
|
-
if (resolved.
|
|
6049
|
+
if (resolved.is_abstract) {
|
|
5488
6050
|
return {
|
|
5489
6051
|
success: false,
|
|
5490
6052
|
file_path,
|
|
5491
|
-
error: `"${component_type}"
|
|
6053
|
+
error: `"${component_type}" is abstract and cannot be added as a component.`
|
|
5492
6054
|
};
|
|
5493
6055
|
}
|
|
6056
|
+
if (resolved.base_class && !COMPONENT_BASE_CLASSES.has(resolved.base_class) && !is_valid_component_base(resolved.base_class, project_path)) {
|
|
6057
|
+
const warn = `"${component_type}" extends ${resolved.base_class}, which does not inherit from MonoBehaviour. Adding anyway, but ensure the script is a valid component. (Re-run "setup" if the inheritance graph is stale).`;
|
|
6058
|
+
if (!extractionError)
|
|
6059
|
+
extractionError = warn;
|
|
6060
|
+
else
|
|
6061
|
+
extractionError += " " + warn;
|
|
6062
|
+
}
|
|
5494
6063
|
let version;
|
|
5495
6064
|
if (project_path) {
|
|
5496
6065
|
try {
|
|
@@ -5505,10 +6074,22 @@ function addComponent(options) {
|
|
|
5505
6074
|
componentYAML = createMonoBehaviourYAML(componentId, gameObjectId, resolved.guid, resolved.fields, version, scriptFileId, type_lookup_comp);
|
|
5506
6075
|
scriptGuid = resolved.guid;
|
|
5507
6076
|
scriptPath = resolved.path || undefined;
|
|
5508
|
-
|
|
6077
|
+
if (resolved.extraction_error) {
|
|
6078
|
+
if (!extractionError)
|
|
6079
|
+
extractionError = resolved.extraction_error;
|
|
6080
|
+
else
|
|
6081
|
+
extractionError += " " + resolved.extraction_error;
|
|
6082
|
+
}
|
|
5509
6083
|
}
|
|
5510
6084
|
if (variantInfo) {
|
|
5511
|
-
appendToAddedComponents(doc, variantInfo.prefab_instance_id, variantInfo.source_ref, componentIdStr);
|
|
6085
|
+
const appendResult = appendToAddedComponents(doc, variantInfo.prefab_instance_id, variantInfo.source_ref, componentIdStr);
|
|
6086
|
+
if (!appendResult.success) {
|
|
6087
|
+
return {
|
|
6088
|
+
success: false,
|
|
6089
|
+
file_path,
|
|
6090
|
+
error: appendResult.error || `Failed to update m_AddedComponents for PrefabInstance ${variantInfo.prefab_instance_id}`
|
|
6091
|
+
};
|
|
6092
|
+
}
|
|
5512
6093
|
} else {
|
|
5513
6094
|
const added = addComponentToGameObject(doc, gameObjectIdStr, componentIdStr);
|
|
5514
6095
|
if (!added) {
|
|
@@ -5697,6 +6278,56 @@ function validate_value_type(current_value, new_value) {
|
|
|
5697
6278
|
}
|
|
5698
6279
|
return null;
|
|
5699
6280
|
}
|
|
6281
|
+
function findStrippedTransformForPrefabInstance(doc, prefabInstanceId) {
|
|
6282
|
+
const piRefPattern = new RegExp(`m_PrefabInstance:[ \\t]*\\{fileID:[ \\t]*${prefabInstanceId}\\}`);
|
|
6283
|
+
for (const block of doc.blocks) {
|
|
6284
|
+
if ((block.class_id === 4 || block.class_id === 224) && block.is_stripped && piRefPattern.test(block.raw)) {
|
|
6285
|
+
return block;
|
|
6286
|
+
}
|
|
6287
|
+
}
|
|
6288
|
+
return null;
|
|
6289
|
+
}
|
|
6290
|
+
function resolvePrefabRootOrderTarget(prefabInstanceBlock) {
|
|
6291
|
+
const modificationPattern = /- target:\s*(\{[^}]+\})\s*\n\s*propertyPath:\s*('?)([^\n']+)\2/gm;
|
|
6292
|
+
const transformPropertyPattern = /^m_(?:RootOrder|LocalPosition|LocalRotation|LocalScale|ConstrainProportionsScale)(?:\.|$)/;
|
|
6293
|
+
let transformTarget = null;
|
|
6294
|
+
for (const match of prefabInstanceBlock.raw.matchAll(modificationPattern)) {
|
|
6295
|
+
const targetRef = match[1];
|
|
6296
|
+
const propertyPath = match[3].trim();
|
|
6297
|
+
if (propertyPath === "m_RootOrder") {
|
|
6298
|
+
return targetRef;
|
|
6299
|
+
}
|
|
6300
|
+
if (!transformTarget && transformPropertyPattern.test(propertyPath)) {
|
|
6301
|
+
transformTarget = targetRef;
|
|
6302
|
+
}
|
|
6303
|
+
}
|
|
6304
|
+
return transformTarget;
|
|
6305
|
+
}
|
|
6306
|
+
function upsertPrefabRootOrderOverride(prefabInstanceBlock, newRootOrder) {
|
|
6307
|
+
const existingRootOrderPattern = /(- target:\s*\{[^}]+\}\s*\n\s*propertyPath:\s*'?m_RootOrder'?\s*\n\s*value:\s*)([^\n]*)(\s*\n\s*objectReference:\s*\{[^}]+\})/m;
|
|
6308
|
+
if (existingRootOrderPattern.test(prefabInstanceBlock.raw)) {
|
|
6309
|
+
prefabInstanceBlock.replace_raw(prefabInstanceBlock.raw.replace(existingRootOrderPattern, `$1${newRootOrder}$3`));
|
|
6310
|
+
return true;
|
|
6311
|
+
}
|
|
6312
|
+
const targetRef = resolvePrefabRootOrderTarget(prefabInstanceBlock) ?? "{fileID: 400000}";
|
|
6313
|
+
const rootOrderOverride = ` - target: ${targetRef}
|
|
6314
|
+
` + ` propertyPath: m_RootOrder
|
|
6315
|
+
` + ` value: ${newRootOrder}
|
|
6316
|
+
` + " objectReference: {fileID: 0}";
|
|
6317
|
+
const removedPattern = /(\n\s*m_RemovedComponents:)/m;
|
|
6318
|
+
if (removedPattern.test(prefabInstanceBlock.raw)) {
|
|
6319
|
+
prefabInstanceBlock.replace_raw(prefabInstanceBlock.raw.replace(removedPattern, `
|
|
6320
|
+
${rootOrderOverride}$1`));
|
|
6321
|
+
return true;
|
|
6322
|
+
}
|
|
6323
|
+
const modsPattern = /(m_Modifications:\s*\n)/;
|
|
6324
|
+
if (modsPattern.test(prefabInstanceBlock.raw)) {
|
|
6325
|
+
prefabInstanceBlock.replace_raw(prefabInstanceBlock.raw.replace(modsPattern, `$1${rootOrderOverride}
|
|
6326
|
+
`));
|
|
6327
|
+
return true;
|
|
6328
|
+
}
|
|
6329
|
+
return false;
|
|
6330
|
+
}
|
|
5700
6331
|
function isAncestor(doc, childTransformId, candidateAncestorTransformId) {
|
|
5701
6332
|
let currentId = candidateAncestorTransformId;
|
|
5702
6333
|
const visited = new Set;
|
|
@@ -5707,17 +6338,34 @@ function isAncestor(doc, childTransformId, candidateAncestorTransformId) {
|
|
|
5707
6338
|
return false;
|
|
5708
6339
|
visited.add(currentId);
|
|
5709
6340
|
const block = doc.find_by_file_id(currentId);
|
|
5710
|
-
if (!block
|
|
6341
|
+
if (!block)
|
|
6342
|
+
break;
|
|
6343
|
+
if (block.class_id === 1001 || block.class_id === 310) {
|
|
6344
|
+
const parentMatch = block.raw.match(/m_TransformParent:\s*\{fileID:\s*(-?\d+)\}/);
|
|
6345
|
+
currentId = parentMatch ? parentMatch[1] : "0";
|
|
6346
|
+
} else if (block.class_id === 4 || block.class_id === 224) {
|
|
6347
|
+
const fatherMatch = block.raw.match(/m_Father:\s*\{fileID:\s*(-?\d+)\}/);
|
|
6348
|
+
currentId = fatherMatch ? fatherMatch[1] : "0";
|
|
6349
|
+
} else {
|
|
5711
6350
|
break;
|
|
5712
|
-
|
|
5713
|
-
currentId = fatherMatch ? fatherMatch[1] : "0";
|
|
6351
|
+
}
|
|
5714
6352
|
}
|
|
5715
6353
|
return false;
|
|
5716
6354
|
}
|
|
5717
6355
|
function resolveTransformByGameObjectId(doc, gameObjectFileId) {
|
|
5718
6356
|
const found = doc.find_by_file_id(gameObjectFileId);
|
|
5719
|
-
if (!found
|
|
5720
|
-
return { error: `
|
|
6357
|
+
if (!found) {
|
|
6358
|
+
return { error: `Block with fileID ${gameObjectFileId} not found` };
|
|
6359
|
+
}
|
|
6360
|
+
if (found.class_id === 4 || found.class_id === 224) {
|
|
6361
|
+
return { id: gameObjectFileId };
|
|
6362
|
+
}
|
|
6363
|
+
if (found.class_id === 1001 || found.class_id === 310) {
|
|
6364
|
+
const strippedTransform = findStrippedTransformForPrefabInstance(doc, gameObjectFileId);
|
|
6365
|
+
return { id: strippedTransform ? strippedTransform.file_id : gameObjectFileId };
|
|
6366
|
+
}
|
|
6367
|
+
if (found.class_id !== 1) {
|
|
6368
|
+
return { error: `fileID ${gameObjectFileId} is not a GameObject, Transform, or PrefabInstance (class ${found.class_id})` };
|
|
5721
6369
|
}
|
|
5722
6370
|
const componentMatch = found.raw.match(/m_Component:\s*\n\s*-\s*component:\s*\{fileID:\s*(-?\d+)\}/);
|
|
5723
6371
|
if (!componentMatch) {
|
|
@@ -5873,7 +6521,7 @@ function editComponentByFileId(options) {
|
|
|
5873
6521
|
return {
|
|
5874
6522
|
success: false,
|
|
5875
6523
|
file_path,
|
|
5876
|
-
error: `Could not resolve "${new_value}" to asset reference. Ensure GUID cache exists (run "setup <
|
|
6524
|
+
error: `Could not resolve "${new_value}" to asset reference. Ensure GUID cache exists (run "setup" first, or "setup -p <path>").`
|
|
5877
6525
|
};
|
|
5878
6526
|
}
|
|
5879
6527
|
if (resolved !== undefined) {
|
|
@@ -5910,7 +6558,7 @@ function editComponentByFileId(options) {
|
|
|
5910
6558
|
}
|
|
5911
6559
|
const targetBlock = doc.find_by_file_id(file_id);
|
|
5912
6560
|
if (!targetBlock) {
|
|
5913
|
-
const hasPrefabInstance = doc.find_by_class_id(1001).length > 0;
|
|
6561
|
+
const hasPrefabInstance = doc.find_by_class_id(1001).length > 0 || doc.find_by_class_id(310).length > 0;
|
|
5914
6562
|
if (hasPrefabInstance) {
|
|
5915
6563
|
return {
|
|
5916
6564
|
success: false,
|
|
@@ -6263,10 +6911,26 @@ function reparentGameObject(options) {
|
|
|
6263
6911
|
}
|
|
6264
6912
|
const childBlock = doc.find_by_file_id(childTransformId);
|
|
6265
6913
|
if (!childBlock) {
|
|
6266
|
-
return { success: false, file_path, error: `Transform ${childTransformId} not found` };
|
|
6914
|
+
return { success: false, file_path, error: `Transform or PrefabInstance ${childTransformId} not found` };
|
|
6915
|
+
}
|
|
6916
|
+
const childHierarchyTransformId = (() => {
|
|
6917
|
+
if (childBlock.class_id === 4 || childBlock.class_id === 224) {
|
|
6918
|
+
return childBlock.file_id;
|
|
6919
|
+
}
|
|
6920
|
+
if (childBlock.class_id === 1001 || childBlock.class_id === 310) {
|
|
6921
|
+
const stripped = findStrippedTransformForPrefabInstance(doc, childBlock.file_id);
|
|
6922
|
+
return stripped ? stripped.file_id : null;
|
|
6923
|
+
}
|
|
6924
|
+
return null;
|
|
6925
|
+
})();
|
|
6926
|
+
let oldParentTransformId = "0";
|
|
6927
|
+
if (childBlock.class_id === 1001 || childBlock.class_id === 310) {
|
|
6928
|
+
const parentMatch = childBlock.raw.match(/m_TransformParent:\s*\{fileID:\s*(-?\d+)\}/);
|
|
6929
|
+
oldParentTransformId = parentMatch ? parentMatch[1] : "0";
|
|
6930
|
+
} else {
|
|
6931
|
+
const fatherMatch = childBlock.raw.match(/m_Father:\s*\{fileID:\s*(-?\d+)\}/);
|
|
6932
|
+
oldParentTransformId = fatherMatch ? fatherMatch[1] : "0";
|
|
6267
6933
|
}
|
|
6268
|
-
const fatherMatch = childBlock.raw.match(/m_Father:\s*\{fileID:\s*(-?\d+)\}/);
|
|
6269
|
-
const oldParentTransformId = fatherMatch ? fatherMatch[1] : "0";
|
|
6270
6934
|
let newParentTransformId = "0";
|
|
6271
6935
|
if (new_parent.toLowerCase() !== "root") {
|
|
6272
6936
|
if (by_id || /^-?\d+$/.test(new_parent)) {
|
|
@@ -6293,11 +6957,55 @@ function reparentGameObject(options) {
|
|
|
6293
6957
|
}
|
|
6294
6958
|
}
|
|
6295
6959
|
if (oldParentTransformId !== "0") {
|
|
6296
|
-
|
|
6960
|
+
if (childHierarchyTransformId) {
|
|
6961
|
+
doc.remove_child_from_parent(oldParentTransformId, childHierarchyTransformId);
|
|
6962
|
+
} else {
|
|
6963
|
+
doc.remove_child_from_parent(oldParentTransformId, childBlock.file_id);
|
|
6964
|
+
}
|
|
6965
|
+
} else {
|
|
6966
|
+
if (childHierarchyTransformId) {
|
|
6967
|
+
doc.remove_root_from_scene_roots(childHierarchyTransformId);
|
|
6968
|
+
} else {
|
|
6969
|
+
doc.remove_root_from_scene_roots(childBlock.file_id);
|
|
6970
|
+
}
|
|
6971
|
+
}
|
|
6972
|
+
if (childBlock.class_id === 1001 || childBlock.class_id === 310) {
|
|
6973
|
+
const tpPattern = new RegExp(`(m_TransformParent:\\s*)\\{fileID:\\s*(-?\\d+)\\}`);
|
|
6974
|
+
if (tpPattern.test(childBlock.raw)) {
|
|
6975
|
+
let updatedChildRaw = childBlock.raw.replace(tpPattern, `$1{fileID: ${newParentTransformId}}`);
|
|
6976
|
+
childBlock.replace_raw(updatedChildRaw);
|
|
6977
|
+
} else {
|
|
6978
|
+
const modPattern = /(m_Modification:\s*\n)/;
|
|
6979
|
+
const modIndentMatch = childBlock.raw.match(/^([ \t]*)m_Modification:/m);
|
|
6980
|
+
const indent = modIndentMatch ? modIndentMatch[1] : "";
|
|
6981
|
+
let updatedChildRaw = childBlock.raw.replace(modPattern, `$1${indent} m_TransformParent: {fileID: ${newParentTransformId}}
|
|
6982
|
+
`);
|
|
6983
|
+
childBlock.replace_raw(updatedChildRaw);
|
|
6984
|
+
}
|
|
6985
|
+
} else {
|
|
6986
|
+
const fatherPattern = new RegExp(`(m_Father:\\s*)\\{fileID:\\s*\\d+\\}`);
|
|
6987
|
+
if (fatherPattern.test(childBlock.raw)) {
|
|
6988
|
+
let updatedChildRaw = childBlock.raw.replace(fatherPattern, `$1{fileID: ${newParentTransformId}}`);
|
|
6989
|
+
childBlock.replace_raw(updatedChildRaw);
|
|
6990
|
+
} else {
|
|
6991
|
+
const anchorPattern = /^([ \t]*(?:m_GameObject|m_CorrespondingSourceObject|m_PrefabInstance):[ \t]*\{fileID:[ \t]*(-?\d+)[^}]*\}.*)/m;
|
|
6992
|
+
const headerPattern = /(^--- !u!(?:4|224) &-?\d+ stripped\n(?:Rect)?Transform:)/m;
|
|
6993
|
+
if (anchorPattern.test(childBlock.raw)) {
|
|
6994
|
+
let updatedChildRaw = childBlock.raw.replace(anchorPattern, `$1
|
|
6995
|
+
m_Father: {fileID: ${newParentTransformId}}`);
|
|
6996
|
+
childBlock.replace_raw(updatedChildRaw);
|
|
6997
|
+
} else if (headerPattern.test(childBlock.raw)) {
|
|
6998
|
+
let updatedChildRaw = childBlock.raw.replace(headerPattern, `$1
|
|
6999
|
+
m_Father: {fileID: ${newParentTransformId}}`);
|
|
7000
|
+
childBlock.replace_raw(updatedChildRaw);
|
|
7001
|
+
} else {
|
|
7002
|
+
let updatedChildRaw = childBlock.raw.trimEnd() + `
|
|
7003
|
+
m_Father: {fileID: ${newParentTransformId}}
|
|
7004
|
+
`;
|
|
7005
|
+
childBlock.replace_raw(updatedChildRaw);
|
|
7006
|
+
}
|
|
7007
|
+
}
|
|
6297
7008
|
}
|
|
6298
|
-
const fatherPattern = new RegExp(`(m_Father:\\s*)\\{fileID:\\s*\\d+\\}`);
|
|
6299
|
-
let updatedChildRaw = childBlock.raw.replace(fatherPattern, `$1{fileID: ${newParentTransformId}}`);
|
|
6300
|
-
childBlock.replace_raw(updatedChildRaw);
|
|
6301
7009
|
{
|
|
6302
7010
|
let newRootOrder;
|
|
6303
7011
|
if (newParentTransformId === "0") {
|
|
@@ -6305,10 +7013,25 @@ function reparentGameObject(options) {
|
|
|
6305
7013
|
} else {
|
|
6306
7014
|
newRootOrder = doc.calculate_root_order(newParentTransformId);
|
|
6307
7015
|
}
|
|
6308
|
-
childBlock.
|
|
7016
|
+
if (childBlock.class_id === 1001 || childBlock.class_id === 310) {
|
|
7017
|
+
const stripped = doc.blocks.find((b) => (b.class_id === 4 || b.class_id === 224) && b.is_stripped && new RegExp(`m_PrefabInstance:[ \\t]*\\{fileID:[ \\t]*${childTransformId}\\}`).test(b.raw));
|
|
7018
|
+
if (stripped) {
|
|
7019
|
+
stripped.set_property("m_RootOrder", String(newRootOrder));
|
|
7020
|
+
} else {
|
|
7021
|
+
upsertPrefabRootOrderOverride(childBlock, newRootOrder);
|
|
7022
|
+
}
|
|
7023
|
+
} else {
|
|
7024
|
+
childBlock.set_property("m_RootOrder", String(newRootOrder));
|
|
7025
|
+
}
|
|
6309
7026
|
}
|
|
6310
7027
|
if (newParentTransformId !== "0") {
|
|
6311
|
-
|
|
7028
|
+
if (childHierarchyTransformId) {
|
|
7029
|
+
doc.add_child_to_parent(newParentTransformId, childHierarchyTransformId);
|
|
7030
|
+
}
|
|
7031
|
+
} else {
|
|
7032
|
+
if (childHierarchyTransformId) {
|
|
7033
|
+
doc.add_root_to_scene_roots(childHierarchyTransformId);
|
|
7034
|
+
}
|
|
6312
7035
|
}
|
|
6313
7036
|
if (!doc.validate()) {
|
|
6314
7037
|
return { success: false, file_path, error: "Validation failed after reparent" };
|
|
@@ -6327,9 +7050,9 @@ function reparentGameObject(options) {
|
|
|
6327
7050
|
}
|
|
6328
7051
|
function findPrefabInstanceBlock(doc, identifier) {
|
|
6329
7052
|
const asId = doc.find_by_file_id(identifier);
|
|
6330
|
-
if (asId && asId.class_id === 1001)
|
|
7053
|
+
if (asId && (asId.class_id === 1001 || asId.class_id === 310))
|
|
6331
7054
|
return asId;
|
|
6332
|
-
const piBlocks = doc.find_by_class_id(1001);
|
|
7055
|
+
const piBlocks = [...doc.find_by_class_id(1001), ...doc.find_by_class_id(310)];
|
|
6333
7056
|
const escaped = identifier.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
6334
7057
|
const namePattern = new RegExp(`propertyPath:\\s*m_Name\\s+value:\\s*${escaped}\\s`);
|
|
6335
7058
|
for (const block of piBlocks) {
|
|
@@ -6338,6 +7061,193 @@ function findPrefabInstanceBlock(doc, identifier) {
|
|
|
6338
7061
|
}
|
|
6339
7062
|
return null;
|
|
6340
7063
|
}
|
|
7064
|
+
function parsePrefabRef2(refText) {
|
|
7065
|
+
const fileIdMatch = refText.match(/fileID:[ \t]*(-?\d+)/i);
|
|
7066
|
+
if (!fileIdMatch)
|
|
7067
|
+
return null;
|
|
7068
|
+
const guidMatch = refText.match(/guid:[ \t]*([a-f0-9]{32})/i);
|
|
7069
|
+
const typeMatch = refText.match(/type:[ \t]*(-?\d+)/i);
|
|
7070
|
+
return {
|
|
7071
|
+
file_id: fileIdMatch[1],
|
|
7072
|
+
guid: guidMatch ? guidMatch[1].toLowerCase() : undefined,
|
|
7073
|
+
type: typeMatch ? typeMatch[1] : undefined
|
|
7074
|
+
};
|
|
7075
|
+
}
|
|
7076
|
+
function extractPrefabSourceGuid2(prefabInstanceRaw) {
|
|
7077
|
+
const sourceMatch = prefabInstanceRaw.match(/m_SourcePrefab:[ \t]*\{[^}]*guid:[ \t]*([a-f0-9]{32})/i);
|
|
7078
|
+
return sourceMatch ? sourceMatch[1].toLowerCase() : null;
|
|
7079
|
+
}
|
|
7080
|
+
function normalizePrefabRef2(refText, sourceGuid) {
|
|
7081
|
+
const parsed = parsePrefabRef2(refText);
|
|
7082
|
+
if (!parsed) {
|
|
7083
|
+
return { error: `Invalid reference "${refText}". Expected format: {fileID: N, guid: ..., type: T}` };
|
|
7084
|
+
}
|
|
7085
|
+
const guid = parsed.guid ?? sourceGuid;
|
|
7086
|
+
if (!guid) {
|
|
7087
|
+
return { error: `Reference "${refText}" is missing guid and source prefab guid could not be inferred.` };
|
|
7088
|
+
}
|
|
7089
|
+
if (!/^[a-f0-9]{32}$/i.test(guid)) {
|
|
7090
|
+
return { error: `Invalid guid in reference "${refText}". Expected 32-character hex GUID.` };
|
|
7091
|
+
}
|
|
7092
|
+
const type = parsed.type ?? "3";
|
|
7093
|
+
if (!/^-?\d+$/.test(type)) {
|
|
7094
|
+
return { error: `Invalid type in reference "${refText}". Expected an integer type value.` };
|
|
7095
|
+
}
|
|
7096
|
+
return {
|
|
7097
|
+
ref: {
|
|
7098
|
+
file_id: parsed.file_id,
|
|
7099
|
+
guid,
|
|
7100
|
+
type
|
|
7101
|
+
}
|
|
7102
|
+
};
|
|
7103
|
+
}
|
|
7104
|
+
function parsePrefabRefMatcher(refText) {
|
|
7105
|
+
const parsed = parsePrefabRef2(refText);
|
|
7106
|
+
if (!parsed) {
|
|
7107
|
+
return { error: `Invalid reference "${refText}". Expected format: {fileID: N, guid: ..., type: T}` };
|
|
7108
|
+
}
|
|
7109
|
+
if (parsed.guid && !/^[a-f0-9]{32}$/i.test(parsed.guid)) {
|
|
7110
|
+
return { error: `Invalid guid in reference "${refText}". Expected 32-character hex GUID.` };
|
|
7111
|
+
}
|
|
7112
|
+
if (parsed.type && !/^-?\d+$/.test(parsed.type)) {
|
|
7113
|
+
return { error: `Invalid type in reference "${refText}". Expected an integer type value.` };
|
|
7114
|
+
}
|
|
7115
|
+
return { matcher: parsed };
|
|
7116
|
+
}
|
|
7117
|
+
function prefabRefKey(ref) {
|
|
7118
|
+
return `${ref.file_id}|${ref.guid}|${ref.type}`;
|
|
7119
|
+
}
|
|
7120
|
+
function serializePrefabRef2(ref) {
|
|
7121
|
+
return `{fileID: ${ref.file_id}, guid: ${ref.guid}, type: ${ref.type}}`;
|
|
7122
|
+
}
|
|
7123
|
+
function splitPrefabArraySection2(rawText, key) {
|
|
7124
|
+
const lines = rawText.split(`
|
|
7125
|
+
`);
|
|
7126
|
+
const keyPattern = new RegExp(`^([ ]*)${key}:[ ]*(.*)$`);
|
|
7127
|
+
let keyIndex = -1;
|
|
7128
|
+
let indent = "";
|
|
7129
|
+
for (let i = 0;i < lines.length; i++) {
|
|
7130
|
+
const match = lines[i].match(keyPattern);
|
|
7131
|
+
if (match) {
|
|
7132
|
+
keyIndex = i;
|
|
7133
|
+
indent = match[1];
|
|
7134
|
+
break;
|
|
7135
|
+
}
|
|
7136
|
+
}
|
|
7137
|
+
if (keyIndex === -1) {
|
|
7138
|
+
return null;
|
|
7139
|
+
}
|
|
7140
|
+
const keyIndentLen = indent.length;
|
|
7141
|
+
let sectionEnd = keyIndex + 1;
|
|
7142
|
+
for (let i = keyIndex + 1;i < lines.length; i++) {
|
|
7143
|
+
const line = lines[i];
|
|
7144
|
+
const trimmed = line.trim();
|
|
7145
|
+
if (trimmed.length === 0) {
|
|
7146
|
+
sectionEnd = i + 1;
|
|
7147
|
+
continue;
|
|
7148
|
+
}
|
|
7149
|
+
const indentLen = (line.match(/^[ \t]*/) || [""])[0].length;
|
|
7150
|
+
if (indentLen < keyIndentLen) {
|
|
7151
|
+
break;
|
|
7152
|
+
}
|
|
7153
|
+
if (indentLen === keyIndentLen && !trimmed.startsWith("-") && /^[A-Za-z_][A-Za-z0-9_]*:/.test(trimmed)) {
|
|
7154
|
+
break;
|
|
7155
|
+
}
|
|
7156
|
+
sectionEnd = i + 1;
|
|
7157
|
+
}
|
|
7158
|
+
return {
|
|
7159
|
+
lines,
|
|
7160
|
+
key_index: keyIndex,
|
|
7161
|
+
section_end: sectionEnd,
|
|
7162
|
+
indent
|
|
7163
|
+
};
|
|
7164
|
+
}
|
|
7165
|
+
function collectNormalizedRefsFromSection(lines, key_index, section_end, sourceGuid) {
|
|
7166
|
+
const sectionText = lines.slice(key_index, section_end).join(`
|
|
7167
|
+
`);
|
|
7168
|
+
const refMatches = sectionText.match(/\{[^}]*\}/g) || [];
|
|
7169
|
+
const dedup = new Map;
|
|
7170
|
+
for (const token of refMatches) {
|
|
7171
|
+
const parsed = parsePrefabRef2(token);
|
|
7172
|
+
if (!parsed)
|
|
7173
|
+
continue;
|
|
7174
|
+
const guid = parsed.guid ?? sourceGuid;
|
|
7175
|
+
if (!guid || !/^[a-f0-9]{32}$/i.test(guid))
|
|
7176
|
+
continue;
|
|
7177
|
+
const type = parsed.type ?? "3";
|
|
7178
|
+
if (!/^-?\d+$/.test(type))
|
|
7179
|
+
continue;
|
|
7180
|
+
const normalized = {
|
|
7181
|
+
file_id: parsed.file_id,
|
|
7182
|
+
guid: guid.toLowerCase(),
|
|
7183
|
+
type
|
|
7184
|
+
};
|
|
7185
|
+
const key = prefabRefKey(normalized);
|
|
7186
|
+
if (!dedup.has(key)) {
|
|
7187
|
+
dedup.set(key, normalized);
|
|
7188
|
+
}
|
|
7189
|
+
}
|
|
7190
|
+
return [...dedup.values()];
|
|
7191
|
+
}
|
|
7192
|
+
function mutatePrefabReferenceList(options) {
|
|
7193
|
+
const { target_block, key, ref_text, action, item_label } = options;
|
|
7194
|
+
const split = splitPrefabArraySection2(target_block.raw, key);
|
|
7195
|
+
if (!split) {
|
|
7196
|
+
return { success: false, error: `${key} property not found in PrefabInstance` };
|
|
7197
|
+
}
|
|
7198
|
+
const sourceGuid = extractPrefabSourceGuid2(target_block.raw);
|
|
7199
|
+
const existing = collectNormalizedRefsFromSection(split.lines, split.key_index, split.section_end, sourceGuid);
|
|
7200
|
+
let next = existing;
|
|
7201
|
+
if (action === "add") {
|
|
7202
|
+
const normalized = normalizePrefabRef2(ref_text, sourceGuid);
|
|
7203
|
+
if (!normalized.ref) {
|
|
7204
|
+
return { success: false, error: normalized.error };
|
|
7205
|
+
}
|
|
7206
|
+
const keyText = prefabRefKey(normalized.ref);
|
|
7207
|
+
if (existing.some((entry) => prefabRefKey(entry) === keyText)) {
|
|
7208
|
+
return { success: true, changed: false };
|
|
7209
|
+
}
|
|
7210
|
+
next = [...existing, normalized.ref];
|
|
7211
|
+
} else {
|
|
7212
|
+
const matcherResult = parsePrefabRefMatcher(ref_text);
|
|
7213
|
+
const matcher = matcherResult.matcher;
|
|
7214
|
+
if (!matcher) {
|
|
7215
|
+
return { success: false, error: matcherResult.error };
|
|
7216
|
+
}
|
|
7217
|
+
next = existing.filter((entry) => {
|
|
7218
|
+
if (entry.file_id !== matcher.file_id)
|
|
7219
|
+
return true;
|
|
7220
|
+
if (matcher.guid && entry.guid !== matcher.guid.toLowerCase())
|
|
7221
|
+
return true;
|
|
7222
|
+
if (matcher.type && entry.type !== matcher.type)
|
|
7223
|
+
return true;
|
|
7224
|
+
return false;
|
|
7225
|
+
});
|
|
7226
|
+
if (next.length === existing.length) {
|
|
7227
|
+
return {
|
|
7228
|
+
success: false,
|
|
7229
|
+
error: `${item_label} reference "${ref_text}" not found in ${key}`
|
|
7230
|
+
};
|
|
7231
|
+
}
|
|
7232
|
+
}
|
|
7233
|
+
const replacement = [];
|
|
7234
|
+
if (next.length === 0) {
|
|
7235
|
+
replacement.push(`${split.indent}${key}: []`);
|
|
7236
|
+
} else {
|
|
7237
|
+
replacement.push(`${split.indent}${key}:`);
|
|
7238
|
+
for (const entry of next) {
|
|
7239
|
+
replacement.push(`${split.indent}- ${serializePrefabRef2(entry)}`);
|
|
7240
|
+
}
|
|
7241
|
+
}
|
|
7242
|
+
const updatedLines = [
|
|
7243
|
+
...split.lines.slice(0, split.key_index),
|
|
7244
|
+
...replacement,
|
|
7245
|
+
...split.lines.slice(split.section_end)
|
|
7246
|
+
];
|
|
7247
|
+
target_block.replace_raw(updatedLines.join(`
|
|
7248
|
+
`));
|
|
7249
|
+
return { success: true, changed: true };
|
|
7250
|
+
}
|
|
6341
7251
|
function editArray(options) {
|
|
6342
7252
|
const { file_path, file_id, array_property, action, value, index } = options;
|
|
6343
7253
|
if (!import_fs11.existsSync(file_path)) {
|
|
@@ -6548,9 +7458,6 @@ function addRemovedComponent(options) {
|
|
|
6548
7458
|
if (!import_fs11.existsSync(file_path)) {
|
|
6549
7459
|
return { success: false, file_path, error: `File not found: ${file_path}` };
|
|
6550
7460
|
}
|
|
6551
|
-
if (!/^\{fileID:\s*-?\d+/.test(component_ref)) {
|
|
6552
|
-
return { success: false, file_path, error: `Invalid component reference "${component_ref}". Expected format: {fileID: N, guid: ..., type: T}` };
|
|
6553
|
-
}
|
|
6554
7461
|
let doc;
|
|
6555
7462
|
try {
|
|
6556
7463
|
doc = UnityDocument.from_file(file_path);
|
|
@@ -6561,19 +7468,16 @@ function addRemovedComponent(options) {
|
|
|
6561
7468
|
if (!targetBlock) {
|
|
6562
7469
|
return { success: false, file_path, error: `PrefabInstance "${prefab_instance}" not found` };
|
|
6563
7470
|
}
|
|
6564
|
-
|
|
6565
|
-
|
|
6566
|
-
|
|
6567
|
-
|
|
6568
|
-
|
|
6569
|
-
|
|
6570
|
-
|
|
6571
|
-
|
|
6572
|
-
|
|
6573
|
-
} else {
|
|
6574
|
-
return { success: false, file_path, error: "m_RemovedComponents property not found in PrefabInstance" };
|
|
7471
|
+
const mutation = mutatePrefabReferenceList({
|
|
7472
|
+
target_block: targetBlock,
|
|
7473
|
+
key: "m_RemovedComponents",
|
|
7474
|
+
ref_text: component_ref,
|
|
7475
|
+
action: "add",
|
|
7476
|
+
item_label: "Component"
|
|
7477
|
+
});
|
|
7478
|
+
if (!mutation.success) {
|
|
7479
|
+
return { success: false, file_path, error: mutation.error };
|
|
6575
7480
|
}
|
|
6576
|
-
targetBlock.replace_raw(rawText);
|
|
6577
7481
|
if (!doc.validate()) {
|
|
6578
7482
|
return { success: false, file_path, error: "Validation failed after adding removed component" };
|
|
6579
7483
|
}
|
|
@@ -6602,13 +7506,16 @@ function removeRemovedComponent(options) {
|
|
|
6602
7506
|
if (!targetBlock) {
|
|
6603
7507
|
return { success: false, file_path, error: `PrefabInstance "${prefab_instance}" not found` };
|
|
6604
7508
|
}
|
|
6605
|
-
const
|
|
6606
|
-
|
|
6607
|
-
|
|
6608
|
-
|
|
7509
|
+
const mutation = mutatePrefabReferenceList({
|
|
7510
|
+
target_block: targetBlock,
|
|
7511
|
+
key: "m_RemovedComponents",
|
|
7512
|
+
ref_text: component_ref,
|
|
7513
|
+
action: "remove",
|
|
7514
|
+
item_label: "Component"
|
|
7515
|
+
});
|
|
7516
|
+
if (!mutation.success) {
|
|
7517
|
+
return { success: false, file_path, error: mutation.error };
|
|
6609
7518
|
}
|
|
6610
|
-
const updatedRaw = targetBlock.raw.replace(refPattern, "");
|
|
6611
|
-
targetBlock.replace_raw(updatedRaw);
|
|
6612
7519
|
if (!doc.validate()) {
|
|
6613
7520
|
return { success: false, file_path, error: "Validation failed after removing component" };
|
|
6614
7521
|
}
|
|
@@ -6627,9 +7534,6 @@ function addRemovedGameObject(options) {
|
|
|
6627
7534
|
if (!import_fs11.existsSync(file_path)) {
|
|
6628
7535
|
return { success: false, file_path, error: `File not found: ${file_path}` };
|
|
6629
7536
|
}
|
|
6630
|
-
if (!/^\{fileID:\s*-?\d+/.test(component_ref)) {
|
|
6631
|
-
return { success: false, file_path, error: `Invalid GameObject reference "${component_ref}". Expected format: {fileID: N, guid: ..., type: T}` };
|
|
6632
|
-
}
|
|
6633
7537
|
let doc;
|
|
6634
7538
|
try {
|
|
6635
7539
|
doc = UnityDocument.from_file(file_path);
|
|
@@ -6640,19 +7544,16 @@ function addRemovedGameObject(options) {
|
|
|
6640
7544
|
if (!targetBlock) {
|
|
6641
7545
|
return { success: false, file_path, error: `PrefabInstance "${prefab_instance}" not found` };
|
|
6642
7546
|
}
|
|
6643
|
-
|
|
6644
|
-
|
|
6645
|
-
|
|
6646
|
-
|
|
6647
|
-
|
|
6648
|
-
|
|
6649
|
-
|
|
6650
|
-
|
|
6651
|
-
|
|
6652
|
-
} else {
|
|
6653
|
-
return { success: false, file_path, error: "m_RemovedGameObjects property not found in PrefabInstance" };
|
|
7547
|
+
const mutation = mutatePrefabReferenceList({
|
|
7548
|
+
target_block: targetBlock,
|
|
7549
|
+
key: "m_RemovedGameObjects",
|
|
7550
|
+
ref_text: component_ref,
|
|
7551
|
+
action: "add",
|
|
7552
|
+
item_label: "GameObject"
|
|
7553
|
+
});
|
|
7554
|
+
if (!mutation.success) {
|
|
7555
|
+
return { success: false, file_path, error: mutation.error };
|
|
6654
7556
|
}
|
|
6655
|
-
targetBlock.replace_raw(rawText);
|
|
6656
7557
|
if (!doc.validate()) {
|
|
6657
7558
|
return { success: false, file_path, error: "Validation failed after adding removed GameObject" };
|
|
6658
7559
|
}
|
|
@@ -6681,13 +7582,16 @@ function removeRemovedGameObject(options) {
|
|
|
6681
7582
|
if (!targetBlock) {
|
|
6682
7583
|
return { success: false, file_path, error: `PrefabInstance "${prefab_instance}" not found` };
|
|
6683
7584
|
}
|
|
6684
|
-
const
|
|
6685
|
-
|
|
6686
|
-
|
|
6687
|
-
|
|
7585
|
+
const mutation = mutatePrefabReferenceList({
|
|
7586
|
+
target_block: targetBlock,
|
|
7587
|
+
key: "m_RemovedGameObjects",
|
|
7588
|
+
ref_text: component_ref,
|
|
7589
|
+
action: "remove",
|
|
7590
|
+
item_label: "GameObject"
|
|
7591
|
+
});
|
|
7592
|
+
if (!mutation.success) {
|
|
7593
|
+
return { success: false, file_path, error: mutation.error };
|
|
6688
7594
|
}
|
|
6689
|
-
const updatedRaw = targetBlock.raw.replace(refPattern, "");
|
|
6690
|
-
targetBlock.replace_raw(updatedRaw);
|
|
6691
7595
|
if (!doc.validate()) {
|
|
6692
7596
|
return { success: false, file_path, error: "Validation failed after removing GameObject" };
|
|
6693
7597
|
}
|
|
@@ -6786,6 +7690,7 @@ function removeComponent(options) {
|
|
|
6786
7690
|
return { success: false, file_path, error: "Cannot remove a Transform with remove-component. Use delete to remove the entire GameObject." };
|
|
6787
7691
|
}
|
|
6788
7692
|
const resolved_file_id = found.file_id;
|
|
7693
|
+
let removed_added_component_overrides = 0;
|
|
6789
7694
|
const goMatch = found.raw.match(/m_GameObject:[ \t]*\{fileID:[ \t]*(-?\d+)\}/);
|
|
6790
7695
|
if (goMatch) {
|
|
6791
7696
|
const parentGoId = goMatch[1];
|
|
@@ -6796,6 +7701,19 @@ function removeComponent(options) {
|
|
|
6796
7701
|
const modifiedRaw = goBlock.raw.replace(compLinePattern, "");
|
|
6797
7702
|
if (modifiedRaw === goBlock.raw) {}
|
|
6798
7703
|
goBlock.replace_raw(modifiedRaw);
|
|
7704
|
+
if (goBlock.is_stripped) {
|
|
7705
|
+
const piMatch = goBlock.raw.match(/m_PrefabInstance:[ \t]*\{fileID:[ \t]*(-?\d+)\}/);
|
|
7706
|
+
if (piMatch) {
|
|
7707
|
+
const piBlock = doc.find_by_file_id(piMatch[1]);
|
|
7708
|
+
if (piBlock && piBlock.class_id === 1001) {
|
|
7709
|
+
const updated = removeAddedComponentOverrideEntries(piBlock.raw, resolved_file_id);
|
|
7710
|
+
if (updated.removed_count > 0) {
|
|
7711
|
+
piBlock.replace_raw(updated.updated_raw);
|
|
7712
|
+
removed_added_component_overrides += updated.removed_count;
|
|
7713
|
+
}
|
|
7714
|
+
}
|
|
7715
|
+
}
|
|
7716
|
+
}
|
|
6799
7717
|
}
|
|
6800
7718
|
}
|
|
6801
7719
|
const dangling = [];
|
|
@@ -6819,7 +7737,7 @@ function removeComponent(options) {
|
|
|
6819
7737
|
file_path,
|
|
6820
7738
|
removed_file_id: resolved_file_id,
|
|
6821
7739
|
removed_class_id: found.class_id,
|
|
6822
|
-
warning: dangling.length > 0 ? `Dangling references to deleted component found in blocks: ${dangling.join(", ")}` : undefined
|
|
7740
|
+
warning: dangling.length > 0 ? `Dangling references to deleted component found in blocks: ${dangling.join(", ")}${removed_added_component_overrides > 0 ? ` (removed ${removed_added_component_overrides} m_AddedComponents override entr${removed_added_component_overrides === 1 ? "y" : "ies"})` : ""}` : undefined
|
|
6823
7741
|
};
|
|
6824
7742
|
}
|
|
6825
7743
|
function removeComponentBatch(options) {
|
|
@@ -6937,6 +7855,124 @@ function findPrefabInstanceBlock2(doc, identifier) {
|
|
|
6937
7855
|
}
|
|
6938
7856
|
return null;
|
|
6939
7857
|
}
|
|
7858
|
+
function splitPrefabArraySection3(rawText, key) {
|
|
7859
|
+
const lines = rawText.split(`
|
|
7860
|
+
`);
|
|
7861
|
+
const keyPattern = new RegExp(`^[ ]*${key}:[ ]*(.*)$`);
|
|
7862
|
+
let keyIndex = -1;
|
|
7863
|
+
let keyIndentLen = 0;
|
|
7864
|
+
for (let i = 0;i < lines.length; i++) {
|
|
7865
|
+
const match = lines[i].match(keyPattern);
|
|
7866
|
+
if (match) {
|
|
7867
|
+
keyIndex = i;
|
|
7868
|
+
keyIndentLen = (lines[i].match(/^[ \t]*/) || [""])[0].length;
|
|
7869
|
+
break;
|
|
7870
|
+
}
|
|
7871
|
+
}
|
|
7872
|
+
if (keyIndex === -1) {
|
|
7873
|
+
return null;
|
|
7874
|
+
}
|
|
7875
|
+
let sectionEnd = keyIndex + 1;
|
|
7876
|
+
for (let i = keyIndex + 1;i < lines.length; i++) {
|
|
7877
|
+
const line = lines[i];
|
|
7878
|
+
const trimmed = line.trim();
|
|
7879
|
+
if (trimmed.length === 0) {
|
|
7880
|
+
sectionEnd = i + 1;
|
|
7881
|
+
continue;
|
|
7882
|
+
}
|
|
7883
|
+
const indentLen = (line.match(/^[ \t]*/) || [""])[0].length;
|
|
7884
|
+
if (indentLen < keyIndentLen) {
|
|
7885
|
+
break;
|
|
7886
|
+
}
|
|
7887
|
+
if (indentLen === keyIndentLen && !trimmed.startsWith("-") && /^[A-Za-z_][A-Za-z0-9_]*:/.test(trimmed)) {
|
|
7888
|
+
break;
|
|
7889
|
+
}
|
|
7890
|
+
sectionEnd = i + 1;
|
|
7891
|
+
}
|
|
7892
|
+
return {
|
|
7893
|
+
lines,
|
|
7894
|
+
key_index: keyIndex,
|
|
7895
|
+
section_end: sectionEnd
|
|
7896
|
+
};
|
|
7897
|
+
}
|
|
7898
|
+
function collectAddedObjectFileIdsFromArray(rawText, key) {
|
|
7899
|
+
const section = splitPrefabArraySection3(rawText, key);
|
|
7900
|
+
if (!section)
|
|
7901
|
+
return [];
|
|
7902
|
+
const keyLine = section.lines[section.key_index].trim();
|
|
7903
|
+
if (new RegExp(`^${key}:[ ]*[]$`).test(keyLine)) {
|
|
7904
|
+
return [];
|
|
7905
|
+
}
|
|
7906
|
+
const sectionText = section.lines.slice(section.key_index, section.section_end).join(`
|
|
7907
|
+
`);
|
|
7908
|
+
const fileIds = new Set;
|
|
7909
|
+
const addedObjectPattern = /addedObject:[ \t]*\{[^}]*fileID:[ \t]*(-?\d+)/g;
|
|
7910
|
+
let match;
|
|
7911
|
+
while ((match = addedObjectPattern.exec(sectionText)) !== null) {
|
|
7912
|
+
fileIds.add(match[1]);
|
|
7913
|
+
}
|
|
7914
|
+
if (fileIds.size === 0) {
|
|
7915
|
+
const inlineEntryPattern = /^[ \t]*-[ \t]*\{[^}]*fileID:[ \t]*(-?\d+)/gm;
|
|
7916
|
+
while ((match = inlineEntryPattern.exec(sectionText)) !== null) {
|
|
7917
|
+
fileIds.add(match[1]);
|
|
7918
|
+
}
|
|
7919
|
+
}
|
|
7920
|
+
return [...fileIds];
|
|
7921
|
+
}
|
|
7922
|
+
function removeAddedComponentOverrideEntries(rawText, componentId) {
|
|
7923
|
+
const section = splitPrefabArraySection3(rawText, "m_AddedComponents");
|
|
7924
|
+
if (!section)
|
|
7925
|
+
return { updated_raw: rawText, removed_count: 0 };
|
|
7926
|
+
const keyLine = section.lines[section.key_index].trim();
|
|
7927
|
+
if (/^m_AddedComponents:[ \t]*\[\]$/.test(keyLine)) {
|
|
7928
|
+
return { updated_raw: rawText, removed_count: 0 };
|
|
7929
|
+
}
|
|
7930
|
+
const bodyLines = section.lines.slice(section.key_index + 1, section.section_end);
|
|
7931
|
+
const keptEntries = [];
|
|
7932
|
+
let removed = 0;
|
|
7933
|
+
let i = 0;
|
|
7934
|
+
while (i < bodyLines.length) {
|
|
7935
|
+
const line = bodyLines[i];
|
|
7936
|
+
const trimmed = line.trimStart();
|
|
7937
|
+
if (!trimmed.startsWith("-")) {
|
|
7938
|
+
i++;
|
|
7939
|
+
continue;
|
|
7940
|
+
}
|
|
7941
|
+
const entryIndent = (line.match(/^[ \t]*/) || [""])[0].length;
|
|
7942
|
+
const entryLines = [line];
|
|
7943
|
+
i++;
|
|
7944
|
+
while (i < bodyLines.length) {
|
|
7945
|
+
const nextLine = bodyLines[i];
|
|
7946
|
+
const nextTrimmed = nextLine.trim();
|
|
7947
|
+
const nextIndent = (nextLine.match(/^[ \t]*/) || [""])[0].length;
|
|
7948
|
+
if (nextTrimmed.length > 0 && nextIndent === entryIndent && nextTrimmed.startsWith("-")) {
|
|
7949
|
+
break;
|
|
7950
|
+
}
|
|
7951
|
+
entryLines.push(nextLine);
|
|
7952
|
+
i++;
|
|
7953
|
+
}
|
|
7954
|
+
const entryText = entryLines.join(`
|
|
7955
|
+
`);
|
|
7956
|
+
const addedObjectMatch = entryText.match(/addedObject:[ \t]*\{[^}]*fileID:[ \t]*(-?\d+)/);
|
|
7957
|
+
if (addedObjectMatch && addedObjectMatch[1] === componentId) {
|
|
7958
|
+
removed++;
|
|
7959
|
+
continue;
|
|
7960
|
+
}
|
|
7961
|
+
keptEntries.push(entryLines);
|
|
7962
|
+
}
|
|
7963
|
+
if (removed === 0) {
|
|
7964
|
+
return { updated_raw: rawText, removed_count: 0 };
|
|
7965
|
+
}
|
|
7966
|
+
const keyIndent = (section.lines[section.key_index].match(/^[ \t]*/) || [""])[0];
|
|
7967
|
+
const replacement = keptEntries.length === 0 ? [`${keyIndent}m_AddedComponents: []`] : [`${keyIndent}m_AddedComponents:`, ...keptEntries.flat()];
|
|
7968
|
+
const updatedLines = [
|
|
7969
|
+
...section.lines.slice(0, section.key_index),
|
|
7970
|
+
...replacement,
|
|
7971
|
+
...section.lines.slice(section.section_end)
|
|
7972
|
+
];
|
|
7973
|
+
return { updated_raw: updatedLines.join(`
|
|
7974
|
+
`), removed_count: removed };
|
|
7975
|
+
}
|
|
6940
7976
|
function deletePrefabInstance(options) {
|
|
6941
7977
|
const { file_path, prefab_instance } = options;
|
|
6942
7978
|
const pathError = validate_file_path(file_path, "write");
|
|
@@ -6964,36 +8000,23 @@ function deletePrefabInstance(options) {
|
|
|
6964
8000
|
allToRemove.add(block.file_id);
|
|
6965
8001
|
}
|
|
6966
8002
|
}
|
|
6967
|
-
const
|
|
6968
|
-
|
|
6969
|
-
|
|
6970
|
-
|
|
6971
|
-
|
|
6972
|
-
|
|
6973
|
-
|
|
6974
|
-
|
|
6975
|
-
|
|
6976
|
-
|
|
6977
|
-
|
|
6978
|
-
if (compMatch) {
|
|
6979
|
-
const transformId = compMatch[1];
|
|
6980
|
-
allToRemove.add(transformId);
|
|
6981
|
-
const descendants = doc.collect_hierarchy(transformId);
|
|
6982
|
-
for (const id of descendants) {
|
|
6983
|
-
allToRemove.add(id);
|
|
6984
|
-
}
|
|
8003
|
+
for (const goId of collectAddedObjectFileIdsFromArray(piBlock.raw, "m_AddedGameObjects")) {
|
|
8004
|
+
allToRemove.add(goId);
|
|
8005
|
+
const goBlock = doc.find_by_file_id(goId);
|
|
8006
|
+
if (goBlock) {
|
|
8007
|
+
const compMatch = goBlock.raw.match(/component:[ \t]*\{fileID:[ \t]*(\d+)\}/);
|
|
8008
|
+
if (compMatch) {
|
|
8009
|
+
const transformId = compMatch[1];
|
|
8010
|
+
allToRemove.add(transformId);
|
|
8011
|
+
const descendants = doc.collect_hierarchy(transformId);
|
|
8012
|
+
for (const id of descendants) {
|
|
8013
|
+
allToRemove.add(id);
|
|
6985
8014
|
}
|
|
6986
8015
|
}
|
|
6987
8016
|
}
|
|
6988
8017
|
}
|
|
6989
|
-
const
|
|
6990
|
-
|
|
6991
|
-
if (addedCompMatch) {
|
|
6992
|
-
const addedCompSection = addedCompMatch[1];
|
|
6993
|
-
const compMatches = addedCompSection.matchAll(/fileID:[ \t]*(\d+)/g);
|
|
6994
|
-
for (const match of compMatches) {
|
|
6995
|
-
allToRemove.add(match[1]);
|
|
6996
|
-
}
|
|
8018
|
+
for (const componentId of collectAddedObjectFileIdsFromArray(piBlock.raw, "m_AddedComponents")) {
|
|
8019
|
+
allToRemove.add(componentId);
|
|
6997
8020
|
}
|
|
6998
8021
|
const parentMatch = piBlock.raw.match(/m_TransformParent:[ \t]*\{fileID:[ \t]*(\d+)\}/);
|
|
6999
8022
|
const parentTransformId = parentMatch ? parentMatch[1] : "0";
|