Skip to content

Commit 8d0c31c

Browse files
committed
Added Map Patch Creation Tool
1 parent 9c0f299 commit 8d0c31c

File tree

6 files changed

+292
-0
lines changed

6 files changed

+292
-0
lines changed

BrickForceDevTools/Global.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ static class Global
1616
public static bool DefaultExportJson = true;
1717
public static bool DefaultExportObj = true;
1818
public static bool DefaultExportPlaintext = true;
19+
public static bool IncludeAssemblyLineInPatchInfo = false;
1920

2021
private static int _regMapCount;
2122
public static int RegMapCount => Volatile.Read(ref _regMapCount);

BrickForceDevTools/PatchBuilder.cs

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.IO;
4+
using System.Linq;
5+
6+
namespace BrickForceDevTools.Patch
7+
{
8+
public static class PatchBuilder
9+
{
10+
public sealed record DiffEntry(
11+
string BaseName,
12+
string RegMapPath,
13+
string GeometryPath,
14+
bool HasGeometry,
15+
string MapName,
16+
string Creator
17+
);
18+
19+
/// <summary>
20+
/// Compute B - A by base filename (without extension).
21+
/// </summary>
22+
public static List<DiffEntry> ComputeDiff(string folderA, string folderB, bool skipMissingGeometry, Action<string>? log = null)
23+
{
24+
// Index A
25+
var aKeys = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
26+
foreach (var regA in Directory.EnumerateFiles(folderA, "*.regmap"))
27+
{
28+
var baseName = Path.GetFileNameWithoutExtension(regA);
29+
var geoA = Path.Combine(folderA, baseName + ".geometry");
30+
31+
if (skipMissingGeometry && !File.Exists(geoA))
32+
continue;
33+
34+
aKeys.Add(baseName);
35+
}
36+
37+
// Scan B
38+
var diffs = new List<DiffEntry>();
39+
40+
foreach (var regB in Directory.EnumerateFiles(folderB, "*.regmap"))
41+
{
42+
var baseName = Path.GetFileNameWithoutExtension(regB);
43+
if (aKeys.Contains(baseName))
44+
continue;
45+
46+
var geoB = Path.Combine(folderB, baseName + ".geometry");
47+
var hasGeo = File.Exists(geoB);
48+
49+
if (skipMissingGeometry && !hasGeo)
50+
{
51+
log?.Invoke($"[Patch] Missing Geometry (skipped): {Path.GetFileName(geoB)}");
52+
continue;
53+
}
54+
55+
// Load map metadata from regmap (adjust fields if needed)
56+
var regMap = RegMapManager.Load(regB);
57+
58+
// These names come from your earlier regmap loader snippet: alias + developer
59+
string mapName = regMap.alias ?? baseName;
60+
string creator = regMap.developer ?? "Unknown";
61+
62+
diffs.Add(new DiffEntry(baseName, regB, geoB, hasGeo, mapName, creator));
63+
}
64+
65+
return diffs
66+
.OrderBy(d => d.MapName, StringComparer.OrdinalIgnoreCase)
67+
.ThenBy(d => d.Creator, StringComparer.OrdinalIgnoreCase)
68+
.ToList();
69+
}
70+
71+
/// <summary>
72+
/// Copy files and generate changelog.txt and info.
73+
/// </summary>
74+
public static void BuildPatchOutput(IEnumerable<DiffEntry> diffs, string outputFolder, Action<string>? log = null)
75+
{
76+
Directory.CreateDirectory(outputFolder);
77+
78+
var changelogLines = new List<string>();
79+
var infoLines = new List<string>();
80+
if (Global.IncludeAssemblyLineInPatchInfo)
81+
{
82+
infoLines.Add("Assembly-CSharp.dll=/BrickForce_Data/Managed/");
83+
}
84+
85+
int copiedMaps = 0;
86+
int copiedFiles = 0;
87+
88+
foreach (var d in diffs)
89+
{
90+
var outReg = Path.Combine(outputFolder, d.BaseName + ".regmap");
91+
File.Copy(d.RegMapPath, outReg, overwrite: true);
92+
copiedFiles++;
93+
94+
changelogLines.Add($"{d.MapName} by {d.Creator}");
95+
infoLines.Add($"{Path.GetFileName(outReg)}=/BrickForce_Data/Resources/Cache");
96+
97+
if (d.HasGeometry)
98+
{
99+
var outGeo = Path.Combine(outputFolder, d.BaseName + ".geometry");
100+
File.Copy(d.GeometryPath, outGeo, overwrite: true);
101+
copiedFiles++;
102+
103+
infoLines.Add($"{Path.GetFileName(outGeo)}=/BrickForce_Data/Resources/Cache");
104+
}
105+
106+
copiedMaps++;
107+
}
108+
109+
File.WriteAllLines(Path.Combine(outputFolder, "changelog.txt"), changelogLines);
110+
File.WriteAllLines(Path.Combine(outputFolder, "info"), infoLines);
111+
112+
log?.Invoke($"[Patch] Done. Maps: {copiedMaps}, Files: {copiedFiles}, Output: {outputFolder}");
113+
}
114+
}
115+
}

