目录

0  总体结构

1  文件头

2  数据

2.1 system section

a) system section page header

b) Section Map page data

c) SectionInfo page data

2.2 Data section


0   总体结构

        从整体上看,一个dwg文件可分为2部分:文件头和数据,数据部分在物理上分页(page)存储的,在逻辑上由一个或多个页组成一个段(section),如图所示:

dwg 文件解析 java dwg文件结构_c++

1  文件头

        文件头长度为0x100个字节,布局如下:

地址

长度(byte)

描述

0x00

7

"AC1018\0"

0x07

4

0x00

0x0B

1

主版本号

0x0C

1

0x00或0x01或0x03

0x0D

4

预览图片的地址

0x11

1

Dwg version

0x12

1

Dwg maintance version

0x13

2

Codepage index

0x15

1

0x00

0x16

1

App version

0x17

1

App maintance version

0x18

4

安全标志

0x0001:加密数据(除了AcDb:Preview and AcDb:SummaryInfo)

0x0002:加密属性(用于AcDb:Preview and AcDb:SummaryInfo)

0x0010:sign data

0x0020:add timestamp

0x1C

4

未知

0x20

4

SummaryInfo地址,points to summary info page + page header size(0x20)

0x24

4

VBA工程地址

0x28

4

0x00000080,(似乎是2004header的地址)

0x2C

0x54

全是0x00

0x80

0x6C

加密的数据,称为2004header

0xEC

0x14

填充数据(据说是魔术字节序列的前0x14字节,未做考证)

开始的7个字节是DWG版本标志串,以AC开头,后跟4个数字表述版本。对应关系为:

标注串

AutoCAD的发行版本

AC1012

R13

AC1014

R14

AC1015

2000 - 2003

AC1018

2004 - 2006

AC1021

2007 - 2009

AC1024

2010 - 2012

AC1027

2013 - 2017

AC1032

2018 - 2020

从AutoCAD2000开始,dwg版本号每3年做一次升级,所以AC标志串中的数字并不是连续的。

 0x80处的0x6C个字节的加密数据,与下列0x6C个魔术字节逐一做XOR运算,即可解密。

29 23 BE 84 E1 6C D6 AE    52 90 49 F1 F1 BB E9 EB

B3 A6 DB 3C 87 0C 3E 99    24 5E 0D 1C 06 B7 47 DE

B3 12 4D C8 43 BB 8B A6    1F 03 5A 7D 09 38 25 1F

5D D4 CB FC 96 F5 45 3B    13 0D 89 0A 1C DB AE 32

20 9A 50 EE 40 78 36 FD    12 49 32 F6 9E 7D 49 DC

AD 4F 14 F2 44 40 66 D0    6B C4 30 B7 32 3B A1 22

F6 22 91 9D E1 8B 1F DA    B0 CA 99 02

这0x6C个魔术字节可以用下面的代码得到

unsigned char pc[0x6c];

int randseed = 1;

for (int i=0; i<0x6c; i++) {

   randseed *= 0x343fd;

   randseed += 0x269ec3;

   pc[i] = (unsigned char)(randseed>>0x10);
}

经过以上运算解密后的数据布局如下表,称之为ACAD2004HeaderData,这段数据在文件尾部还有一份,即SecondHeaderData。

字地址

长度(byte)

描述

0x00

12

"AcFssFcAJMB\0" 标志串

0x0C

4

0x00 (long) header address

0x10

4

0x6c(long) header size

0x14

4

0x04 (long)

0x18

4

Root tree node gap

0x1C

4

Lowermost left tree node gap

0x20

4

Lowermost right tree node gap

0x24

4

未知(long)

0x28

4

Last section id

0x2C

4

Last section address

0x30

4

0x00

0x34

4

Second header address

0x38

4

0x00

0x3C

4

Gap 总数

0x40

4

Section总数

0x44

4

0x20 (long)

0x48

4

0x80(long)

0x4C

4

0x40(long)

0x50

4

Page Map Id

0x54

4

Page Map Address

该值是以数据部分为起点的偏移,在整个文件中的偏移需要加上文件头的大小(0x100)

0x58

4

0x00

0x5C

4

Section map Id

0x60

4

Section page array size

0x64

4

Gap array size

0x68

4

CRC32 (long)

在这些数据当中,对解析最重要的当属标红的3个数据了。

(5.25,补充一下文件头的解析)

解析2004的文件头比较简单,只需要很少的代码。先定义如下的文件头结构:

