exFAT update

This commit is contained in:
overcuriousity
2025-11-12 20:50:02 +01:00
parent e2b26c292c
commit 02e3700828
3 changed files with 188 additions and 2 deletions

View File

@@ -4,8 +4,13 @@ fscalc ist eine einfache, statische Weboberfläche zur Berechnung von Hex-Offset
## Hauptfunktionen ## Hauptfunktionen
- Interaktive Berechnung von Hex-Offsets für verschiedene Dateisysteme - 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 - Übersichtliche Anzeige und Export von berechneten Werten
- Integrierte Hilfswerkzeuge und Utilities (z.B. eingebettete CyberChef-Instanz) - Integrierte Hilfswerkzeuge und Utilities (z.B. eingebettete CyberChef-Instanz)
- Hex-Rechner für schnelle Berechnungen
## Voraussetzungen ## Voraussetzungen
@@ -41,3 +46,22 @@ 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. - Öffne die Startseite und wähle das gewünschte Dateisystem bzw. die Berechnungsoption.
- Gib die Ausgangsadresse/den Sektor und die benötigten Parameter ein. - 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. - 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)

View File

@@ -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);
}
}
}

View File

@@ -4,13 +4,15 @@ import { setupTooltips, setupCopyButtons } from './utils.js';
import { Calculator } from './calculator.js'; import { Calculator } from './calculator.js';
import { FAT12_16Filesystem, FAT32Filesystem } from './filesystems/fat.js'; import { FAT12_16Filesystem, FAT32Filesystem } from './filesystems/fat.js';
import { NTFSFilesystem } from './filesystems/ntfs.js'; import { NTFSFilesystem } from './filesystems/ntfs.js';
import { ExFATFilesystem } from './filesystems/exfat.js';
class FilesystemCalculator { class FilesystemCalculator {
constructor() { constructor() {
this.filesystems = [ this.filesystems = [
new FAT12_16Filesystem(), new FAT12_16Filesystem(),
new FAT32Filesystem(), new FAT32Filesystem(),
new NTFSFilesystem() new NTFSFilesystem(),
new ExFATFilesystem()
]; ];
this.calculator = new Calculator(); this.calculator = new Calculator();
this.activeTab = null; this.activeTab = null;