Files
fscalc/webroot/js/filesystems/hfsplus.js
2026-01-22 16:09:09 +01:00

409 lines
27 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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: '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,
dataForkSizeBytesHFS,
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>
`;
}
}