Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 27 additions & 8 deletions Editor/Tools/LightmapTransferTool.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1997,7 +1997,7 @@ async Task ExecRepackImpl(List<MeshEntry> entries, bool useAsync)

async Task ExecRepackCoreImpl(List<MeshEntry> entries, bool useAsync)
{
uint resolvedResolution = (uint)ctx.AtlasResolution;
uint resolvedResolution = (uint)SanitizeAtlasResolution(ctx.AtlasResolution);
if (ctx.RepackResolutionMode == ResolutionMode.AutoFromTexelDensity)
{
double area = MeshAreaHelper.ComputeTotal3DAreaMeters(
Expand All @@ -2013,7 +2013,9 @@ async Task ExecRepackCoreImpl(List<MeshEntry> entries, bool useAsync)
// where the resolved value can differ by an octave from the user
// setting.
BenchmarkRecorder.Current?.SetResolvedAtlasResolution((int)resolvedResolution);
UvtLog.Info($"[Repack] {entries.Count} meshes, res={resolvedResolution}, pad={ctx.ShellPaddingPx}, bdr={ctx.BorderPaddingPx}");
int safeShellPadding = SanitizePadding(ctx.ShellPaddingPx);
int safeBorderPadding = SanitizePadding(ctx.BorderPaddingPx);
UvtLog.Info($"[Repack] {entries.Count} meshes, res={resolvedResolution}, pad={safeShellPadding}, bdr={safeBorderPadding}");
var validEntries = new List<MeshEntry>();
var meshCopies = new List<Mesh>();
foreach (var e in entries)
Expand All @@ -2030,8 +2032,8 @@ async Task ExecRepackCoreImpl(List<MeshEntry> entries, bool useAsync)

var opts = RepackOptions.Default;
opts.resolution = resolvedResolution;
opts.padding = (uint)ctx.ShellPaddingPx;
opts.borderPadding = (uint)ctx.BorderPaddingPx;
opts.padding = (uint)safeShellPadding;
opts.borderPadding = (uint)safeBorderPadding;
opts.bruteForce = ctx.XatlasBruteForce;
opts.rotateCharts = ctx.XatlasRotateCharts;
opts.rotateChartsToAxis = ctx.XatlasRotateChartsToAxis;
Expand Down Expand Up @@ -4462,17 +4464,34 @@ void TryLoadSettingsFromSidecar()
var data = AssetDatabase.LoadAssetAtPath<Uv2DataAsset>(selectedSidecarPath);
if (data?.toolSettings == null) return;
var s = data.toolSettings;
ctx.AtlasResolution = s.atlasResolution;
ctx.ShellPaddingPx = s.shellPaddingPx;
ctx.BorderPaddingPx = s.borderPaddingPx;
ctx.AtlasResolution = SanitizeAtlasResolution(s.atlasResolution);
ctx.ShellPaddingPx = SanitizePadding(s.shellPaddingPx);
ctx.BorderPaddingPx = SanitizePadding(s.borderPaddingPx);
ctx.RepackPerMesh = s.repackPerMesh;
symSplitThresholdMode = Enum.IsDefined(typeof(SymmetrySplitShells.ThresholdMode), s.symmetrySplitThresholdMode)
? (SymmetrySplitShells.ThresholdMode)s.symmetrySplitThresholdMode
: SymmetrySplitShells.ThresholdMode.LegacyFixed;
SymmetrySplitShells.CurrentThresholdMode = symSplitThresholdMode;
ctx.SourceLodIndex = Mathf.Clamp(s.sourceLodIndex, 0, Mathf.Max(0, ctx.LodCount - 1));
ctx.PipeSettings.saveNewMeshAssets = s.saveNewMeshAssets;
if (!string.IsNullOrEmpty(s.savePath)) ctx.PipeSettings.savePath = s.savePath;
if (IsSafeAssetFolderPath(s.savePath)) ctx.PipeSettings.savePath = s.savePath;
}

static int SanitizeAtlasResolution(int resolution) => Mathf.Clamp(resolution, 64, 4096);

static int SanitizePadding(int padding) => Mathf.Clamp(padding, 0, 16);

static bool IsSafeAssetFolderPath(string path)
{
if (string.IsNullOrWhiteSpace(path)) return false;
string normalized = path.Replace('\\', '/').TrimEnd('/');
if (normalized != "Assets" && !normalized.StartsWith("Assets/", StringComparison.Ordinal))
return false;

var segments = normalized.Split('/');
foreach (string segment in segments)
if (segment.Length == 0 || segment == "." || segment == "..") return false;
return true;
}

void SaveSettingsToSidecar()
Expand Down
39 changes: 39 additions & 0 deletions Tests/Editor/ToolSettingsValidationTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using System.Reflection;
using NUnit.Framework;

namespace SashaRX.UnityMeshLab.Tests
{
public class ToolSettingsValidationTests
{
static object Invoke(string methodName, object value)
{
var method = typeof(LightmapTransferTool).GetMethod(
methodName,
BindingFlags.NonPublic | BindingFlags.Static);
Assert.IsNotNull(method, $"Missing validation method {methodName}");
return method.Invoke(null, new[] { value });
}

[TestCase(-1, 64)]
[TestCase(0, 64)]
[TestCase(1024, 1024)]
[TestCase(int.MaxValue, 4096)]
public void AtlasResolution_IsClampedToSupportedRange(int input, int expected)
=> Assert.AreEqual(expected, Invoke("SanitizeAtlasResolution", input));

[TestCase(-1, 0)]
[TestCase(2, 2)]
[TestCase(int.MaxValue, 16)]
public void Padding_IsClampedToUiRange(int input, int expected)
=> Assert.AreEqual(expected, Invoke("SanitizePadding", input));

[TestCase("Assets/Generated", true)]
[TestCase("Assets", true)]
[TestCase("Assets/../Library", false)]
[TestCase("/tmp/output", false)]
[TestCase("Packages/output", false)]
[TestCase("", false)]
public void SavePath_MustRemainInsideAssets(string input, bool expected)
=> Assert.AreEqual(expected, Invoke("IsSafeAssetFolderPath", input));
}
}
2 changes: 2 additions & 0 deletions Tests/Editor/ToolSettingsValidationTests.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading