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+
125module MAT_subsys
226
327const FWRAP_VERSION = 4
@@ -39,27 +63,34 @@ mutable struct Subsys
3963end
4064
4165const subsys_cache = Ref {Union{Nothing,Subsys}} (nothing )
66+ const object_cache = Ref{Union{Nothing, Dict{UInt32, Dict{String,Any}}}}(nothing )
4267
4368function clear_subsys! ()
4469 subsys_cache[] = nothing
70+ object_cache[] = nothing
4571end
4672
4773function load_subsys! (subsystem_data:: Dict{String,Any} , swap_bytes:: Bool )
4874 subsys_cache[] = Subsys ()
75+ object_cache[] = Dict {UInt32, Dict{String,Any}} ()
4976 subsys_cache[]. handle_data = get (subsystem_data, " handle" , nothing )
5077 subsys_cache[]. java_data = get (subsystem_data, " java" , nothing )
5178 mcos_data = get (subsystem_data, " MCOS" , nothing )
5279 if mcos_data === nothing
5380 return
5481 end
5582
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 ])
5788
5889 # FIXME : Is this the best way to read?
5990 # Integers are written as uint8 (with swap), interpret as uint32
6091 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 " )
6394 end
6495
6596 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)
86117 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 ]])
87118 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 ]])
88119 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 ]])
91120
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 ]
94134 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 ]
97137 end
98138
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 ]
101140end
102141
103142function get_classname (class_id:: UInt32 )
@@ -107,10 +146,10 @@ function get_classname(class_id::UInt32)
107146 namespace = if namespace_idx == 0
108147 " "
109148 else
110- subsys_cache[]. mcos_names[namespace_idx- 1 ] * " ."
149+ subsys_cache[]. mcos_names[namespace_idx] * " ."
111150 end
112151
113- classname = namespace * subsys_cache[]. mcos_names[classname_idx- 1 ]
152+ classname = namespace * subsys_cache[]. mcos_names[classname_idx]
114153 return classname
115154end
116155
@@ -138,6 +177,33 @@ function get_property_idxs(obj_type_id::UInt32, saveobj_ret_type::Bool)
138177 return prop_field_idxs[offset: offset+ nprops* nfields- 1 ]
139178end
140179
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+
141207function get_saved_properties (obj_type_id:: UInt32 , saveobj_ret_type:: Bool )
142208 save_prop_map = Dict {String,Any} ()
143209 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)
148214 if prop_type == 0
149215 prop_value = subsys_cache[]. mcos_names[prop_field_idxs[i* 3 + 3 ]]
150216 elseif prop_type == 1
151- # FIXME : Search for nested objects
152217 prop_value = subsys_cache[]. prop_vals_saved[prop_field_idxs[i* 3 + 3 ]+ 1 ]
153218 elseif prop_type == 2
154219 prop_value = prop_field_idxs[i* 3 + 3 ]
155220 else
156221 error (" Unknown property type ID: $prop_type encountered during deserialization" )
157222 end
158- save_prop_map[prop_name] = prop_value
223+ save_prop_map[prop_name] = find_nested_prop ( prop_value)
159224 end
160225 return save_prop_map
161226end
@@ -181,8 +246,6 @@ function get_properties(object_id::UInt32)
181246end
182247
183248function load_mcos_object (metadata:: Any , type_name:: String )
184- # TODO : Add support for handle class objects
185-
186249 if type_name != " MCOS"
187250 @warn " Loading Type:$type_name is not implemented. Returning metadata."
188251 return metadata
@@ -213,9 +276,17 @@ function load_mcos_object(metadata::Any, type_name::String)
213276 classname = get_classname (class_id)
214277
215278 object_arr = Array {Dict{String,Any}} (undef, convert (Vector{Int}, dims)... )
279+
216280 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
219290 object_arr[i] = prop_dict
220291 end
221292
0 commit comments