BrickForceDevTools/ViewModels/MainWindowViewModel.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ namespace BrickForceDevTools.ViewModels
1515
public partial class MainWindowViewModel : ViewModelBase
1616
{
1717
public RegMapsViewModel RegMapsViewModel { get; } = RegMapsViewModel.Instance;
18+
public PatchBuilderViewModel PatchBuilder => PatchBuilderViewModel.Instance;
19+
1820

1921
private RegMap _selectedRegMap;
2022
public RegMap SelectedRegMap
@@ -52,6 +54,7 @@ private void UpdateMapDetails(RegMap selectedRegMap)
5254
[ObservableProperty] private bool defaultExportJson = true;
5355
[ObservableProperty] private bool defaultExportObj = true;
5456
[ObservableProperty] private bool defaultExportPlaintext = true;
57+
[ObservableProperty] private bool includeAssemblyLineInPatchInfo = false;
5558

5659
private TemplateFile _selectedTemplateFile;
5760
public TemplateFile SelectedTemplateFile
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
using System;
2+
using System.Collections.ObjectModel;
3+
4+
namespace BrickForceDevTools.ViewModels
5+
{
6+
public class PatchBuilderViewModel : ViewModelBase
7+
{
8+
private static PatchBuilderViewModel _instance;
9+
public static PatchBuilderViewModel Instance => _instance ??= new PatchBuilderViewModel();
10+
11+
private string? _folderA;
12+
public string? FolderA
13+
{
14+
get => _folderA;
15+
set => SetProperty(ref _folderA, value);
16+
}
17+
18+
private string? _folderB;
19+
public string? FolderB
20+
{
21+
get => _folderB;
22+
set => SetProperty(ref _folderB, value);
23+
}
24+
25+
private string _summary = "Select Folder A and Folder B.";
26+
public string Summary
27+
{
28+
get => _summary;
29+
set => SetProperty(ref _summary, value);
30+
}
31+
32+
private bool _canBuildPatch;
33+
public bool CanBuildPatch
34+
{
35+
get => _canBuildPatch;
36+
set => SetProperty(ref _canBuildPatch, value);
37+
}
38+
39+
public ObservableCollection<PatchDiffItem> DiffItems { get; } = new();
40+
41+
private PatchBuilderViewModel() { }
42+
}
43+
44+
public class PatchDiffItem : ViewModelBase
45+
{
46+
public string BaseName { get; set; } = "";
47+
public string RegMapFile { get; set; } = "";
48+
public string GeometryFile { get; set; } = "";
49+
public bool HasGeometry { get; set; }
50+
public string MapName { get; set; } = "";
51+
public string Creator { get; set; } = "";
52+
}
53+
}

BrickForceDevTools/Views/MainWindow.axaml

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,44 @@
127127
</Grid>
128128
</Grid>
129129
</TabItem>
130+
<TabItem Header="Patch Builder">
131+
<StackPanel Margin="12" Spacing="10">
132+
133+
<TextBlock Text="{Binding PatchBuilder.Summary}" />
134+
135+
<StackPanel Orientation="Horizontal" Spacing="8">
136+
<Button Content="Select Folder A then B" Click="OnSelectFoldersForPatchClick"/>
137+
<Button x:Name="BtnBuildPatch" Content="Build Patch" Click="OnBuildPatchClick"
138+
IsEnabled="{Binding PatchBuilder.CanBuildPatch}" />
139+
</StackPanel>
140+
141+
<Grid ColumnDefinitions="Auto,*" RowDefinitions="Auto,Auto,Auto">
142+
<TextBlock Grid.Row="0" Grid.Column="0" Text="Folder A:" Margin="0,0,8,0"/>
143+
<TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding PatchBuilder.FolderA}" />
144+
145+
<TextBlock Grid.Row="1" Grid.Column="0" Text="Folder B:" Margin="0,0,8,0"/>
146+
<TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding PatchBuilder.FolderB}" />
147+
148+
<TextBlock Grid.Row="2" Grid.ColumnSpan="2" Text="Diff (B ? A):" Margin="0,8,0,0"/>
149+
</Grid>
150+
151+
<DataGrid ItemsSource="{Binding PatchBuilder.DiffItems}"
152+
AutoGenerateColumns="False"
153+
IsReadOnly="True"
154+
Height="340">
155+
<DataGrid.Columns>
156+
<DataGridTextColumn Header="Base" Binding="{Binding BaseName}" Width="1.5*"/>
157+
<DataGridTextColumn Header="MapName" Binding="{Binding MapName}" Width="2*"/>
158+
<DataGridTextColumn Header="Creator" Binding="{Binding Creator}" Width="2*"/>
159+
<DataGridCheckBoxColumn Header=".geometry" Binding="{Binding HasGeometry}" Width="*"/>
160+
<DataGridTextColumn Header="RegMap" Binding="{Binding RegMapFile}" Width="2*"/>
161+
<DataGridTextColumn Header="Geometry" Binding="{Binding GeometryFile}" Width="2*"/>
162+
</DataGrid.Columns>
163+
</DataGrid>
164+
165+
</StackPanel>
166+
</TabItem>
167+
130168
<TabItem Header="Settings" VerticalContentAlignment="Center">
131169
<Grid RowDefinitions="Auto, *" Margin="10">
132170
<StackPanel Orientation="Horizontal" Spacing="5">
@@ -142,6 +180,7 @@
142180
<CheckBox Content="Default Export JSON" IsChecked="{Binding DefaultExportJson}" />
143181
<CheckBox Content="Default Export OBJ" IsChecked="{Binding DefaultExportObj}" />
144182
<CheckBox Content="Default Export Plaintext" IsChecked="{Binding DefaultExportPlaintext}" />
183+
<CheckBox Content="Include Assembly-CSharp.dll in patch info" IsChecked="{Binding IncludeAssemblyLineInPatchInfo}" />
145184
</StackPanel>
146185
</Grid>
147186
</TabItem>

