Skip to content

Commit 19b8283

Browse files
Axel051171claude
andcommitted
feat(amiga): der AmigaDOS-Encoder, den MF-539 als fehlend benannt hat - abgenommen an einer ECHTEN Diskette, nicht an sich selbst (MF-1081)
Hebt den Blocker aus MF-539 auf. ADF -> HFE ist zurueck. ## Was fehlte MF-539 hat gemessen, dass UFT fuer eine ADF eine IBM-System-34-Spur mit einem AMIGA_MFM-Kopf darueber schrieb. Gegen die echte Aufnahme `tests/corpus_free/gw_amigados.hfe`: echte HFE UFTs Ausgabe Sync 0x4489 22 0 haeufigstes Byte 0x55 (12498) 0x4E (6712) rohe Nullbytes 0 von 12792 5969 von 12800 Seither wurde die Wandlung ABGELEHNT - richtig, denn eine plausible unlesbare Datei ist schlimmer als ein ehrlicher Fehler. Was fehlte, war ein AmigaDOS-MFM-Encoder. Der Baum hatte einen IBM-System-34-Encoder und einen AmigaDOS-DEKODER, aber nicht die Gegenrichtung. ## Was jetzt da ist `src/core/uft_amiga_mfm_encoder.c`, eigenstaendig geschrieben als exakte Umkehrung des baumeigenen, bereits abgenommenen `decode_amiga_sector()`. Benannte zweite Referenzen, beide gelesen und nicht uebernommen: * `mfmdisk` (Serge Vakulenko, GPL-2.0) - sein `src/amiga.c` schreibt denselben Aufbau. UFT steht selbst unter GPL-2.0, ein Port waere also zulaessig gewesen; gebraucht wurde er nicht. * `disk-utilities`/`libdisk` (Keir Fraser, PUBLIC DOMAIN) - sein `format/amiga/amigados.c` dokumentiert das Spurlayout vollstaendig. ## Abgenommen an einer echten Diskette, nicht an sich selbst Ein Rundlauf durch die eigene Umkehrung beweist wenig: der Dekoder maskiert nur die 0x55-Positionen und SIEHT DIE TAKTBITS GAR NICHT AN. Er koennte byteidentisch aufgehen, waehrend die Spur auf einem echten Laufwerk unlesbar ist - woertlich die Falle aus MF-1079, wo jede Spurlaenge auf das Bit stimmte und trotzdem 587 unmoegliche Zellpaare darin standen. Deshalb drei unabhaengige Messungen: 1. DIE ECHTE AUFNAHME. `gw_amigados.hfe` Spur 0/0 dekodiert, mit dem neuen Encoder neu kodiert: **11 von 11 Sektoren stehen BYTEIDENTISCH in der Originalspur**, je 1084 Byte samt Sync, Info-Long, Label, beiden Pruefsummen und jedem Taktbit. 2. DIE ZELLREGEL. Ueber die kodierte Spur: 0 benachbarte 1-Zellen, laengster Nulllauf 3. Das ist die Regel, die den Zellabstand einer MFM-Diskette ausmacht, und der Dekoder prueft sie nicht. 3. DIE SIGNATUR. 22 Sync 0x4489 und KEIN rohes Nullbyte - genau die drei Zahlen, an denen MF-539 den alten Zustand gemessen hat. ## Ein Widerspruch zwischen zwei Referenzen, am Objekt entschieden Keir Frasers `libdisk` nennt die Feldreihenfolge `info_even, info_odd`; UFTs Dekoder nennt die erste Haelfte `odd`. Das sah nach einem echten Widerspruch aus. Entschieden hat ihn die Messung an der echten Aufnahme, nicht eine Auswahl zwischen zwei Beschreibungen: es sind dieselben Bits, nur anders benannt. ## Ein Denkfehler im eigenen Entwurf, vor dem Code bemerkt Die Datenpruefsumme steht auf der Spur VOR den Daten, wird aber ueber sie gebildet. Der erste Entwurf rechnete sie deshalb voraus und fuehrte den Takt-Kontext zwischen beiden Feldern mit - ein Aufwand auf einem Denkfehler. Der Dekoder vergleicht `hchk_stored == (hdr_csum & 0x55555555)`: die Maske faellt am ENDE, und da XOR bitweise ist, tragen die Taktbits nichts bei. Die Pruefsumme haengt allein an den Datenbits. Die Zirkularitaet existiert nicht, der Block entfiel ersatzlos. ## Und ein Fehler in der Verdrahtung, den die Abnahme gefunden hat Die Geometriepruefung testete `cylinders <= 0 || > 84` - also den QUOTIENTEN. 901 119 Byte ergeben ganzzahlig 79 Zylinder, wurden also angenommen, und eine Spur fiel still weg. Gefunden hat das die Gegenprobe im neuen Test, bevor der Commit lag. Jetzt wird der REST geprueft. ## Zwei vorher gruene Tests wurden rot - und nachgezogen, nicht stillgelegt * `test_convert_roundtrip_measured` nagelte die ABLEHNUNG fest und sagte in seinem eigenen Text, was zu tun ist: "Wenn ein AmigaDOS-Encoder dazugekommen ist, gehoert hier ein Rundlauf hin, kein Ablehnungstest." Genau das steht jetzt dort. UND DIE FALLE AUS MF-538 WIRD MITGEPRUEFT: die Quelle `xdftool_dd_ofs.adf` ist eine LEERE OFS-Diskette mit 733 Byte ungleich null, an der damals eine Rueckwandlung aus lauter Nullen wie 0,08 % Abweichung aussah. Der Test prueft deshalb 0 abweichende Byte UND die Nulllinie. Gemessen: 733 Byte ungleich null hinein, **733 heraus**, 0 abweichend. * `test_roundtrip_matrix` nagelte die ZAHL der verlustfreien Paare auf 6. Sie ist jetzt 7 - als Folge der Messung (MF-1077), nicht damit sie steigt. ## Abnahme `tests/test_convert_adf_hfe_roundtrip.c`, 5 Zusagen, ohne Korpusbedarf (die Pruef-ADF baut er selbst, jeder der 1760 Sektoren benennt sich). Rundlauf ADF -> HFE -> ADF: **0 abweichende Byte von 901 120**. ctest **446/446** gruen, ein benannter Skip (`test_freezer`). `check_consistency.py` ohne Befund. ## Kennzahl Angebotene Wandlungspfade **14 -> 15**, verlustfrei **6 -> 7**, Matrix-Eintraege 16 -> 17. Abgeleitet aus `src/core/uft_roundtrip.c`, nachgezogen in CLAUDE.md, docs/SUBSYSTEM_MATURITY.md und docs/MASTER_PLAN.md. Die Grenze steht dabei: die erzeugte HFE ist eine REKONSTRUKTION, keine Aufnahme. Luecken und Sektorverschraenkung sind erzeugt, nicht gemessen. Und HD (22 Sektoren) ist verdrahtet, aber UNGEPRUEFT - im Baum liegt keine HD-ADF. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BHdqfaZ5RFb1HPcw7NYoEY
1 parent a2d26ce commit 19b8283

