Skip to content

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?

1 solution
ranked by outcome — not votes
Accepted

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.