hfs+ update

This commit is contained in:
2026-01-22 16:03:38 +01:00
parent 8a39d708a1
commit 42e2c4e2b5
4 changed files with 470 additions and 3 deletions

View File

@@ -0,0 +1,411 @@
// HFS+ (Hierarchical File System Plus) filesystem implementation
// ⚠️ Important: All multi-byte values in HFS+ are stored in Big-Endian byte order!
import { BaseFilesystem } from './base.js';
import { checkDependencies, updateResultItem } from '../utils.js';
export class HFSPlusFilesystem extends BaseFilesystem {
constructor() {
super('HFS+', [
{
id: 'hfsplus',
name: 'HFS+',
constants: [
{ id: 'partitionStartHFS', label: 'Partitionsstart (Basis-Offset)', unit: 'Bytes', default: '0x5000' },
{ id: 'volumeHeaderOffsetHFS', label: 'Volume Header Offset (relativ zur Partition)', unit: 'Bytes', default: '0x400' }
],
inputs: [
{ id: 'blockSizeHFS', label: 'Block-Größe (Volume Header Offset 0x28)', unit: 'Bytes', placeholder: '0x1000' },
{ id: 'totalBlocksHFS', label: 'Partitionsgröße (Volume Header Offset 0x2C)', unit: 'Blöcke', placeholder: '0x10000' },
{ id: 'catalogStartBlockHFS', label: 'Fork Catalog File > Catalog File Startblock (Volume Header Offset 0x110 + 0x10)', unit: 'Block-Nummer', placeholder: '0x354' },
{ id: 'catalogSizeBlocksHFS', label: 'Fork Catalog File > Catalog File Größe (Volume Header Offset 0x110 + 0x0C)', unit: 'Blöcke', placeholder: '0x3D' },
{ id: 'catalogRootNodeHFS', label: 'Knotennummer Wurzelknoten (Catalog File Offset 0x10 = Header Node 0x0E + 0x02)', unit: 'Knotennummer', placeholder: '0x3' },
{ id: 'catalogNodeSizeHFS', label: 'Knoten-Größe (Catalog File Offset 0x20 = Header Node 0x0E + 0x12)', unit: 'Bytes', placeholder: '0x1000' },
{ id: 'targetNodeNumberHFS', label: 'B-Baum Knotennummer (für Offset-Berechnung)', unit: 'Nummer', placeholder: '0x2' },
{ id: 'targetBlockNumberHFS', label: 'Ziel-Block-Nummer', unit: 'Nummer', placeholder: '0x100' },
{ id: 'dataForkSizeBytesHFS', label: 'Data Fork Größe (aus Catalog Data Offset 0x58 + 0x00)', unit: 'Bytes', placeholder: '0x7C26E' },
{ id: 'dataForkSizeBlocksHFS', label: 'Data Fork Größe (aus Catalog Data Offset 0x58 + 0x0C)', unit: 'Blöcke', placeholder: '0x7D' },
{ id: 'dataForkStartBlockHFS', label: 'Data Fork Startblock (aus Catalog Data Offset 0x58 + 0x10)', unit: 'Block-Nummer', placeholder: '0xFC' },
{ id: 'resourceForkSizeBytesHFS', label: 'Resource Fork Größe (aus Catalog Data Offset 0xA8 + 0x00)', unit: 'Bytes', placeholder: '0x0' }
],
resultGroups: [
{
name: 'Volume Header',
results: [
{ id: 'volumeHeaderPosHFS', label: 'Volume Header Position', dependencies: ['partitionStartHFS', 'volumeHeaderOffsetHFS'], formula: 'Partitionsstart + 0x400' },
{ id: 'partitionSizeBytesHFS', label: 'Partitionsgröße (Bytes)', dependencies: ['totalBlocksHFS', 'blockSizeHFS'], formula: 'Gesamtblöcke × Block-Größe' },
{ id: 'backupVolumeHeaderPosHFS', label: 'Backup Volume Header Position', dependencies: ['partitionStartHFS', 'totalBlocksHFS', 'blockSizeHFS'], formula: 'Partitionsstart + Partitionsgröße - 0x200' }
]
},
{
name: 'Special Files (aus Volume Header Forks)',
results: [
{ id: 'allocationFileForkOffsetHFS', label: 'Allocation File Fork', dependencies: ['partitionStartHFS', 'volumeHeaderOffsetHFS'], formula: 'Volume Header Position + 0x70' },
{ id: 'extentsFileForkOffsetHFS', label: 'Extents File Fork', dependencies: ['partitionStartHFS', 'volumeHeaderOffsetHFS'], formula: 'Volume Header Position + 0xC0' },
{ id: 'catalogFileForkOffsetHFS', label: 'Catalog File Fork', dependencies: ['partitionStartHFS', 'volumeHeaderOffsetHFS'], formula: 'Volume Header Position + 0x110' },
{ id: 'attributesFileForkOffsetHFS', label: 'Attributes File Fork', dependencies: ['partitionStartHFS', 'volumeHeaderOffsetHFS'], formula: 'Volume Header Position + 0x160' },
{ id: 'startupFileForkOffsetHFS', label: 'Startup File Fork', dependencies: ['partitionStartHFS', 'volumeHeaderOffsetHFS'], formula: 'Volume Header Position + 0x1B0' }
]
},
{
name: 'Catalog File (Special File)',
results: [
{ id: 'catalogFileStartHFS', label: 'Catalog File Start', dependencies: ['partitionStartHFS', 'catalogStartBlockHFS', 'blockSizeHFS'], formula: 'Partitionsstart + (Startblock × Block-Größe)' },
{ id: 'catalogFileSizeBytesHFS', label: 'Catalog File Größe (Bytes)', dependencies: ['catalogSizeBlocksHFS', 'blockSizeHFS'], formula: 'Größe in Blöcken × Block-Größe' },
{ id: 'catalogHeaderNodeHFS', label: 'Catalog Header Node (Knoten 0)', dependencies: ['partitionStartHFS', 'catalogStartBlockHFS', 'blockSizeHFS'], formula: 'Catalog File Start + (0 × Knoten-Größe)' },
{ id: 'catalogRootNodeOffsetHFS', label: 'Catalog Wurzelknoten Offset', dependencies: ['partitionStartHFS', 'catalogStartBlockHFS', 'blockSizeHFS', 'catalogRootNodeHFS', 'catalogNodeSizeHFS'], formula: 'Catalog File Start + (Wurzelknoten-Nr. × Knoten-Größe)' }
]
},
{
name: 'B-Baum Knoten-Navigation',
results: [
{ id: 'targetNodeOffsetHFS', label: 'Ziel-Knoten Offset', dependencies: ['partitionStartHFS', 'catalogStartBlockHFS', 'blockSizeHFS', 'targetNodeNumberHFS', 'catalogNodeSizeHFS'], formula: 'Catalog File Start + (Knotennummer × Knoten-Größe)' },
{ id: 'nodeDescriptorOffsetHFS', label: 'Node Descriptor (Knotenanfang)', dependencies: ['partitionStartHFS', 'catalogStartBlockHFS', 'blockSizeHFS', 'targetNodeNumberHFS', 'catalogNodeSizeHFS'], formula: 'Ziel-Knoten Offset + 0x00' },
{ id: 'firstRecordOffsetHFS', label: 'Erster Eintrag (Record) Offset', dependencies: ['partitionStartHFS', 'catalogStartBlockHFS', 'blockSizeHFS', 'targetNodeNumberHFS', 'catalogNodeSizeHFS'], formula: 'Ziel-Knoten Offset + 0x0E' },
{ id: 'offsetArrayStartHFS', label: 'Offset-Array Start (Knotenende)', dependencies: ['partitionStartHFS', 'catalogStartBlockHFS', 'blockSizeHFS', 'targetNodeNumberHFS', 'catalogNodeSizeHFS'], formula: 'Ziel-Knoten Offset + Knoten-Größe - 0x02' }
]
},
{
name: 'Reservierte CNIDs (System-Dateien)',
results: [
{ id: 'rootParentCNIDHFS', label: 'CNID 0x01 - Eltern-CNID des Wurzelverzeichnisses', dependencies: [], formula: 'CNID = 0x01 (Spezialwert)' },
{ id: 'rootDirCNIDHFS', label: 'CNID 0x02 - Wurzelverzeichnis', dependencies: [], formula: 'CNID = 0x02' },
{ id: 'extentsOverflowCNIDHFS', label: 'CNID 0x03 - Extents Overflow File', dependencies: [], formula: 'CNID = 0x03' },
{ id: 'catalogFileCNIDHFS', label: 'CNID 0x04 - Catalog File', dependencies: [], formula: 'CNID = 0x04' },
{ id: 'badBlockCNIDHFS', label: 'CNID 0x05 - Bad Block File', dependencies: [], formula: 'CNID = 0x05' },
{ id: 'allocationFileCNIDHFS', label: 'CNID 0x06 - Allocation File (Bitmap)', dependencies: [], formula: 'CNID = 0x06' },
{ id: 'startupFileCNIDHFS', label: 'CNID 0x07 - Startup File', dependencies: [], formula: 'CNID = 0x07' },
{ id: 'attributesFileCNIDHFS', label: 'CNID 0x08 - Attributes File', dependencies: [], formula: 'CNID = 0x08' },
{ id: 'firstFreeCNIDHFS', label: 'CNID 0x10 - Erste frei verfügbare CNID', dependencies: [], formula: 'CNID = 0x10' }
]
},
{
name: 'Block-Berechnungen',
results: [
{ id: 'blockOffsetHFS', label: 'Block Offset', dependencies: ['partitionStartHFS', 'targetBlockNumberHFS', 'blockSizeHFS'], formula: 'Partitionsstart + (Block-Nummer × Block-Größe)' }
]
},
{
name: 'Fork-Berechnungen (Dateiinhalte)',
results: [
{ id: 'dataForkOffsetHFS', label: 'Data Fork Start Offset', dependencies: ['partitionStartHFS', 'dataForkStartBlockHFS', 'blockSizeHFS'], formula: 'Partitionsstart + (Startblock × Block-Größe)' },
{ id: 'totalFileSizeHFS', label: 'Gesamte Dateigröße (Data + Resource Fork)', dependencies: ['dataForkSizeBytesHFS', 'resourceForkSizeBytesHFS'], formula: 'Data Fork Größe + Resource Fork Größe' }
]
}
],
formulas: [
{ name: 'Volume Header Position', expression: 'Partitionsstart + 0x400 (immer fix bei Offset 0x400)' },
{ name: 'Backup Volume Header', expression: 'Partitionsende - 0x200 (512 Bytes vor Partitionsende)' },
{ name: 'Partitionsgröße', expression: 'Gesamtanzahl Blöcke × Block-Größe in Bytes' },
{ name: 'Special File Fork Offsets', expression: 'Volume Header + fixer Offset (0x70, 0xC0, 0x110, 0x160, 0x1B0)' },
{ name: 'Catalog File Start', expression: 'Partitionsstart + (Catalog-Startblock × Block-Größe)' },
{ name: 'B-Baum Knoten Offset', expression: 'Start Special File + (Knotennummer × Knoten-Größe)' },
{ name: 'Erster Eintrag im Knoten', expression: 'Knoten-Offset + 0x0E (nach Node Descriptor)' },
{ name: 'Offset-Array (Eintragszeiger)', expression: 'Knotenende - ((Eintragsnummer + 1) × 2), dann 2 Bytes lesen für relatives Offset' },
{ name: 'Block Offset', expression: 'Partitionsstart + (Block-Nummer × Block-Größe)' },
{ name: 'Data Fork Offset', expression: 'Partitionsstart + (Fork-Startblock × Block-Größe)' },
{ name: 'Gesamte Dateigröße', expression: 'Data Fork Größe + Resource Fork Größe' },
{ name: 'Reservierte CNIDs', expression: 'CNID 0x01-0x08 für Systemdateien, 0x10 erste freie CNID' }
]
}
]);
}
calculate(variantId) {
if (variantId !== 'hfsplus') return;
const values = this.getInputValues(variantId);
// Validate input ranges specific to HFS+ filesystem
const validation = this.validateFilesystemSpecific(variantId, values);
if (!validation.valid) {
console.warn('HFS+ validation errors:', validation.errors);
// Continue with calculation but log warnings
}
const results = {};
this.calculateHFSPlus(values, results);
}
validateFilesystemSpecific(variantId, values) {
if (variantId !== 'hfsplus') return { valid: true, errors: [] };
const errors = [];
// Validate block size is reasonable (typically 4096 bytes, but can range from 512 to 1MB)
if (values.blockSizeHFS !== undefined) {
if (values.blockSizeHFS < 512 || values.blockSizeHFS > 1048576) {
errors.push(`Block-Größe: ${values.blockSizeHFS} außerhalb akzeptabler Grenzen (512-1048576)`);
}
}
// Validate CNID is not in reserved range if it's meant to be a user file
if (values.targetCNIDHFS !== undefined) {
if (values.targetCNIDHFS === 0) {
errors.push('targetCNIDHFS: CNID 0 ist ungültig');
}
}
// Validate catalog node size is reasonable
if (values.catalogNodeSizeHFS !== undefined) {
if (values.catalogNodeSizeHFS < 512 || values.catalogNodeSizeHFS > 32768) {
errors.push(`Knoten-Größe: ${values.catalogNodeSizeHFS} außerhalb akzeptabler Grenzen (512-32768)`);
}
}
// Validate node number doesn't exceed reasonable bounds
if (values.targetNodeNumberHFS !== undefined && values.catalogSizeBlocksHFS !== undefined && values.catalogNodeSizeHFS !== undefined && values.blockSizeHFS !== undefined) {
const catalogSizeBytes = values.catalogSizeBlocksHFS * values.blockSizeHFS;
const maxNodes = Math.floor(catalogSizeBytes / values.catalogNodeSizeHFS);
if (values.targetNodeNumberHFS >= maxNodes) {
errors.push(`targetNodeNumberHFS: ${values.targetNodeNumberHFS} >= maximale Knotenanzahl: ${maxNodes}`);
}
}
return {
valid: errors.length === 0,
errors: errors
};
}
getInputValues(variantId) {
const variant = this.variants.find(v => v.id === variantId);
if (!variant) return {};
const values = {};
// Get all constants
variant.constants.forEach(constant => {
const input = document.getElementById(constant.id);
if (input && input.value.trim()) {
const val = parseInt(input.value.trim(), 16);
if (!isNaN(val)) {
values[constant.id] = val;
}
} else if (constant.default) {
const val = parseInt(constant.default, 16);
if (!isNaN(val)) {
values[constant.id] = val;
}
}
});
// Get all inputs
variant.inputs.forEach(input => {
const element = document.getElementById(input.id);
if (element && element.value.trim()) {
const val = parseInt(element.value.trim(), 16);
if (!isNaN(val)) {
values[input.id] = val;
}
}
});
return values;
}
calculateHFSPlus(values, results) {
const {
partitionStartHFS = 0,
volumeHeaderOffsetHFS = 0x400,
blockSizeHFS,
totalBlocksHFS,
catalogStartBlockHFS,
catalogSizeBlocksHFS,
catalogRootNodeHFS,
catalogNodeSizeHFS,
targetNodeNumberHFS,
targetBlockNumberHFS,
targetCNIDHFS,
dataForkSizeBytesHFS,
dataForkSizeBlocksHFS,
dataForkStartBlockHFS,
resourceForkSizeBytesHFS
} = values;
// Volume Header calculations
if (checkDependencies(['partitionStartHFS', 'volumeHeaderOffsetHFS'])) {
const volumeHeaderPos = partitionStartHFS + volumeHeaderOffsetHFS;
results.volumeHeaderPosHFS = volumeHeaderPos;
updateResultItem('volumeHeaderPosHFS', { bytes: volumeHeaderPos }, true,
`0x${partitionStartHFS.toString(16).toUpperCase()} + 0x${volumeHeaderOffsetHFS.toString(16).toUpperCase()} = 0x${volumeHeaderPos.toString(16).toUpperCase()}`);
} else {
updateResultItem('volumeHeaderPosHFS', 0, false);
}
if (checkDependencies(['totalBlocksHFS', 'blockSizeHFS'])) {
const partitionSizeBytes = totalBlocksHFS * blockSizeHFS;
results.partitionSizeBytesHFS = partitionSizeBytes;
updateResultItem('partitionSizeBytesHFS', { bytes: partitionSizeBytes }, true,
`0x${totalBlocksHFS.toString(16).toUpperCase()} × 0x${blockSizeHFS.toString(16).toUpperCase()} = 0x${partitionSizeBytes.toString(16).toUpperCase()}`);
// Backup Volume Header
if (checkDependencies(['partitionStartHFS'])) {
const backupVolumeHeaderPos = partitionStartHFS + partitionSizeBytes - 0x200;
results.backupVolumeHeaderPosHFS = backupVolumeHeaderPos;
updateResultItem('backupVolumeHeaderPosHFS', { bytes: backupVolumeHeaderPos }, true,
`0x${partitionStartHFS.toString(16).toUpperCase()} + 0x${partitionSizeBytes.toString(16).toUpperCase()} - 0x200 = 0x${backupVolumeHeaderPos.toString(16).toUpperCase()}`);
} else {
updateResultItem('backupVolumeHeaderPosHFS', 0, false);
}
} else {
updateResultItem('partitionSizeBytesHFS', 0, false);
updateResultItem('backupVolumeHeaderPosHFS', 0, false);
}
// Special Files Fork Offsets (in Volume Header)
if (results.volumeHeaderPosHFS !== undefined) {
const vhPos = results.volumeHeaderPosHFS;
results.allocationFileForkOffsetHFS = vhPos + 0x70;
updateResultItem('allocationFileForkOffsetHFS', { bytes: results.allocationFileForkOffsetHFS }, true,
`0x${vhPos.toString(16).toUpperCase()} + 0x70 = 0x${results.allocationFileForkOffsetHFS.toString(16).toUpperCase()}`);
results.extentsFileForkOffsetHFS = vhPos + 0xC0;
updateResultItem('extentsFileForkOffsetHFS', { bytes: results.extentsFileForkOffsetHFS }, true,
`0x${vhPos.toString(16).toUpperCase()} + 0xC0 = 0x${results.extentsFileForkOffsetHFS.toString(16).toUpperCase()}`);
results.catalogFileForkOffsetHFS = vhPos + 0x110;
updateResultItem('catalogFileForkOffsetHFS', { bytes: results.catalogFileForkOffsetHFS }, true,
`0x${vhPos.toString(16).toUpperCase()} + 0x110 = 0x${results.catalogFileForkOffsetHFS.toString(16).toUpperCase()}`);
results.attributesFileForkOffsetHFS = vhPos + 0x160;
updateResultItem('attributesFileForkOffsetHFS', { bytes: results.attributesFileForkOffsetHFS }, true,
`0x${vhPos.toString(16).toUpperCase()} + 0x160 = 0x${results.attributesFileForkOffsetHFS.toString(16).toUpperCase()}`);
results.startupFileForkOffsetHFS = vhPos + 0x1B0;
updateResultItem('startupFileForkOffsetHFS', { bytes: results.startupFileForkOffsetHFS }, true,
`0x${vhPos.toString(16).toUpperCase()} + 0x1B0 = 0x${results.startupFileForkOffsetHFS.toString(16).toUpperCase()}`);
} else {
updateResultItem('allocationFileForkOffsetHFS', 0, false);
updateResultItem('extentsFileForkOffsetHFS', 0, false);
updateResultItem('catalogFileForkOffsetHFS', 0, false);
updateResultItem('attributesFileForkOffsetHFS', 0, false);
updateResultItem('startupFileForkOffsetHFS', 0, false);
}
// Catalog File calculations
if (checkDependencies(['partitionStartHFS', 'catalogStartBlockHFS', 'blockSizeHFS'])) {
const catalogFileStart = partitionStartHFS + (catalogStartBlockHFS * blockSizeHFS);
results.catalogFileStartHFS = catalogFileStart;
updateResultItem('catalogFileStartHFS', { bytes: catalogFileStart }, true,
`0x${partitionStartHFS.toString(16).toUpperCase()} + (0x${catalogStartBlockHFS.toString(16).toUpperCase()} × 0x${blockSizeHFS.toString(16).toUpperCase()}) = 0x${catalogFileStart.toString(16).toUpperCase()}`);
if (checkDependencies(['catalogSizeBlocksHFS'])) {
const catalogFileSizeBytes = catalogSizeBlocksHFS * blockSizeHFS;
results.catalogFileSizeBytesHFS = catalogFileSizeBytes;
updateResultItem('catalogFileSizeBytesHFS', { bytes: catalogFileSizeBytes }, true,
`0x${catalogSizeBlocksHFS.toString(16).toUpperCase()} × 0x${blockSizeHFS.toString(16).toUpperCase()} = 0x${catalogFileSizeBytes.toString(16).toUpperCase()}`);
} else {
updateResultItem('catalogFileSizeBytesHFS', 0, false);
}
// Header Node (always node 0)
results.catalogHeaderNodeHFS = catalogFileStart;
updateResultItem('catalogHeaderNodeHFS', { bytes: catalogFileStart }, true,
`0x${catalogFileStart.toString(16).toUpperCase()} + (0 × Knoten-Größe) = 0x${catalogFileStart.toString(16).toUpperCase()}`);
// Root Node
if (checkDependencies(['catalogRootNodeHFS', 'catalogNodeSizeHFS'])) {
const catalogRootNodeOffset = catalogFileStart + (catalogRootNodeHFS * catalogNodeSizeHFS);
results.catalogRootNodeOffsetHFS = catalogRootNodeOffset;
updateResultItem('catalogRootNodeOffsetHFS', { bytes: catalogRootNodeOffset }, true,
`0x${catalogFileStart.toString(16).toUpperCase()} + (0x${catalogRootNodeHFS.toString(16).toUpperCase()} × 0x${catalogNodeSizeHFS.toString(16).toUpperCase()}) = 0x${catalogRootNodeOffset.toString(16).toUpperCase()}`);
} else {
updateResultItem('catalogRootNodeOffsetHFS', 0, false);
}
// Target Node calculations
if (checkDependencies(['targetNodeNumberHFS', 'catalogNodeSizeHFS'])) {
const targetNodeOffset = catalogFileStart + (targetNodeNumberHFS * catalogNodeSizeHFS);
results.targetNodeOffsetHFS = targetNodeOffset;
updateResultItem('targetNodeOffsetHFS', { bytes: targetNodeOffset }, true,
`0x${catalogFileStart.toString(16).toUpperCase()} + (0x${targetNodeNumberHFS.toString(16).toUpperCase()} × 0x${catalogNodeSizeHFS.toString(16).toUpperCase()}) = 0x${targetNodeOffset.toString(16).toUpperCase()}`);
// Node Descriptor (always at start of node)
results.nodeDescriptorOffsetHFS = targetNodeOffset;
updateResultItem('nodeDescriptorOffsetHFS', { bytes: targetNodeOffset }, true,
`0x${targetNodeOffset.toString(16).toUpperCase()} + 0x00 = 0x${targetNodeOffset.toString(16).toUpperCase()}`);
// First Record (always at 0x0E after Node Descriptor)
results.firstRecordOffsetHFS = targetNodeOffset + 0x0E;
updateResultItem('firstRecordOffsetHFS', { bytes: results.firstRecordOffsetHFS }, true,
`0x${targetNodeOffset.toString(16).toUpperCase()} + 0x0E = 0x${results.firstRecordOffsetHFS.toString(16).toUpperCase()}`);
// Offset Array Start (at end of node)
results.offsetArrayStartHFS = targetNodeOffset + catalogNodeSizeHFS - 0x02;
updateResultItem('offsetArrayStartHFS', { bytes: results.offsetArrayStartHFS }, true,
`0x${targetNodeOffset.toString(16).toUpperCase()} + 0x${catalogNodeSizeHFS.toString(16).toUpperCase()} - 0x02 = 0x${results.offsetArrayStartHFS.toString(16).toUpperCase()}`);
} else {
updateResultItem('targetNodeOffsetHFS', 0, false);
updateResultItem('nodeDescriptorOffsetHFS', 0, false);
updateResultItem('firstRecordOffsetHFS', 0, false);
updateResultItem('offsetArrayStartHFS', 0, false);
}
} else {
updateResultItem('catalogFileStartHFS', 0, false);
updateResultItem('catalogFileSizeBytesHFS', 0, false);
updateResultItem('catalogHeaderNodeHFS', 0, false);
updateResultItem('catalogRootNodeOffsetHFS', 0, false);
updateResultItem('targetNodeOffsetHFS', 0, false);
updateResultItem('nodeDescriptorOffsetHFS', 0, false);
updateResultItem('firstRecordOffsetHFS', 0, false);
updateResultItem('offsetArrayStartHFS', 0, false);
}
// Reserved CNIDs (these are constants, just display them)
updateResultItem('rootParentCNIDHFS', 0x01, true, 'CNID = 0x01 (Spezialwert für Eltern-CNID des Wurzelverzeichnisses)');
updateResultItem('rootDirCNIDHFS', 0x02, true, 'CNID = 0x02 (Wurzelverzeichnis)');
updateResultItem('extentsOverflowCNIDHFS', 0x03, true, 'CNID = 0x03 (Extents Overflow File)');
updateResultItem('catalogFileCNIDHFS', 0x04, true, 'CNID = 0x04 (Catalog File)');
updateResultItem('badBlockCNIDHFS', 0x05, true, 'CNID = 0x05 (Bad Block File)');
updateResultItem('allocationFileCNIDHFS', 0x06, true, 'CNID = 0x06 (Allocation File / Bitmap)');
updateResultItem('startupFileCNIDHFS', 0x07, true, 'CNID = 0x07 (Startup File)');
updateResultItem('attributesFileCNIDHFS', 0x08, true, 'CNID = 0x08 (Attributes File)');
updateResultItem('firstFreeCNIDHFS', 0x10, true, 'CNID = 0x10 (Erste frei verfügbare CNID für Benutzer-Dateien)');
// Block calculations
if (checkDependencies(['partitionStartHFS', 'targetBlockNumberHFS', 'blockSizeHFS'])) {
const blockOffset = partitionStartHFS + (targetBlockNumberHFS * blockSizeHFS);
results.blockOffsetHFS = blockOffset;
updateResultItem('blockOffsetHFS', { bytes: blockOffset }, true,
`0x${partitionStartHFS.toString(16).toUpperCase()} + (0x${targetBlockNumberHFS.toString(16).toUpperCase()} × 0x${blockSizeHFS.toString(16).toUpperCase()}) = 0x${blockOffset.toString(16).toUpperCase()}`);
} else {
updateResultItem('blockOffsetHFS', 0, false);
}
// Fork calculations
if (checkDependencies(['partitionStartHFS', 'dataForkStartBlockHFS', 'blockSizeHFS'])) {
const dataForkOffset = partitionStartHFS + (dataForkStartBlockHFS * blockSizeHFS);
results.dataForkOffsetHFS = dataForkOffset;
updateResultItem('dataForkOffsetHFS', { bytes: dataForkOffset }, true,
`0x${partitionStartHFS.toString(16).toUpperCase()} + (0x${dataForkStartBlockHFS.toString(16).toUpperCase()} × 0x${blockSizeHFS.toString(16).toUpperCase()}) = 0x${dataForkOffset.toString(16).toUpperCase()}`);
} else {
updateResultItem('dataForkOffsetHFS', 0, false);
}
if (checkDependencies(['dataForkSizeBytesHFS', 'resourceForkSizeBytesHFS'])) {
const totalFileSize = dataForkSizeBytesHFS + resourceForkSizeBytesHFS;
results.totalFileSizeHFS = totalFileSize;
updateResultItem('totalFileSizeHFS', { bytes: totalFileSize }, true,
`0x${dataForkSizeBytesHFS.toString(16).toUpperCase()} + 0x${resourceForkSizeBytesHFS.toString(16).toUpperCase()} = 0x${totalFileSize.toString(16).toUpperCase()}`);
} else {
updateResultItem('totalFileSizeHFS', 0, false);
}
}
// Generate HTML for the big-endian warning
generateTimestampConverterHTML(variantId) {
if (variantId !== 'hfsplus') return '';
return `
<div class="section info-section">
<h2>⚠️ Wichtiger Hinweis: Big-Endian Byte-Reihenfolge</h2>
<div class="info-box">
<p><strong>HFS+ verwendet Big-Endian für alle Multi-Byte-Werte!</strong></p>
</div>
</div>
`;
}
}

View File

@@ -6,6 +6,7 @@ import { FAT12_16Filesystem, FAT32Filesystem } from './filesystems/fat.js';
import { NTFSFilesystem } from './filesystems/ntfs.js';
import { ExFATFilesystem } from './filesystems/exfat.js';
import { EXTFilesystem } from './filesystems/ext.js';
import { HFSPlusFilesystem } from './filesystems/hfsplus.js';
import { initializeUXEnhancements } from './ux-enhancements.js';
class FilesystemCalculator {
@@ -15,7 +16,8 @@ class FilesystemCalculator {
new FAT32Filesystem(),
new NTFSFilesystem(),
new ExFATFilesystem(),
new EXTFilesystem()
new EXTFilesystem(),
new HFSPlusFilesystem()
];
this.calculator = new Calculator();
this.activeTab = null;