WIN7 SHD文件格式

SHD - 即Windows Shadow FIle ,WIN98/NT/2000/XP/2003的SHD文件格式在 http://www.undocprint.org/formats/winspool/shd 已有描述。WIN7下SHD文件格式略有差异,如下图:

00002.SHD为WIN7的SHD文件,00003.SHD为WIN2003的SHD文件。观察结果:第一行是一致的,第4~7字节记录头长度,WIN7下是0XD0,即208,WIN2003是0X78,即120,相差88字节。WIN2003的SHD文件格式从第17字节开始有22个DWORD型数据(4字节),由此推测WIN7下这些数据为8字节。实际测试与推测相符,WIN7的SHD文件头文件格式可如此表示:

//SHD文件结构体 WIN7
typedef struct _SHADOW_FILE_HEADER_WIN7 {
	DWORD dwSignature;        //SHD_SIGNATURE_WIN7
	DWORD dwHeaderSize;
	WORD wStatus;
	WORD wUnknown1;
	DWORD dwJobID;
	UINT64 dwPriority;
	UINT64 offUserName;        //Offset of WideChar+0
	UINT64 offNotifyName;      //Offset of WideChar+0
	UINT64 offDocumentName;    //Offset of WideChar+0
	UINT64 offPort;            //Offset of WideChar+0
	UINT64 offPrinterName;     //Offset of WideChar+0
	UINT64 offDriverName;      //Offset of WideChar+0
	UINT64 offDevMode;         //Offset of DEVMODE
	//Note that the dmCopies in this structure will hold
	//the wrong value when the Microsoft Word multiple 
	//copies bug occurs.
	//In this case use the dmCopies from the SPL file
	UINT64 offPrintProcessor;  //Offset of WideChar+0
	UINT64 offDataFormat;      //Offset of WideChar+0
	UINT64 dwUnknown2;
	SYSTEMTIME stSubmitTime;
	UINT64 dwStartTime;
	UINT64 dwUntilTime;
	UINT64 dwUnknown6;          
	UINT64 dwPageCount;
	UINT64 dwSizeSecurityInfo; //Size of SecurityInfo
	UINT64 offSecurityInfo;    //Offset of SECURITY_DESCRIPTOR
	UINT64 dwUnknown3;
	UINT64 dwUnknown4;
	UINT64 dwUnknown5;
	UINT64 offComputername;    //Offset of WideChar+0
	UINT64 dwSPLSize;          //Size of SPL File
} SHADOW_FILE_HEADER_WIN7, *PSHADOW_FILE_HEADER_WIN7;


----------------------------------

2015.11.10更新,上面的结构体后半部分存在问题,更新一下。WIN7 SHD头部长度为0XD0,而旧的系统(如WIN2000/2003)的头部长度为0X78。

//SHD文件结构体 WIN7
typedef struct _SHADOW_FILE_HEADER_WIN7 {
	DWORD dwSignature;        //SHD_SIGNATURE_WIN7
	DWORD dwHeaderSize;
	WORD wStatus;
	WORD wUnknown1;
	DWORD dwJobID;
	UINT64 dwPriority;
	UINT64 offUserName;        //Offset of WideChar+0
	UINT64 offNotifyName;      //Offset of WideChar+0
	UINT64 offDocumentName;    //Offset of WideChar+0
	UINT64 offPort;            //Offset of WideChar+0
	UINT64 offPrinterName;     //Offset of WideChar+0
	UINT64 offDriverName;      //Offset of WideChar+0
	UINT64 offDevMode;         //Offset of DEVMODE
	//Note that the dmCopies in this structure will hold
	//the wrong value when the Microsoft Word multiple 
	//copies bug occurs.
	//In this case use the dmCopies from the SPL file
	UINT64 offPrintProcessor;  //Offset of WideChar+0
	UINT64 offDataFormat;      //Offset of WideChar+0
	UINT64 dwUnknown2;
	SYSTEMTIME stSubmitTime;
	UINT32 dwStartTime; 
	UINT32 dwUntilTime;
	UINT32 dwUnknown6;          
	UINT32 dwPageCount;
	UINT64 dwSizeSecurityInfo; //Size of SecurityInfo
	UINT64 offSecurityInfo;    //Offset of SECURITY_DESCRIPTOR
	UINT32 dwUnknown3;
	UINT32 dwUnknown4;
	UINT64 dwUnknown5;
	UINT64 offComputername;    //Offset of WideChar+0
	UINT64 dwSPLSize;          //Size of SPL File
	UINT64 offUserID;
	UINT64 dwUnknown7;
	UINT64 dwUnknown8;
} SHADOW_FILE_HEADER_WIN7, *PSHADOW_FILE_HEADER_WIN7;



