From 02e3700828d54e798df1ac978d99878684d0427e Mon Sep 17 00:00:00 2001 From: overcuriousity Date: Wed, 12 Nov 2025 20:50:02 +0100 Subject: [PATCH] exFAT update --- README.md | 26 +++++- webroot/js/filesystems/exfat.js | 160 ++++++++++++++++++++++++++++++++ webroot/js/main.js | 4 +- 3 files changed, 188 insertions(+), 2 deletions(-) create mode 100644 webroot/js/filesystems/exfat.js diff --git a/README.md b/README.md index c9901ba..2bd6cdb 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,13 @@ fscalc ist eine einfache, statische Weboberfläche zur Berechnung von Hex-Offset ## Hauptfunktionen - Interaktive Berechnung von Hex-Offsets für verschiedene Dateisysteme + - **FAT12/16**: Klassische FAT-Dateisysteme mit festem Root-Directory + - **FAT32**: Erweiterte FAT-Version mit flexiblem Root-Directory + - **NTFS**: Windows New Technology File System + - **exFAT**: Extensible FAT mit Zeitstempel-Konverter - Übersichtliche Anzeige und Export von berechneten Werten - Integrierte Hilfswerkzeuge und Utilities (z.B. eingebettete CyberChef-Instanz) +- Hex-Rechner für schnelle Berechnungen ## Voraussetzungen @@ -40,4 +45,23 @@ Alternativ kann jeder andere statische Server (z. B. `live-server`, `http-server - Öffne die Startseite und wähle das gewünschte Dateisystem bzw. die Berechnungsoption. - Gib die Ausgangsadresse/den Sektor und die benötigten Parameter ein. -- Die berechneten Hex-Offsets werden sofort berechnet und lassen sich kopieren oder lokal weiterverarbeiten. \ No newline at end of file +- Die berechneten Hex-Offsets werden sofort berechnet und lassen sich kopieren oder lokal weiterverarbeiten. + +### exFAT-Spezifika + +Der exFAT-Tab enthält zusätzlich einen **Zeitstempel-Konverter**, der die komplexen exFAT-Zeitstempel dekodiert: +- Zeitstempel-Wert (4 Bytes) aus Datei-Einträgen +- Millisekunden-Feld (1 Byte) +- Zeitzone-Feld (1 Byte) mit UTC/Lokal-Kennzeichnung + +Detaillierte Anleitung: Siehe `exFAT_ANLEITUNG.md` + +## Dateisystem-Referenzen + +- **FAT12/16/32**: Dokumentation in CheatSheet.md +- **NTFS**: Interne NTFS-Strukturen und MFT-Berechnungen +- **exFAT**: Vollständige Anleitung in `exFAT_ANLEITUNG.md` und Praktikumsdokument `Praktikum 0x05 exFAT (kommentiert).pdf` + +## Lizenz + +BSD-3-Clause (siehe LICENSE) diff --git a/webroot/js/filesystems/exfat.js b/webroot/js/filesystems/exfat.js new file mode 100644 index 0000000..9a425e1 --- /dev/null +++ b/webroot/js/filesystems/exfat.js @@ -0,0 +1,160 @@ +// exFAT filesystem implementation + +import { BaseFilesystem } from './base.js'; +import { checkDependencies, updateResultItem } from '../utils.js'; + +export class ExFATFilesystem extends BaseFilesystem { + constructor() { + super('exFAT', [ + { + id: 'exfat', + name: 'exFAT', + constants: [ + { id: 'partitionStartExfat', label: 'Partitions-Start (GPT-Offset)', unit: 'Bytes', default: '0x100000' }, + { id: 'sectorSizeExfat', label: 'Sektor-Größe 2^n (Offset 0x6C)', unit: 'Bytes (2^n)', default: '0x09' }, + { id: 'clusterSizeSectorsExfat', label: 'Cluster-Größe 2^n (Offset 0x6D)', unit: 'Sektoren (2^n)', default: '0x03' }, + { id: 'numFATsExfat', label: 'Anzahl FATs (Offset 0x6E)', unit: 'Anzahl', default: '0x01' } + ], + inputs: [ + { id: 'fatPositionExfat', label: 'FAT-Position (Offset 0x50)', unit: 'Sektoren', placeholder: '0x80' }, + { id: 'dataAreaPositionExfat', label: 'Daten-Bereich Position (Offset 0x58)', unit: 'Sektoren', placeholder: '0x90' }, + { id: 'rootDirClusterExfat', label: 'Wurzelverzeichnis Cluster (Offset 0x60)', unit: 'Cluster', placeholder: '0x05' }, + { id: 'clusterNumberExfat', label: 'Cluster-Nummer', unit: 'Nummer', placeholder: '0x05' } + ], + resultGroups: [ + { + name: 'Boot-Struktur', + results: [ + { id: 'backupBootExfat', label: 'Backup Boot-Sektor', dependencies: ['partitionStartExfat'], formula: 'Partitions-Start + (12 Sektoren × Sektorgröße)' } + ] + }, + { + name: 'FAT-Bereich', + results: [ + { id: 'fatStartExfat', label: 'FAT-Bereich Anfang', dependencies: ['partitionStartExfat', 'fatPositionExfat', 'sectorSizeExfat'], formula: 'Partitions-Start + (FAT-Position × Sektorgröße)' }, + { id: 'dataAreaStartExfat', label: 'Daten-Bereich Anfang', dependencies: ['partitionStartExfat', 'dataAreaPositionExfat', 'sectorSizeExfat'], formula: 'Partitions-Start + (Daten-Position × Sektorgröße)' } + ] + }, + { + name: 'Cluster-Berechnungen', + results: [ + { id: 'clusterSizeBytesExfat', label: 'Cluster-Größe (Bytes)', dependencies: ['sectorSizeExfat', 'clusterSizeSectorsExfat'], formula: '2^Sektor-Größe × 2^Cluster-Größe' }, + { id: 'rootDirOffsetExfat', label: 'Wurzelverzeichnis Offset', dependencies: ['partitionStartExfat', 'dataAreaPositionExfat', 'sectorSizeExfat', 'clusterSizeSectorsExfat', 'rootDirClusterExfat'], formula: 'Daten-Bereich + (Cluster - 2) × Cluster-Größe' }, + { id: 'clusterOffsetExfat', label: 'Spezifischer Cluster Offset', dependencies: ['partitionStartExfat', 'dataAreaPositionExfat', 'sectorSizeExfat', 'clusterSizeSectorsExfat', 'clusterNumberExfat'], formula: 'Daten-Bereich + (Cluster - 2) × Cluster-Größe' }, + { id: 'fatEntryOffsetExfat', label: 'FAT-Eintrag Offset', dependencies: ['partitionStartExfat', 'fatPositionExfat', 'sectorSizeExfat', 'clusterNumberExfat'], formula: 'FAT-Start + (Cluster × 4)' } + ] + } + ], + formulas: [ + { name: 'Backup Boot-Sektor', expression: 'Partitions-Start + (12 × Sektorgröße)' }, + { name: 'Sektorgröße (Bytes)', expression: '2^n (aus Offset 0x6C)' }, + { name: 'Cluster-Größe (Sektoren)', expression: '2^n (aus Offset 0x6D)' }, + { name: 'Cluster-Größe (Bytes)', expression: 'Sektorgröße × Cluster-Größe (Sektoren)' }, + { name: 'FAT-Bereich Anfang', expression: 'Partitions-Start + (FAT-Position × Sektorgröße)' }, + { name: 'Daten-Bereich Anfang', expression: 'Partitions-Start + (Daten-Position × Sektorgröße)' }, + { name: 'Wurzelverzeichnis Offset', expression: 'Daten-Bereich + ((Cluster - 2) × Cluster-Größe)' }, + { name: 'Cluster Offset', expression: 'Daten-Bereich + ((Cluster-Nummer - 2) × Cluster-Größe)' }, + { name: 'FAT-Eintrag Offset', expression: 'FAT-Start + (Cluster-Nummer × 4)' } + ] + } + ]); + } + + calculate(variantId) { + if (variantId !== 'exfat') return; + + const values = {}; + const results = {}; + + // Gather all input values for this variant + const variant = this.variants.find(v => v.id === variantId); + if (!variant) return; + + [...variant.constants, ...variant.inputs].forEach(field => { + const el = document.getElementById(field.id); + if (el) { + let val = el.value; + if (val && val.startsWith('0x')) { + values[field.id] = parseInt(val, 16); + } else { + values[field.id] = Number(val) || 0; + } + } + }); + + this.calculateExFAT(values, results); + } + + calculateExFAT(values, results) { + // Calculate actual sector size from 2^n + const sectorSize = Math.pow(2, values.sectorSizeExfat); + // Calculate actual cluster size in sectors from 2^n + const clusterSizeSectors = Math.pow(2, values.clusterSizeSectorsExfat); + + // Backup Boot-Sektor (12 sectors after partition start) + if (checkDependencies(['partitionStartExfat'])) { + results.backupBoot = values.partitionStartExfat + (12 * sectorSize); + updateResultItem('backupBootExfat', results.backupBoot, true, + `0x${values.partitionStartExfat.toString(16)} + (12 × 0x${sectorSize.toString(16)}) = 0x${results.backupBoot.toString(16)}`); + } else { + updateResultItem('backupBootExfat', 0, false); + } + + // FAT-Bereich Anfang + if (checkDependencies(['partitionStartExfat', 'fatPositionExfat', 'sectorSizeExfat'])) { + results.fatStart = values.partitionStartExfat + (values.fatPositionExfat * sectorSize); + results.fatStartSector = values.fatPositionExfat; + updateResultItem('fatStartExfat', { bytes: results.fatStart, sectors: results.fatStartSector }, true, + `0x${values.partitionStartExfat.toString(16)} + (0x${values.fatPositionExfat.toString(16)} × 0x${sectorSize.toString(16)}) = 0x${results.fatStart.toString(16)}`); + } else { + updateResultItem('fatStartExfat', 0, false); + } + + // Daten-Bereich Anfang + if (checkDependencies(['partitionStartExfat', 'dataAreaPositionExfat', 'sectorSizeExfat'])) { + results.dataAreaStart = values.partitionStartExfat + (values.dataAreaPositionExfat * sectorSize); + results.dataAreaStartSector = values.dataAreaPositionExfat; + updateResultItem('dataAreaStartExfat', { bytes: results.dataAreaStart, sectors: results.dataAreaStartSector }, true, + `0x${values.partitionStartExfat.toString(16)} + (0x${values.dataAreaPositionExfat.toString(16)} × 0x${sectorSize.toString(16)}) = 0x${results.dataAreaStart.toString(16)}`); + } else { + updateResultItem('dataAreaStartExfat', 0, false); + } + + // Cluster-Größe in Bytes + if (checkDependencies(['sectorSizeExfat', 'clusterSizeSectorsExfat'])) { + results.clusterSizeBytes = sectorSize * clusterSizeSectors; + updateResultItem('clusterSizeBytesExfat', results.clusterSizeBytes, true, + `2^${values.sectorSizeExfat} × 2^${values.clusterSizeSectorsExfat} = 0x${sectorSize.toString(16)} × 0x${clusterSizeSectors.toString(16)} = 0x${results.clusterSizeBytes.toString(16)}`); + } else { + updateResultItem('clusterSizeBytesExfat', 0, false); + } + + // Wurzelverzeichnis Offset (beachtet Index-Verschiebung von -2) + if (checkDependencies(['partitionStartExfat', 'dataAreaPositionExfat', 'sectorSizeExfat', 'clusterSizeSectorsExfat', 'rootDirClusterExfat']) && results.dataAreaStart !== undefined && results.clusterSizeBytes !== undefined) { + results.rootDirOffset = results.dataAreaStart + ((values.rootDirClusterExfat - 2) * results.clusterSizeBytes); + updateResultItem('rootDirOffsetExfat', results.rootDirOffset, true, + `0x${results.dataAreaStart.toString(16)} + ((0x${values.rootDirClusterExfat.toString(16)} - 0x2) × 0x${results.clusterSizeBytes.toString(16)}) = 0x${results.rootDirOffset.toString(16)}`); + } else { + updateResultItem('rootDirOffsetExfat', 0, false); + } + + // Spezifischer Cluster Offset (beachtet Index-Verschiebung von -2) + if (checkDependencies(['partitionStartExfat', 'dataAreaPositionExfat', 'sectorSizeExfat', 'clusterSizeSectorsExfat', 'clusterNumberExfat']) && results.dataAreaStart !== undefined && results.clusterSizeBytes !== undefined) { + results.clusterOffset = results.dataAreaStart + ((values.clusterNumberExfat - 2) * results.clusterSizeBytes); + updateResultItem('clusterOffsetExfat', results.clusterOffset, true, + `0x${results.dataAreaStart.toString(16)} + ((0x${values.clusterNumberExfat.toString(16)} - 0x2) × 0x${results.clusterSizeBytes.toString(16)}) = 0x${results.clusterOffset.toString(16)}`); + } else { + updateResultItem('clusterOffsetExfat', 0, false); + } + + // FAT-Eintrag Offset (32-Bit = 4 Bytes pro Eintrag) + if (checkDependencies(['partitionStartExfat', 'fatPositionExfat', 'sectorSizeExfat', 'clusterNumberExfat']) && results.fatStart !== undefined) { + const entrySize = 4; // exFAT uses 4 bytes (32-bit) per FAT entry + results.fatEntryOffset = results.fatStart + (values.clusterNumberExfat * entrySize); + updateResultItem('fatEntryOffsetExfat', results.fatEntryOffset, true, + `0x${results.fatStart.toString(16)} + (0x${values.clusterNumberExfat.toString(16)} × ${entrySize}) = 0x${results.fatEntryOffset.toString(16)}`); + } else { + updateResultItem('fatEntryOffsetExfat', 0, false); + } + } +} diff --git a/webroot/js/main.js b/webroot/js/main.js index 885b081..17ce81a 100644 --- a/webroot/js/main.js +++ b/webroot/js/main.js @@ -4,13 +4,15 @@ import { setupTooltips, setupCopyButtons } from './utils.js'; import { Calculator } from './calculator.js'; import { FAT12_16Filesystem, FAT32Filesystem } from './filesystems/fat.js'; import { NTFSFilesystem } from './filesystems/ntfs.js'; +import { ExFATFilesystem } from './filesystems/exfat.js'; class FilesystemCalculator { constructor() { this.filesystems = [ new FAT12_16Filesystem(), new FAT32Filesystem(), - new NTFSFilesystem() + new NTFSFilesystem(), + new ExFATFilesystem() ]; this.calculator = new Calculator(); this.activeTab = null;