exFAT update
This commit is contained in:
26
README.md
26
README.md
@@ -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
|
||||||
|
|
||||||
@@ -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.
|
- Ö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)
|
||||||
|
|||||||
160
webroot/js/filesystems/exfat.js
Normal file
160
webroot/js/filesystems/exfat.js
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user