13 files changed

Lines changed: 751 additions & 63 deletions

CLAUDE.md

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -169,14 +169,22 @@ Liest/schreibt Disk-Images von praktisch jedem 8-Bit- und 16-Bit-Computer:
169169
> Rückfrage. **Die Erkennung ist nicht schlechter geworden — sie war
170170
> vorher nur zuversichtlicher, als sie durfte.**
171171
172-
### 3. Format-Konvertierung (44 Pfade registriert, **14 angeboten**)
172+
### 3. Format-Konvertierung (44 Pfade registriert, **15 angeboten**)
173173

174174
> **Ehrlichkeits-Hinweis (MF-526, Zahlen neu gemessen MF-541):** die
175175
> Wandlungstabelle fuehrt **44** Paare. Die Rundlauf-Matrix hat **16**
176176
> Eintraege; **14** davon werden angeboten (zwei stehen als UNMOEGLICH):
177177
>
178-
> * **6 verlustfrei (je mit Messung)** — D64→D64, ADF→ADF, D64→G64,
179-
> IMG→HFE, **ATR→XFD**, **XFD→ATR**. Jedes einzelne mit einer
178+
> * **7 verlustfrei (je mit Messung)** — D64→D64, ADF→ADF, D64→G64,
179+
> IMG→HFE, **ATR→XFD**, **XFD→ATR**, **ADF→HFE** (neu MF-1081: der
180+
> AmigaDOS-Encoder, den MF-539 als fehlend benannt hat, ist da und an
181+
> einer ECHTEN Aufnahme abgenommen — aus `gw_amigados.hfe` dekodiert
182+
> und neu kodiert stehen **11 von 11 Sektoren byteidentisch** in der
183+
> Originalspur. Rundlauf ADF→HFE→ADF: **0 von 901 120 Byte**
184+
> abweichend, an einer Quelle MIT Inhalt — genau das fehlte MF-538,
185+
> dessen Ruecknahme an einer LEEREN Diskette scheiterte, deren
186+
> Nulllinie 0,08 % Abweichung wie eine fast perfekte Wandlung aussehen
187+
> liess. Die erzeugte HFE ist eine **Rekonstruktion, keine Aufnahme**). Jedes einzelne mit einer
180188
> Bit-Identitaets-Messung im Baum (MF-532/533/539/655), keines auf
181189
> Zusicherung. Die beiden Atari-Paare sind seit MF-655 die ersten
182190
> ihrer Familie in der Matrix: XFD ist das ATR ohne seinen
@@ -200,7 +208,7 @@ Liest/schreibt Disk-Images von praktisch jedem 8-Bit- und 16-Bit-Computer:
200208
> woertlich Fabrikation nennt. Der Kommentar an der Stelle sagte seit
201209
> UFT-A01, der Umweg sei geschlossen.
202210
>
203-
> Die Matrix hat **14** Eintraege. Es waren 17; die drei ohne Wandler
211+
> Die Matrix hat **15** Eintraege (MF-1081: ADF→HFE zurueck). Es waren 17; die drei ohne Wandler
204212
> (`SCP→IMD`, `IPF→ADF`, `STX→ST`) sind seit MF-567 entfernt. Zwei davon
205213
> standen hier seit MF-526 als „Verdikte ohne Konsumenten" — festgestellt
206214
> und stehen gelassen ist nicht behoben, und es war nicht folgenlos: ein
@@ -658,8 +666,8 @@ tests/ — 77 C-Tests + 1 Qt-Test
658666
- 138 Format-IDs, 137 Plugin-Definitionen (88 ausgeschrieben + 49 DSK-Makro;
659667
84 davon mit Registrar-Funktion, die niemand aufruft — MF-446; SSOT:
660668
`scripts/gen_format_list.py`), 44 Konvertierungspfade registriert /
661-
**14 angeboten**, davon **6 verlustfrei (je mit Messung)**
662-
(MF-541/567/655), 16 Roundtrip-Matrix-Einträge (SSOT in
669+
**15 angeboten**, davon **7 verlustfrei (je mit Messung)**
670+
(MF-541/567/655/1081), 17 Roundtrip-Matrix-Einträge (SSOT in
663671
`src/core/uft_roundtrip.c`;
664672
die Zahlen sind seit MF-541 abgeleitet, nicht gepflegt; MF-567 hat drei
665673
Urteile ohne Wandler entfernt)

UnifiedFloppyTool.pro

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -723,6 +723,7 @@ SOURCES += \
723723
src/core/uft_fs_registry.c \
724724
src/core/uft_capture.c \
725725
src/core/uft_mfm_encoder.c \
726+
src/core/uft_amiga_mfm_encoder.c \
726727
src/core/uft_detect_format_impl.c \
727728
src/core/uft_detect_buffer_impl.c \
728729
src/core/uft_probe_format_impl.c \

docs/MASTER_PLAN.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ Jede Session beginnt hier — nicht mit einem frischen Scan.
5858
hält nur die generierte Datei aktuell, nicht diese Aufzählung. (Die frühere
5959
„41 % mit realen Tests"-Angabe war hand-gepflegt und zählte synthetische
6060
Tests mit.)
61-
- 44 Konvertierungspfade registriert, 16 Roundtrip-Matrix-Einträge
61+
- 44 Konvertierungspfade registriert, 17 Roundtrip-Matrix-Einträge
6262
(MF-983: hier stand „45 … 13“. Beide Zahlen hingen seit MF-541 an
6363
einer Ableitung — aber nur in `CLAUDE.md`; dieses Dokument fuehrte
6464
sie von Hand und ist mitgedriftet. Seit MF-983 gilt die Ableitung

docs/STAND.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
|---|---|---|---|
1111
| ungeprüfte **Format-Plugins** (T3) | **2** von 86 | runter | `docs/VERIFICATION_TIERS.md` |
1212
| ungeprüfte **Dateisystem-Leser** | T0 1 · T1 4 · T1b 0 · T2 3 | runter | `docs/VERIFICATION_TIERS_FS.md` (MF-694) |
13-
| angebotene **Wandlungspfade** | **14**, davon 6 verlustfrei | rauf | `src/core/uft_roundtrip.c` |
13+
| angebotene **Wandlungspfade** | **15**, davon 7 verlustfrei | rauf | `src/core/uft_roundtrip.c` |
1414
| leckende Tests | 0 zu halten | null halten | ASan/UBSan in CI |
1515
| **Bench-Alter je Controller** | keine Hardware (MF-310) | runter | `docs/CAPABILITIES.md` |
1616

@@ -23,7 +23,7 @@
2323
- **7** Port-Erklärungen im Quellkopf, davon **1** ohne SPDX-Kopf
2424
- src/formats/amiga/uft_amiga_protection.c C99 port of XCopy Pro (1989-2011) 68000 Assembly algorithms:
2525
- SPDX außerhalb der Politik: **0**
26-
- Fließtext-Attributionen (Verdachts-Stufe, `LIZ-1`): **177**
26+
- Fließtext-Attributionen (Verdachts-Stufe, `LIZ-1`): **179**
2727
- Quarantäne: 2 vollzogen, 6 vorgemerkt, 2 aufgelöst (`docs/QUARANTINE.md`, abgeleitet)
2828

2929
### Gesichtete Fremd-Repos, nach Lizenzzone

docs/SUBSYSTEM_MATURITY.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ Legende: ✅ real verifiziert · 🟡 synthetisch/Spec-verifiziert · 🔴 ungep
2323
| **Kopierschutz-Erkennung** (55+ Schemes) | Heuristik-Detektoren + Titel-DB | 🟡 5 aktive Tests (synthetische Signaturen) + seit MF-377 **1 Scheme gegen realen geschützten Code**: CopyLock-ST Series 2 auf 14 echten 1989er-Loadern verifiziert (`magic32`/Offset exakt), Series 1 auf allen 16 realen Samples strukturell blind (PROT-1); Series 1 seit MF-380 **16/16** realer Loader (Erkennung am unverschlüsselten TVD-Prolog, Varianten a–e differenziert); zusammen also **30/30 CopyLock-Samples bei 0 Falsch-Positiven** über 15 Fremdschutzsysteme — PROT-1 geschlossen; C64-Pfad seit MF-381 durchgehend (G64 → GCR → `ufm_c64_metrics_from_gcr()` → Klassifikator, gegen reales VICE-G64 verifiziert, Negativkontrolle grün) — `has_custom_sync` seit MF-382 gegen nibtools belegt; seit MF-383 **beidseitig an realen Disks kontrolliert** (Bounty Bob 1985 geschützt: flaggt exakt 15 gepinnte Spuren; Alien Syndrome 1987 unprotected: 0 Hits) — offen bleiben dort die unbelegten Scheme-*Namen* (PROT-5, als Defekt festgenagelt) und die Halbspur-Blindheit des G64-Plugins, die Halbspur-Schutz und RapidLok unerreichbar macht (PROT-6); der fabrizierte `uft_dec0de_detect()` (0/34) wurde entfernt (PROT-3 ✓); 🔴 übrige 50+ Schemes weiterhin nie gegen echte Daten | test_protection_*, test_corpus_protection_copylock |
2424
| **Recovery-Pipeline** | Multiread-Voting, Adaptive, CRC-Korrektur | 🟡 1 aktiver Test (`test_recovery`, synthetisch) | ctest-Census |
2525
| **Filesystems** | AmigaDOS, FAT12/16, CBM DOS, Apple, CP/M, … | 🟡 FAT: 3 aktive Tests (inkl. FAT32/MBR seit MF-373); AmigaDOS-Validate + TI-99 seit MF-374 aktiv; CP/M-Test excluded (Header-Zwillinge, MF-374-Befund); übrige FS ungeprüft | ctest-Census + MF-373/374 |
26-
| **Format-Konverter** | 44 Pfade registriert | 🟡 16 Roundtrip-Matrix-Einträge (SSOT `uft_roundtrip.c`): 6 verlustfrei, 8 mit ausdrücklicher Zustimmung, 2 unmöglich → **14 angeboten**; die übrigen 30 weist das Preflight-Tor als ungeprüft ab 🔴 (MF-983: hier stand „45 Pfade … 13 Einträge … übrige 32“) | `src/core/uft_roundtrip.c` |
26+
| **Format-Konverter** | 44 Pfade registriert | 🟡 17 Roundtrip-Matrix-Einträge (SSOT `uft_roundtrip.c`): 7 verlustfrei, 8 mit ausdrücklicher Zustimmung, 2 unmöglich → **15 angeboten**; die übrigen 30 weist das Preflight-Tor als ungeprüft ab 🔴 (MF-983: hier stand „45 Pfade … 13 Einträge … übrige 32“) | `src/core/uft_roundtrip.c` |
2727
| **Audit-Trail / Forensik-Report** | 40+ Event-Typen, 6 Export-Formate, Hash-Chain | 🔴 kein aktiver Test des Audit-Trail-Subsystems | ctest-Census |
2828
| **Flux-Decoder/PLL-Kern** | MFM/FM/GCR, PLL, Sync | 🟡 PLL/MFM-Tests aktiv (synthetisch); nie gegen reale Flux-Captures | test_pll*, test_mfm* |
2929

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/**
2+
* @file uft_amiga_mfm_encoder.h
3+
* @brief AmigaDOS-Trackdisk-Spur aus Sektordaten kodieren (MF-1081)
4+
*
5+
* Hebt den Blocker aus **MF-539** auf und gibt damit `ADF -> HFE` zurueck.
6+
*
7+
* ── Was vorher da war und warum es nicht trug ───────────────────────────
8+
*
9+
* Der Baum hat einen **IBM-System-34**-Encoder (`uft_mfm_encoder.c`, seit
10+
* MF-938 belegt) und einen **AmigaDOS-Dekoder**
11+
* (`decode_amiga_sector()` in `src/flux/uft_flux_decoder.c`). Was fehlte,
12+
* war die Gegenrichtung fuer Amiga. `uft_format_convert_bitstream.c`
13+
* schrieb deshalb bis MF-539 eine IBM-kodierte Spur mit einem
14+
* AMIGA_MFM-Kopf darueber; gemessen gegen die echte Aufnahme
15+
* `tests/corpus_free/gw_amigados.hfe`:
16+
*
17+
* echte HFE UFTs Ausgabe
18+
* Sync 0x4489 22 0
19+
* haeufigstes Byte 0x55 (12498) 0x4E (6712)
20+
* rohe Nullbytes 0 von 12792 5969 von 12800
21+
*
22+
* Seit MF-539 wird die Wandlung deshalb ABGELEHNT statt eine Datei zu
23+
* erzeugen, die niemand lesen kann. Diese Datei liefert, was fehlte.
24+
*
25+
* ── Die Regel, und woher sie kommt ──────────────────────────────────────
26+
*
27+
* **Eigenstaendig geschrieben als exakte Umkehrung des baumeigenen,
28+
* bereits abgenommenen Dekoders** `decode_amiga_sector()`. Benannte
29+
* zweite Referenz: `mfmdisk` (Serge Vakulenko, **GPL-2.0**, im Baum unter
30+
* `tools/uft-scout/work/mfmdisk`, Klon `42f560badf`), dessen
31+
* `src/amiga.c` mit `mfm_write_amiga()` denselben Aufbau schreibt —
32+
* **gelesen, nicht uebernommen**; UFT steht selbst unter GPL-2.0, ein
33+
* Port waere also zulaessig, wird hier aber nicht gebraucht.
34+
*
35+
* Aufbau je Sektor, wie der Dekoder ihn liest:
36+
*
37+
* 2 Byte 0x00 (als MFM-Zellen), 2x Sync 0x4489 (ROH),
38+
* info(4) + label(16) + hdr-Pruefsumme(4) + daten-Pruefsumme(4)
39+
* + daten(512), jedes Feld ODD-Haelfte dann EVEN-Haelfte
40+
*
41+
* Je Byte gilt `roh_odd = (b >> 1) & 0x55`, `roh_even = b & 0x55`; die
42+
* Taktbits an den `0xAA`-Positionen kommen aus der MFM-Regel ueber den
43+
* ganzen Strom. Die Pruefsummen sind XOR der big-endian ROH-Langworte,
44+
* maskiert mit `0x55555555`.
45+
*
46+
* ── Warum die Taktbits eigens geprueft werden ───────────────────────────
47+
*
48+
* Der Dekoder maskiert nur die `0x55`-Positionen und **sieht die Taktbits
49+
* nicht an**. Ein Rundlauf durch die eigene Umkehrung koennte also
50+
* byteidentisch aufgehen, waehrend die Spur auf einem echten Laufwerk
51+
* unlesbar ist. Das ist woertlich die Falle aus MF-1079, wo jede
52+
* Spurlaenge auf das Bit stimmte und trotzdem 587 unmoegliche Zellpaare
53+
* darin standen. Die Abnahme misst deshalb die **Zellregel** mit:
54+
* keine zwei benachbarten 1-Zellen, hoechstens drei Nullen am Stueck.
55+
*
56+
* ── Grenze ──────────────────────────────────────────────────────────────
57+
*
58+
* Das **Sektor-Label** wird als 16 Nullbytes geschrieben, wenn der
59+
* Aufrufer keines liefert. Eine ADF traegt keine Labels; sie als Nullen
60+
* zu schreiben ist die Konvention jedes ADF-Schreibers und wird hier
61+
* benannt statt verschwiegen.
62+
*/
63+
#ifndef UFT_AMIGA_MFM_ENCODER_H
64+
#define UFT_AMIGA_MFM_ENCODER_H
65+
66+
#include <stddef.h>
67+
#include <stdint.h>
68+
69+
#ifdef __cplusplus
70+
extern "C" {
71+
#endif
72+
73+
/** Rohe MFM-Bytes je Sektor: 2 Luecke + 4 Sync + 2*(4+16+4+4+512). */
74+
#define UFT_AMIGA_SECTOR_MFM_BYTES (2u + 4u + 1080u)
75+
76+
/**
77+
* @brief Kodiert eine ganze AmigaDOS-Spur.
78+
*
79+
* @param data `sectors * 512` Byte Sektordaten, in Sektorreihenfolge
80+
* @param sectors Sektoren je Spur (11 = DD, 22 = HD)
81+
* @param track AmigaDOS-Spurnummer `cyl * 2 + head` (0..167)
82+
* @param labels optional `sectors * 16` Byte Sektor-Label, oder NULL
83+
* @param out Ausgabepuffer fuer die rohen MFM-Bytes
84+
* @param out_cap Groesse von `out`
85+
*
86+
* @return Zahl der geschriebenen Bytes, oder 0 bei ungueltiger Eingabe
87+
* oder zu kleinem Puffer. Gebraucht werden
88+
* `sectors * UFT_AMIGA_SECTOR_MFM_BYTES` Byte.
89+
*/
90+
size_t uft_amiga_mfm_encode_track(const uint8_t *data, unsigned sectors,
91+
unsigned track, const uint8_t *labels,
92+
uint8_t *out, size_t out_cap);
93+
94+
#ifdef __cplusplus
95+
}
96+
#endif
97+
98+
#endif /* UFT_AMIGA_MFM_ENCODER_H */

src/core/uft_amiga_mfm_encoder.c

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
/**
2+
* @file uft_amiga_mfm_encoder.c
3+
* @brief AmigaDOS-Trackdisk-Encoder (MF-1081, hebt MF-539 auf)
4+
*
5+
* Warum es diese Datei gibt, welche Referenzen sie benennt und was sie
6+
* NICHT tut, steht im Kopf von `include/uft/uft_amiga_mfm_encoder.h`.
7+
* Hier steht die Umsetzung.
8+
*
9+
* **Eigenstaendig geschrieben** als exakte Umkehrung von
10+
* `decode_amiga_sector()` (`src/flux/uft_flux_decoder.c`). Zweite
11+
* benannte Referenz: `mfmdisk`, Serge Vakulenko, **GPL-2.0**
12+
* (`tools/uft-scout/work/mfmdisk/src/amiga.c`, Klon `42f560badf`) —
13+
* gelesen, nicht uebernommen.
14+
*
15+
* ── Eine Zirkularitaet, die keine ist ───────────────────────────────────
16+
*
17+
* Die Datenpruefsumme steht auf der Spur VOR den Daten, wird aber ueber
18+
* sie gebildet. Der erste Entwurf hier rechnete sie deshalb voraus und
19+
* versuchte, den Takt-Kontext zwischen beiden Feldern mitzufuehren " ein
20+
* Aufwand, der auf einem Denkfehler beruhte.
21+
*
22+
* Der Dekoder vergleicht `hchk_stored == (hdr_csum & 0x55555555)`: die
23+
* Maske faellt am ENDE. Da XOR bitweise ist, tragen die Taktbits an den
24+
* `0xAA`-Positionen zum Ergebnis nichts bei " sie werden weggemaskt. Die
25+
* Pruefsumme haengt also **allein an den Datenbits** und ist vom
26+
* Takt-Kontext unabhaengig. Damit ist die Reihenfolge frei, und der
27+
* Vorausrechnungs-Block entfaellt ersatzlos.
28+
*/
29+
#include "uft/uft_amiga_mfm_encoder.h"
30+
31+
#include <string.h>
32+
33+
#define AMIGA_CKSUM_MASK 0x55555555u
34+
#define AMIGA_SECTOR_BYTES 512u
35+
#define AMIGA_MAX_SECTOR 21u
36+
#define AMIGA_MAX_TRACK 167u
37+
38+
/**
39+
* Ein rohes MFM-Byte bilden und dabei die TAKTBITS fuellen.
40+
*
41+
* `vorher` traegt das zuletzt geschriebene ZELLbit ueber Byte- und
42+
* Feldgrenzen hinweg. Die Regel ist die uebliche: ein Taktbit ist genau
43+
* dann 1, wenn das Datenbit davor UND das Datenbit danach 0 sind.
44+
*
45+
* Die Datenbits stehen hier schon an den `0x55`-Positionen des Bytes
46+
* (das ist die Amiga-odd/even-Aufteilung); gefuellt werden nur die
47+
* `0xAA`-Positionen. Genau diese Bits sieht der Dekoder NICHT an " sie
48+
* entscheiden aber, ob ein echtes Laufwerk die Spur lesen kann. Deshalb
49+
* misst die Abnahme die Zellregel eigens (MF-1079).
50+
*/
51+
static uint8_t takt_fuellen(uint8_t daten, int *vorher)
52+
{
53+
uint8_t aus = 0;
54+
int i;
55+
for (i = 3; i >= 0; i--) {
56+
const int d = (daten >> (i * 2)) & 1; /* Datenbit */
57+
const int takt = (!*vorher && !d) ? 1 : 0;
58+
aus = (uint8_t)((aus << 2) | (unsigned)((takt << 1) | d));
59+
*vorher = d;
60+
}
61+
return aus;
62+
}
63+
64+
/** Ein Byte roher Zellen unveraendert anhaengen (Sync, Luecke). */
65+
static void roh(uint8_t *out, size_t *n, uint8_t b, int *vorher)
66+
{
67+
out[(*n)++] = b;
68+
*vorher = b & 1;
69+
}
70+
71+
/** Die odd- bzw. even-Haelfte eines Bytes an den 0x55-Positionen. */
72+
static uint8_t haelfte(uint8_t b, int even)
73+
{
74+
return even ? (uint8_t)(b & 0x55u) : (uint8_t)((b >> 1) & 0x55u);
75+
}
76+
77+
/**
78+
* Ein odd/even-Feld schreiben: erst `nbytes` Rohbytes mit den ODD-Bits,
79+
* dann `nbytes` mit den EVEN-Bits. Umkehrung von `amiga_read_field()`.
80+
*
81+
* `csum` sammelt XOR der big-endian Langworte " gerechnet ueber die
82+
* DATENBITS, weil die Maske am Ende die Taktbits ohnehin entfernt
83+
* (siehe Dateikopf).
84+
*/
85+
static void feld(uint8_t *out, size_t *n, const uint8_t *in, size_t nbytes,
86+
uint32_t *csum, int *vorher)
87+
{
88+
int half;
89+
for (half = 0; half < 2; half++) { /* 0 = odd, 1 = even */
90+
uint32_t acc = 0;
91+
int acc_n = 0;
92+
size_t j;
93+
for (j = 0; j < nbytes; j++) {
94+
const uint8_t daten = haelfte(in[j], half);
95+
out[(*n)++] = takt_fuellen(daten, vorher);
96+
if (csum) {
97+
acc = (acc << 8) | daten;
98+
if (++acc_n == 4) { *csum ^= acc; acc = 0; acc_n = 0; }
99+
}
100+
}
101+
if (csum && acc_n) { /* nbytes nicht /4 */
102+
acc <<= 8 * (4 - acc_n);
103+
*csum ^= acc;
104+
}
105+
}
106+
}
107+
108+
/** Dieselbe Rechnung wie `feld()`, aber ohne zu schreiben. */
109+
static uint32_t feld_summe(const uint8_t *in, size_t nbytes)
110+
{
111+
uint32_t csum = 0;
112+
int half;
113+
for (half = 0; half < 2; half++) {
114+
uint32_t acc = 0;
115+
int acc_n = 0;
116+
size_t j;
117+
for (j = 0; j < nbytes; j++) {
118+
acc = (acc << 8) | haelfte(in[j], half);
119+
if (++acc_n == 4) { csum ^= acc; acc = 0; acc_n = 0; }
120+
}
121+
if (acc_n) {
122+
acc <<= 8 * (4 - acc_n);
123+
csum ^= acc;
124+
}
125+
}
126+
return csum;
127+
}
128+
129+
size_t uft_amiga_mfm_encode_track(const uint8_t *data, unsigned sectors,
130+
unsigned track, const uint8_t *labels,
131+
uint8_t *out, size_t out_cap)
132+
{
133+
size_t n = 0;
134+
unsigned s;
135+
int vorher = 0;
136+
137+
if (!data || !out || sectors == 0u) return 0;
138+
if (sectors - 1u > AMIGA_MAX_SECTOR) return 0;
139+
if (track > AMIGA_MAX_TRACK) return 0;
140+
if (out_cap < (size_t)sectors * UFT_AMIGA_SECTOR_MFM_BYTES) return 0;
141+
142+
for (s = 0; s < sectors; s++) {
143+
uint8_t info[4], label[16], hchk[4], dchk[4];
144+
uint32_t hdr_csum = 0, data_csum, ist = 0;
145+
const uint8_t *sek = data + (size_t)s * AMIGA_SECTOR_BYTES;
146+
147+
/* Luecke: zwei Byte 0x00, MFM-kodiert. */
148+
out[n++] = takt_fuellen(0x00u, &vorher);
149+
out[n++] = takt_fuellen(0x00u, &vorher);
150+
151+
/* Zwei Synchronworte 0x4489, ROH. Sie tragen absichtlich ein
152+
* FEHLENDES Taktbit " das ist die Marke, die ein Laufwerk sucht,
153+
* und sie darf deshalb nicht durch die Regel oben laufen. */
154+
roh(out, &n, 0x44u, &vorher);
155+
roh(out, &n, 0x89u, &vorher);
156+
roh(out, &n, 0x44u, &vorher);
157+
roh(out, &n, 0x89u, &vorher);
158+
159+
/* info = [0xFF][track][sector][Sektoren bis zur Luecke] */
160+
info[0] = 0xFFu;
161+
info[1] = (uint8_t)track;
162+
info[2] = (uint8_t)s;
163+
info[3] = (uint8_t)(sectors - s);
164+
165+
if (labels) memcpy(label, labels + (size_t)s * 16u, 16u);
166+
else memset(label, 0, sizeof label);
167+
168+
feld(out, &n, info, 4u, &hdr_csum, &vorher);
169+
feld(out, &n, label, 16u, &hdr_csum, &vorher);
170+
hdr_csum &= AMIGA_CKSUM_MASK;
171+
hchk[0] = (uint8_t)(hdr_csum >> 24);
172+
hchk[1] = (uint8_t)(hdr_csum >> 16);
173+
hchk[2] = (uint8_t)(hdr_csum >> 8);
174+
hchk[3] = (uint8_t)(hdr_csum);
175+
176+
data_csum = feld_summe(sek, AMIGA_SECTOR_BYTES) & AMIGA_CKSUM_MASK;
177+
dchk[0] = (uint8_t)(data_csum >> 24);
178+
dchk[1] = (uint8_t)(data_csum >> 16);
179+
dchk[2] = (uint8_t)(data_csum >> 8);
180+
dchk[3] = (uint8_t)(data_csum);
181+
182+
/* Die Pruefsummenfelder selbst zaehlen nicht mit (csum = NULL) "
183+
* eine Pruefsumme pruefsummt sich nicht selbst. */
184+
feld(out, &n, hchk, 4u, NULL, &vorher);
185+
feld(out, &n, dchk, 4u, NULL, &vorher);
186+
187+
feld(out, &n, sek, AMIGA_SECTOR_BYTES, &ist, &vorher);
188+
189+
/* Sicherung, keine Zierde: geht die Summe hier nicht auf, stimmt
190+
* die Rechnung nicht, und ein Sektor mit falscher Pruefsumme
191+
* waere eine stille Luege. Dann wird NICHTS ausgegeben. */
192+
if ((ist & AMIGA_CKSUM_MASK) != data_csum) return 0;
193+
}
194+
return n;
195+
}

0 commit comments

Comments
 (0)