# 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`](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): ```powershell 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 ` | | 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" (`` 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` |