Skip to content

Commit e9fe648

Browse files
committed
Move snap profile into core types, expose it in interop
- When a mod has it, the values are now loaded and passed through to interop - Not yet used by native code but will be soon to inform tangent space handling - Add some additional checks for interop structure sizes on init - native code now passes the size of (some of) its structs, and managed code will now fail if the mod struct size isn't what it expects
1 parent 159e8e8 commit e9fe648

File tree

14 files changed

+302
-85
lines changed

14 files changed

+302
-85
lines changed

MMLaunch/ModUtil.fs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ module ModUtil =
200200
Ref: string
201201
ModType: string // subtype of the mod (gpureplacement, etc)
202202
MeshPath: string
203-
Profile: ModelMod.SnapshotProfile.Profile
203+
Profile: ModelMod.CoreTypes.SnapProfile
204204
}
205205

206206
let getOutputPath modRoot modName = Path.GetFullPath(Path.Combine(modRoot, modName))

MMManaged/CoreTypes.fs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
namespace ModelMod
1818

19+
open System
1920
open SharpDX.Direct3D9
2021

2122
// Shorthand type defs
@@ -218,6 +219,41 @@ module CoreTypes =
218219
ReverseTransform = true
219220
}
220221

222+
/// A snapshot profile controls what types of data transformations
223+
/// (typically vertex position and uv coordinates) that are applied by the snapshotter. These are typically used to
224+
/// position the snapshotted mesh in a location that is convenient for use in a 3D tool (therefore, different tools may
225+
/// need different profiles for the same game). The transforms are automatically reversed on load so that the data is
226+
/// in the correct space for the game.
227+
/// More recently the profile has been extended to specify how certain parts of the mesh data (e.g tangent space vectors)
228+
/// should be interpreted, both during snapshot and mod load.
229+
type SnapProfile() =
230+
let mutable name:string = "";
231+
let mutable posX:ResizeArray<String> = new ResizeArray<string>();
232+
let mutable uvX:ResizeArray<String> = new ResizeArray<string>();
233+
let mutable flipTangent:bool = false;
234+
let mutable vecEncoding:string = "";
235+
236+
static member Create(name,posX,uvX,flipTangent,vecEncoding:string):SnapProfile =
237+
let p = new SnapProfile()
238+
p.Name <- name
239+
p.PosXForm <- posX
240+
p.UVXForm <- uvX
241+
p.FlipTang <- flipTangent
242+
p.VecEncoding <- vecEncoding
243+
p
244+
245+
member x.Name with get() = name and set v = name <- v
246+
member x.PosXForm with get() = posX and set v = posX <- v
247+
member x.UVXForm with get() = uvX and set v = uvX <- v
248+
member x.FlipTang with get() = flipTangent and set v = flipTangent <- v
249+
member x.VecEncoding with get() = vecEncoding and set v = vecEncoding <- v
250+
251+
member x.IsPackedVec() = vecEncoding.Trim().ToLowerInvariant() = "packed"
252+
member x.IsOctaVec() = vecEncoding.Trim().ToLowerInvariant() = "octa"
253+
254+
override x.ToString() =
255+
sprintf "[SnapshotProfile: %s; pos: %A; uv: %A, fliptangent: %A, vecencoding: %A]" name posX uvX flipTangent vecEncoding
256+
221257
/// Basic storage for everything that we consider to be "mesh data". This is intentionally pretty close to the
222258
/// renderer level; i.e. we don't have fields like "NormalMap" because the texture stage used for will vary
223259
/// across games or even within the same game. Generally if you want to customize a texture its up to you to make
@@ -303,6 +339,8 @@ module CoreTypes =
303339
/// Setting this to false disables the update (and thus the fixed coordinate axis will be used). Setting this to true always
304340
/// regenerates even if tangent space is globally disabled in the game profile. When left unspecified the global default is used.
305341
UpdateTangentSpace: bool option
342+
/// Snapshot profile; optional since many older mods will not have this.
343+
Profile: SnapProfile option
306344
}
307345

308346
/// Union Parent type for the yaml objects.

MMManaged/Interop.fs

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,8 @@ type InteropInitStruct =
5656
/// loading the assembly.
5757
type Main() =
5858
/// The native code version that this managed code is compatible with. This should be bumped each
59-
/// time the interop interface (e.g struct layouts) change.
60-
static let NativeCodeVersion = 3
59+
/// time the interop interface (e.g struct layouts) change. (also see NATIVE_CODE_VERSION in rust code)
60+
static let NativeCodeVersion = 4
6161

6262
static let mutable oninitialized: ((MMNative.ManagedCallbacks * uint64) -> int) option = None
6363
static let mutable log:Logging.ILog option = None
@@ -179,6 +179,8 @@ type Main() =
179179

