# Decodifica strutturale del .asm Creo: record, membri, family table Questo documento descrive il formato **record per record** del `.asm` Creo, ricavato per reverse engineering su un campione di assiemi reali e verificato confrontando l'output con le BOM esportate da Creo per gli stessi file. Sostituisce, per l'estrazione dei componenti, l'euristica testuale ("codice che compare almeno due volte") descritta in [`formato-asm.md`](formato-asm.md): quella era una statistica sui nomi, questa legge la struttura effettiva dei record. Implementazione: lettura in [`PrepStep/AsmRecordReader.cs`](../PrepStep/AsmRecordReader.cs) (condivisa con la GUI, `Form1.EstraiCodiciReferenziati` in fase "Trova componenti referenziati"/"Verifica completezza" - con fallback su `AssemblyComponentScanner` per i file che non rispettano il formato validato), resa in formato BOM e ricorsione sui sottoassiemi in [`scripts/CreoBom.cs`](../scripts/CreoBom.cs) (via `#:project`). **Stato della validazione.** Due livelli di prova, da non confondere: 1. **Byte-per-byte contro BOM Creo di riferimento** (la prova forte): 3 assiemi del reverse engineering iniziale, più 5 casi reali aggiunti in seguito, ciascuno con la sua BOM esportata da Creo messa a confronto riga per riga con l'output di `CreoBom.cs` — `9284218024b.asm` (albero profondo, multi-livello), `2546220101.asm`/`2546220101_c04.asm.1` (istanza di family table isolata), `2546220101.asm` risalvato con un componente soppresso, `9244218080_c01.asm` (sottoassieme montato due volte), `9284208092_D110.asm` (revisione del figlio). Ognuno di questi è citato per nome nella sezione del documento a cui si riferisce, così resta verificabile quale affermazione poggia su quale file. 2. **Su tutti i 350 `.asm` reali di `raw/`**, solo conteggi interni e coerenza con l'euristica testuale (`AssemblyComponentScanner`), **non** contro BOM di riferimento: usato per calibrare le soglie dei controlli di sicurezza (§7) su scala, non per dimostrare la correttezza dei valori estratti file per file. Un'affermazione verificata solo al livello 2 (conteggi su scala) è più debole di una verificata al livello 1 (BOM reale) - il testo lo distingue sempre esplicitamente. --- ## 1. Intestazione Il file si apre con un header ASCII a righe terminate da `\` + newline: ``` #UGC:2 ASSEMBLY 2038 1760 805 1 1 15 4000 2022414 000002d0 \ #- VERS 0 0 \ #- HOST \ #- LINK \ #- DBID \ #- REVS 0, \ ``` `ASSEMBLY` distingue il tipo di modello. Gli altri campi numerici non sono stati decodificati e non servono per la BOM. Dopo l'header inizia il corpo binario. **Il corpo non è compresso**: i nomi dei modelli sono in chiaro, 1 byte per carattere (Latin1 — `Encoding.Latin1.GetString`, stesso metodo già usato in `AssemblyComponentScanner.cs` per leggere il file come testo senza alterarne i byte). ## 2. Il formato dei record Il corpo è una serializzazione a marcatori. I byte `\xe0`-`\xff` sono marcatori di struttura, tutti gli altri sono dati. I marcatori osservati: | byte | significato dedotto | |------|---------------------| | `\xe0` | inizio campo: `\xe0 \0 ` | | `\xe1` | valore nullo / vuoto | | `\xe2` | inizio struttura o unione (per i parametri: il byte dopo è il discriminante) | | `\xe3` | inizio struttura, usato per gli elementi di un array | | `\xf1` | apre un contenitore (visto prima di un array) | | `\xf7` | id di schema: `\xf7 ` identifica il layout dei record che seguono | | `\xf8` | array: `\xf8 ... ` | | `\xfb` | lo schema è definito in linea nel primo elemento dell'array | | `\xf4` | chiude una sezione (seguito da `#\n#\n`) | Il byte `` dopo `\xe0` codifica il tipo del valore. Osservati: `\x00` struttura/nullable, `\x01` intero, `\x02` array numerico, `\x0a` stringa terminata da `\0`. ### 2.1 Interi Gli interi sono big-endian con la **lunghezza codificata nei bit alti del primo byte**, per non collidere con i marcatori `\xe0`+: | primo byte | byte totali | valore | |------------|-------------|--------| | `0x00`-`0x7f` | 1 | il byte stesso | | `0x80`-`0xbf` | 2 | `(b0 & 0x3f) << 8 \| b1` | | `0xc0`-`0xdf` | 3 | `(b0 & 0x1f) << 16 \| b1 << 8 \| b2` | Esempi verificati: `\x2e` = 46; `\x80\x81` = 129; `\xc0\x4d\x51` = 19793. Questa regola è stata **ricavata** dalla necessità di far quadrare il conteggio dei membri: con la lettura a 1 byte fisso i campioni grandi si disallineavano. Non è confermata da una specifica; regge sui campioni disponibili al momento del reverse engineering. In `CreoBom.cs` è `AsmRecordReader.AsInt`, applicata al testo decodificato Latin1 (ogni `char` della stringa corrisponde 1:1 a un byte, quindi indicizzare la stringa o l'array di byte originale dà lo stesso offset). ### 2.2 Compressione per schema Un array `\xf8 \xf7 \xfb` scrive il **primo** elemento con tutti i nomi di campo per esteso; gli elementi successivi sono **posizionali**, senza nomi. È il motivo per cui nel file si vedono i nomi dei componenti "nudi", uno dopo l'altro, senza etichetta. Per leggere l'elemento N bisogna conoscere lo schema definito dall'elemento 1. --- ## 3. L'elenco dei componenti: la chiave `members` La chiave `members` è l'elenco dei **componenti diretti** dell'assieme (livello 1), nell'ordine di montaggio. ``` \xe0 \x00 "members" \0 \xf1 \xf8 \xf7 \xfb \xe3 \xe0\x01 "id"\0 \xe0\x01 "type"\0 \xe0\x0a "name"\0 \0 \xe0\x0a "family_name"\0 \0 \xe0\x00 "tha_ref"\0 \xe1 \xe0\x02 "e1"\0 ... "e2" ... "e3" ... "origin" ... \xe3 \0 \0 \xe3 \0 \0 ... ``` Campi utili: - **`id`** — identificatore del componente **dentro questo assieme**. È lo stesso numero che Creo mostra come "Componente ID N". Serve da chiave per collegare il membro ad altre sezioni (vedi family table, §4). - **`type`** — `1` = sottoassieme (`.asm`), `2` = parte (`.prt`). Sui campioni osservati compaiono solo questi due valori. - **`name`** — il **file su disco**, senza estensione. Per le family table è il **generico** (`UNI6592`), non l'istanza. - **`family_name`** — sui campioni disponibili è **sempre uguale a `name`**. Non porta quindi l'informazione sull'istanza; il nome è fuorviante. - `e1`, `e2`, `e3`, `origin` — la matrice di posizionamento (assi + origine). Non decodificati: non servono per la BOM, ma occupano il resto del record e hanno lunghezza variabile. `N` (il conteggio dichiarato) è il numero totale di **occorrenze**, non di codici distinti: una parte montata 4 volte conta 4 e compare in 4 record con id diversi. Il conteggio va usato come controllo di correttezza: se i record trovati non sono esattamente `N`, la lettura si è disallineata e va segnalata un'eccezione invece di restituire un risultato parziale silenzioso — vedi `AsmRecordReader.LeggiMembri`, che lancia `InvalidDataException` in questo caso. La sezione termina a `\xf4\x04#\n#MdlStatus\n`. ### 3.1 Come lo legge `AsmRecordReader.LeggiMembri` Il parser non ricostruisce lo schema completo (servirebbe decodificare le matrici). Si ancora invece al pattern `\xe3 \0 \0` (`AsmRecordReader.MemberRegex`, una regex .NET sul testo Latin1 del file) e salta il resto del record fino al `\xe3` successivo che soddisfa il pattern. La doppia stringa terminata da `\0` rende i falsi positivi improbabili, e il conteggio dichiarato `N` fa da rete di sicurezza. > `ponytail:` scansione a pattern invece del parser di schema — il punto debole > è che si appoggia al conteggio per accorgersi di un errore. Da sostituire con > la lettura dello schema se dovessero comparire campioni che non quadrano. --- ## 4. Family table: dove sta il nome dell'istanza Le parti normalizzate (viti, rondelle, spine, boccole) sono **istanze di family table**. In Creo esiste un solo file, il **generico** (`UNI6592.prt`), che contiene una tabella di varianti; ogni variante è un'**istanza** (`R8--_-_U6592_AISI304`). La BOM elenca le istanze, il disco contiene il generico. La sezione `members` cita **solo il generico**: senza risolvere `ConfigName` (sotto), il livello 1 produrrebbe `UNI6592` dove la BOM dice `R8--_-_U6592_AISI304`. Il collegamento sta in un record di **parametro** applicato al membro: ``` \xe3 "ConfigName"\0 \xe2 \x33 [\xf7 ] \0 \x82\x7b \xe1\xe1 ``` - `\xe2\x33` — parametro di tipo stringa (`\x33` = `'3'`). Confronto: `\xe2\x32` è un parametro numerico, es. `DENSITA`. - `` — il nome esatto che la BOM stampa. - `` — l'`id` del record `members` a cui il parametro si applica, con la codifica intera di §2.1. **È questo il collegamento.** - `\xf7 ` compare solo sulla prima occorrenza (schema definito in linea), poi è omesso. - `\x82\x7b` — costante (= 635) identica su tutte le occorrenze di tutti i campioni. Significato ignoto, presumibilmente attributi/flag del parametro. `AsmRecordReader.ConfigNameRegex` la usa come ancora di fine record; se un campione futuro avesse un valore diverso, il match salterebbe silenziosamente (nessun contatore di sicurezza analogo a quello di `members` copre questo caso). `ConfigName` compare **esattamente** per i membri che sono istanze, e per nessun altro. L'algoritmo è quindi: leggere `members`, costruire la mappa `id membro → nome istanza` dai record `ConfigName` (`AsmRecordReader.LeggiMembri`, dizionario `istanze`), e sostituire il nome dove la mappa ha una voce. Il generico resta disponibile come `Membro.Generico`. > **Avvertenza.** `ConfigName` sta in mezzo a parametri palesemente aziendali > (`TIPO_PARTE`, `CLASSE_RICAR`, `GR_PRODOTTO`, `TECN_RIORDINO`), quindi > potrebbe non essere un meccanismo standard di Creo ma una convenzione > interna. Regge sui campioni disponibili, ma su file provenienti da > un'altra installazione va riverificato prima di fidarsene. ### 4.0.1 Eccezione: componenti da pattern/array feature (`#AF`) Un componente creato da un **pattern/array feature** (stessa geometria ripetuta N volte da una feature di pattern, non N placement manuali distinti) ha un `ConfigName` con un nome nella forma `#AF` (`n` = indice dell'istanza nel pattern), es. `9244250136_C01#AF0`. **Non è un'istanza di family table**: è un tag interno di disambiguazione fra le ripetizioni del pattern. La BOM reale di Creo lo **ignora** e mostra il codice pulito, unendo le occorrenze duplicate in una sola riga con la quantità totale. Osservato su `9244250136.asm`: 4 dei suoi 8 membri hanno `ConfigName` con suffisso `#AF`, due dei quali (`_C03#AF0` e `_C03#AF1`) sono lo **stesso codice** con `n` diverso — la BOM li mostra come un'unica riga `9244250136_C03` con quantità 2. Senza normalizzare il suffisso, ogni istanza del pattern risulterebbe un componente distinto (e con nome sbagliato). `AsmRecordReader` rimuove `#AF\d+$` da ogni valore di `ConfigName` prima di usarlo (`SuffissoArrayFeatureRegex`). Scoperto durante un test end-to-end su un albero reale più ampio degli 8 campioni originali (`9284218024b.asm`, confrontato con la sua BOM Creo di riferimento) — non era presente in nessuno degli 8 campioni usati per il reverse engineering iniziale. ### 4.1 Una seconda pista, che NON va usata per la BOM Altrove nel file compaiono stringhe nella forma `istanza`: ``` \xe2\x02 "R8--_-_U6592_AISI304"\0 ``` Documenta la stessa relazione istanza→generico in modo autonomo ed è utile come riscontro incrociato (è lo stesso pattern `` già sfruttato dall'euristica testuale in `formato-asm.md` per le family table non-UNI). **Non è però una lista dei componenti**: su un campione produceva 26 voci contro 17 membri reali, includeva alcuni nipoti (figli dei sottoassiemi) ma non tutti, e ripeteva le stesse voci più volte. È la lista dei riferimenti geometrici dei vincoli di accoppiamento, non una distinta. Usarla per la BOM dà risultati sbagliati sia in eccesso sia in difetto. --- ## 5. Il nome proprio del modello La BOM intesta i blocchi col nome **del modello**, non del file: il file `9244248550b.asm.11` produce `Assieme 9244248550B contiene:`. Il nome sta nel primo record `objname` del file: ``` \x00 "objname"\0 \xe2 \xe0\x01 "object_type"\0 \xe0\x0a "name"\0 \0 ``` La **prima** occorrenza è il modello stesso; quelle successive sono altri oggetti referenziati (disegni, modelli citati). `AsmRecordReader.NomeProprio` cerca solo la prima occorrenza (`Regex.Match`, non `Matches`), verificato su tutti i campioni disponibili durante il reverse engineering. --- ## 6. Generare la BOM: ricorsione e formato di output `members` dà un solo livello. La BOM di Creo è ricorsiva: apre anche i `.asm` dei sottoassiemi. Implementato in `CreoAsm.GeneraBom`/`Render` in [`scripts/CreoBom.cs`](../scripts/CreoBom.cs). ### 6.1 Struttura del file BOM - Codifica **UTF-8 con BOM** (`EF BB BF`), fine riga **CRLF**. - Due righe vuote in testa, poi il primo blocco. - **Tre** righe vuote fra un blocco e il successivo, **due** prima del riepilogo. Il file termina con un solo CRLF. - Riga voce: `{qta:6} {tipo:>12} {nome}` — cioè quantità allineata a destra su 6 caratteri, spazio, tipo allineato a destra su 12, due spazi, nome. - Intestazioni: `Assieme contiene:` per la radice, `Sottoassieme contiene:` per gli altri. ### 6.2 Ordine dei blocchi Visita **depth-first pre-order**: si emette il blocco di un sottoassieme e subito dopo, ricorsivamente, quelli dei suoi sottoassiemi, prima di passare al fratello successivo. Ogni modello compare **una volta sola** anche se montato più volte (deduplica per nome, `CreoAsm.Blocchi`). > **Verificato.** `9244218080_C01.asm` monta `2605410010_C01` due volte > (`scripts/9244218080_c01.asm.1` + `scripts/9244218080_c01.bom.2` di riferimento): la BOM > Creo mostra un solo blocco `Sottoassieme 2605410010_C01 contiene:` con quantità 2 in testa, > non due blocchi separati, e `CreoBom.cs` riproduce l'output esatto senza modifiche. Confermata > anche la moltiplicazione delle quantità nel riepilogo (§6.3): le parti di `2605410010_C01` > compaiono raddoppiate (1→2, 1→2, 16→32). ### 6.3 Riepilogo finale `Summary of items for :` elenca **solo le parti**, con la quantità totale moltiplicata lungo l'albero (una parte in 4 esemplari dentro un sottoassieme montato 2 volte conta 8), nell'ordine di **prima comparsa** nella visita depth-first (`CreoAsm.Sommario`). Una parte che compare a più livelli occupa la posizione della sua prima comparsa e somma le quantità. ### 6.4 Risoluzione dei file dei sottoassiemi È il punto che il formato **non** risolve: il `.asm` registra il nome del modello, non il percorso. Servono quindi cartelle di ricerca fornite dall'esterno (`-p`/`--path`). `CreoAsm.CreaFinder` indicizza `.asm.` senza distinzione di maiuscole e, a parità di nome, prende la **revisione più alta** presente. > **Verificato: è la scelta giusta.** Il `.asm` padre non registra la revisione attesa del > figlio (cercata e non trovata, vedi §7), ma non serve: confermato che **Creo stesso apre > sempre la revisione più alta disponibile** di un componente, indipendentemente da quale > fosse presente quando l'assieme padre fu salvato l'ultima volta. Caso reale controllato: > `9284208092_D110.asm` (salvato 2018-08-01) referenzia `9284202481.prt`, che sulla share ha > revisioni sia del 2014 (v1-v3, presenti all'epoca) sia del 2024 (v4-v6, arrivate anni dopo) - > Creo carica v6, non v3. `CreaFinder` che prende sempre la revisione più alta in cartella > replica esattamente questo comportamento, non è un'assunzione azzardata. Vedi `formato- > asm.md` §"Risoluzione ricorsiva dell'albero" per le trappole storiche su questo punto (con > l'euristica testuale - restano valide, sono su un problema diverso: trovare la cartella > giusta, non quale revisione preferire). Se un sottoassieme non si trova, il blocco viene emesso con `*** NON RISOLTO: file .asm non trovato ***` e le sue parti restano fuori dal riepilogo. Il risultato è dichiaratamente incompleto invece che silenziosamente sbagliato. --- ## 7. Stato di ogni punto aperto Tabella di riferimento: cosa è provato, come, e cosa resta genuinamente ignoto. Il dettaglio di ciascun punto segue sotto, in due gruppi. | Punto | Stato | Prova | |---|---|---| | Deduplica blocchi + moltiplicazione quantità (§6.2/§6.3) | Risolto | BOM reale (§7.1) | | Revisione del figlio che il padre si aspetta (§6.4) | Risolto | BOM reale (§7.1) | | Stato soppresso/escluso in `members` | Risolto | BOM reale (§7.1) | | Family table di **assiemi** (rilevamento) | Risolto | BOM reale + scala 350 file (§7.1) | | Family table di **assiemi** (contenuto di ogni istanza) | Non risolto | — (§7.2) | | Costante `\x82\x7b` in `ConfigName` | Significato ignoto, rischio coperto | Scala 350 file (§7.2) | | Occorrenze multiple di `members` (simplified rep) | Mai osservato, rischio coperto | Scala 350 file (§7.2) | | Matrice di posizionamento (`e1`/`e2`/`e3`/`origin`) | Non letto, nessun impatto sulla BOM | — (§7.2) | | Campi numerici dell'header dopo `ASSEMBLY` | Non letto, nessun impatto sulla BOM | — (§7.2) | | Divergenza `family_name` ≠ `name` | Mai osservata, non letta | — (§7.2) | "Rischio coperto" vuol dire: il codice non sa cosa significhi il byte, ma verifica un invariante che lo renderebbe visibile (eccezione) se mai smettesse di valere - non è una prova che il byte non conti mai, è la garanzia che se contasse il programma lo segnalerebbe invece di sbagliare in silenzio. ### 7.1 Risolti con una BOM Creo di riferimento - **Deduplica blocchi e moltiplicazione quantità.** `9244218080_C01.asm` monta `2605410010_C01` due volte (`scripts/9244218080_c01.asm.1` + `scripts/9244218080_c01.bom.2`): la BOM Creo mostra un solo blocco `Sottoassieme 2605410010_C01 contiene:` con quantità 2 in testa (non due blocchi separati), e le sue parti compaiono raddoppiate nel riepilogo (1→2, 1→2, 16→32). `CreoBom.cs` riproduce l'output esatto senza modifiche: la deduplica per nome (`CreoAsm.Blocchi`) e la moltiplicazione lungo l'albero (`CreoAsm.Sommario`) sono il comportamento reale di Creo, non solo un'assunzione. - **Revisione del figlio.** `9284208092_D110.asm` (salvato 2018-08-01) referenzia `9284202481.prt`, che sulla share ha revisioni sia del 2014 (v1-v3, presenti quando il padre fu salvato) sia del 2024 (v4-v6, arrivate anni dopo). Confermato direttamente in Creo: apre v6, non v3 - **sempre la revisione più alta disponibile**, indipendentemente da quale fosse presente all'ultimo salvataggio del padre. `CreoAsm.CreaFinder`, che prende sempre la revisione più alta trovata in cartella, replica esattamente questo comportamento. - **Stato soppresso/escluso.** Stesso assieme `2546220101.asm` risalvato da Creo con `2546220101_C02` soppresso (`scripts/2546220101.asm.1` + `scripts/2546220101.bom.1`, con le parti/sottoassiemi correlati). Un componente soppresso **non produce alcun record** in `members`: sparisce e basta, il conteggio dichiarato scende di conseguenza (`\xf8 03` invece di `\xf8 04`). Nessun marcatore di stato da gestire: `CreoBom.cs` sul file risalvato riproduce esattamente la BOM di riferimento senza modifiche al parser. - **Family table di assiemi — rilevamento.** Non solo le parti normalizzate (§4) possono essere family table: un intero **assieme** può esserlo. Osservato su `2546220101.asm` (dentro l'albero di `9284218024b.asm`): il file generico ha una propria sezione `members` che elenca il contenuto di UNA rappresentazione (3 cuscinetti + 1 sotto-assieme, dati arbitrari), ma la BOM Creo per lo stesso file mostra 4 **istanze diverse** (`2546220101_C01`.."_C04", tre "Parte" e una "Sottoassieme" - tipi diversi fra loro), ciascuna con un contenuto diverso. Il nome istanza qui **non** passa da `ConfigName` (verificato: nessuna occorrenza letterale nel file), ma da una coppia di record distinta subito dopo l'header del modello: ``` \xe0\x01model_t_type\0\xe0\x0amodel_name\0_C0n\0\xe0\x0ageneric_name\0\0 ...stamp\0\xe2\xf2\xf7 (\xe3\x02_C0n\0\0\xe2)* -- un'occorrenza per ogni istanza sorella \xf4 ``` Il file generico è già di per sé un'istanza (`model_name` porta il suffisso `_C0n`, non è neutro), e `stamp` elenca le sorelle. **Segnale affidabile**: la combinazione `model_t_type`+`model_name` (con suffisso `_C0n`)+`generic_name` vuoto (`AsmRecordReader.OwnModelIdentityRegex` + `SuffissoIstanzaFamilyTableRegex`) - **non** la sola presenza di `generic_name`, che su altri 119 file del corpus da 350 compare in un contesto diverso e non correlato (`model_name` lì è un codice slegato dal nome del file, e `members` in quei casi è già corretto). Verificato: solo 25/350 file rispettano il segnale affidabile, e su quei 25 `AsmRecordReader.LeggiMembri` lancia `InvalidDataException` invece di restituire il contenuto sbagliato della rappresentazione di default. Verificato anche che il fallback su `AssemblyComponentScanner` **risolve già correttamente** il caso vero: i nomi delle istanze compaiono come testo puro nel campo `stamp`, e l'euristica ("codice che compare almeno due volte") li trova senza bisogno di decodificare `stamp`. E che una singola istanza, se esportata da Creo come file a sé (Family Table → istanza → "Salva una copia"), è un `.asm`/`.prt` normale senza traccia di family table al suo interno - letto correttamente dal parser esatto (`2546220101_c04.asm.1`, verificato contro `2546220101.bom.1`). ### 7.2 Non risolti - **Family table di assiemi — contenuto di ogni istanza.** Come recuperare il contenuto specifico di un'istanza **senza** che esista già un file separato per essa sul disco (il caso comune, se nessuno l'ha mai esportata a parte con "Salva una copia"): il meccanismo dietro `stamp` non è stato decodificato oltre a nome+tipo di ciascuna istanza sorella. Aperto anche cosa sia davvero il record `generic_name` sui 119 file dove non è questo sintomo. - **Costante `\x82\x7b`** nei record `ConfigName`: significato ignoto. Rischio coperto - `LeggiMembri` conta le occorrenze grezze di `ConfigName` e le confronta col numero di match del regex; se un campione avesse un valore diverso, il regex non farebbe più match (prima: in silenzio) e ora lancia eccezione invece di restituire il nome generico al posto dell'istanza senza dirlo. Verificato su tutti i 350 `.asm` di `raw/`: 215 usano davvero `ConfigName`, 0 mismatch. - **Occorrenze multiple di `members`** (rappresentazioni semplificate, mai osservate). Rischio coperto - `LeggiMembri` lancia eccezione se ne trova più di una invece di prendere silenziosamente la prima senza sapere se sia quella giusta. Verificato su tutti i 350 `.asm` di `raw/`: sempre esattamente una occorrenza. - **La matrice di posizionamento** (`e1`, `e2`, `e3`, `origin`): formato numerico non decodificato, nessun impatto sulla BOM (non letta da nessuna parte del codice). - **I campi numerici dell'header** dopo `ASSEMBLY`: non letti, nessun impatto sulla BOM. - **`family_name`**: sempre uguale a `name` su tutti i campioni disponibili; non si sa in quale situazione divergerebbe, né se il codice dovrebbe fare qualcosa se succedesse (oggi non la usa in nessun modo, legge solo `name`). ## 8. Uso ```powershell dotnet run scripts/CreoBom.cs -- raw/9244228558.asm.20 -p M:\modelli -o out\9244228558.bom.1 dotnet run scripts/CreoBom.cs -- raw/9244228558.asm.20 -p M:\modelli # stampa a video dotnet run scripts/Analyze.cs -- raw/9244228558.asm.20 "ConfigName" # dump dei byte per investigare a mano ``` Senza `-p`/`--path`, la ricerca dei sottoassiemi usa la cartella del file di partenza. `-p` è ripetibile per più cartelle. `-o` scrive il file col BOM UTF-8 e CRLF descritti al §6.1; senza `-o` il testo va su stdout (con `\n` al posto di `\r\n`, per leggibilità in console).