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

161 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 <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` |