180180
static member Main(args:string) =
181181
let mutable ret = InteropTypes.Assplosion
182+
let mutable loginit = false
183+
182184
try
183185
// args are | delimited, first arg is nativeGlobalState handle (opaque to managed code)
184186
// second is load context (i.e is the native code in d3d9.dll or mm_native.dll)
@@ -203,17 +205,40 @@ type Main() =
203205
ret <- Main.IdentifyInLog()
204206
if ret <> 0 then
205207
failwithf "Log init failed: %A" ret
208+
loginit <- true
209+
210+
let checkSizeMatch (argstr:string) (paramstr:string) (mtype:System.Type) =
211+
if argstr.StartsWith(paramstr) then
212+
213+
let arg = argstr.Split([|paramstr|], StringSplitOptions.RemoveEmptyEntries)
214+
let size = arg.[0] |> int
215+
let esize = Marshal.SizeOf(mtype)
216+
if size <> esize then
217+
failwithf "Managed struct size of %A does not match matches native code expected: %A, got: %A" argstr esize size
218+
else
219+
Main.Log.Info " Native/Managed: struct size ok: %A" argstr
220+
221+
if args.Length > 2 then
222+
let args = args |> Array.skip 3
223+
for arg in args do
224+
let arg = arg.ToLowerInvariant()
225+
checkSizeMatch arg ("mod_structsize=") (typeof<InteropTypes.ModData>)
226+
checkSizeMatch arg ("mod_snapprofile_structsize=") (typeof<InteropTypes.ModSnapProfile>)
206227

207228
if not versionChecked then
208-
Main.Log.Warn "Native code did not pass a version, a crash is possible."
229+
Main.Log.Warn "Native code did not provide its version, a crash is possible."
209230

210231
ret <- Main.InitCallbacks(nativeGlobalState,context)
211232
ret
212233
with
213234
| e ->
214-
// print it, but it will likely go nowhere
215-
printfn "An exception occured."
216-
printfn "Exception: %A" e
235+
if loginit then
236+
Main.Log.Error "%A" e
237+
else
238+
// print it, but it will likely go nowhere
239+
printfn "An exception occured."
240+
printfn "Exception: %A" e
241+
217242
if ret = 0 then
218243
InteropTypes.Assplosion
219244
else

MMManaged/InteropTypes.fs

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,69 @@ module InteropTypes =
4949
val mutable Data:System.IntPtr
5050
val mutable Size:int32
5151

52+
let MaxModSnapProfileXFormLen = 8
53+
54+
[<Struct; StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)>]
55+
type ModSnapProfileXFormString = {
56+
[<MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)>]
57+
Text : string
58+
}
59+
60+
let makeXFormString (s:string) =
61+
let maxChars = 255
62+
let trimmed =
63+
if System.String.IsNullOrEmpty s then ""
64+
elif s.Length > maxChars then s.Substring(0, maxChars)
65+
else s
66+
{ Text = trimmed }
67+
68+
/// The profile that was used to snapshot the mod. Manage code will typically reverse Pos and UV transforms during mod load,
69+
/// but unmanaged code can handle other details such as generating tangent space or packing vectors in correct format.
70+
/// Not all mods will have this, so unmanaged code should check the "Valid" field to see if it is populated.
71+
[<Struct; StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)>]
72+
type ModSnapProfile = {
73+
/// Whether the fields have been set on this profile, unmanaged code uses this to determine
74+
/// whether to use it or not (essentially makes it a implicit "option" type)
75+
[<MarshalAs(UnmanagedType.U1)>]
76+
Valid: bool
77+
78+
/// Name of this profile
79+
[<MarshalAs(UnmanagedType.ByValTStr, SizeConst=256)>]
80+
Name: string
81+
82+
/// Number of position transforms
83+
PosXLength: int
84+
[<MarshalAs(UnmanagedType.ByValArray, SizeConst=8)>]
85+
/// Position transforms
86+
PosX: ModSnapProfileXFormString[]
87+
88+
/// Number of UV transforms
89+
UVXLength: int
90+
[<MarshalAs(UnmanagedType.ByValArray, SizeConst=8)>]
91+
/// UV transforms
92+
UVX: ModSnapProfileXFormString[]
93+
94+
/// Whether to flip tangents
95+
[<MarshalAs(UnmanagedType.U1)>]
96+
FlipTangent: bool
97+
98+
/// How vectors should be encoded in d3d data
99+
[<MarshalAs(UnmanagedType.ByValTStr, SizeConst=256)>]
100+
VecEncoding: string
101+
}
102+
103+
// Example empty value for InteropProfile
104+
let EmptyModSnapProfile = {
105+
Valid = false
106+
Name = ""
107+
PosXLength = 0
108+
PosX = Array.create MaxModSnapProfileXFormLen (makeXFormString "")
109+
UVXLength = 0
110+
UVX = Array.create MaxModSnapProfileXFormLen (makeXFormString "")
111+
FlipTangent = false
112+
VecEncoding = ""
113+
}
114+
52115
/// Various mod metadata. Derived from Mesh, DBReference, and DBMod types.
53116
[<StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)>]
54117
type ModData = {
@@ -79,6 +142,7 @@ module InteropTypes =
79142
ParentModName: string
80143
[<MarshalAs(UnmanagedType.ByValTStr, SizeConst=8192)>]
81144
PixelShaderPath: string
145+
SnapProfile: ModSnapProfile
82146
}
83147

