100 خطوط
4.7 KiB
Python
100 خطوط
4.7 KiB
Python
"""Tiny CFB writer solely for bounded native-format unit fixtures (never application data)."""
|
|
import math
|
|
import struct
|
|
|
|
FREE=0xffffffff; END=0xfffffffe; FAT=0xfffffffd
|
|
|
|
|
|
def cfb(streams):
|
|
entries=[('Root Entry',5,b'',[])]
|
|
storages={}
|
|
for name,data in streams.items():
|
|
parts=name.split('/'); parent=0
|
|
for part in parts[:-1]:
|
|
key=(parent,part)
|
|
if key not in storages:
|
|
storages[key]=len(entries);entries.append((part,1,b'',[]));entries[parent][3].append(storages[key])
|
|
parent=storages[key]
|
|
entries[parent][3].append(len(entries));entries.append((parts[-1],2,data,[]))
|
|
# This fixture deliberately uses normal >=4096-byte streams, with padding INSIDE native records.
|
|
assert all(len(e[2])>=4096 for e in entries if e[1]==2)
|
|
sectors=[];links=[];starts={}
|
|
def allocate(data):
|
|
start=len(sectors); count=math.ceil(len(data)/512)
|
|
for i in range(count):sectors.append(data[i*512:(i+1)*512].ljust(512,b'\0'));links.append(start+i+1 if i+1<count else END)
|
|
return start
|
|
for i,e in enumerate(entries):
|
|
if e[1]==2:starts[i]=allocate(e[2])
|
|
directory=bytearray(math.ceil(len(entries)/4)*512)
|
|
for i,(name,kind,data,children) in enumerate(entries):
|
|
offset=i*128;encoded=(name+'\0').encode('utf-16le');directory[offset:offset+len(encoded)]=encoded
|
|
struct.pack_into('<HBB',directory,offset+64,len(encoded),kind,1)
|
|
siblings=[]
|
|
for parent in entries:
|
|
if i in parent[3]:siblings=parent[3];break
|
|
right=siblings[siblings.index(i)+1] if siblings and siblings.index(i)+1<len(siblings) else FREE
|
|
struct.pack_into('<III',directory,offset+68,FREE,right,children[0] if children else FREE)
|
|
struct.pack_into('<IQ',directory,offset+116,starts.get(i,END),len(data))
|
|
dir_start=allocate(directory)
|
|
fat_id=len(sectors); assert fat_id<128
|
|
links.append(FAT)
|
|
fat=struct.pack('<128I',*(links+[FREE]*(128-len(links))))
|
|
sectors.append(fat)
|
|
header=bytearray(512);header[:8]=bytes.fromhex('d0cf11e0a1b11ae1')
|
|
struct.pack_into('<HHHHH',header,24,0x3e,3,0xfffe,9,6)
|
|
struct.pack_into('<IIIIIIIII',header,40,0,1,dir_start,0,4096,END,0,END,0)
|
|
struct.pack_into('<109I',header,76,fat_id,*([FREE]*108))
|
|
return bytes(header)+b''.join(sectors)
|
|
|
|
|
|
def text(props):
|
|
data=('|'+'|'.join(f'{k}={v}' for k,v in props.items())+'|_PADDING=').encode()
|
|
data=data.ljust(4091,b' ')+b'\0'
|
|
return struct.pack('<I',len(data))+data
|
|
|
|
|
|
def record(tag,data):
|
|
data=bytes(data).ljust(4091,b'\0')
|
|
return bytes([tag])+struct.pack('<I',len(data))+data
|
|
|
|
|
|
def pcb(*, width=100000, x=100000, net='GND', version='5.01', track=True, corrupt=False):
|
|
header=b'PCB 6.0 Binary File'
|
|
board={'KIND':'Protel_Advanced_PCB','VERSION':version,'LAYER1NEXT':'32','LAYER32PREV':'1',
|
|
'VX0':'0mil','VY0':'0mil','VX1':'100mil','VY1':'0mil','VX2':'100mil','VY2':'100mil','VX3':'0mil','VY3':'100mil'}
|
|
streams={'FileHeaderSix':(struct.pack('<IB',len(header),len(header))+header).ljust(4096,b'\0'),
|
|
'Board6/Data':text(board),'Nets6/Data':text({'NAME':net})}
|
|
if track:
|
|
p=bytearray(45);p[0]=1
|
|
struct.pack_into('<HHH',p,3,0,65535,65535)
|
|
struct.pack_into('<iiiii',p,13,x,100000,x+200000,100000,width)
|
|
streams['Tracks6/Data']=record(4,p)
|
|
if corrupt:streams['Tracks6/Data']=b'\x01'+streams['Tracks6/Data'][1:]
|
|
return cfb(streams)
|
|
|
|
|
|
def rich_pcb():
|
|
"""Pad stack and blind via records follow the documented V6 field offsets."""
|
|
import io
|
|
import olefile
|
|
with olefile.OleFileIO(io.BytesIO(pcb())) as ole:
|
|
streams={'/'.join(e):ole.openstream(e).read() for e in ole.listdir()}
|
|
streams['Components6/Data']=text({'SOURCEDESIGNATOR':'U1','PATTERN':'QFN','SOURCEUNIQUEID':'FOOTPRINT_UID',
|
|
'X':'10mil','Y':'10mil','LAYER':'TOP','ROTATION':'0'})
|
|
p=bytearray(110);p[0]=74
|
|
struct.pack_into('<HHH',p,3,0,65535,0)
|
|
struct.pack_into('<ii',p,13,100000,100000)
|
|
struct.pack_into('<iiiiiii',p,21,100000,200000,150000,250000,200000,300000,50000)
|
|
struct.pack_into('<BBB',p,49,1,2,3);p[60]=1
|
|
stack=bytearray(3959);stack[262]=2
|
|
struct.pack_into('<i',stack,263,100000) # slotted hole
|
|
parts=[b'\x011',b'',b'',b'',bytes(p),bytes(stack)]
|
|
streams['Pads6/Data']=b'\x02'+b''.join(struct.pack('<I',len(part))+part for part in parts)
|
|
v=bytearray(203);v[0]=74
|
|
struct.pack_into('<HHH',v,3,0,65535,65535)
|
|
struct.pack_into('<iiii',v,13,500000,500000,100000,50000)
|
|
v[29]=1;v[30]=32;struct.pack_into('<32i',v,75,*([100000]*31+[200000]))
|
|
streams['Vias6/Data']=record(3,v)
|
|
streams['UniqueIDPrimitiveInformation/Data']=text({'PRIMITIVEINDEX':'0','PRIMITIVEOBJECTID':'Pad','UNIQUEID':'PAD_UID'})
|
|
return cfb(streams)
|