@@ -0,0 +1,99 @@
|
||||
"""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)
|
||||
مرجع در شماره جدید
مسدود کردن کاربر