BrickForceDevTools/Views/MainWindow.axaml.cs

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
using System.Reflection.PortableExecutable;
2121
using System.Threading.Tasks;
2222
using Avalonia.Threading;
23+
using BrickForceDevTools.Patch;
2324

2425
namespace BrickForceDevTools.Views
2526
{
@@ -29,6 +30,9 @@ public partial class MainWindow : Window
2930

3031
private readonly char crypt = 'E';
3132

33+
private readonly PatchBuilderViewModel _patchVm = PatchBuilderViewModel.Instance;
34+
private System.Collections.Generic.List<PatchBuilder.DiffEntry> _currentDiff = new();
35+
3236
public MainWindow()
3337
{
3438
InitializeComponent();
@@ -455,5 +459,82 @@ private void CookAndSaveFile(TemplateFile file, string pathName)
455459
}
456460
Global.PrintLine($"Saved Cooked file: {pathName}");
457461
}
462+
463+
private async void OnSelectFoldersForPatchClick(object? sender, RoutedEventArgs e)
464+
{
465+
// Pick A
466+
var a = await StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions
467+
{
468+
Title = "Select Folder A (baseline)",
469+
AllowMultiple = false
470+
});
471+
if (a.Count == 0) return;
472+
473+
// Pick B
474+
var b = await StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions
475+
{
476+
Title = "Select Folder B (new)",
477+
AllowMultiple = false
478+
});
479+
if (b.Count == 0) return;
480+
481+
var folderA = a[0].Path.LocalPath;
482+
var folderB = b[0].Path.LocalPath;
483+
484+
_patchVm.FolderA = folderA;
485+
_patchVm.FolderB = folderB;
486+
_patchVm.Summary = "Computing diff…";
487+
_patchVm.CanBuildPatch = false;
488+
_patchVm.DiffItems.Clear();
489+
490+
// Compute diff off UI thread
491+
_currentDiff = await Task.Run(() =>
492+
PatchBuilder.ComputeDiff(folderA, folderB, Global.SkipMissingGeometry, Global.PrintLine)
493+
);
494+
495+
// Fill grid
496+
foreach (var d in _currentDiff)
497+
{
498+
_patchVm.DiffItems.Add(new PatchDiffItem
499+
{
500+
BaseName = d.BaseName,
501+
MapName = d.MapName,
502+
Creator = d.Creator,
503+
HasGeometry = d.HasGeometry,
504+
RegMapFile = Path.GetFileName(d.RegMapPath),
505+
GeometryFile = d.HasGeometry ? Path.GetFileName(d.GeometryPath) : ""
506+
});
507+
}
508+
509+
_patchVm.Summary = $"New maps in B not in A: {_currentDiff.Count}";
510+
_patchVm.CanBuildPatch = _currentDiff.Count > 0;
511+
}
512+
513+
private async void OnBuildPatchClick(object? sender, RoutedEventArgs e)
514+
{
515+
if (_currentDiff == null || _currentDiff.Count == 0)
516+
{
517+
Global.PrintLine("[Patch] No diff computed.");
518+
return;
519+
}
520+
521+
var outFolderPick = await StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions
522+
{
523+
Title = "Select output folder for patch",
524+
AllowMultiple = false
525+
});
526+
if (outFolderPick.Count == 0) return;
527+
528+
var outFolder = outFolderPick[0].Path.LocalPath;
529+
530+
_patchVm.Summary = "Building patch…";
531+
532+
await Task.Run(() =>
533+
{
534+
PatchBuilder.BuildPatchOutput(_currentDiff, outFolder, Global.PrintLine);
535+
});
536+
537+
_patchVm.Summary = $"Patch built into: {outFolder}";
538+
}
458539
}
459540
}

0 commit comments

Comments
 (0)