From d38b4c7813dfcaa209bb80a03510ac07a5f3c802 Mon Sep 17 00:00:00 2001 From: mstoeck3 Date: Sun, 7 Dec 2025 20:12:48 +0100 Subject: [PATCH] multiple inconsistencies fixed (AI) --- webroot/js/calculator.js | 8 ++- webroot/js/filesystems/base.js | 43 +++++++++-- webroot/js/filesystems/exfat.js | 36 ++++++++++ webroot/js/filesystems/ext.js | 123 +++++++++++++++++++++++--------- webroot/js/filesystems/fat.js | 64 +++++++++++++++++ webroot/js/filesystems/ntfs.js | 33 +++++++++ webroot/js/main.js | 20 ++++-- webroot/js/utils.js | 6 -- 8 files changed, 283 insertions(+), 50 deletions(-) diff --git a/webroot/js/calculator.js b/webroot/js/calculator.js index 510fdfb..7d95c85 100644 --- a/webroot/js/calculator.js +++ b/webroot/js/calculator.js @@ -140,11 +140,17 @@ export class Calculator { } setupEventListeners() { - // Event delegation for calculator buttons + // Event delegation for calculator buttons - only handle from active tab's calculator document.addEventListener('click', (e) => { // Only handle clicks on calculator buttons if (!e.target.classList.contains('calc-btn')) return; + // Verify the button is in the active tab's calculator (if app is initialized) + const activeContent = document.querySelector('.tab-content.active'); + if (activeContent && !activeContent.contains(e.target)) { + return; // Button is not in active tab's calculator + } + const button = e.target; if (button.classList.contains('number')) { diff --git a/webroot/js/filesystems/base.js b/webroot/js/filesystems/base.js index 21dbc8d..6d65363 100644 --- a/webroot/js/filesystems/base.js +++ b/webroot/js/filesystems/base.js @@ -22,8 +22,8 @@ export class BaseFilesystem { // Helper function to format labels with offset information const formatLabel = (label) => { - // Match patterns like "(Boot-Offset 0x00)", "(MFT-Header-Offset 0x14)", "(Offset 0x00)" and wrap offset info - const offsetPattern = /(\((Boot-Offset|MFT-Header-Offset|Offset) [^)]+\))/g; + // Match patterns like "(Boot-Offset 0x00)", "(MFT-Header-Offset 0x14)", "(Superblock-Offset 0x00)", "(GDT-Offset 0x08)", "(Offset 0x00)" and wrap offset info + const offsetPattern = /(\((Boot-Offset|MFT-Header-Offset|Superblock-Offset|GDT-Offset|Offset) [^)]+\))/g; return label.replace(offsetPattern, '$1'); }; @@ -57,8 +57,8 @@ export class BaseFilesystem { // Helper function to format labels with offset information const formatLabel = (label) => { - // Match patterns like "(Boot-Offset 0x00)", "(MFT-Header-Offset 0x14)", "(Offset 0x00)" and wrap offset info - const offsetPattern = /(\((Boot-Offset|MFT-Header-Offset|Offset) [^)]+\))/g; + // Match patterns like "(Boot-Offset 0x00)", "(MFT-Header-Offset 0x14)", "(Superblock-Offset 0x00)", "(GDT-Offset 0x08)", "(Offset 0x00)" and wrap offset info + const offsetPattern = /(\((Boot-Offset|MFT-Header-Offset|Superblock-Offset|GDT-Offset|Offset) [^)]+\))/g; return label.replace(offsetPattern, '$1'); }; @@ -164,6 +164,35 @@ export class BaseFilesystem { throw new Error('calculate method must be implemented by subclass'); } + // Validate that input values are within reasonable ranges + validateInputRanges(variantId, values) { + const variant = this.variants.find(v => v.id === variantId); + if (!variant) return { valid: true, errors: [] }; + + const errors = []; + + // Basic sanity checks that can be applied to all filesystems + // Check for extremely large values that would cause issues + const maxSafeValue = Number.MAX_SAFE_INTEGER / 1024; // Allow some headroom + + Object.entries(values).forEach(([key, value]) => { + if (typeof value === 'number' && value > maxSafeValue) { + errors.push(`${key}: Wert zu groß (${value})`); + } + }); + + return { + valid: errors.length === 0, + errors: errors + }; + } + + // Abstract method for subclasses to override with specific validation + validateFilesystemSpecific(variantId, values) { + // Subclasses can override this for filesystem-specific validation + return { valid: true, errors: [] }; + } + // Setup input event listeners for this filesystem setupInputListeners(variantId) { const variant = this.variants.find(v => v.id === variantId); @@ -196,7 +225,8 @@ export class BaseFilesystem { variant.constants.forEach(constant => { const element = document.getElementById(constant.id); if (element) { - values[constant.id] = parseHex(element.value) || 0; + const parsedValue = parseHex(element.value); + values[constant.id] = parsedValue !== null ? parsedValue : 0; } }); @@ -204,7 +234,8 @@ export class BaseFilesystem { variant.inputs.forEach(input => { const element = document.getElementById(input.id); if (element) { - values[input.id] = parseHex(element.value); + const parsedValue = parseHex(element.value); + values[input.id] = parsedValue !== null ? parsedValue : 0; } }); diff --git a/webroot/js/filesystems/exfat.js b/webroot/js/filesystems/exfat.js index 549e998..7c3b1e9 100644 --- a/webroot/js/filesystems/exfat.js +++ b/webroot/js/filesystems/exfat.js @@ -64,11 +64,47 @@ export class ExFATFilesystem extends BaseFilesystem { if (variantId !== 'exfat') return; const values = this.getInputValues(variantId); + + // Validate input ranges + const validation = this.validateFilesystemSpecific(variantId, values); + if (!validation.valid) { + console.warn('exFAT validation errors:', validation.errors); + } + const results = {}; this.calculateExFAT(values, results); } + validateFilesystemSpecific(variantId, values) { + if (variantId !== 'exfat') return { valid: true, errors: [] }; + + const errors = []; + + // Validate cluster number is reasonable + if (values.clusterNumberExfat !== undefined && values.clusterNumberExfat < 2) { + errors.push('clusterNumberExfat: Cluster-Nummer muss >= 2 sein'); + } + + // Validate sector and cluster size exponents + if (values.sectorSizeExfat !== undefined) { + if (values.sectorSizeExfat < 0 || values.sectorSizeExfat > 15) { + errors.push('sectorSizeExfat: Exponent muss zwischen 0 und 15 liegen'); + } + } + + if (values.clusterSizeSectorsExfat !== undefined) { + if (values.clusterSizeSectorsExfat < 0 || values.clusterSizeSectorsExfat > 25) { + errors.push('clusterSizeSectorsExfat: Exponent muss zwischen 0 und 25 liegen'); + } + } + + return { + valid: errors.length === 0, + errors: errors + }; + } + calculateExFAT(values, results) { // Calculate actual sector size from 2^n const sectorSize = Math.pow(2, values.sectorSizeExfat); diff --git a/webroot/js/filesystems/ext.js b/webroot/js/filesystems/ext.js index 1a13b1f..72b8d3f 100644 --- a/webroot/js/filesystems/ext.js +++ b/webroot/js/filesystems/ext.js @@ -97,26 +97,71 @@ export class EXTFilesystem extends BaseFilesystem { if (variantId !== 'ext') return; const values = this.getInputValues(variantId); + + // Validate input ranges specific to EXT filesystem + const validation = this.validateFilesystemSpecific(variantId, values); + if (!validation.valid) { + console.warn('EXT validation errors:', validation.errors); + // Continue with calculation but log warnings + } + const results = {}; this.calculateEXT(values, results); } + validateFilesystemSpecific(variantId, values) { + if (variantId !== 'ext') return { valid: true, errors: [] }; + + const errors = []; + + // Validate blockgroup number doesn't exceed total blockgroups + if (values.blockgroupNumberEXT !== undefined && values.totalBlocksEXT !== undefined && values.blocksPerGroupEXT !== undefined && values.blocksPerGroupEXT > 0) { + const totalBlockgroups = Math.ceil(values.totalBlocksEXT / values.blocksPerGroupEXT); + if (values.blockgroupNumberEXT >= totalBlockgroups) { + errors.push(`blockgroupNumberEXT: ${values.blockgroupNumberEXT} >= Gesamtblockgruppen: ${totalBlockgroups}`); + } + } + + // Validate inode number isn't 0 (no inode 0 exists) + if (values.targetInodeEXT !== undefined && values.targetInodeEXT === 0) { + errors.push('targetInodeEXT: Inode 0 existiert nicht'); + } + + // Validate block and inode sizes are reasonable + if (values.blockSizeExpEXT !== undefined) { + const blockSize = Math.pow(2, 10 + values.blockSizeExpEXT); + if (blockSize < 1024 || blockSize > 65536) { + errors.push(`Block-Größe: ${blockSize} außerhalb akzeptabeler Grenzen (1024-65536)`); + } + } + + if (values.inodeSizeEXT !== undefined) { + if (values.inodeSizeEXT < 128 || values.inodeSizeEXT > 4096) { + errors.push(`Inode-Größe: ${values.inodeSizeEXT} außerhalb akzeptabeler Grenzen (128-4096)`); + } + } + + return { + valid: errors.length === 0, + errors: errors + }; + } + calculateEXT(values, results) { + try { // Superblock Position if (checkDependencies(['partitionStartEXT', 'superblockOffsetEXT'])) { results.superblockPos = values.partitionStartEXT + values.superblockOffsetEXT; - updateResultItem('superblockPosEXT', results.superblockPos, true, - `0x${values.partitionStartEXT.toString(16)} + 0x${values.superblockOffsetEXT.toString(16)} = 0x${results.superblockPos.toString(16)}`); + updateResultItem('superblockPosEXT', { bytes: results.superblockPos, other: `Block 1 (bei 1024-Byte Blöcken)` }, true, + `0x${Math.floor(values.partitionStartEXT).toString(16).toUpperCase()} + 0x${Math.floor(values.superblockOffsetEXT).toString(16).toUpperCase()} = 0x${Math.floor(results.superblockPos).toString(16).toUpperCase()}`); } else { updateResultItem('superblockPosEXT', 0, false); - } - - // Block Size (2^(10+n)) + } // Block Size (2^(10+n)) if (checkDependencies(['blockSizeExpEXT'])) { results.blockSize = Math.pow(2, 10 + values.blockSizeExpEXT); - updateResultItem('blockSizeBytesEXT', results.blockSize, true, - `2^(10 + ${values.blockSizeExpEXT}) = 2^${10 + values.blockSizeExpEXT} = 0x${results.blockSize.toString(16)} (${results.blockSize} Bytes)`); + updateResultItem('blockSizeBytesEXT', { bytes: results.blockSize, other: `2^${10 + values.blockSizeExpEXT}` }, true, + `2^(10 + ${values.blockSizeExpEXT}) = 2^${10 + values.blockSizeExpEXT} = 0x${Math.floor(results.blockSize).toString(16).toUpperCase()} (${results.blockSize} Bytes)`); } else { updateResultItem('blockSizeBytesEXT', 0, false); } @@ -132,13 +177,18 @@ export class EXTFilesystem extends BaseFilesystem { // GDT Start (block after superblock) if (checkDependencies(['partitionStartEXT', 'superblockOffsetEXT', 'blockSizeExpEXT']) && results.blockSize !== undefined) { - // If block size is 1024 (0x400), superblock is in block 1, GDT starts at block 2 - // Otherwise, superblock is in block 0, GDT starts at block 1 + // Special handling for 1024-byte block size: + // - Superblock is in block 1 (not block 0) because it starts at offset 0x400 + // - GDT follows in block 2 + // For other block sizes (2048, 4096, ...): + // - Superblock is in block 0 (starts at offset 0) + // - GDT follows in block 1 const superblockBlock = (results.blockSize === 1024) ? 1 : 0; const gdtBlock = superblockBlock + 1; results.gdtStart = values.partitionStartEXT + (gdtBlock * results.blockSize); - updateResultItem('gdtStartEXT', results.gdtStart, true, - `0x${values.partitionStartEXT.toString(16)} + (Block ${gdtBlock} × 0x${results.blockSize.toString(16)}) = 0x${results.gdtStart.toString(16)}`); + const gdtBlockNum = Math.floor(results.gdtStart / results.blockSize) - Math.floor(values.partitionStartEXT / results.blockSize); + updateResultItem('gdtStartEXT', { bytes: results.gdtStart, other: `Block ${gdtBlock}` }, true, + `0x${Math.floor(values.partitionStartEXT).toString(16).toUpperCase()} + (Block ${gdtBlock} × 0x${Math.floor(results.blockSize).toString(16).toUpperCase()}) = 0x${Math.floor(results.gdtStart).toString(16).toUpperCase()}`); } else { updateResultItem('gdtStartEXT', 0, false); } @@ -146,8 +196,8 @@ export class EXTFilesystem extends BaseFilesystem { // GDT Entry for specific Blockgroup if (checkDependencies(['blockgroupNumberEXT', 'groupDescSizeEXT']) && results.gdtStart !== undefined) { results.gdtBlockgroupOffset = results.gdtStart + (values.blockgroupNumberEXT * values.groupDescSizeEXT); - updateResultItem('gdtBlockgroupOffsetEXT', results.gdtBlockgroupOffset, true, - `0x${results.gdtStart.toString(16)} + (0x${values.blockgroupNumberEXT.toString(16)} × 0x${values.groupDescSizeEXT.toString(16)}) = 0x${results.gdtBlockgroupOffset.toString(16)}`); + updateResultItem('gdtBlockgroupOffsetEXT', { bytes: results.gdtBlockgroupOffset, other: `Eintrag ${values.blockgroupNumberEXT}` }, true, + `0x${Math.floor(results.gdtStart).toString(16).toUpperCase()} + (0x${Math.floor(values.blockgroupNumberEXT).toString(16).toUpperCase()} × 0x${Math.floor(values.groupDescSizeEXT).toString(16).toUpperCase()}) = 0x${Math.floor(results.gdtBlockgroupOffset).toString(16).toUpperCase()}`); } else { updateResultItem('gdtBlockgroupOffsetEXT', 0, false); } @@ -173,8 +223,8 @@ export class EXTFilesystem extends BaseFilesystem { // Inode Table Start if (checkDependencies(['partitionStartEXT', 'inodeTableBlockGroupEXT', 'blockSizeExpEXT']) && results.blockSize !== undefined) { results.inodeTableStart = values.partitionStartEXT + (values.inodeTableBlockGroupEXT * results.blockSize); - updateResultItem('inodeTableStartEXT', results.inodeTableStart, true, - `0x${values.partitionStartEXT.toString(16)} + (0x${values.inodeTableBlockGroupEXT.toString(16)} × 0x${results.blockSize.toString(16)}) = 0x${results.inodeTableStart.toString(16)}`); + updateResultItem('inodeTableStartEXT', { bytes: results.inodeTableStart, other: `Block ${Math.floor(values.inodeTableBlockGroupEXT)}` }, true, + `0x${Math.floor(values.partitionStartEXT).toString(16).toUpperCase()} + (0x${Math.floor(values.inodeTableBlockGroupEXT).toString(16).toUpperCase()} × 0x${Math.floor(results.blockSize).toString(16).toUpperCase()}) = 0x${Math.floor(results.inodeTableStart).toString(16).toUpperCase()}`); } else { updateResultItem('inodeTableStartEXT', 0, false); } @@ -182,8 +232,8 @@ export class EXTFilesystem extends BaseFilesystem { // Inode Offset if (checkDependencies(['targetInodeEXT', 'inodesPerGroupEXT', 'inodeSizeEXT']) && results.inodeTableStart !== undefined && results.inodeRelativeIndex !== undefined) { results.inodeOffset = results.inodeTableStart + (results.inodeRelativeIndex * values.inodeSizeEXT); - updateResultItem('inodeOffsetEXT', results.inodeOffset, true, - `0x${results.inodeTableStart.toString(16)} + (0x${results.inodeRelativeIndex.toString(16)} × 0x${values.inodeSizeEXT.toString(16)}) = 0x${results.inodeOffset.toString(16)}`); + updateResultItem('inodeOffsetEXT', { bytes: results.inodeOffset, other: `Inode-Größe: 0x${Math.floor(values.inodeSizeEXT).toString(16).toUpperCase()}` }, true, + `0x${Math.floor(results.inodeTableStart).toString(16).toUpperCase()} + (0x${Math.floor(results.inodeRelativeIndex).toString(16).toUpperCase()} × 0x${Math.floor(values.inodeSizeEXT).toString(16).toUpperCase()}) = 0x${Math.floor(results.inodeOffset).toString(16).toUpperCase()}`); } else { updateResultItem('inodeOffsetEXT', 0, false); } @@ -191,8 +241,8 @@ export class EXTFilesystem extends BaseFilesystem { // Block Offset if (checkDependencies(['partitionStartEXT', 'targetBlockEXT', 'blockSizeExpEXT']) && results.blockSize !== undefined) { results.blockOffset = values.partitionStartEXT + (values.targetBlockEXT * results.blockSize); - updateResultItem('blockOffsetEXT', results.blockOffset, true, - `0x${values.partitionStartEXT.toString(16)} + (0x${values.targetBlockEXT.toString(16)} × 0x${results.blockSize.toString(16)}) = 0x${results.blockOffset.toString(16)}`); + updateResultItem('blockOffsetEXT', { bytes: results.blockOffset, other: `Block 0x${Math.floor(values.targetBlockEXT).toString(16).toUpperCase()}` }, true, + `0x${Math.floor(values.partitionStartEXT).toString(16).toUpperCase()} + (0x${Math.floor(values.targetBlockEXT).toString(16).toUpperCase()} × 0x${Math.floor(results.blockSize).toString(16).toUpperCase()}) = 0x${Math.floor(results.blockOffset).toString(16).toUpperCase()}`); } else { updateResultItem('blockOffsetEXT', 0, false); } @@ -201,33 +251,33 @@ export class EXTFilesystem extends BaseFilesystem { if (results.inodeTableStart !== undefined && checkDependencies(['inodeSizeEXT'])) { // Inode 1 (Bad Blocks) - Index 0 results.badBlocksInode = results.inodeTableStart + (0 * values.inodeSizeEXT); - updateResultItem('badBlocksInodeEXT', results.badBlocksInode, true, - `0x${results.inodeTableStart.toString(16)} + (0 × 0x${values.inodeSizeEXT.toString(16)}) = 0x${results.badBlocksInode.toString(16)}`); + updateResultItem('badBlocksInodeEXT', { bytes: results.badBlocksInode, other: 'Inode 1 (Bad Blocks)' }, true, + `0x${Math.floor(results.inodeTableStart).toString(16).toUpperCase()} + (0 × 0x${Math.floor(values.inodeSizeEXT).toString(16).toUpperCase()}) = 0x${Math.floor(results.badBlocksInode).toString(16).toUpperCase()}`); // Inode 2 (Root Directory) - Index 1 results.rootDirInode = results.inodeTableStart + (1 * values.inodeSizeEXT); - updateResultItem('rootDirInodeEXT', results.rootDirInode, true, - `0x${results.inodeTableStart.toString(16)} + (1 × 0x${values.inodeSizeEXT.toString(16)}) = 0x${results.rootDirInode.toString(16)}`); + updateResultItem('rootDirInodeEXT', { bytes: results.rootDirInode, other: 'Inode 2 (Root Dir)' }, true, + `0x${Math.floor(results.inodeTableStart).toString(16).toUpperCase()} + (1 × 0x${Math.floor(values.inodeSizeEXT).toString(16).toUpperCase()}) = 0x${Math.floor(results.rootDirInode).toString(16).toUpperCase()}`); // Inode 3 (User Quotas) - Index 2 results.userQuotaInode = results.inodeTableStart + (2 * values.inodeSizeEXT); - updateResultItem('userQuotaInodeEXT', results.userQuotaInode, true, - `0x${results.inodeTableStart.toString(16)} + (2 × 0x${values.inodeSizeEXT.toString(16)}) = 0x${results.userQuotaInode.toString(16)}`); + updateResultItem('userQuotaInodeEXT', { bytes: results.userQuotaInode, other: 'Inode 3 (User Quota)' }, true, + `0x${Math.floor(results.inodeTableStart).toString(16).toUpperCase()} + (2 × 0x${Math.floor(values.inodeSizeEXT).toString(16).toUpperCase()}) = 0x${Math.floor(results.userQuotaInode).toString(16).toUpperCase()}`); // Inode 4 (Group Quotas) - Index 3 results.groupQuotaInode = results.inodeTableStart + (3 * values.inodeSizeEXT); - updateResultItem('groupQuotaInodeEXT', results.groupQuotaInode, true, - `0x${results.inodeTableStart.toString(16)} + (3 × 0x${values.inodeSizeEXT.toString(16)}) = 0x${results.groupQuotaInode.toString(16)}`); + updateResultItem('groupQuotaInodeEXT', { bytes: results.groupQuotaInode, other: 'Inode 4 (Group Quota)' }, true, + `0x${Math.floor(results.inodeTableStart).toString(16).toUpperCase()} + (3 × 0x${Math.floor(values.inodeSizeEXT).toString(16).toUpperCase()}) = 0x${Math.floor(results.groupQuotaInode).toString(16).toUpperCase()}`); // Inode 5 (Bootloader) - Index 4 results.bootloaderInode = results.inodeTableStart + (4 * values.inodeSizeEXT); - updateResultItem('bootloaderInodeEXT', results.bootloaderInode, true, - `0x${results.inodeTableStart.toString(16)} + (4 × 0x${values.inodeSizeEXT.toString(16)}) = 0x${results.bootloaderInode.toString(16)}`); + updateResultItem('bootloaderInodeEXT', { bytes: results.bootloaderInode, other: 'Inode 5 (Bootloader)' }, true, + `0x${Math.floor(results.inodeTableStart).toString(16).toUpperCase()} + (4 × 0x${Math.floor(values.inodeSizeEXT).toString(16).toUpperCase()}) = 0x${Math.floor(results.bootloaderInode).toString(16).toUpperCase()}`); // Inode 8 (Journal) - Index 7 results.journalInode = results.inodeTableStart + (7 * values.inodeSizeEXT); - updateResultItem('journalInodeEXT', results.journalInode, true, - `0x${results.inodeTableStart.toString(16)} + (7 × 0x${values.inodeSizeEXT.toString(16)}) = 0x${results.journalInode.toString(16)}`); + updateResultItem('journalInodeEXT', { bytes: results.journalInode, other: 'Inode 8 (Journal)' }, true, + `0x${Math.floor(results.inodeTableStart).toString(16).toUpperCase()} + (7 × 0x${Math.floor(values.inodeSizeEXT).toString(16).toUpperCase()}) = 0x${Math.floor(results.journalInode).toString(16).toUpperCase()}`); } else { updateResultItem('badBlocksInodeEXT', 0, false); updateResultItem('rootDirInodeEXT', 0, false); @@ -240,8 +290,8 @@ export class EXTFilesystem extends BaseFilesystem { // Inode Table Size (Bytes) if (checkDependencies(['inodesPerGroupEXT', 'inodeSizeEXT'])) { results.inodeTableSize = values.inodesPerGroupEXT * values.inodeSizeEXT; - updateResultItem('inodeTableSizeEXT', results.inodeTableSize, true, - `0x${values.inodesPerGroupEXT.toString(16)} × 0x${values.inodeSizeEXT.toString(16)} = 0x${results.inodeTableSize.toString(16)} (${results.inodeTableSize} Bytes)`); + updateResultItem('inodeTableSizeEXT', { bytes: results.inodeTableSize, other: `für 0x${Math.floor(values.inodesPerGroupEXT).toString(16).toUpperCase()} Inodes` }, true, + `0x${Math.floor(values.inodesPerGroupEXT).toString(16).toUpperCase()} × 0x${Math.floor(values.inodeSizeEXT).toString(16).toUpperCase()} = 0x${Math.floor(results.inodeTableSize).toString(16).toUpperCase()} (${Math.floor(results.inodeTableSize)} Bytes)`); } else { updateResultItem('inodeTableSizeEXT', 0, false); } @@ -254,5 +304,12 @@ export class EXTFilesystem extends BaseFilesystem { } else { updateResultItem('inodeTableSizeBlocksEXT', 0, false); } + } catch (error) { + console.error('Fehler in EXT-Berechnung:', error); + // Mark all results as unavailable on error + updateResultItem('superblockPosEXT', 0, false); + updateResultItem('blockSizeBytesEXT', 0, false); + updateResultItem('totalBlockgroupsEXT', 0, false); + } } } diff --git a/webroot/js/filesystems/fat.js b/webroot/js/filesystems/fat.js index b0e6db8..19e5879 100644 --- a/webroot/js/filesystems/fat.js +++ b/webroot/js/filesystems/fat.js @@ -10,10 +10,42 @@ export class FAT12_16Filesystem extends BaseFilesystem { if (variantId !== 'fat1216') return; const values = this.getInputValues(variantId); + + // Validate input ranges + const validation = this.validateFilesystemSpecific(variantId, values); + if (!validation.valid) { + console.warn('FAT12/16 validation errors:', validation.errors); + } + const results = {}; this.calculateFAT1216(values, results); } + + validateFilesystemSpecific(variantId, values) { + if (variantId !== 'fat1216') return { valid: true, errors: [] }; + + const errors = []; + + // Validate cluster number doesn't exceed total clusters + if (values.clusterNumber1216 !== undefined && values.clusterNumber1216 < 2) { + errors.push('clusterNumber1216: Cluster-Nummer muss >= 2 sein'); + } + + // Validate partition size is greater than reserved + FAT areas + if (values.partitionSizeInSectors1216 !== undefined && values.reservedSektoren1216 !== undefined && values.numFATs1216 !== undefined && values.fatSizeSectors1216 !== undefined) { + const minPartitionSize = values.reservedSektoren1216 + (values.numFATs1216 * values.fatSizeSectors1216) + 1; + if (values.partitionSizeInSectors1216 < minPartitionSize) { + errors.push(`partitionSizeInSectors1216: ${values.partitionSizeInSectors1216} < minimum ${minPartitionSize}`); + } + } + + return { + valid: errors.length === 0, + errors: errors + }; + } + constructor() { super('FAT12/16', [ { @@ -201,10 +233,42 @@ export class FAT32Filesystem extends BaseFilesystem { if (variantId !== 'fat32') return; const values = this.getInputValues(variantId); + + // Validate input ranges + const validation = this.validateFilesystemSpecific(variantId, values); + if (!validation.valid) { + console.warn('FAT32 validation errors:', validation.errors); + } + const results = {}; this.calculateFAT32(values, results); } + + validateFilesystemSpecific(variantId, values) { + if (variantId !== 'fat32') return { valid: true, errors: [] }; + + const errors = []; + + // Validate cluster number doesn't exceed total clusters + if (values.clusterNumber32 !== undefined && values.clusterNumber32 < 2) { + errors.push('clusterNumber32: Cluster-Nummer muss >= 2 sein'); + } + + // Validate partition size is greater than reserved + FAT areas + if (values.partitionSizeInSectors32 !== undefined && values.reservedSectors32 !== undefined && values.numFATs32 !== undefined && values.fatSizeSectors32 !== undefined) { + const minPartitionSize = values.reservedSectors32 + (values.numFATs32 * values.fatSizeSectors32) + 1; + if (values.partitionSizeInSectors32 < minPartitionSize) { + errors.push(`partitionSizeInSectors32: ${values.partitionSizeInSectors32} < minimum ${minPartitionSize}`); + } + } + + return { + valid: errors.length === 0, + errors: errors + }; + } + constructor() { super('FAT32', [ { diff --git a/webroot/js/filesystems/ntfs.js b/webroot/js/filesystems/ntfs.js index 5e1b959..25880a1 100644 --- a/webroot/js/filesystems/ntfs.js +++ b/webroot/js/filesystems/ntfs.js @@ -103,6 +103,13 @@ export class NTFSFilesystem extends BaseFilesystem { calculate(variantId) { const values = this.getInputValues(variantId); + + // Validate input ranges + const validation = this.validateFilesystemSpecific(variantId, values); + if (!validation.valid) { + console.warn('NTFS validation errors:', validation.errors); + } + const results = {}; if (variantId === 'ntfs') { @@ -110,6 +117,32 @@ export class NTFSFilesystem extends BaseFilesystem { } } + validateFilesystemSpecific(variantId, values) { + if (variantId !== 'ntfs') return { valid: true, errors: [] }; + + const errors = []; + + // Validate cluster number is reasonable + if (values.clusterNumberNTFS !== undefined && values.partitionSizeSectorsNTFS !== undefined && values.sectorSizeNTFS !== undefined && values.clusterSizeSectorsNTFS !== undefined) { + const clusterSizeBytes = Math.pow(2, values.sectorSizeNTFS) * Math.pow(2, values.clusterSizeSectorsNTFS); + const partitionSizeBytes = values.partitionSizeSectorsNTFS * Math.pow(2, values.sectorSizeNTFS); + const maxClusters = partitionSizeBytes / clusterSizeBytes; + if (values.clusterNumberNTFS > maxClusters) { + errors.push(`clusterNumberNTFS: ${values.clusterNumberNTFS} > max ${Math.floor(maxClusters)}`); + } + } + + // Validate MFT entry number is reasonable + if (values.mftEntryNumberNTFS !== undefined && values.mftEntryNumberNTFS < 0) { + errors.push('mftEntryNumberNTFS: MFT-Eintrag muss >= 0 sein'); + } + + return { + valid: errors.length === 0, + errors: errors + }; + } + calculateMFTEntrySize(rawValue, clusterSizeBytes) { // Special encoding from offset 0x40 and 0x44 // If positive (0x00-0x7F): size = value * cluster size diff --git a/webroot/js/main.js b/webroot/js/main.js index f12257a..ef65751 100644 --- a/webroot/js/main.js +++ b/webroot/js/main.js @@ -1,6 +1,6 @@ // Main application file that coordinates all modules -import { setupTooltips, setupCopyButtons } from './utils.js'; +import { setupTooltips, setupCopyButtons, copyToClipboard } from './utils.js'; import { Calculator } from './calculator.js'; import { FAT12_16Filesystem, FAT32Filesystem } from './filesystems/fat.js'; import { NTFSFilesystem } from './filesystems/ntfs.js'; @@ -34,6 +34,19 @@ class FilesystemCalculator { } } + // Get active tab's filesystem for context-aware operations + getActiveFilesystem() { + return this.findFilesystemForTab(this.activeTab); + } + + // Get active tab's variant + getActiveVariant() { + if (!this.activeTab) return null; + const filesystem = this.getActiveFilesystem(); + if (!filesystem) return null; + return filesystem.variants.find(v => v.id === this.activeTab); + } + renderTabs() { const tabsContainer = document.getElementById('filesystem-tabs'); const tabs = []; @@ -93,10 +106,9 @@ class FilesystemCalculator { // Initialize UX enhancements initializeUXEnhancements(); + // Bind copyToClipboard at module scope - no dynamic import needed window.copyToClipboard = (elementId) => { - import('./utils.js').then(module => { - module.copyToClipboard(elementId); - }); + copyToClipboard(elementId); }; } diff --git a/webroot/js/utils.js b/webroot/js/utils.js index 4b18e88..3cf26ea 100644 --- a/webroot/js/utils.js +++ b/webroot/js/utils.js @@ -186,12 +186,6 @@ export function setupTooltips() { } }); - document.addEventListener('mouseout', function (e) { - if (e.target.classList.contains('result-label') || e.target.classList.contains('result-value')) { - hideTooltip(); - } - }); - document.addEventListener('scroll', hideTooltip); }