84148
/// Default value. Also used as an error return value, since we don't throw exceptions accross interop.
@@ -101,6 +165,7 @@ module InteropTypes =
101165
ParentModName = ""
102166
PixelShaderPath = ""
103167
UpdateTangentSpace = -1
168+
SnapProfile = EmptyModSnapProfile
104169
}
105170

106171
[<StructLayout(LayoutKind.Sequential, Pack=4)>]

MMManaged/MMManaged.fsproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,9 +72,9 @@
7272
<Compile Include="StartConf.fs" />
7373
<Compile Include="MeshRelation.fs" />
7474
<Compile Include="MemoryCache.fs" />
75+
<Compile Include="SnapshotProfile.fs" />
7576
<Compile Include="ModDB.fs" />
7677
<Compile Include="DataEncoding.fs" />
77-
<Compile Include="SnapshotProfile.fs" />
7878
<Compile Include="State.fs" />
7979
<Compile Include="ModDBInterop.fs" />
8080
<Compile Include="Snapshot.fs" />

MMManaged/ModDB.fs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ open StartConf
3131
open CoreTypes
3232
open InteropTypes
3333
open VertexTypes
34+
open SnapshotProfile
3435

3536
/// Contains the "Mod Database"; functions for reading yaml, mmobj, and other files and storing them in memory.
3637
module ModDB =
@@ -227,6 +228,12 @@ module ModDB =
227228

228229
let computeTS = node |> Yaml.getOptionalValue "UpdateTangentSpace" |> Yaml.toOptionalBool
229230

231+
// has a profile?
232+
let profile =
233+
match node |> Yaml.getOptionalValue "Profile" |> Yaml.toOptionalMapping with
234+
| Some(profile) -> Some(loadSingleProfile "" profile)
235+
| None -> None
236+
230237
let md = {
231238
DBMod.RefName = refName
232239
Ref = None // defer ref resolution until all files have been loaded - avoids forward ref problems
@@ -237,6 +244,7 @@ module ModDB =
237244
PixelShader = pixelShader
238245
ParentModName = parentModName
239246
UpdateTangentSpace = computeTS
247+
Profile = profile
240248
}
241249

242250
let numOverrideTextures =
@@ -246,7 +254,7 @@ module ModDB =
246254
let oneIfNotEmpty (s:string) = if s.Trim() <> "" then 1 else 0
247255
oneIfNotEmpty mesh.Tex0Path + oneIfNotEmpty mesh.Tex1Path + oneIfNotEmpty mesh.Tex2Path + oneIfNotEmpty mesh.Tex3Path
248256

249-
log().Info "Mod: %A: type: %A, ref: %A, weightmode: %A, override textures: %d" modName modType refName weightMode numOverrideTextures
257+
log().Info "Mod: %A: type: %A, ref: %A, weightmode: %A, override textures: %d: profile: %A" modName modType refName weightMode numOverrideTextures profile
250258
Mod(md)
251259

252260
/// Read an SDX vertex element from the specified stream.

MMManaged/ModDBInterop.fs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,11 @@ module ModDBInterop =
252252
| None -> -1
253253
| Some(upd) -> if upd then 1 else 0
254254

255+
let profile =
256+
match meshrel.DBMod.Profile with
257+
| None -> EmptyModSnapProfile
258+
| Some(p) -> SnapshotProfile.toInteropStruct p
259+
255260
{
256261
InteropTypes.ModData.ModType = modType
257262
PrimType = primType
@@ -271,6 +276,7 @@ module ModDBInterop =
271276
ModName = mname
272277
ParentModName = parentModName
273278
UpdateTangentSpace = updateTS
279+
SnapProfile = profile
274280
}
275281

276282
/// Get the mod data at the specified index. If index is out of range, returns InteropTypes.EmptyModData.

MMManaged/Snapshot.fs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ module Snapshot =
119119
}
120120

121121
type SnapMeta() =
122-
let mutable profile:SnapshotProfile.Profile = SnapshotProfile.EmptyProfile
122+
let mutable profile:CoreTypes.SnapProfile = SnapshotProfile.EmptyProfile
123123
let mutable context:string = ""
124124

125125
static member Create(profile):SnapMeta =
@@ -133,7 +133,7 @@ module Snapshot =
133133

134134
/// Reads a vertex element. Uses the read output functions to pipe the data to an appropriate handler
135135
/// function, depending on the type.
136-
let private readElement (snapProfile:SnapshotProfile.Profile) (fns:ReadOutputFunctions) (ignoreFns:ReadOutputFunctions) reader (el:VertexTypes.MMVertexElement) =
136+
let private readElement (snapProfile:CoreTypes.SnapProfile) (fns:ReadOutputFunctions) (ignoreFns:ReadOutputFunctions) reader (el:VertexTypes.MMVertexElement) =
137137
let fns =
138138
if el.SemanticIndex = 0 then
139139
fns

0 commit comments

Comments
 (0)