1
+ # MAT_subsys.jl
2
+ # Tools for processing MAT-file subsystem data in Julia
3
+ #
4
+ # Copyright (C) 2025 Nithin Lakshmisha
5
+ #
6
+ # Permission is hereby granted, free of charge, to any person obtaining
7
+ # a copy of this software and associated documentation files (the
8
+ # "Software"), to deal in the Software without restriction, including
9
+ # without limitation the rights to use, copy, modify, merge, publish,
10
+ # distribute, sublicense, and/or sell copies of the Software, and to
11
+ # permit persons to whom the Software is furnished to do so, subject to
12
+ # the following conditions:
13
+ #
14
+ # The above copyright notice and this permission notice shall be
15
+ # included in all copies or substantial portions of the Software.
16
+ #
17
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
21
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
22
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24
+
1
25
module MAT_subsys
2
26
3
27
const FWRAP_VERSION = 4
@@ -39,27 +63,34 @@ mutable struct Subsys
39
63
end
40
64
41
65
const subsys_cache = Ref {Union{Nothing,Subsys}} (nothing )
66
+ const object_cache = Ref{Union{Nothing, Dict{UInt32, Dict{String,Any}}}}(nothing )
42
67
43
68
function clear_subsys! ()
44
69
subsys_cache[] = nothing
70
+ object_cache[] = nothing
45
71
end
46
72
47
73
function load_subsys! (subsystem_data:: Dict{String,Any} , swap_bytes:: Bool )
48
74
subsys_cache[] = Subsys ()
75
+ object_cache[] = Dict {UInt32, Dict{String,Any}} ()
49
76
subsys_cache[]. handle_data = get (subsystem_data, " handle" , nothing )
50
77
subsys_cache[]. java_data = get (subsystem_data, " java" , nothing )
51
78
mcos_data = get (subsystem_data, " MCOS" , nothing )
52
79
if mcos_data === nothing
53
80
return
54
81
end
55
82
56
- fwrap_metadata = vec (mcos_data[2 ][1 , 1 ])
83
+ if mcos_data isa Tuple
84
+ # Backward compatibility with MAT_v5
85
+ mcos_data = mcos_data[2 ]
86
+ end
87
+ fwrap_metadata = vec (mcos_data[1 , 1 ])
57
88
58
89
# FIXME : Is this the best way to read?
59
90
# Integers are written as uint8 (with swap), interpret as uint32
60
91
version = reinterpret (UInt32, swap_bytes ? reverse (fwrap_metadata[1 : 4 ]) : fwrap_metadata[1 : 4 ])[1 ]
61
- if version > FWRAP_VERSION
62
- error (" Unsupported FileWrapper version: $version " )
92
+ if version <= 1 || version > FWRAP_VERSION
93
+ error (" Cannot read subsystem: Unsupported FileWrapper version: $version " )
63
94
end
64
95
65
96
subsys_cache[]. num_names = reinterpret (UInt32, swap_bytes ? reverse (fwrap_metadata[5 : 8 ]) : fwrap_metadata[5 : 8 ])[1 ]
@@ -86,18 +117,26 @@ function load_subsys!(subsystem_data::Dict{String,Any}, swap_bytes::Bool)
86
117
subsys_cache[]. object_id_metadata = reinterpret (UInt32, swap_bytes ? reverse (fwrap_metadata[region_offsets[3 ]+ 1 : region_offsets[4 ]]) : fwrap_metadata[region_offsets[3 ]+ 1 : region_offsets[4 ]])
87
118
subsys_cache[]. obj_prop_metadata = reinterpret (UInt32, swap_bytes ? reverse (fwrap_metadata[region_offsets[4 ]+ 1 : region_offsets[5 ]]) : fwrap_metadata[region_offsets[4 ]+ 1 : region_offsets[5 ]])
88
119
subsys_cache[]. dynprop_metadata = reinterpret (UInt32, swap_bytes ? reverse (fwrap_metadata[region_offsets[5 ]+ 1 : region_offsets[6 ]]) : fwrap_metadata[region_offsets[5 ]+ 1 : region_offsets[6 ]])
89
- subsys_cache[]. _u6_metadata = reinterpret (UInt32, swap_bytes ? reverse (fwrap_metadata[region_offsets[6 ]+ 1 : region_offsets[7 ]]) : fwrap_metadata[region_offsets[6 ]+ 1 : region_offsets[7 ]])
90
- subsys_cache[]. _u7_metadata = reinterpret (UInt32, swap_bytes ? reverse (fwrap_metadata[region_offsets[7 ]+ 1 : region_offsets[8 ]]) : fwrap_metadata[region_offsets[7 ]+ 1 : region_offsets[8 ]])
91
120
92
- if version < 4
93
- subsys_cache[]. prop_vals_saved = mcos_data[2 ][3 : end - 2 , 1 ]
121
+ if region_offsets[6 ] != 0
122
+ subsys_cache[]. _u6_metadata = reinterpret (UInt32, swap_bytes ? reverse (fwrap_metadata[region_offsets[6 ]+ 1 : region_offsets[7 ]]) : fwrap_metadata[region_offsets[6 ]+ 1 : region_offsets[7 ]])
123
+ end
124
+
125
+ if region_offsets[7 ] != 0
126
+ subsys_cache[]. _u7_metadata = reinterpret (UInt32, swap_bytes ? reverse (fwrap_metadata[region_offsets[7 ]+ 1 : region_offsets[8 ]]) : fwrap_metadata[region_offsets[7 ]+ 1 : region_offsets[8 ]])
127
+ end
128
+
129
+ if version == 2
130
+ subsys_cache[]. prop_vals_saved = mcos_data[3 : end - 1 , 1 ]
131
+ elseif version == 3
132
+ subsys_cache[]. prop_vals_saved = mcos_data[3 : end - 2 , 1 ]
133
+ subsys_cache[]. _c2 = mcos_data[end - 1 , 1 ]
94
134
else
95
- subsys_cache[]. prop_vals_saved = mcos_data[2 ][ 3 : end - 3 , 1 ]
96
- subsys_cache[]. _c3 = mcos_data[2 ][ end - 2 , 1 ]
135
+ subsys_cache[]. prop_vals_saved = mcos_data[3 : end - 3 , 1 ]
136
+ subsys_cache[]. _c3 = mcos_data[end - 2 , 1 ]
97
137
end
98
138
99
- subsys_cache[]. _c2 = mcos_data[2 ][end - 1 , 1 ]
100
- subsys_cache[]. prop_vals_defaults = mcos_data[2 ][end , 1 ]
139
+ subsys_cache[]. prop_vals_defaults = mcos_data[end , 1 ]
101
140
end
102
141
103
142
function get_classname (class_id:: UInt32 )
@@ -107,10 +146,10 @@ function get_classname(class_id::UInt32)
107
146
namespace = if namespace_idx == 0
108
147
" "
109
148
else
110
- subsys_cache[]. mcos_names[namespace_idx- 1 ] * " ."
149
+ subsys_cache[]. mcos_names[namespace_idx] * " ."
111
150
end
112
151
113
- classname = namespace * subsys_cache[]. mcos_names[classname_idx- 1 ]
152
+ classname = namespace * subsys_cache[]. mcos_names[classname_idx]
114
153
return classname
115
154
end
116
155
@@ -138,6 +177,33 @@ function get_property_idxs(obj_type_id::UInt32, saveobj_ret_type::Bool)
138
177
return prop_field_idxs[offset: offset+ nprops* nfields- 1 ]
139
178
end
140
179
180
+ function find_nested_prop (prop_value:: Any )
181
+ # Hacky way to find a nested object
182
+ # Nested objects are stored as a uint32 Matrix with a unique signature
183
+ # MATLAB probably uses some kind of placeholders to decode
184
+ # But this should work here
185
+ if prop_value isa Dict
186
+ # Handle nested objects in a dictionary (struct)
187
+ for (key, value) in prop_value
188
+ prop_value[key] = find_nested_prop (value)
189
+ end
190
+ end
191
+
192
+ if prop_value isa Matrix{Any}
193
+ # Handle nested objects in a Cell
194
+ for i in eachindex (prop_value)
195
+ prop_value[i] = find_nested_prop (prop_value[i])
196
+ end
197
+ end
198
+
199
+ if prop_value isa Matrix{UInt32} && prop_value[1 ,1 ] == 0xdd000000
200
+ # MATLAB identifies any uint32 array with first value 0xdd000000 as an MCOS object
201
+ return load_mcos_object (prop_value, " MCOS" )
202
+ end
203
+
204
+ return prop_value
205
+ end
206
+
141
207
function get_saved_properties (obj_type_id:: UInt32 , saveobj_ret_type:: Bool )
142
208
save_prop_map = Dict {String,Any} ()
143
209
prop_field_idxs = get_property_idxs (obj_type_id, saveobj_ret_type)
@@ -148,14 +214,13 @@ function get_saved_properties(obj_type_id::UInt32, saveobj_ret_type::Bool)
148
214
if prop_type == 0
149
215
prop_value = subsys_cache[]. mcos_names[prop_field_idxs[i* 3 + 3 ]]
150
216
elseif prop_type == 1
151
- # FIXME : Search for nested objects
152
217
prop_value = subsys_cache[]. prop_vals_saved[prop_field_idxs[i* 3 + 3 ]+ 1 ]
153
218
elseif prop_type == 2
154
219
prop_value = prop_field_idxs[i* 3 + 3 ]
155
220
else
156
221
error (" Unknown property type ID: $prop_type encountered during deserialization" )
157
222
end
158
- save_prop_map[prop_name] = prop_value
223
+ save_prop_map[prop_name] = find_nested_prop ( prop_value)
159
224
end
160
225
return save_prop_map
161
226
end
@@ -181,8 +246,6 @@ function get_properties(object_id::UInt32)
181
246
end
182
247
183
248
function load_mcos_object (metadata:: Any , type_name:: String )
184
- # TODO : Add support for handle class objects
185
-
186
249
if type_name != " MCOS"
187
250
@warn " Loading Type:$type_name is not implemented. Returning metadata."
188
251
return metadata
@@ -213,9 +276,17 @@ function load_mcos_object(metadata::Any, type_name::String)
213
276
classname = get_classname (class_id)
214
277
215
278
object_arr = Array {Dict{String,Any}} (undef, convert (Vector{Int}, dims)... )
279
+
216
280
for i = 1 : length (object_arr)
217
- prop_dict = get_properties (object_ids[i])
218
- prop_dict[" __class__" ] = classname
281
+ oid = object_ids[i]
282
+ if haskey (object_cache[], oid)
283
+ prop_dict = object_cache[][oid]
284
+ else
285
+ prop_dict = Dict {String,Any} ()
286
+ object_cache[][oid] = prop_dict
287
+ merge! (prop_dict, get_properties (oid))
288
+ prop_dict[" __class__" ] = classname
289
+ end
219
290
object_arr[i] = prop_dict
220
291
end
221
292
0 commit comments