Files
prep-step/docs/history.md
T
davideandClaude 2d7ace07e2 Documenta il formato PSB dei file Creo e aggiungi il parser di riferimento
Reverse engineering di 9253000117a.prt.35: contenitore (offset dall'ultimo
campo dell'intestazione, indice a catena), grammatica completa delle sezioni
(id progressivi dei campi, tabella "dopo X viene Y", marcatori di chiusura),
tabella dei double verificata sulla norma delle matrici, sezioni compresse e
catalogo del contenuto. Parser Python in scripts/psb/. docs/history.md
raccoglie stato, metodo e punti aperti per riprendere il lavoro.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-11-10 14:34:29 +01:00

10 KiB
Raw Blame History

Reverse engineering dei file Creo: stato e ripresa

Diario di passaggio di consegne per il lavoro sul formato dei .prt/.asm. Serve a chi riprende (anche in un'altra sessione) per sapere dove siamo, cosa è provato, da dove ripartire, senza rifare la strada. Il risultato del lavoro sta in formato-psb.md: qui c'è solo lo stato, le decisioni e i vicoli ciechi. Quando un punto si chiude, il contenuto va nel doc di formato e qui resta una riga.

Ultimo aggiornamento: 2026-11-10, ramo reverse-engineering-creo.

Stato in una riga

Il formato di serializzazione (PSB) è decodificato e tutte le sezioni in chiaro di 9253000117a.prt.35 si leggono fino all'ultimo byte con nome e valore di ogni campo; nessun valore è stato ancora confrontato con Creo, e tre cose bloccano "tutti i dati senza Creo" (vedi "Cosa manca").

Da non perdere

  • Niente è committato. In sospeso: docs/formato-psb.md, docs/history.md, scripts/psb/, e le modifiche a CHANGELOG.md, docs/README.md, docs/architettura.md, docs/formato-prt-geometria.md.
  • 9253000117a.prt.35 è nella root, non tracciato: è il campione di tutto il lavoro. Decidere se tenerlo nel repo (2 MB) o in raw/.
  • scripts/psb/__pycache__/ va ignorato, non committato.
  • docs/formato-prt-feature.md contiene byte NUL: gli strumenti di modifica lo rifiutano. Le correzioni che lo riguardano sono in formato-psb.md §9, non applicate al file. Va ripulito dai NUL (conservando la codifica) prima di poterlo correggere.

Come ripartire in cinque minuti

Dalla root del repo, con Python 3.12 (solo libreria standard):

python scripts/psb/prt.py 9253000117a.prt.35                 # indice delle sezioni
python scripts/psb/psb.py 9253000117a.prt.35 '*' out         # dump di ogni sezione in out/
python scripts/psb/psb.py 9253000117a.prt.35 LargeText - t   # traccia campo per campo (debug)
python scripts/psb/unz.py 9253000117a.prt.35 out             # decomprime le sezioni .Z
python scripts/psb/pbin.py out/SolidPrimdata.bin             # legge una sezione decompressa
python scripts/psb/batch.py "raw/*"                          # regressione sugli 89 file
python scripts/psb/norms.py 9253000117a.prt.35               # controllo norma 1 delle matrici
python scripts/psb/tagmap.py 9253000117a.prt.35 "raw/*"      # tag double -> prefisso, dalle norme
python scripts/psb/ctx.py raw/x.prt.1,VisibGeom,cf8          # hexdump attorno a un offset

Valori attesi oggi, da usare come regressione prima e dopo ogni modifica al parser:

Controllo Atteso
psb.py 9253000117a.prt.35 29 sezioni OK, 5 no: AllFeatur (pop underflow sull'ultimo byte), MdlStatus e FeatDefs (pila non a zero), più l'intestazione delle due ultime
norms.py good rows 5397 bad 0
batch.py "raw/*" 2152 sezioni lette, 74 no (misurato prima delle ultime correzioni ai tag: va rilanciato)

Se norms.py dà righe "bad" dopo una modifica, si è rotta la tabella dei double: è il controllo più sensibile che abbiamo.

Cosa è provato, e come

Cosa Prova
Contenitore (offset = ultimo campo della prima riga, riga #Nome inclusa, indice a catena) tutte le sezioni cominciano con e0 00 <radice>
Grammatica e tabella "dopo X viene Y" lettura fino all'ultimo byte di tutte le sezioni; l'id nell'intestazione degli array coincide con quello calcolato
Tag dei double norma 1 su 5397 righe; prefissi 3fd0-3fef e bfd0-bfef verificati uno a uno sugli 89 file
Costanti π, π/2, 2π, 3π/2 e negative stesso rumore di mantissa nelle forme modificate (02 xx ...)
Sezioni .Z la dimensione decompressa coincide con quella dichiarata nell'indice
Significato dei campi nessuna: dedotto dai nomi

Cosa manca, in ordine di valore

  1. Confronto con Creo. Aprire un file piccolo, annotare quote, parametri, massa, posizione di una faccia, e confrontare con il dump. Finché non c'è, tutto il doc è "livello 1 e 2".
  2. Tabella dei float (tag 50-df). È una tabella di prefissi scelti per frequenza, non una formula. Il parser usa FBASE lineare, sbagliato per quei tag (lunghezza giusta, valore no). Senza, la mesh di SolidPrimdata (mv_p_NxNyNzxyz) non si esporta. Metodo che ha funzionato per le 29 voci note: normali unitarie, risolvere il modulo dalla norma. Va ripetuto su tutti i file di raw/ (ognuno ha SolidPrimdata), poi sostituire FBASE con un dizionario e far fallire il parser sui tag ignoti invece di inventare.
  3. La pila dei contenitori. I valori sono giusti, l'annidamento no in alcuni punti:
    • LargeText di 24 file di raw/: alberi bilanciati (lesser_child, greater_child, balance_factor, backup_size). La regola post (puntatore "al successivo" scritto al livello del padre) copre un solo campo per struttura; qui i figli sono due.
    • srf_flip_dat (geometria), end2 (FeatDefs): "contenitore non in pila".
    • fine di AllFeatur/MdlStatus/FeatDefs: pila non a zero. Punto di partenza: default_close e locate in psb.py, con la traccia (- t).
  4. Sezioni tipo b (IndentTag, FeatRefData) e tipo 11 Wildfire: illeggibili. Stessa lunghezza del chiaro, primi 4 byte in chiaro. Già escluso (vedi formato-prt-feature.md §1.3): XOR a chiave fissa, zlib, LZW. Non riprovare senza una coppia chiaro/cifrato dello stesso modello.
  5. DispDataTable (tipo c): comincia con 1f 9d 10 ma l'LZW standard fallisce al secondo codice. Non indagato.
  6. PSB_Tokens (ND:0:*): letti i quattro array, non il significato delle lettere di type_har. Ipotesi da cui partire: I consuma un intero, F un double, una lettera per le stringhe; M/O aprono oggetto e campo. Contare le occorrenze contro le lunghezze degli array la conferma o la smentisce in pochi minuti.
  7. Costanti double ignote: 04 (vista come componente di tangente e come estremo di inviluppo), 0d (−0,5 da un solo caso), buco bffe contro bffd non deciso.
  8. Portare il parser in C# accanto ad AsmRecordReader: solo dopo i punti 1 e 3.

Il ragionamento da adottare

È il metodo che ha portato fin qui. Va seguito anche per i punti aperti: ogni volta che è stato saltato, si è perso tempo.

  1. Partire dal modello di chi scrive, non dai byte. Il file è l'uscita di un serializzatore generico che percorre strutture C. La domanda utile non è "cosa significano questi byte" ma "che regola semplice userebbe un programma per scriverli". Ogni marcatore ridondante (f7, f0-f4) esiste perché il lettore, da solo, avrebbe previsto un'altra cosa: capire la previsione spiega il marcatore.
  2. Un'ipotesi alla volta, e un controllo che la possa smentire. Prima di scrivere codice dire cosa ci si aspetta di vedere se l'ipotesi è vera. Esempio: "se gli id sono progressivi, l'id nell'intestazione di unit_arr deve essere 0x14" (contato a mano: lo era).
  3. Il parser è lo strumento di misura. Una regola è buona se fa arrivare la lettura all'ultimo byte di tutte le sezioni; l'errore dice dove guardare. Si corregge la regola generale, mai il caso singolo: un'eccezione scritta per una sezione ha sempre rotto un'altra.
  4. Cercare i controesempi, non le conferme. Quando una regola spiega cinque casi, elencare per iscritto i casi noti (con i byte) e verificare che li spieghi tutti, compreso quello scomodo. La regola sulle chiusure implicite è nata mettendo in tabella dieci casi e cercando l'unica formulazione che non ne contraddiceva nessuno.
  5. Ancorare i numeri a vincoli fisici, non all'aspetto. Un valore "plausibile" non è una prova. Lo sono: la norma 1 delle righe di una matrice di rotazione, valori noti (π, √2/2, 0,1, 25,4), la coerenza tra sezioni (lo stesso punto scritto due volte). Per i tag dei numeri: far risolvere il prefisso al vincolo (tagmap.py), non estrapolare una formula.
  6. Separare ciò che è provato da ciò che è dedotto. Tokenizzazione (sequenza campo-valore), annidamento e significato dei campi sono tre cose con tre livelli di prova diversi. Nel doc ognuna va dichiarata col suo livello; "regge sui campioni" non è "è così".
  7. Allargare il campione appena una regola regge. Un file solo nasconde le tabelle: i buchi nei prefissi negativi e la tabella dei float sono emersi solo confrontando più file. Dopo ogni modifica: norms.py sul campione, poi batch.py su raw/.
  8. Quando due spiegazioni reggono entrambe, cercare il dato che le distingue invece di scegliere la più elegante (esempio: i tag 91-a0 sembravano negativi; li ha decisi una curva i cui parametri dovevano crescere da 3π/2).
  9. Scrivere subito nel doc, con il sintomo e i byte. Una scoperta non scritta si rifà. Scrivere anche ciò che è stato escluso e perché.
  10. Dire quello che non si sa. Una costante ignota resta "ignota" (<c04> nel dump), non riceve un valore inventato; un parser che non capisce si ferma, non restituisce un risultato parziale in silenzio.

Vicoli ciechi e trappole, per non rifarli

  • Modello "pop = chiudi N livelli" puro: non basta. I marcatori f0-f4 compaiono solo quando la chiusura reale è diversa da quella che il lettore farebbe da solo; f0 vuol dire "resta qui". Ci sono volute quattro riscritture per arrivarci.
  • Ordine dei campi per struttura (primo/successivo per schema): sbagliato. La tabella è piatta, per id, e vale l'ultima transizione vista.
  • e4-e7 come "zeri": sono ripetizioni del valore precedente.
  • Lo stesso id in contenitori diversi: succede con le varianti di unione (typed_data(...)); il parser le accetta e le conta in lenient.
  • Tag double negativi lineari: no, ci sono tre buchi. Non fidarsi di un'estrapolazione fuori dagli intervalli verificati.
  • Heredoc in Bash con \\0: lo strumento collassa le barre e scrive un NUL nel sorgente. Per creare file Python usare lo strumento di scrittura, non un heredoc.
  • Git Bash e cd: gli script vanno lanciati dalla root del repo; dotnet run dentro PrepStep/ avvia la GUI.

Cronologia

Data Fatto
2026-10-21 Albero delle feature, quote e geometria degli schizzi letti a pattern (formato-prt-feature.md, formato-prt-geometria.md)
2026-11-10 Contenitore corretto; grammatica PSB completa; tabella dei double; sezioni .Z; catalogo delle sezioni; parser di riferimento in scripts/psb/; formato-psb.md