#pragma pack(push, 1)
typedef struct _tagDwg2004Header
{	// 务必保持成员的顺序!
	char				version[7];			    // 0x00: "AC1018\0"
	char				x07_unknown_0[4];	    // 0x07: 4zeros
	char				is_maint;			    // 0x0B
	char				x0c_0_1_3;			    // 0x0C: 0 or 1 or 3
	uint32_t			thumbnail_addr;		    // 0x0D
	char				dwg_version;		    // 0x11
	char				dwg_maint_version;	    // 0x12
	short				codepage;			    // 0x13
	char				x15_unknown_0;		    // 0x15
	char				app_version;		    // 0x16
	char				app_maint_version;	    // 0x17
	uint32_t			security_type;		    // 0x18
	uint32_t			x1c_unknown_0;		    // 0x1C
	uint32_t			summary_info_address;	// 0x20
	uint32_t			vba_proj_address;		// 0x24
	uint32_t			r2004_header_address;	// 0x28: 0x80
	char				x2c_0[0x54];			// 0x2C: 0
	union
	{
		char			encrypted_data[0x6c];	// 0x80
		struct _r2004
		{
			char		file_id_string[12];		// "AcFssFcAJMB\0"
			uint32_t	header_address;		    // 0x00
			uint32_t	header_size;			// 0x6c
			uint32_t	x14_unknown_0;			// 0
			uint32_t	root_tree_node_gap;
			uint32_t	lowermost_left_tree_node_gap;
			uint32_t	lowermost_right_tree_node_gap;
			uint32_t	x24_uknown;
			uint32_t	last_section_id;
			uint64_t	last_page_address;
			uint64_t	second_header_address;
			uint32_t	gap_amount;
			uint32_t	page_amount;
			uint32_t	x44_x20;
			uint32_t	x48_x80;
			uint32_t	x4c_x40;
			uint32_t	page_map_id;
			uint64_t	page_map_address;	// offset from DATA, not include this HEADER size
			uint32_t	section_map_id;
			uint32_t	section_array_size;
			uint32_t	gap_array_size;
			uint32_t	crc32;
		} r2004;
	};
	char				padding[0x14];
} Dwg2004Header;		// total 0x100 bytes
#pragma pack(pop)

 解析的代码:

{
    // 说明:m_header的类型是上面定义的Dwg2004Header,DWG_DATA是dwg文件的原始数据
    memcpy(&m_header, DWG_DATA, sizeof(Dwg2004Header));
	// decrypt r2004 header
	int rseed = 1;
	for (int i = 0; i < 0x6c; i++)
	{
		rseed *= 0x343fd;
		rseed += 0x269ec3;
		m_header.encrypted_data[i] ^= (rseed >> 0x10);
	}
}

2   数据

dwg的数据是分段(section)组织的,文件中涉及到的数据段有:

/// \enum Section type of R2004+
typedef enum _tagDwg2004SectionType
{
	SECTION_UNKNOWN = 0,                  ///< The very first 160 byte
	SECTION_HEADER = 1,                   ///< AcDb:Header
	SECTION_AUXHEADER = 2,                ///< AcDb:AuxHeader
	SECTION_CLASSES = 3,                  ///< AcDb:Classes
	SECTION_HANDLES = 4,                  ///< AcDb:Handles
	SECTION_TEMPLATE = 5,                 ///< AcDb:Template
	SECTION_OBJFREESPACE = 6,             ///< AcDb:ObjFreeSpace
	SECTION_OBJECTS = 7,                  ///< AcDb:AcDbObjects
	SECTION_REVHISTORY = 8,               ///< AcDb:RevHistory
	SECTION_SUMMARYINFO = 9,              ///< AcDb:SummaryInfo
	SECTION_PREVIEW = 10,                 ///< AcDb:Preview
	SECTION_APPINFO = 11,                 ///< AcDb:AppInfo
	SECTION_APPINFOHISTORY = 12,          ///< AcDb:AppInfoHistory
	SECTION_FILEDEPLIST = 13,             ///< AcDb:FileDepList
	SECTION_SECURITY,                     ///< AcDb:Security, if stored with a password
	SECTION_VBAPROJECT,                   ///< AcDb:VBAProject
	SECTION_SIGNATURE,                    ///< AcDb:Signature
	SECTION_ACDS,                         ///< AcDb:AcDsPrototype_1b = 12 (ACIS datastorage)
	SECTION_SECTIONMAP,                   ///< system section: section map
	SECTION_PAGEMAP,				      ///< system section: page map
} Dwg2004SectionType;

        所有的section中,page map段和section map段,这2个section至关重要,必须先根据ACAD2004HeaderData中标红的三个参数从文件中读取,再根据读到的结果,一一读取不同的数据段予以解析。

        首先从page map address处读取pagen map,然后从此map中取得section map Id对应的段(即section map section)的地址,并从文件中读出它的数据。这两种section统称为system section,其他的section则称为data section。之后,由这两兄弟配合就可以通过section name确定各种section的数据页在文件中的位置,根据描述信息予以读取。

        system section都只有一页,而data section 至少有一页。

2.1 system section

system section的数据是按页(page)组织的。一个page包含2部分,页头和页数据:

项目

长度

header

0x14bytes

data

压缩数据,长度由header.CompDataSize指定

