@mflrevan/ucp 0.4.4 → 0.4.5

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.
Files changed (81) hide show
  1. package/README.md +1 -1
  2. package/bridge/com.ucp.bridge/CHANGELOG.md +145 -0
  3. package/bridge/com.ucp.bridge/CHANGELOG.md.meta +7 -0
  4. package/bridge/com.ucp.bridge/Editor/Bridge/BridgeServer.cs +583 -0
  5. package/bridge/com.ucp.bridge/Editor/Bridge/BridgeServer.cs.meta +2 -0
  6. package/bridge/com.ucp.bridge/Editor/Bridge.meta +8 -0
  7. package/bridge/com.ucp.bridge/Editor/Controllers/AssetController.cs +425 -0
  8. package/bridge/com.ucp.bridge/Editor/Controllers/AssetController.cs.meta +2 -0
  9. package/bridge/com.ucp.bridge/Editor/Controllers/AssetImportSupport.cs +355 -0
  10. package/bridge/com.ucp.bridge/Editor/Controllers/AssetImportSupport.cs.meta +2 -0
  11. package/bridge/com.ucp.bridge/Editor/Controllers/BuildController.cs +233 -0
  12. package/bridge/com.ucp.bridge/Editor/Controllers/BuildController.cs.meta +2 -0
  13. package/bridge/com.ucp.bridge/Editor/Controllers/CompilationController.cs +26 -0
  14. package/bridge/com.ucp.bridge/Editor/Controllers/CompilationController.cs.meta +2 -0
  15. package/bridge/com.ucp.bridge/Editor/Controllers/EditorController.cs +31 -0
  16. package/bridge/com.ucp.bridge/Editor/Controllers/EditorController.cs.meta +2 -0
  17. package/bridge/com.ucp.bridge/Editor/Controllers/EditorSettingsController.cs +527 -0
  18. package/bridge/com.ucp.bridge/Editor/Controllers/EditorSettingsController.cs.meta +2 -0
  19. package/bridge/com.ucp.bridge/Editor/Controllers/FileController.cs +141 -0
  20. package/bridge/com.ucp.bridge/Editor/Controllers/FileController.cs.meta +2 -0
  21. package/bridge/com.ucp.bridge/Editor/Controllers/HierarchyController.cs +326 -0
  22. package/bridge/com.ucp.bridge/Editor/Controllers/HierarchyController.cs.meta +2 -0
  23. package/bridge/com.ucp.bridge/Editor/Controllers/ImporterController.cs +209 -0
  24. package/bridge/com.ucp.bridge/Editor/Controllers/ImporterController.cs.meta +2 -0
  25. package/bridge/com.ucp.bridge/Editor/Controllers/LogsController.cs +409 -0
  26. package/bridge/com.ucp.bridge/Editor/Controllers/LogsController.cs.meta +2 -0
  27. package/bridge/com.ucp.bridge/Editor/Controllers/MaterialController.cs +354 -0
  28. package/bridge/com.ucp.bridge/Editor/Controllers/MaterialController.cs.meta +2 -0
  29. package/bridge/com.ucp.bridge/Editor/Controllers/ObjectReferenceResolver.cs +93 -0
  30. package/bridge/com.ucp.bridge/Editor/Controllers/ObjectReferenceResolver.cs.meta +2 -0
  31. package/bridge/com.ucp.bridge/Editor/Controllers/PackagesController.cs +503 -0
  32. package/bridge/com.ucp.bridge/Editor/Controllers/PackagesController.cs.meta +2 -0
  33. package/bridge/com.ucp.bridge/Editor/Controllers/PlayModeController.cs +188 -0
  34. package/bridge/com.ucp.bridge/Editor/Controllers/PlayModeController.cs.meta +2 -0
  35. package/bridge/com.ucp.bridge/Editor/Controllers/PrefabController.cs +260 -0
  36. package/bridge/com.ucp.bridge/Editor/Controllers/PrefabController.cs.meta +2 -0
  37. package/bridge/com.ucp.bridge/Editor/Controllers/ProfilerController.cs +1679 -0
  38. package/bridge/com.ucp.bridge/Editor/Controllers/ProfilerController.cs.meta +2 -0
  39. package/bridge/com.ucp.bridge/Editor/Controllers/PropertyController.cs +563 -0
  40. package/bridge/com.ucp.bridge/Editor/Controllers/PropertyController.cs.meta +2 -0
  41. package/bridge/com.ucp.bridge/Editor/Controllers/SceneChangeTracker.cs +166 -0
  42. package/bridge/com.ucp.bridge/Editor/Controllers/SceneChangeTracker.cs.meta +2 -0
  43. package/bridge/com.ucp.bridge/Editor/Controllers/SceneController.cs +318 -0
  44. package/bridge/com.ucp.bridge/Editor/Controllers/SceneController.cs.meta +2 -0
  45. package/bridge/com.ucp.bridge/Editor/Controllers/ScreenshotController.cs +125 -0
  46. package/bridge/com.ucp.bridge/Editor/Controllers/ScreenshotController.cs.meta +2 -0
  47. package/bridge/com.ucp.bridge/Editor/Controllers/ScriptController.cs +104 -0
  48. package/bridge/com.ucp.bridge/Editor/Controllers/ScriptController.cs.meta +2 -0
  49. package/bridge/com.ucp.bridge/Editor/Controllers/SnapshotController.cs +227 -0
  50. package/bridge/com.ucp.bridge/Editor/Controllers/SnapshotController.cs.meta +2 -0
  51. package/bridge/com.ucp.bridge/Editor/Controllers/TestRunnerController.cs +240 -0
  52. package/bridge/com.ucp.bridge/Editor/Controllers/TestRunnerController.cs.meta +2 -0
  53. package/bridge/com.ucp.bridge/Editor/Controllers/VcsController.cs +611 -0
  54. package/bridge/com.ucp.bridge/Editor/Controllers/VcsController.cs.meta +2 -0
  55. package/bridge/com.ucp.bridge/Editor/Controllers.meta +8 -0
  56. package/bridge/com.ucp.bridge/Editor/Protocol/CommandRouter.cs +53 -0
  57. package/bridge/com.ucp.bridge/Editor/Protocol/CommandRouter.cs.meta +2 -0
  58. package/bridge/com.ucp.bridge/Editor/Protocol/MessageTypes.cs +80 -0
  59. package/bridge/com.ucp.bridge/Editor/Protocol/MessageTypes.cs.meta +2 -0
  60. package/bridge/com.ucp.bridge/Editor/Protocol/MiniJson.cs +358 -0
  61. package/bridge/com.ucp.bridge/Editor/Protocol/MiniJson.cs.meta +2 -0
  62. package/bridge/com.ucp.bridge/Editor/Protocol.meta +8 -0
  63. package/bridge/com.ucp.bridge/Editor/Scripts/IUCPScript.cs +37 -0
  64. package/bridge/com.ucp.bridge/Editor/Scripts/IUCPScript.cs.meta +2 -0
  65. package/bridge/com.ucp.bridge/Editor/Scripts.meta +8 -0
  66. package/bridge/com.ucp.bridge/Editor/UCP.Bridge.Editor.asmdef +16 -0
  67. package/bridge/com.ucp.bridge/Editor/UCP.Bridge.Editor.asmdef.meta +7 -0
  68. package/bridge/com.ucp.bridge/Editor.meta +8 -0
  69. package/bridge/com.ucp.bridge/Runtime/UCP.Bridge.Runtime.asmdef +14 -0
  70. package/bridge/com.ucp.bridge/Runtime/UCP.Bridge.Runtime.asmdef.meta +7 -0
  71. package/bridge/com.ucp.bridge/Runtime.meta +8 -0
  72. package/bridge/com.ucp.bridge/Tests/Editor/ControllerSmokeTests.cs +1085 -0
  73. package/bridge/com.ucp.bridge/Tests/Editor/ControllerSmokeTests.cs.meta +2 -0
  74. package/bridge/com.ucp.bridge/Tests/Editor/UCP.Bridge.Editor.Tests.asmdef +12 -0
  75. package/bridge/com.ucp.bridge/Tests/Editor/UCP.Bridge.Editor.Tests.asmdef.meta +7 -0
  76. package/bridge/com.ucp.bridge/Tests/Editor.meta +8 -0
  77. package/bridge/com.ucp.bridge/Tests.meta +8 -0
  78. package/bridge/com.ucp.bridge/package.json +27 -0
  79. package/bridge/com.ucp.bridge/package.json.meta +7 -0
  80. package/package.json +2 -2
  81. package/scripts/install.js +4 -6
