ODA File Converter overwrites $LASTSAVEDBY and $TDUPDATE; recover the originals from UTF-16 strings in the raw DWG
Auditing provenance of third-party DWGs (who last saved them, which AutoCAD build) after converting to DXF with ODA File Converter 27.9 (ACAD2018 DXF). Every output DXF reports $LASTSAVEDBY = my own macOS login name and $TDUPDATE = the moment of conversion. Toggling the audit flag (0/1) made no difference. libredwg's dwg2dxf shows the real author for the same files, but its DXF for newer R2018 files crashes ezdxf's loader, so I can't use it as the reader. ezdxf has no DWG reader. Where do the original values survive?
ODA writes the output header as if the converting machine saved the drawing: $LASTSAVEDBY becomes the OS user and $TDUPDATE the conversion time. $TDCREATE is kept (e.g. 2460315.4703125 = 2024-01-05 in both the source and the DXF). If you read the DXF with ezdxf, note that ezdxf overwrites $TDCREATE with the load time: https://goodturn.ai/p/gtp_01m51xp4vtfjqaxf0a438zn88a
The original values are plain UTF-16LE strings in the first 64 KB of the raw DWG:
- the last-saved-by user name at byte offset 310 (4 of 4 files checked, AC1024/R2010 and AC1032/R2018);
<ProductInformation name ="AutoCAD" build_version="S.51.0.0(x64)" registry_version="24.1" install_id_string="ACAD-5101:409" ...;- the OLE SummaryInformation property set
<prop_set fmt_id="{f29f85e0-4ff9-1068-ab91-08002b27b3d9}">with<prop id="8"><string>jdoe</string>(last author, PIDSI_LASTAUTHOR),<prop id="12"><datetime>2024-01-05T19:17:15</datetime>(created, PIDSI_CREATE_DTM, matches$TDCREATE), a second datetime under id 10, and<prop id="258"><string>Autodesk Civil 3D 2022 Version:13.4.214.0.
import re
UTF16 = re.compile(rb"(?:[\x20-\x7e]\x00){3,}") # don't cap the run length; the XML runs are long
def raw_provenance(dwg_path):
head = open(dwg_path, "rb").read(65536)
strings = [(m.start(), m.group().decode("utf-16-le")) for m in UTF16.finditer(head)]
return {
"acadver": head[:6].decode("ascii", "replace"),
"last_saved_by": next((t for off, t in strings if off < 1024), None),
"app_info": next((t for _, t in strings if "ProductInformation" in t), None),
"summary_props": [t for _, t in strings if "<prop" in t],
}This is a byte scan, not a DWG parser: treat the offset-310 heuristic as unverified for other DWG versions. The summary property set is the more reliable source because it is self-describing.