其中header对于两种system section来说,结构是一样的,差别在于data的结构的不同。

a) system section page header

Header的结构:

地址(偏移)

长度(byte

含义

0x00

4

Page 类型,有2种system page:

page map page: 0x41630e3b

section map page: 0x4163003b

0x04

4

解压后的数据长度 (DecompDataSize)

0x08

4

压缩后的数据长度 (CompDataSize)

0x0C

4

压缩类型(0x02)

0x10

4

CRC

当 page.CompDataSize==0时,这个system section就结束了,后面也不会再跟随page data。如下图所示就是system section的组织形式:

dwg 文件解析 java dwg文件结构_sed_02

b) Page Map page data

page map段的页数据经解压缩后,为多个(page number, page size)数据对,其中的page number从1开始计数,该数据对记录的是文件中的各数据页的编号和大小。

地址

长度

描述

0x00

4

page number, 1-based

0x04

4

page size

这种数据对一直重复,直到本页的数据耗尽。在读取时,可以顺便计算出各section在文件中的绝对偏移地址:第一个读到的section的地址总是从文件头后(即0x100处)开始,后续读到的section的偏移可从其前一个section的偏移和大小计算得到。

c) Section Mappage data

从上面的pagemap只能得到已知某个编号的page的位置和大小,无法得到该page的具体信息,包括压缩否,加密否等等,这时就该section map上场了。section map的page.data解压缩后的布局如下,这是一个复合结构:

偏移

长度

含义

0x00

4

Number of descriptions(num_descs)

0x04

4

Compressed?(1=no, 2=yes, normally 2)

0x08

4

Max_size(normally 0x7400)

0x0C

4

Excrypted?(0=no, 1=yes, 2=unknown)

0x10

4

Num descriptions 2

共有num_descs

0x00

8

Size of section

0x08

4

Number of pages(num_pages)

0x0C

4

Max decompress size

0x10

4

未知(会不会是long型的Max decompress size的高位?)

0x14

4

Compressed?(1=no,2=yes,normally 2)

0x18

4

Section type

0x1C

4

Encrypted?(0=no,1=yes,2=unknow)

0x20

64

Section name ("AcDbObjects"等等)

共有

num_pages组

0x00

4

page number

0x04

4

page data size

0x08

8

Start offset

对system section的归纳图示:

dwg 文件解析 java dwg文件结构_sed_03

下面是一个解析实例中,关于AcDbObjects的section map的描述。

Description No.7
	------------------
	size: 0x2b0e9
	num_pages: 0x6
	max_decomp_size: 0x7400
	unknown2: 0x1
	compressed: 0x2
	type: 0x7
	encrypted: 0x0
	name: AcDb:AcDbObjects

		No.1
		.............
		page number: 6
		data size: 0x30ea
		offset: 0x0
		unknown: 0x0

		No.2
		.............
		page number: 7
		data size: 0x7f0
		offset: 0x7400

		No.3
		.............
		number: 8
		data size: 0x628
		offset: 0xe800

		No.4
		.............
		page number: 9
		data size: 0xf1f
		offset: 0x15c00

		No.5
		.............
		page number: 10
		data size: 0xcc0
		offset: 0x1d000

		No.6
		.............
		page number: 11
		data size: 0xe0c
		offset: 0x24400

2.2 Data section page

data section page的布局为:

项目

长度

含义

Header

0x20

加密头

Data

DataSize

数据

Padding

page size

- DataSzie

-0x20

填充


 对加密头解密后的数据结构为:

偏移

长度

含义

0x00

4

Tag of data section (0x4163043B)

0x04

4

Section type

0x08

4

DataSize, Data的长度

0x0C

4

page size 本page的总长度(包括header,data和padding)

0x10

8

offset,Data解压后在解压缓冲中的存放偏移

0x18

4

Check sum 1

0x1C

4

Check sum 2

加密头的解密方法:

假设0x20长度的头数据存放在header[8]中,offset是本page在dwg文件中的绝对偏移,可从page map中得到。

int32_t  header[8];

int32_t  mask = 0x4164536b ^ offset;

for (int i=0; i<8; ++i)

{

   header[i] ^= mask;

}

从中可以看出Autodesk也挺鸡贼的,还整了一个动态加密。

Data section page的图示:

dwg 文件解析 java dwg文件结构_数据_04

当要读取某种类型的数据时,先从Setion map中查找此类型的description,依据description中的page列表,然后透过page map定位到各page,依次读取page数据,将之解压且存放到目的缓冲的startoffset处,最后拼接出此类型数据的数据流供后续解析。

大致过程如下:

dwg 文件解析 java dwg文件结构_dwg 文件解析 java_05

附一个实例示意图:

dwg 文件解析 java dwg文件结构_Data_06

关于dwg文件的结构性的解析,至此就算完成了。

dwg 文件解析 java dwg文件结构_sed_07