@@ -0,0 +1,527 @@
1
+ using System;
2
+ using System.Collections.Generic;
3
+ using UnityEditor;
4
+ using UnityEditor.Build;
5
+ using UnityEngine;
6
+ using UnityEngine.Rendering;
7
+
8
+ namespace UCP.Bridge
9
+ {
10
+ public static class EditorSettingsController
11
+ {
12
+ public static void Register(CommandRouter router)
13
+ {
14
+ router.Register("settings/player", HandlePlayerSettings);
15
+ router.Register("settings/player-set", HandleSetPlayerSetting);
16
+ router.Register("settings/quality", HandleQualitySettings);
17
+ router.Register("settings/quality-set", HandleSetQualitySetting);
18
+ router.Register("settings/physics", HandlePhysicsSettings);
19
+ router.Register("settings/physics-set", HandleSetPhysicsSetting);
20
+ router.Register("settings/lighting", HandleLightingSettings);
21
+ router.Register("settings/lighting-set", HandleSetLightingSetting);
22
+ router.Register("settings/tags-layers", HandleTagsAndLayers);
23
+ router.Register("settings/add-tag", HandleAddTag);
24
+ router.Register("settings/add-layer", HandleAddLayer);
25
+ }
26
+
27
+ private static NamedBuildTarget GetSelectedNamedBuildTarget()
28
+ {
29
+ return NamedBuildTarget.FromBuildTargetGroup(EditorUserBuildSettings.selectedBuildTargetGroup);
30
+ }
31
+
32
+ private static object HandlePlayerSettings(string paramsJson)
33
+ {
34
+ var namedTarget = GetSelectedNamedBuildTarget();
35
+ return new Dictionary<string, object>
36
+ {
37
+ ["companyName"] = PlayerSettings.companyName,
38
+ ["productName"] = PlayerSettings.productName,
39
+ ["bundleVersion"] = PlayerSettings.bundleVersion,
40
+ ["defaultIsNativeResolution"] = PlayerSettings.defaultIsNativeResolution,
41
+ ["runInBackground"] = PlayerSettings.runInBackground,
42
+ ["colorSpace"] = PlayerSettings.colorSpace.ToString(),
43
+ ["graphicsApi"] = PlayerSettings.GetGraphicsAPIs(EditorUserBuildSettings.activeBuildTarget)?[0].ToString() ?? "Unknown",
44
+ ["scriptingBackend"] = PlayerSettings.GetScriptingBackend(namedTarget).ToString(),
45
+ ["apiCompatibilityLevel"] = PlayerSettings.GetApiCompatibilityLevel(namedTarget).ToString(),
46
+ ["activeInputHandler"] = ReadPlayerSettingInt("activeInputHandler"),
47
+ ["activeInputHandlerName"] = DescribeActiveInputHandler(ReadPlayerSettingInt("activeInputHandler")),
48
+ ["targetFrameRate"] = Application.targetFrameRate,
49
+ ["defaultScreenWidth"] = PlayerSettings.defaultScreenWidth,
50
+ ["defaultScreenHeight"] = PlayerSettings.defaultScreenHeight
51
+ };
52
+ }
53
+
54
+ private static object HandleSetPlayerSetting(string paramsJson)
55
+ {
56
+ var p = MiniJson.Deserialize(paramsJson) as Dictionary<string, object>;
57
+ if (p == null || !p.TryGetValue("key", out var keyObj))
58
+ throw new ArgumentException("Missing 'key' parameter");
59
+ if (!p.ContainsKey("value"))
60
+ throw new ArgumentException("Missing 'value' parameter");
61
+
62
+ string key = keyObj.ToString();
63
+ object value = p["value"];
64
+
65
+ switch (key)
66
+ {
67
+ case "companyName":
68
+ PlayerSettings.companyName = value.ToString();
69
+ break;
70
+ case "productName":
71
+ PlayerSettings.productName = value.ToString();
72
+ break;
73
+ case "bundleVersion":
74
+ PlayerSettings.bundleVersion = value.ToString();
75
+ break;
76
+ case "runInBackground":
77
+ PlayerSettings.runInBackground = Convert.ToBoolean(value);
78
+ break;
79
+ case "defaultIsNativeResolution":
80
+ PlayerSettings.defaultIsNativeResolution = Convert.ToBoolean(value);
81
+ break;
82
+ case "defaultScreenWidth":
83
+ PlayerSettings.defaultScreenWidth = Convert.ToInt32(value);
84
+ break;
85
+ case "defaultScreenHeight":
86
+ PlayerSettings.defaultScreenHeight = Convert.ToInt32(value);
87
+ break;
88
+ case "colorSpace":
89
+ if (Enum.TryParse<ColorSpace>(value.ToString(), out var cs))
90
+ PlayerSettings.colorSpace = cs;
91
+ break;
92
+ case "activeInputHandler":
93
+ case "activeInputHandling":
94
+ case "inputHandling":
95
+ WritePlayerSettingInt("activeInputHandler", ParseActiveInputHandler(value));
96
+ break;
97
+ default:
98
+ throw new ArgumentException($"Unknown player setting: {key}");
99
+ }
100
+
101
+ return new Dictionary<string, object> { ["status"] = "ok", ["key"] = key };
102
+ }
103
+
104
+ private static object HandleQualitySettings(string paramsJson)
105
+ {
106
+ var names = QualitySettings.names;
107
+ var levels = new List<object>();
108
+ for (int i = 0; i < names.Length; i++)
109
+ {
110
+ levels.Add(new Dictionary<string, object>
111
+ {
112
+ ["index"] = i,
113
+ ["name"] = names[i],
114
+ ["isCurrent"] = i == QualitySettings.GetQualityLevel()
115
+ });
116
+ }
117
+
118
+ return new Dictionary<string, object>
119
+ {
120
+ ["currentLevel"] = QualitySettings.GetQualityLevel(),
121
+ ["currentName"] = names[QualitySettings.GetQualityLevel()],
122
+ ["levels"] = levels,
123
+ ["shadowDistance"] = (double)QualitySettings.shadowDistance,
124
+ ["shadowCascades"] = QualitySettings.shadowCascades,
125
+ ["antiAliasing"] = QualitySettings.antiAliasing,
126
+ ["vSyncCount"] = QualitySettings.vSyncCount
127
+ };
128
+ }
129
+
130
+ private static object HandleSetQualitySetting(string paramsJson)
131
+ {
132
+ var p = MiniJson.Deserialize(paramsJson) as Dictionary<string, object>;
133
+ if (p == null || !p.TryGetValue("key", out var keyObj))
134
+ throw new ArgumentException("Missing 'key' parameter");
135
+ if (!p.ContainsKey("value"))
136
+ throw new ArgumentException("Missing 'value' parameter");
137
+
138
+ string key = keyObj.ToString();
139
+ object value = p["value"];
140
+
141
+ switch (key)
142
+ {
143
+ case "level":
144
+ QualitySettings.SetQualityLevel(Convert.ToInt32(value));
145
+ break;
146
+ case "shadowDistance":
147
+ QualitySettings.shadowDistance = Convert.ToSingle(value);
148
+ break;
149
+ case "shadowCascades":
150
+ QualitySettings.shadowCascades = Convert.ToInt32(value);
151
+ break;
152
+ case "antiAliasing":
153
+ QualitySettings.antiAliasing = Convert.ToInt32(value);
154
+ break;
155
+ case "vSyncCount":
156
+ QualitySettings.vSyncCount = Convert.ToInt32(value);
157
+ break;
158
+ default:
159
+ throw new ArgumentException($"Unknown quality setting: {key}");
160
+ }
161
+
162
+ return new Dictionary<string, object> { ["status"] = "ok", ["key"] = key };
163
+ }
164
+
165
+ private static int ReadPlayerSettingInt(string propertyName)
166
+ {
167
+ var serialized = new SerializedObject(GetPlayerSettingsAsset());
168
+ var property = serialized.FindProperty(propertyName);
169
+ if (property == null)
170
+ throw new ArgumentException($"Unknown serialized player setting: {propertyName}");
171
+
172
+ return property.intValue;
173
+ }
174
+
175
+ private static void WritePlayerSettingInt(string propertyName, int value)
176
+ {
177
+ var serialized = new SerializedObject(GetPlayerSettingsAsset());
178
+ var property = serialized.FindProperty(propertyName);
179
+ if (property == null)
180
+ throw new ArgumentException($"Unknown serialized player setting: {propertyName}");
181
+
182
+ property.intValue = value;
183
+ serialized.ApplyModifiedPropertiesWithoutUndo();
184
+ AssetDatabase.SaveAssets();
185
+ }
186
+
187
+ private static UnityEngine.Object GetPlayerSettingsAsset()
188
+ {
189
+ var asset = Unsupported.GetSerializedAssetInterfaceSingleton("PlayerSettings");
190
+ if (asset == null)
191
+ throw new InvalidOperationException("Unable to resolve PlayerSettings serialized asset");
192
+
193
+ return asset;
194
+ }
195
+
196
+ private static int ParseActiveInputHandler(object value)
197
+ {
198
+ if (value == null)
199
+ throw new ArgumentException("Input handling value cannot be null");
200
+
201
+ if (int.TryParse(value.ToString(), out var numeric))
202
+ {
203
+ if (numeric < 0 || numeric > 2)
204
+ throw new ArgumentException("activeInputHandler must be 0 (Old), 1 (Input System), or 2 (Both)");
205
+ return numeric;
206
+ }
207
+
208
+ switch (value.ToString().Trim().ToLowerInvariant())
209
+ {
210
+ case "old":
211
+ case "legacy":
212
+ case "inputmanager":
213
+ return 0;
214
+ case "new":
215
+ case "inputsystem":
216
+ case "inputsystempackage":
217
+ return 1;
218
+ case "both":
219
+ return 2;
220
+ default:
221
+ throw new ArgumentException("activeInputHandler must be one of: old, inputsystem, both, 0, 1, 2");
222
+ }
223
+ }
224
+
225
+ private static string DescribeActiveInputHandler(int value)
226
+ {
227
+ switch (value)
228
+ {
229
+ case 0:
230
+ return "Old";
231
+ case 1:
232
+ return "InputSystemPackage";
233
+ case 2:
234
+ return "Both";
235
+ default:
236
+ return $"Unknown({value})";
237
+ }
238
+ }
239
+
240
+ private static object HandlePhysicsSettings(string paramsJson)
241
+ {
242
+ return new Dictionary<string, object>
243
+ {
244
+ ["gravity"] = new List<object>
245
+ {
246
+ (double)Physics.gravity.x,
247
+ (double)Physics.gravity.y,
248
+ (double)Physics.gravity.z
249
+ },
250
+ ["defaultSolverIterations"] = Physics.defaultSolverIterations,
251
+ ["defaultSolverVelocityIterations"] = Physics.defaultSolverVelocityIterations,
252
+ ["bounceThreshold"] = (double)Physics.bounceThreshold,
253
+ ["sleepThreshold"] = (double)Physics.sleepThreshold,
254
+ ["defaultContactOffset"] = (double)Physics.defaultContactOffset,
255
+ ["autoSimulation"] = Physics.simulationMode.ToString()
256
+ };
257
+ }
258
+
259
+ private static object HandleSetPhysicsSetting(string paramsJson)
260
+ {
261
+ var p = MiniJson.Deserialize(paramsJson) as Dictionary<string, object>;
262
+ if (p == null || !p.TryGetValue("key", out var keyObj))
263
+ throw new ArgumentException("Missing 'key' parameter");
264
+ if (!p.ContainsKey("value"))
265
+ throw new ArgumentException("Missing 'value' parameter");
266
+
267
+ string key = keyObj.ToString();
268
+ object value = p["value"];
269
+
270
+ switch (key)
271
+ {
272
+ case "gravity":
273
+ if (value is List<object> g && g.Count >= 3)
274
+ Physics.gravity = new Vector3(
275
+ Convert.ToSingle(g[0]),
276
+ Convert.ToSingle(g[1]),
277
+ Convert.ToSingle(g[2]));
278
+ break;
279
+ case "defaultSolverIterations":
280
+ Physics.defaultSolverIterations = Convert.ToInt32(value);
281
+ break;
282
+ case "defaultSolverVelocityIterations":
283
+ Physics.defaultSolverVelocityIterations = Convert.ToInt32(value);
284
+ break;
285
+ case "bounceThreshold":
286
+ Physics.bounceThreshold = Convert.ToSingle(value);
287
+ break;
288
+ case "sleepThreshold":
289
+ Physics.sleepThreshold = Convert.ToSingle(value);
290
+ break;
291
+ default:
292
+ throw new ArgumentException($"Unknown physics setting: {key}");
293
+ }
294
+
295
+ return new Dictionary<string, object> { ["status"] = "ok", ["key"] = key };
296
+ }
297
+
298
+ private static object HandleLightingSettings(string paramsJson)
299
+ {
300
+ var result = new Dictionary<string, object>
301
+ {
302
+ ["ambientMode"] = RenderSettings.ambientMode.ToString(),
303
+ ["ambientIntensity"] = (double)RenderSettings.ambientIntensity,
304
+ ["fog"] = RenderSettings.fog,
305
+ ["fogMode"] = RenderSettings.fogMode.ToString(),
306
+ ["fogDensity"] = (double)RenderSettings.fogDensity,
307
+ ["fogStartDistance"] = (double)RenderSettings.fogStartDistance,
308
+ ["fogEndDistance"] = (double)RenderSettings.fogEndDistance,
309
+ };
310
+
311
+ var c = RenderSettings.ambientLight;
312
+ result["ambientColor"] = new List<object> { (double)c.r, (double)c.g, (double)c.b, (double)c.a };
313
+
314
+ var fc = RenderSettings.fogColor;
315
+ result["fogColor"] = new List<object> { (double)fc.r, (double)fc.g, (double)fc.b, (double)fc.a };
316
+
317
+ if (RenderSettings.skybox != null)
318
+ result["skybox"] = RenderSettings.skybox.name;
319
+
320
+ if (RenderSettings.sun != null)
321
+ result["sunSource"] = RenderSettings.sun.gameObject.name;
322
+
323
+ return result;
324
+ }
325
+
326
+ private static object HandleSetLightingSetting(string paramsJson)
327
+ {
328
+ var p = MiniJson.Deserialize(paramsJson) as Dictionary<string, object>;
329
+ if (p == null || !p.TryGetValue("key", out var keyObj))
330
+ throw new ArgumentException("Missing 'key' parameter");
331
+ if (!p.ContainsKey("value"))
332
+ throw new ArgumentException("Missing 'value' parameter");
333
+
334
+ string key = keyObj.ToString();
335
+ object value = p["value"];
336
+
337
+ switch (key)
338
+ {
339
+ case "ambientMode":
340
+ if (Enum.TryParse<AmbientMode>(value.ToString(), out var am))
341
+ RenderSettings.ambientMode = am;
342
+ break;
343
+ case "ambientIntensity":
344
+ RenderSettings.ambientIntensity = Convert.ToSingle(value);
345
+ break;
346
+ case "ambientColor":
347
+ if (value is List<object> ac && ac.Count >= 3)
348
+ RenderSettings.ambientLight = new Color(
349
+ Convert.ToSingle(ac[0]),
350
+ Convert.ToSingle(ac[1]),
351
+ Convert.ToSingle(ac[2]),
352
+ ac.Count >= 4 ? Convert.ToSingle(ac[3]) : 1f);
353
+ break;
354
+ case "fog":
355
+ RenderSettings.fog = Convert.ToBoolean(value);
356
+ break;
357
+ case "fogMode":
358
+ if (Enum.TryParse<FogMode>(value.ToString(), out var fm))
359
+ RenderSettings.fogMode = fm;
360
+ break;
361
+ case "fogDensity":
362
+ RenderSettings.fogDensity = Convert.ToSingle(value);
363
+ break;
364
+ case "fogColor":
365
+ if (value is List<object> fca && fca.Count >= 3)
366
+ RenderSettings.fogColor = new Color(
367
+ Convert.ToSingle(fca[0]),
368
+ Convert.ToSingle(fca[1]),
369
+ Convert.ToSingle(fca[2]),
370
+ fca.Count >= 4 ? Convert.ToSingle(fca[3]) : 1f);
371
+ break;
372
+ case "fogStartDistance":
373
+ RenderSettings.fogStartDistance = Convert.ToSingle(value);
374
+ break;
375
+ case "fogEndDistance":
376
+ RenderSettings.fogEndDistance = Convert.ToSingle(value);
377
+ break;
378
+ default:
379
+ throw new ArgumentException($"Unknown lighting setting: {key}");
380
+ }
381
+
382
+ return new Dictionary<string, object> { ["status"] = "ok", ["key"] = key };
383
+ }
384
+
385
+ private static object HandleTagsAndLayers(string paramsJson)
386
+ {
387
+ var tags = new List<object>();
388
+ foreach (var tag in UnityEditorInternal.InternalEditorUtility.tags)
389
+ tags.Add(tag);
390
+
391
+ var layers = new List<object>();
392
+ for (int i = 0; i < 32; i++)
393
+ {
394
+ string name = LayerMask.LayerToName(i);
395
+ if (!string.IsNullOrEmpty(name))
396
+ {
397
+ layers.Add(new Dictionary<string, object>
398
+ {
399
+ ["index"] = i,
400
+ ["name"] = name
401
+ });
402
+ }
403
+ }
404
+
405
+ var sortingLayers = new List<object>();
406
+ foreach (var sl in SortingLayer.layers)
407
+ {
408
+ sortingLayers.Add(new Dictionary<string, object>
409
+ {
410
+ ["id"] = sl.id,
411
+ ["name"] = sl.name,
412
+ ["value"] = sl.value
413
+ });
414
+ }
415
+
416
+ return new Dictionary<string, object>
417
+ {
418
+ ["tags"] = tags,
419
+ ["layers"] = layers,
420
+ ["sortingLayers"] = sortingLayers
421
+ };
422
+ }
423
+
424
+ private static object HandleAddTag(string paramsJson)
425
+ {
426
+ var p = MiniJson.Deserialize(paramsJson) as Dictionary<string, object>;
427
+ if (p == null || !p.TryGetValue("tag", out var tagObj))
428
+ throw new ArgumentException("Missing 'tag' parameter");
429
+
430
+ string tag = tagObj.ToString();
431
+
432
+ // Check if tag already exists
433
+ var so = new SerializedObject(
434
+ AssetDatabase.LoadMainAssetAtPath("ProjectSettings/TagManager.asset"));
435
+ var tagsProp = so.FindProperty("tags");
436
+
437
+ for (int i = 0; i < tagsProp.arraySize; i++)
438
+ {
439
+ if (tagsProp.GetArrayElementAtIndex(i).stringValue == tag)
440
+ return new Dictionary<string, object>
441
+ {
442
+ ["status"] = "exists",
443
+ ["tag"] = tag
444
+ };
445
+ }
446
+
447
+ tagsProp.InsertArrayElementAtIndex(tagsProp.arraySize);
448
+ tagsProp.GetArrayElementAtIndex(tagsProp.arraySize - 1).stringValue = tag;
449
+ so.ApplyModifiedProperties();
450
+ so.Dispose();
451
+
452
+ return new Dictionary<string, object>
453
+ {
454
+ ["status"] = "ok",
455
+ ["tag"] = tag
456
+ };
457
+ }
458
+
459
+ private static object HandleAddLayer(string paramsJson)
460
+ {
461
+ var p = MiniJson.Deserialize(paramsJson) as Dictionary<string, object>;
462
+ if (p == null || !p.TryGetValue("name", out var nameObj))
463
+ throw new ArgumentException("Missing 'name' parameter");
464
+
465
+ string layerName = nameObj.ToString();
466
+ int targetIndex = -1;
467
+ if (p.TryGetValue("index", out var idxObj))
468
+ targetIndex = Convert.ToInt32(idxObj);
469
+
470
+ var so = new SerializedObject(
471
+ AssetDatabase.LoadMainAssetAtPath("ProjectSettings/TagManager.asset"));
472
+ var layersProp = so.FindProperty("layers");
473
+
474
+ // Check if layer already exists
475
+ for (int i = 0; i < layersProp.arraySize; i++)
476
+ {
477
+ if (layersProp.GetArrayElementAtIndex(i).stringValue == layerName)
478
+ return new Dictionary<string, object>
479
+ {
480
+ ["status"] = "exists",
481
+ ["name"] = layerName,
482
+ ["index"] = i
483
+ };
484
+ }
485
+
486
+ // Find first empty user layer (8-31) or use specified index
487
+ int assignedIndex = -1;
488
+ if (targetIndex >= 8 && targetIndex < 32)
489
+ {
490
+ if (string.IsNullOrEmpty(layersProp.GetArrayElementAtIndex(targetIndex).stringValue))
491
+ {
492
+ layersProp.GetArrayElementAtIndex(targetIndex).stringValue = layerName;
493
+ assignedIndex = targetIndex;
494
+ }
495
+ else
496
+ {
497
+ throw new ArgumentException($"Layer index {targetIndex} is already in use");
498
+ }
499
+ }
500
+ else
501
+ {
502
+ for (int i = 8; i < 32; i++)
503
+ {
504
+ if (string.IsNullOrEmpty(layersProp.GetArrayElementAtIndex(i).stringValue))
505
+ {
506
+ layersProp.GetArrayElementAtIndex(i).stringValue = layerName;
507
+ assignedIndex = i;
508
+ break;
509
+ }
510
+ }
511
+ }
512
+
513
+ if (assignedIndex < 0)
514
+ throw new InvalidOperationException("No empty layer slots available");
515
+
516
+ so.ApplyModifiedProperties();
517
+ so.Dispose();
518
+
519
+ return new Dictionary<string, object>
520
+ {
521
+ ["status"] = "ok",
522
+ ["name"] = layerName,
523
+ ["index"] = assignedIndex
524
+ };
525
+ }
526
+ }
527
+ }
@@ -0,0 +1,2 @@
1
+ fileFormatVersion: 2
2
+ guid: c362ce042c615a7439353d87c10bd831
@@ -0,0 +1,141 @@
1
+ using System;
2
+ using System.Collections.Generic;
3
+ using System.IO;
4
+ using UnityEditor;
5
+ using UnityEngine;
6
+
7
+ namespace UCP.Bridge
8
+ {
9
+ public static class FileController
10
+ {
11
+ public static void Register(CommandRouter router)
12
+ {
13
+ router.Register("file/read", HandleRead);
14
+ router.Register("file/write", HandleWrite);
15
+ router.Register("file/patch", HandlePatch);
16
+ }
17
+
18
+ private static string ProjectRoot =>
19
+ Path.GetDirectoryName(Application.dataPath);
20
+
21
+ private static string ResolveSafePath(string relativePath)
22
+ {
23
+ var projectRoot = ProjectRoot;
24
+ var fullPath = Path.GetFullPath(Path.Combine(projectRoot, relativePath));
25
+
26
+ // Security: ensure path is within project root
27
+ if (!fullPath.StartsWith(projectRoot, StringComparison.OrdinalIgnoreCase))
28
+ throw new UnauthorizedAccessException(
29
+ $"Path escapes project root: {relativePath}");
30
+
31
+ return fullPath;
32
+ }
33
+
34
+ private static object HandleRead(string paramsJson)
35
+ {
36
+ var p = MiniJson.Deserialize(paramsJson) as Dictionary<string, object>;
37
+ if (p == null || !p.TryGetValue("path", out var pathObj))
38
+ throw new ArgumentException("Missing 'path' parameter");
39
+
40
+ var fullPath = ResolveSafePath(pathObj.ToString());
41
+
42
+ if (!File.Exists(fullPath))
43
+ throw new FileNotFoundException($"File not found: {pathObj}");
44
+
45
+ var content = File.ReadAllText(fullPath);
46
+
47
+ return new Dictionary<string, object>
48
+ {
49
+ ["path"] = pathObj.ToString(),
50
+ ["content"] = content,
51
+ ["size"] = content.Length
52
+ };
53
+ }
54
+
55
+ private static object HandleWrite(string paramsJson)
56
+ {
57
+ var p = MiniJson.Deserialize(paramsJson) as Dictionary<string, object>;
58
+ if (p == null || !p.TryGetValue("path", out var pathObj))
59
+ throw new ArgumentException("Missing 'path' parameter");
60
+ if (!p.TryGetValue("content", out var contentObj))
61
+ throw new ArgumentException("Missing 'content' parameter");
62
+ var noReimport = p.TryGetValue("noReimport", out var noReimportObj)
63
+ && noReimportObj != null
64
+ && Convert.ToBoolean(noReimportObj);
65
+
66
+ var fullPath = ResolveSafePath(pathObj.ToString());
67
+
68
+ // Create directory if needed
69
+ var dir = Path.GetDirectoryName(fullPath);
70
+ if (!string.IsNullOrEmpty(dir) && !Directory.Exists(dir))
71
+ Directory.CreateDirectory(dir);
72
+
73
+ File.WriteAllText(fullPath, contentObj.ToString());
74
+ var reimport = AssetImportSupport.ReimportOrDescribe(pathObj.ToString(), noReimport);
75
+
76
+ return new Dictionary<string, object>
77
+ {
78
+ ["path"] = pathObj.ToString(),
79
+ ["written"] = true,
80
+ ["size"] = contentObj.ToString().Length,
81
+ ["reimport"] = reimport
82
+ };
83
+ }
84
+
85
+ private static object HandlePatch(string paramsJson)
86
+ {
87
+ var p = MiniJson.Deserialize(paramsJson) as Dictionary<string, object>;
88
+ if (p == null || !p.TryGetValue("path", out var pathObj))
89
+ throw new ArgumentException("Missing 'path' parameter");
90
+ if (!p.TryGetValue("patch", out var patchObj))
91
+ throw new ArgumentException("Missing 'patch' parameter");
92
+ var noReimport = p.TryGetValue("noReimport", out var noReimportObj)
93
+ && noReimportObj != null
94
+ && Convert.ToBoolean(noReimportObj);
95
+
96
+ var fullPath = ResolveSafePath(pathObj.ToString());
97
+
98
+ if (!File.Exists(fullPath))
99
+ throw new FileNotFoundException($"File not found: {pathObj}");
100
+
101
+ var original = File.ReadAllText(fullPath);
102
+
103
+ // Support patch as either a dict with find/replace keys, or a JSON string
104
+ Dictionary<string, object> patchData = null;
105
+
106
+ if (patchObj is Dictionary<string, object> dict)
107
+ {
108
+ patchData = dict;
109
+ }
110
+ else
111
+ {
112
+ var patchContent = patchObj.ToString();
113
+ if (patchContent.TrimStart().StartsWith("{"))
114
+ patchData = MiniJson.Deserialize(patchContent) as Dictionary<string, object>;
115
+ }
116
+
117
+ if (patchData != null &&
118
+ patchData.TryGetValue("find", out var findObj) &&
119
+ patchData.TryGetValue("replace", out var replaceObj))
120
+ {
121
+ var find = findObj.ToString();
122
+ var replace = replaceObj.ToString();
123
+ if (!original.Contains(find))
124
+ throw new Exception("Patch target not found in file");
125
+
126
+ var patched = original.Replace(find, replace);
127
+ File.WriteAllText(fullPath, patched);
128
+ var reimport = AssetImportSupport.ReimportOrDescribe(pathObj.ToString(), noReimport);
129
+
130
+ return new Dictionary<string, object>
131
+ {
132
+ ["path"] = pathObj.ToString(),
133
+ ["patched"] = true,
134
+ ["reimport"] = reimport
135
+ };
136
+ }
137
+
138
+ throw new ArgumentException("Unsupported patch format. Use {\"find\": \"...\", \"replace\": \"...\"}");
139
+ }
140
+ }
141
+ }
@@ -0,0 +1,2 @@
1
+ fileFormatVersion: 2
2
+ guid: 2183be6281cf441469323bee5c73467b