import tkinter as tk from tkinter import filedialog, messagebox, ttk import os import struct import binascii class PrinterSpoolAnalyzer: """打印机脱机文件分析器""" STATUS_CODES = { 0: ("暂停", "打印任务被暂停"), 1: ("错误", "打印过程中发生错误"), 2: ("正在删除", "打印任务正在被删除"), 3: ("正在打印", "打印任务正在进行中"), 4: ("脱机", "打印机处于脱机状态"), 5: ("缺纸", "打印机缺纸"), 6: ("用户干预", "需要用户干预"), 7: ("正在初始化", "打印机正在初始化"), 8: ("正在预热", "打印机正在预热"), 9: ("正在处理", "打印机正在处理任务"), 10: ("正在输出", "打印机正在输出"), 11: ("就绪", "打印任务准备就绪"), 12: ("正在等待", "打印任务正在等待"), 13: ("完成", "打印任务成功完成"), 14: ("保留", "保留状态"), 15: ("未知", "未知状态") } def __init__(self, root): self.root = root self.root.title("打印机脱机文件分析器") self.root.geometry("800x600") self.root.resizable(True, True) self.create_widgets() self.current_file = None def create_widgets(self): """创建GUI界面组件""" # 文件选择区域 file_frame = ttk.LabelFrame(self.root, text="文件选择") file_frame.pack(fill=tk.X, padx=10, pady=5) self.file_path = tk.StringVar() ttk.Entry(file_frame, textvariable=self.file_path, width=70).pack( side=tk.LEFT, padx=5, pady=5, fill=tk.X, expand=True) ttk.Button(file_frame, text="浏览...", command=self.browse_file).pack( side=tk.LEFT, padx=5, pady=5) ttk.Button(file_frame, text="分析文件", command=self.analyze_file).pack( side=tk.LEFT, padx=5, pady=5) # 文件信息显示区域 info_frame = ttk.LabelFrame(self.root, text="文件信息") info_frame.pack(fill=tk.BOTH, padx=10, pady=5, expand=True) columns = ("属性", "值") self.tree = ttk.Treeview(info_frame, columns=columns, show="headings") self.tree.heading("属性", text="属性") self.tree.heading("值", text="值") self.tree.column("属性", width=150, anchor=tk.W) self.tree.column("值", width=600, anchor=tk.W) scrollbar = ttk.Scrollbar(info_frame, orient=tk.VERTICAL, command=self.tree.yview) self.tree.configure(yscroll=scrollbar.set) scrollbar.pack(side=tk.RIGHT, fill=tk.Y) self.tree.pack(fill=tk.BOTH, expand=True) # 状态分析区域 status_frame = ttk.LabelFrame(self.root, text="打印状态分析") status_frame.pack(fill=tk.X, padx=10, pady=5) self.status_text = tk.Text(status_frame, height=5, wrap=tk.WORD) self.status_text.pack(fill=tk.X, padx=5, pady=5) self.status_text.config(state=tk.DISABLED) # 十六进制查看器 hex_frame = ttk.LabelFrame(self.root, text="十六进制查看器") hex_frame.pack(fill=tk.BOTH, padx=10, pady=5, expand=True) self.hex_text = tk.Text(hex_frame, wrap=tk.NONE, font=("Courier", 10)) self.hex_text.pack(fill=tk.BOTH, expand=True) scrollbar_x = ttk.Scrollbar(hex_frame, orient=tk.HORIZONTAL, command=self.hex_text.xview) scrollbar_y = ttk.Scrollbar(hex_frame, orient=tk.VERTICAL, command=self.hex_text.yview) self.hex_text.configure(xscrollcommand=scrollbar_x.set, yscrollcommand=scrollbar_y.set) scrollbar_x.pack(side=tk.BOTTOM, fill=tk.X) scrollbar_y.pack(side=tk.RIGHT, fill=tk.Y) def browse_file(self): """打开文件选择对话框""" file_types = [ ("打印机脱机文件", "*.shd *.spl *.tmp"), ("所有文件", "*.*") ] file_path = filedialog.askopenfilename( title="选择打印机脱机文件", filetypes=file_types ) if file_path: self.file_path.set(file_path) self.current_file = file_path def analyze_file(self): """分析选定的打印机脱机文件""" file_path = self.file_path.get() if not file_path or not os.path.isfile(file_path): messagebox.showerror("错误", "请选择有效的文件") return try: # 清空之前的显示 for item in self.tree.get_children(): self.tree.delete(item) # 解析文件 file_info = self.parse_file(file_path) # 显示文件信息 for key, value in file_info.items(): self.tree.insert("", tk.END, values=(key, value)) # 显示状态分析 self.display_status_analysis(file_info) # 显示十六进制内容 self.display_hex_content(file_path) except Exception as e: messagebox.showerror("解析错误", f"解析文件时出错: {str(e)}") def parse_file(self, file_path): """解析打印机脱机文件""" file_info = { "文件名": os.path.basename(file_path), "文件路径": os.path.dirname(file_path), "文件大小": f"{os.path.getsize(file_path)} 字节", "文件类型": self.get_file_type(file_path) } # 根据文件类型进行特定解析 try: with open(file_path, 'rb') as f: if file_path.lower().endswith('.shd'): # SHD文件格式解析 header = f.read(128) if len(header) >= 128: # 解析状态码 status_code = struct.unpack('<I', header[0x48:0x4C])[0] file_info["状态码"] = f"0x{status_code:08X}" # 解析作业ID job_id = struct.unpack('<I', header[0x00:0x04])[0] file_info["作业ID"] = str(job_id) # 解析打印机名称 printer_name = header[0x08:0x28].decode('utf-16le', errors='ignore').strip('\x00') file_info["打印机名称"] = printer_name # 解析文档名称 doc_name = header[0x28:0x48].decode('utf-16le', errors='ignore').strip('\x00') file_info["文档名称"] = doc_name # 解析提交时间 submit_time = struct.unpack('<Q', header[0x60:0x68])[0] file_info["提交时间"] = self.filetime_to_str(submit_time) elif file_path.lower().endswith('.spl'): # SPL文件格式解析 header = f.read(64) if len(header) >= 64: # 解析作业ID job_id = struct.unpack('<I', header[0x00:0x04])[0] file_info["作业ID"] = str(job_id) # 解析文档名称 doc_name = header[0x08:0x28].decode('utf-16le', errors='ignore').strip('\x00') file_info["文档名称"] = doc_name # 解析数据类型 data_type = struct.unpack('<I', header[0x2C:0x30])[0] file_info["数据类型"] = self.get_data_type(data_type) except Exception as e: file_info["解析错误"] = str(e) return file_info def get_file_type(self, file_path): """获取文件类型描述""" ext = os.path.splitext(file_path)[1].lower() file_types = { '.shd': "打印机影子文件 (SHD)", '.spl': "打印机假脱机文件 (SPL)", '.tmp': "打印机临时文件 (TMP)" } return file_types.get(ext, "未知文件类型") def get_data_type(self, data_type): """获取数据类型描述""" data_types = { 0: "原始数据", 1: "EMF (增强型图元文件)", 2: "文本", 3: "XPS (XML纸张规范)", 4: "PCL (打印机命令语言)", 5: "PostScript" } return data_types.get(data_type, f"未知数据类型 ({data_type})") def filetime_to_str(self, filetime): """将Windows FILETIME转换为可读时间""" if filetime == 0: return "未设置" # FILETIME是自1601-01-01以来的100纳秒间隔数 # 转换为UNIX时间戳(自1970-01-01以来的秒数) unix_time = (filetime - 116444736000000000) // 10000000 from datetime import datetime return datetime.utcfromtimestamp(unix_time).strftime('%Y-%m-%d %H:%M:%S') def display_status_analysis(self, file_info): """显示状态分析结果""" self.status_text.config(state=tk.NORMAL) self.status_text.delete(1.0, tk.END) if "状态码" in file_info: status_hex = file_info["状态码"] try: # 提取状态码的低4位(主要状态) status_code = int(status_hex, 16) & 0xF status_name, status_desc = self.STATUS_CODES.get( status_code, ("未知", "无法识别的状态码")) # 判断打印是否成功 is_success = (status_code == 13) # 13 = 完成 # 显示分析结果 self.status_text.insert(tk.END, f"状态码: {status_hex}\n") self.status_text.insert(tk.END, f"状态: {status_name}\n") self.status_text.insert(tk.END, f"描述: {status_desc}\n") self.status_text.insert(tk.END, "\n打印结果: ") if is_success: self.status_text.insert(tk.END, "✅ 打印成功完成", "success") else: self.status_text.insert(tk.END, "❌ 打印未成功完成", "error") # 设置文本样式 self.status_text.tag_config("success", foreground="green") self.status_text.tag_config("error", foreground="red") except ValueError: self.status_text.insert(tk.END, "错误: 无效的状态码格式") else: self.status_text.insert(tk.END, "此文件类型不包含可解析的状态信息") self.status_text.config(state=tk.DISABLED) def display_hex_content(self, file_path): """以十六进制格式显示文件内容""" self.hex_text.config(state=tk.NORMAL) self.hex_text.delete(1.0, tk.END) try: with open(file_path, 'rb') as f: content = f.read() # 每行显示16字节 for i in range(0, len(content), 16): # 地址偏移量 line = f"{i:08X}: " # 十六进制表示 hex_part = ' '.join( f"{content[j]:02X}" if j < len(content) else " " for j in range(i, i+8) ) hex_part += " " hex_part += ' '.join( f"{content[j]:02X}" if j < len(content) else " " for j in range(i+8, i+16) ) # ASCII表示 ascii_part = ''.join( chr(content[j]) if 32 <= content[j] <= 126 else '.' for j in range(i, min(i+16, len(content))) ) self.hex_text.insert(tk.END, f"{line}{hex_part} |{ascii_part}|\n") except Exception as e: self.hex_text.insert(tk.END, f"读取文件时出错: {str(e)}") self.hex_text.config(state=tk.DISABLED) if __name__ == "__main__": root = tk.Tk() app = PrinterSpoolAnalyzer(root) root.mainloop()根据这个代码修改上述问题,给出全部代码
08-17
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值