Skip to content

Commit b88b7c9

Browse files
committed
Merge branch 'MMP-Dev' into copilot/merge-mmp-dev-into-master
2 parents cbe4bd8 + b75be32 commit b88b7c9

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

61 files changed

+10281
-493
lines changed

.config/dotnet-tools.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"isRoot": true,
44
"tools": {
55
"cake.tool": {
6-
"version": "5.0.0",
6+
"version": "5.1.0",
77
"commands": [
88
"dotnet-cake"
99
],

.github/dependabot.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,17 @@ updates:
55
directories:
66
- /
77
schedule:
8-
interval: weekly
8+
interval: monthly
99
labels: []
1010
- package-ecosystem: github-actions
1111
directories:
1212
- /
1313
schedule:
14-
interval: weekly
14+
interval: monthly
1515
labels: []
1616
- package-ecosystem: nuget
1717
directories:
1818
- /
1919
schedule:
20-
interval: weekly
20+
interval: monthly
2121
labels: []

.github/workflows/build.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ jobs:
1111
uses: actions/checkout@v5
1212

1313
- name: Setup .NET
14-
uses: actions/setup-dotnet@v4
14+
uses: actions/setup-dotnet@v5
1515
with:
16-
dotnet-version: '9.0.x'
16+
dotnet-version: '10.0.x'
1717

1818
- name: Build Rusty Hearts Toolkit
1919
run: dotnet publish ./RHToolkit.sln --configuration Release --self-contained false --runtime win-x64 /p:PublishSingleFile=false /p:PublishDir=./publish/RHToolkit
@@ -22,7 +22,7 @@ jobs:
2222
run: Compress-Archive -Path "./publish/RHToolkit" -DestinationPath RustyHearts-Toolkit.zip
2323

2424
- name: Upload Artifact
25-
uses: actions/upload-artifact@v4
25+
uses: actions/upload-artifact@v5
2626
with:
2727
name: Rusty Hearts Toolkit
2828
path: RustyHearts-Toolkit.zip

.github/workflows/release.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@ jobs:
1515
lfs: true
1616

1717
- name: Setup .NET
18-
uses: actions/setup-dotnet@v4
18+
uses: actions/setup-dotnet@v5
1919
with:
20-
dotnet-version: '9.0.x'
20+
dotnet-version: '10.0.x'
2121

2222
# Build with --self-contained false
2323
- name: Build Rusty Hearts Toolkit (No Self-Contained)

App.xaml.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using RHToolkit.Models.Database;
33
using RHToolkit.Models.Localization;
44
using RHToolkit.Models.MessageBox;
5+
using RHToolkit.Models.Model3D;
56
using RHToolkit.Models.SQLite;
67
using RHToolkit.Services;
78
using RHToolkit.Services.Contracts;
@@ -65,6 +66,8 @@ public partial class App : Application
6566
_ = services.AddSingleton<CouponViewModel>();
6667
_ = services.AddSingleton<PCKToolPage>();
6768
_ = services.AddSingleton<PCKToolViewModel>();
69+
_ = services.AddSingleton<ModelToolsPage>();
70+
_ = services.AddSingleton<ModelToolsViewModel>();
6871
// All other services and viewmodels
6972
_ = services.AddSingleton<ISqlDatabaseService, SqlDatabaseService>();
7073
_ = services.AddSingleton<IDatabaseService, DatabaseService>();
@@ -102,6 +105,8 @@ public partial class App : Application
102105
_ = services.AddTransient<RHEditorViewModel>();
103106
_ = services.AddTransient<WDataEditorWindow>();
104107
_ = services.AddTransient<WDataEditorViewModel>();
108+
_ = services.AddTransient<ModelViewWindow>();
109+
_ = services.AddTransient<ModelViewManager>();
105110
_ = services.AddTransient<CashShopEditorWindow>();
106111
_ = services.AddTransient<CashShopEditorViewModel>();
107112
_ = services.AddTransient<SetItemEditorWindow>();

Models/BinaryReaderExtensions.cs

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
using System.Diagnostics;
2+
using System.Numerics;
3+
4+
namespace RHToolkit.Models;
5+
6+
public class BinaryReaderExtensions
7+
{
8+
#region Strings
9+
10+
/// <summary>
11+
/// Read a 16-bit length-prefixed Unicode string (UTF-16LE) from the binary stream.
12+
/// </summary>
13+
/// <param name="br"></param>
14+
/// <param name="emptyIfDot"></param>
15+
/// <returns> Returns the string read from the stream, or an empty string if the read value is ".\".</returns>
16+
public static string ReadRHString(BinaryReader br, bool emptyIfDot = false)
17+
{
18+
int charCount = br.ReadUInt16();
19+
if (charCount == 0) return string.Empty;
20+
21+
var bytes = br.ReadBytes(charCount * 2);
22+
var s = Encoding.Unicode.GetString(bytes).TrimEnd('\0');
23+
24+
return emptyIfDot && s == ".\\" ? string.Empty : s;
25+
}
26+
27+
/// <summary>Reads a UTF-16LE string of the specified character count.</summary>
28+
public static string ReadUtf16String(BinaryReader br, int charCount)
29+
=> charCount <= 0 ? string.Empty : Encoding.Unicode.GetString(br.ReadBytes(charCount * 2));
30+
31+
/// <summary> Reads a fixed-length ASCII string, trimming any trailing nulls.
32+
public static string ReadAsciiFixed(BinaryReader br, int length)
33+
{
34+
var bytes = br.ReadBytes(length);
35+
// Trim any trailing zeros; keep ASCII
36+
int end = Array.FindLastIndex(bytes, b => b != 0) + 1;
37+
if (end <= 0) return string.Empty;
38+
return Encoding.ASCII.GetString(bytes, 0, end);
39+
}
40+
41+
/// <summary>
42+
/// Reads a null-terminated ASCII string from the given byte array.
43+
/// </summary>
44+
/// <param name="bytes"></param>
45+
/// <returns>The read string, or the entire byte array as a string if no null terminator is found. </returns>
46+
public static string ReadAsciiZ(byte[] bytes)
47+
{
48+
int len = Array.IndexOf<byte>(bytes, 0);
49+
return Encoding.ASCII.GetString(bytes, 0, len >= 0 ? len : bytes.Length);
50+
}
51+
52+
public static string ReadUnicode256Count(BinaryReader br, int size = 512)
53+
{
54+
byte[] data = br.ReadBytes(size);
55+
int len = 0;
56+
while (len + 1 < data.Length)
57+
{
58+
if (data[len] == 0 && data[len + 1] == 0) break;
59+
len += 2;
60+
}
61+
return Encoding.Unicode.GetString(data, 0, len);
62+
}
63+
64+
public static string? ReadUtf16ZFromBuffer(byte[] buffer)
65+
{
66+
for (int i = 0; i + 1 < buffer.Length; i += 2)
67+
{
68+
if (buffer[i] == 0 && buffer[i + 1] == 0)
69+
return Encoding.Unicode.GetString(buffer, 0, i);
70+
}
71+
return string.Empty;
72+
}
73+
#endregion
74+
75+
#region Vectors
76+
public static Vector3 ReadVector3(BinaryReader reader)
77+
{
78+
Vector3 v = new()
79+
{
80+
X = reader.ReadSingle(),
81+
Y = reader.ReadSingle(),
82+
Z = reader.ReadSingle()
83+
};
84+
return v;
85+
}
86+
87+
public static Vector4 ReadVector4(BinaryReader reader)
88+
{
89+
Vector4 v = new()
90+
{
91+
X = reader.ReadSingle(),
92+
Y = reader.ReadSingle(),
93+
Z = reader.ReadSingle(),
94+
W = reader.ReadSingle()
95+
};
96+
return v;
97+
}
98+
99+
public static Quaternion ReadQuaternion(BinaryReader reader)
100+
{
101+
Quaternion q = new()
102+
{
103+
X = reader.ReadSingle(),
104+
Y = reader.ReadSingle(),
105+
Z = reader.ReadSingle(),
106+
W = reader.ReadSingle()
107+
};
108+
return q;
109+
}
110+
#endregion
111+
112+
#region Matrices
113+
/// <summary>
114+
/// Reads 16 floats and returns them as a System.Numerics.Matrix4x4.
115+
/// Assumes the 16 floats in the stream are in row-major order:
116+
/// m11, m12, m13, m14,
117+
/// m21, m22, m23, m24,
118+
/// m31, m32, m33, m34,
119+
/// m41, m42, m43, m44
120+
/// If file uses column-major (OpenGL style), use ReadMatrix4x4(columnMajor: true).
121+
/// </summary>
122+
public static Matrix4x4 ReadMatrix4x4(BinaryReader br, bool columnMajor = false)
123+
{
124+
ArgumentNullException.ThrowIfNull(br);
125+
126+
// Read 16 floats in natural stream order
127+
float f0 = br.ReadSingle();
128+
float f1 = br.ReadSingle();
129+
float f2 = br.ReadSingle();
130+
float f3 = br.ReadSingle();
131+
float f4 = br.ReadSingle();
132+
float f5 = br.ReadSingle();
133+
float f6 = br.ReadSingle();
134+
float f7 = br.ReadSingle();
135+
float f8 = br.ReadSingle();
136+
float f9 = br.ReadSingle();
137+
float f10 = br.ReadSingle();
138+
float f11 = br.ReadSingle();
139+
float f12 = br.ReadSingle();
140+
float f13 = br.ReadSingle();
141+
float f14 = br.ReadSingle();
142+
float f15 = br.ReadSingle();
143+
144+
if (!columnMajor)
145+
{
146+
// Treat the sequence as row-major:
147+
// [ f0 f1 f2 f3 ]
148+
// [ f4 f5 f6 f7 ]
149+
// [ f8 f9 f10 f11]
150+
// [ f12 f13 f14 f15]
151+
return new Matrix4x4(
152+
f0, f1, f2, f3,
153+
f4, f5, f6, f7,
154+
f8, f9, f10, f11,
155+
f12, f13, f14, f15
156+
);
157+
}
158+
else
159+
{
160+
// Treat the sequence as column-major (typical for GL),
161+
// convert into Matrix4x4 which stores elements by named fields M11..M44
162+
// column-major stream order:
163+
// [ f0 f4 f8 f12 ]
164+
// [ f1 f5 f9 f13 ]
165+
// [ f2 f6 f10 f14 ]
166+
// [ f3 f7 f11 f15 ]
167+
return new Matrix4x4(
168+
f0, f4, f8, f12,
169+
f1, f5, f9, f13,
170+
f2, f6, f10, f14,
171+
f3, f7, f11, f15
172+
);
173+
}
174+
}
175+
176+
#endregion
177+
178+
#region Debugging
179+
public static int ReadSmartInt32(BinaryReader br)
180+
{
181+
long pos = br.BaseStream.Position;
182+
byte[] bytes = br.ReadBytes(4);
183+
184+
int asInt = BitConverter.ToInt32(bytes, 0);
185+
float asFloat = BitConverter.ToSingle(bytes, 0);
186+
187+
if (float.IsNaN(asFloat) || float.IsInfinity(asFloat))
188+
return asInt;
189+
190+
if (asInt == 0x00000000)
191+
return asInt;
192+
193+
if (!(asFloat == float.MinValue) && !(asFloat == float.MaxValue))
194+
{
195+
float abs = Math.Abs(asFloat);
196+
if (abs < float.MinValue || abs > float.MaxValue)
197+
return asInt;
198+
}
199+
200+
Debug.Write($"[{pos - 4:X8}] float? 0x{asInt:X8} = {asFloat}");
201+
202+
return asInt;
203+
}
204+
205+
#endregion
206+
}

Models/BinaryWriterExtensions.cs

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
using System.Numerics;
2+
3+
namespace RHToolkit.Models;
4+
5+
public class BinaryWriterExtensions
6+
{
7+
#region Vectors and Matrices
8+
/// <summary>
9+
/// Write a 4x4 matrix in row-major order
10+
/// </summary>
11+
/// <param name="bw"></param>
12+
/// <param name="m"></param>
13+
public static void WriteMatrix(BinaryWriter bw, Matrix4x4 m)
14+
{
15+
bw.Write(m.M11); bw.Write(m.M12); bw.Write(m.M13); bw.Write(m.M14);
16+
bw.Write(m.M21); bw.Write(m.M22); bw.Write(m.M23); bw.Write(m.M24);
17+
bw.Write(m.M31); bw.Write(m.M32); bw.Write(m.M33); bw.Write(m.M34);
18+
bw.Write(m.M41); bw.Write(m.M42); bw.Write(m.M43); bw.Write(m.M44);
19+
}
20+
21+
/// <summary> Write a Vector2 </summary>
22+
public static void WriteVector2(BinaryWriter bw, Vector2 v)
23+
{
24+
bw.Write(v.X);
25+
bw.Write(v.Y);
26+
}
27+
28+
/// <summary> Write a Vector3 </summary>
29+
public static void WriteVector3(BinaryWriter bw, Vector3 v)
30+
{
31+
bw.Write(v.X);
32+
bw.Write(v.Y);
33+
bw.Write(v.Z);
34+
}
35+
36+
/// <summary> Write a Vector4 </summary>
37+
public static void WriteVector4(BinaryWriter bw, Vector4 v)
38+
{
39+
bw.Write(v.X);
40+
bw.Write(v.Y);
41+
bw.Write(v.Z);
42+
bw.Write(v.W);
43+
}
44+
45+
/// <summary> Write a Quaternion </summary>
46+
public static void WriteQuaternion(BinaryWriter bw, Quaternion q)
47+
{
48+
bw.Write(q.X);
49+
bw.Write(q.Y);
50+
bw.Write(q.Z);
51+
bw.Write(q.W);
52+
}
53+
#endregion
54+
55+
#region Strings
56+
/// <summary> Write a UTF-16LE string with length prefix characters.</summary>
57+
public static void WriteUtf16String(BinaryWriter bw, string s)
58+
{
59+
if (s.Length == 0) return;
60+
bw.Write(Encoding.Unicode.GetBytes(s));
61+
}
62+
63+
/// <summary> Write a fixed-length ASCII string, padding with zeros or truncating as needed.</summary>
64+
public static void WriteAsciiFixed(BinaryWriter bw, string s, int width)
65+
{
66+
var bytes = Encoding.ASCII.GetBytes(s ?? string.Empty);
67+
if (bytes.Length >= width)
68+
{
69+
bw.Write(bytes, 0, width);
70+
return;
71+
}
72+
73+
bw.Write(bytes);
74+
// pad with zeros
75+
Span<byte> pad = stackalloc byte[Math.Min(1024, width - bytes.Length)];
76+
pad.Clear();
77+
int remain = width - bytes.Length;
78+
while (remain > 0)
79+
{
80+
int chunk = Math.Min(pad.Length, remain);
81+
bw.Write(pad.Slice(0, chunk));
82+
remain -= chunk;
83+
}
84+
}
85+
#endregion
86+
}

0 commit comments

Comments
 (0)