//------------------------------------------------ //--- 010 Editor v9.0.1 Binary Template // // File: ADF.bt // Authors: Volker Broemmel (VB), Howard Price (HP) // Version: 1.11 // Purpose: Detect block types of AmigaDOS disk images. // Category: Drives // File Mask: *.adf // ID Bytes: // History: // 1.11 2023-11-29 HP: Revert data block bgcolor cNone // 1.1 2023-11-29 HP: Add block checksum validation // 1.0 2019-02-xx VB: tested on HD disk images -- works. // fixed ROOTBLOCK.bmflag datatype and all sec_type datatypes. // 0.9 2019-02-07 VB: initial release // // Remarks: use template at your own risk. Please make backups of your files first. // //Documentation: // The knowledge that has flowed in here came mainly // from the outstanding "The .ADF (Amiga Disk File) format FAQ" // of Laurent Clévy (http://lclevy.free.fr/adflib/adf_info.html). // // DiskType of boot block (first int32 of disk): // 'D''O''S' + flags // flags = 3 least signifiant bits // set clr // 0 FFS OFS // 1 INTL ONLY NO_INTL ONLY // 2 DIRC&INTL NO_DIRC&INTL // find further information in Laurent's FAQ. // // Protection bit flags (File Header Block / User Directory Block): // Bit If set, means // If MultiUser FileSystem : Owner // 0 delete forbidden (D) // 1 not executable (E) // 2 not writable (W) // 3 not readable (R) // // 4 is archived (A) // 5 pure (reetrant safe), can be made resident (P) // 6 file is a script (Arexx or Shell) (S) // 7 Hold bit. if H+P (and R+E) are set the file can be made resident on first load (OS 2.x and 3.0) // // 8 Group (D) : is delete protected // 9 Group (E) : is executable // 10 Group (W) : is writable // 11 Group (R) : is readable // // 12 Other (D) : is delete protected // 13 Other (E) : is executable // 14 Other (W) : is writable // 15 Other (R) : is readable // // 30-16 reserved // 31 SUID, MultiUserFS Only // // Program flow: // First off, the important info about the image file is gathered like // block count and if it's a FFS or an OFS file system. Also the global // block info array is initialized which has the size of block count // and holds information on each block if it is a known block type or not. // Then the image file is analyzed almost sequentially block by block. // There are a few exceptions, though. Block-Types like // Bitmap-Block or FFS-Data-Block are hard or even impossible to tetect // by themselves. Thus certain collections are followed right away, // "breaking" the sequence: // * bm_pages collection in Root-Block // * data_blocks collection in File-Header-Block (FFS only) // * data_blocks collection in File-Extension-Block (FFS only) // Analyzing such a block again is avoided by marking it as KnownBlockType. // The sequence-loop skips those blocks. // // Known issues: // * Link-Blocks are not supported // * Directory cache blocks (FFS) are not supported // // TODO: // * Display checksum INVALID text in chksum member. Try using parent(this) to access enclosing struct // * Write accompanying script to fix bad checksums (templates cannot write data back) // * Walk blocks in root tree order and flag blocks as referenced // * This will allow 'orphaned' blocks to be identified // * This will allow the Bitmap to be validated //------------------------------------------------ /* * Prerequisites */ // Values in 68xxx CPU's are BigEndian BigEndian(); // Define constants const int BSIZE = 512; // sizeof a disk sector in bytes const int64 FSIZE = FileSize(); const int BCOUNT = FSIZE/BSIZE; enum BlockType { Bootblock, Rootblock, FileHeaderBlock, FileExtensionBlock, UserDirectoryBlock, DataBlock, BitmapBlock, UnknownBlock, }; // Define enums enum BLOCKINFO { TBA = 0, // To be analyzed KnownBlockType = 1, UnknownBlockType = 2 }; local BLOCKINFO _blockInfo[BCOUNT]; _initBlockInfo(); // Define globals local int isFFS = 0; // determines if this is an OFS or FFS disk _getFileSystemType(); /* * Block structure definitions */ // Boot block structure typedef struct { uchar DiskType[3] ; uchar flags ; ulong chksum ; ulong Rootblock ; uchar Bootblock_code[1012] ; } BOOTBLOCK ; // Root block structure typedef struct { ulong type ; ulong header_key ; ulong high_seq ; ulong ht_size ; ulong first_data ; ulong chksum ; ulong ht[(BSIZE/4)-56] ; long bm_flag ; ulong bm_pages[25] ; ulong bm_ext ; ulong r_days ; ulong r_mins ; ulong r_ticks ; uchar name_len ; char diskname[30] ; uchar UNUSED ; ulong UNUSED ; ulong UNUSED ; ulong v_days ; ulong v_mins ; ulong v_ticks ; ulong c_days ; ulong c_mins ; ulong c_ticks ; ulong next_hash ; ulong parent_dir ; ulong extension ; long sec_type ; } ROOTBLOCK ; // Bitmap block structure typedef struct { ulong chksum ; ulong map[(BSIZE/4) - 1] ; } BITMAPBLOCK ; // Bitmap extension block (hard disk only) typedef struct { ulong map[(BSIZE/4) - 1] ; ulong next ; } BITMAPEXTENSIONBLOCK ; // File header block structure typedef struct { ulong type ; ulong header_key ; ulong high_seq ; ulong data_size ; ulong first_data ; ulong chksum ; ulong data_blocks[(BSIZE/4)-56] ; ulong UNUSED0 ; ushort UID ; ushort GID ; ulong protect ; ulong byte_size ; uchar comm_len ; uchar comment[79] ; uchar UNUSED1[12] ; ulong days ; ulong mins ; ulong ticks ; uchar name_len ; char filename[30] ; uchar UNUSED2 ; ulong UNUSED3 ; ulong real_entry ; ulong next_link ; ulong UNUSED4[5] ; ulong hash_chain ; ulong parent ; ulong extension ; long sec_type ; } FILEHEADERBLOCK ; // File extension block structure (sometimes also called "file list block") typedef struct { ulong type ; ulong header_key ; ulong high_seq ; ulong UNUSED0 ; ulong UNUSED1 ; ulong chksum ; ulong data_blocks[(BSIZE/4)-56] ; ulong info[46] ; ulong UNUSED2 ; ulong parent ; ulong extension ; long sec_type ; } FILEEXTENSIONBLOCK ; // OFS data block structure typedef struct { ulong type ; ulong header_key ; ulong seq_num ; ulong data_size ; ulong next_data ; ulong chksum ; uchar data[BSIZE-24] ; } DATABLOCKOFS ; // FFS data block structure typedef struct { uchar data[BSIZE] ; } DATABLOCKFFS ; // User directory block structure typedef struct { ulong type ; ulong header_key ; ulong UNUSED0[3] ; ulong chksum ; ulong ht[(BSIZE/4)-56] ; ulong UNUSED1[2] ; ushort UID ; ulong GID ; ulong protect ; ulong UNUSED ; uchar comm_len ; char comment[79] ; uchar UNUSED2[6] ; ulong days ; ulong mins ; ulong ticks ; uchar name_len ; char dirname[30] ; uchar UNUSED ; ulong UNUSED3[2] ; ulong next_link ; ulong UNUSED4[5] ; ulong hash_chain ; ulong parent_dir ; ulong extension ; long sec_type ; } USERDIRECTORYBLOCK ; // The checksum is longword 5 for the Root, FileHeader, Extension, UserDirectory, and Data blocks const uint kRootblockChecksumMemberLongwordIndex = 5; // Bitmap blocks store the their checksum in the first longword. // TODO: Does 010 have offsetof() ? const uint kBitmapChecksumMemberLongwordIndex = 0; int64 BOOTBLOCK_bgcolor(BOOTBLOCK& bootblock) { local int64 blockAddr = startof(bootblock); local uint32 expectedChecksum = calculateBootblockChecksum(blockAddr); return bootblock.chksum == expectedChecksum ? cGray : cRed; } string BOOTBLOCK_comment(BOOTBLOCK& bootblock) { local int64 blockAddr = startof(bootblock); local uint32 expectedChecksum = calculateBootblockChecksum(blockAddr); if (bootblock.chksum == expectedChecksum) return Str("Boot block"); else return Str("Boot block checksum invalid, expected: %08Xh", expectedChecksum); } int64 ROOTBLOCK_bgcolor(ROOTBLOCK& rootblock) { local int64 blockAddr = startof(rootblock); local uint32 expectedChecksum = calculateRootblockChecksum(blockAddr); return rootblock.chksum == expectedChecksum ? cGray : cRed; } string ROOTBLOCK_comment(ROOTBLOCK& rootblock) { local int64 blockAddr = startof(rootblock); local uint32 expectedChecksum = calculateRootblockChecksum(blockAddr); if (rootblock.chksum == expectedChecksum) return Str("Root block"); else return Str("Root block Expected checksum: %08Xh", expectedChecksum); } int64 BITMAPBLOCK_bgcolor(BITMAPBLOCK& bitmapBlock) { local int64 blockAddr = startof(bitmapBlock); local uint32 expectedChecksum = calculateBitmapBlockChecksum(blockAddr); return bitmapBlock.chksum == expectedChecksum ? cGray : cRed; } string BITMAPBLOCK_comment(BITMAPBLOCK& bitmapBlock) { local int64 blockAddr = startof(bitmapBlock); local uint32 expectedChecksum = calculateBitmapBlockChecksum(blockAddr); if (bitmapBlock.chksum == expectedChecksum) return Str("Bitmap block"); else return Str("Bitmap block Expected checksum: %08Xh", expectedChecksum); } int64 FILEHEADERBLOCK_bgcolor(FILEHEADERBLOCK& fileHeaderBlock) { local int64 blockAddr = startof(fileHeaderBlock); local uint32 expectedChecksum = calculateRootblockChecksum(blockAddr); return fileHeaderBlock.chksum == expectedChecksum ? cGray : cRed; } string FILEHEADERBLOCK_comment(FILEHEADERBLOCK& fileHeaderBlock) { // TODO: BPL string is not null-terminated. Take care here? local string name = fileHeaderBlock.filename; local int64 blockAddr = startof(fileHeaderBlock); local uint32 expectedChecksum = calculateRootblockChecksum(blockAddr); if (fileHeaderBlock.chksum == expectedChecksum) return Str("File header block \"%s\"", name); else return Str("File header block \"%s\" Expected checksum: %08Xh", name, expectedChecksum); } int64 FILEEXTENSIONBLOCK_bgcolor(FILEEXTENSIONBLOCK& fileExtensionBlock) { local int64 blockAddr = startof(fileExtensionBlock); local uint32 expectedChecksum = calculateRootblockChecksum(blockAddr); return fileExtensionBlock.chksum == expectedChecksum ? cGray : cRed; } string FILEEXTENSIONBLOCK_comment(FILEEXTENSIONBLOCK& fileExtensionBlock) { local string filename = getFileHeaderBlockName(fileExtensionBlock.parent); local int64 blockAddr = startof(fileExtensionBlock); local uint32 expectedChecksum = calculateRootblockChecksum(blockAddr); if (fileExtensionBlock.chksum == expectedChecksum) return Str("File extension block \"%s\"", filename); else return Str("File extension block \"%s\" Expected checksum: %08Xh", filename, expectedChecksum); } int64 USERDIRECTORYBLOCK_bgcolor(USERDIRECTORYBLOCK& userDirectoryBlock) { local int64 blockAddr = startof(userDirectoryBlock); local uint32 expectedChecksum = calculateRootblockChecksum(blockAddr); return userDirectoryBlock.chksum == expectedChecksum ? cGray : cRed; } string USERDIRECTORYBLOCK_comment(USERDIRECTORYBLOCK& userDirectoryBlock) { // TODO: BPL string is not null-terminated. Take care here? local string dirname = userDirectoryBlock.dirname; local int64 blockAddr = startof(userDirectoryBlock); local uint32 expectedChecksum = calculateRootblockChecksum(blockAddr); if (userDirectoryBlock.chksum == expectedChecksum) return Str("User directory block \"%s\"", dirname); else return Str("User directory block \"%s\" Expected checksum: %08Xh", dirname, expectedChecksum); } int64 DATABLOCKOFS_bgcolor(DATABLOCKOFS& dataBlockOFS) { local int64 blockAddr = startof(dataBlockOFS); local uint32 expectedChecksum = calculateRootblockChecksum(blockAddr); return dataBlockOFS.chksum == expectedChecksum ? cNone : cRed; } string DATABLOCKOFS_comment(DATABLOCKOFS& dataBlockOFS) { local string filename = getFileHeaderBlockName(dataBlockOFS.header_key); local int64 blockAddr = startof(dataBlockOFS); local uint32 expectedChecksum = calculateRootblockChecksum(blockAddr); if (dataBlockOFS.chksum == expectedChecksum) return Str("Data block (OFS) File: \"%s\"", filename); else return Str("Data block (OFS) File: \"%s\" Expected checksum: %08Xh", filename, expectedChecksum); } string DataBlockOFS_header_key_comment(ulong& fileHeaderBlockIndex) { if (!isFileHeaderBlock(fileHeaderBlockIndex)) return "invalid file header"; local int64 fileHeaderBlockAddr = fileHeaderBlockIndex * BSIZE; // TODO: Why does this causes 010 Editor to crash? // local FILEHEADERBLOCK fileHeaderBlock; // ReadBytes(fileHeaderBlock, fileHeaderBlockAddr, BSIZE); // local uchar name_len = fileHeaderBlock.name_len; local int64 name_len_offset_bytes = BSIZE- 80; local int64 name_offset_bytes = BSIZE- 79; local uchar name_len = ReadUByte(fileHeaderBlockAddr + name_len_offset_bytes); local string name = ReadString(fileHeaderBlockAddr + name_offset_bytes, name_len); return Str("FileHeader addr: %Xh File: \"%s\"", fileHeaderBlockAddr, name); } string DATABLOCKOFS_chksum_comment(ulong& chksum) { // TODO: Is it possible to get the data block address here and calculate the correct checksum? return "rootblock algorithm"; } /* * helper functions */ // TODO: Does 010 have a bool type? int isFileHeaderBlock(ulong blockNumber) { local int primary_type = ReadInt( blockNumber * BSIZE ); local int sec_type = ReadInt( (blockNumber * BSIZE) + 508 ); if ( primary_type == 2 && sec_type == -3 ) return 1; return 0; } string getFileHeaderBlockName(ulong fileHeaderBlockIndex) { if (!isFileHeaderBlock(fileHeaderBlockIndex)) return "invalid"; local int64 fileHeaderBlockAddr = fileHeaderBlockIndex * BSIZE; // TODO: Why does this causes 010 Editor to crash? // local FILEHEADERBLOCK fileHeaderBlock; // ReadBytes(fileHeaderBlock, fileHeaderBlockAddr, BSIZE); // local uchar name_len = fileHeaderBlock.name_len; local int64 name_len_offset_bytes = BSIZE- 80; local int64 name_offset_bytes = BSIZE- 79; local uchar name_len = ReadUByte(fileHeaderBlockAddr + name_len_offset_bytes); // TODO: BPL string is not null-terminated. Take care here? local string name = ReadString(fileHeaderBlockAddr + name_offset_bytes, name_len); return name; } uint32 calculateBootblockChecksum(int64 blockAddr) { local uint32 sum = 0; const uint kBootblockSizeLongwords = 2 * BSIZE / 4; // bootblock is 2 blocks (OFS at least) local uint longwordIndex; local uint32 val; local uint32 prevSum; for (longwordIndex = 0; longwordIndex < kBootblockSizeLongwords; ++longwordIndex) { // Don't include checksum field itself; it's value will be set such that the additive carry wraparound sum equals 0xffffffff (see end of this function) if (longwordIndex == 1) // bb_chksum is second longword in the bootblock header continue; val = ReadUInt(blockAddr + 4 * longwordIndex); prevSum = sum; sum += val; if (sum < prevSum) // check for carry (not possible to check CPU carry flag in C) ++sum; // add carry } // To make the additive carry wraparound sum equal 0xffffffff, the checksum needs to // be changed from zero to the complement of sum, such that checksum + sum = 0xffffffff // because all bits in x + ~x will be 1 by definition of One's complement. local uint32 checksum = ~sum; return checksum; } // // Calculates the checksum for most block types: Root, FileHeader, Extension, UserDirectory, and Data // Does NOT work for Boot or Bitmap blocks. // // See: // [1] Amiga Guru Book section 15.3.3.3 (p356) // [2] http://lclevy.free.fr/adflib/adf_info.html section 4.2.3 // uint32 calculateBlockChecksum(int64 blockAddr, uint checksumMemberLongwordIndex) { const uint kBlockSizeLongwords = BSIZE / 4; local int32 sum = 0; local uint longwordIndex; for (longwordIndex = 0; longwordIndex < kBlockSizeLongwords; longwordIndex++) { if (longwordIndex == checksumMemberLongwordIndex) continue; sum += ReadUInt(blockAddr + 4 * longwordIndex); } // The checksum is the sum negated, such that the checksum of the *entire* rootblock, including checksum is // zero, which can be used for validation. local int32 checksum = -sum; return checksum; } // // Calculates the checksum for most block types: Root, FileHeader, Extension, UserDirectory, Data and Bitmap // Does NOT work for Bootblocks // uint32 calculateRootblockChecksum(int64 blockAddr) { return calculateBlockChecksum(blockAddr, kRootblockChecksumMemberLongwordIndex); } uint32 calculateBitmapBlockChecksum(int64 blockAddr) { return calculateBlockChecksum(blockAddr, kBitmapChecksumMemberLongwordIndex); } // initialize block info array void _initBlockInfo(){ local int i; for( i = 0; i < BCOUNT; i++ ){ _blockInfo[i] = TBA; } //_printBlockInfoArray(); } // print block info for debugging purposes void _printBlockInfoArray(){ local int i, j; for ( i = 0; i < BCOUNT; i++ ) { if(i%16==0){Printf("\n");} Printf("%02x ",_blockInfo[i]); } Printf("\n\n"); } // get file system type void _getFileSystemType(){ local uchar firstBytes[4]; FSeek(0); ReadBytes( firstBytes, 0, 4 ); if( ((firstBytes[3]) & (1<<(0))) ) { isFFS = 1; } } // get disk type flags string for comments string _getDiskFlagsString( byte flags ) { string strFlags = ""; if( ((flags) & (1<<(0))) ) { strFlags += "| FFS "; } else { strFlags += "| OFS "; } if( ((flags) & (1<<(1))) ) { strFlags += "| INTL ONLY "; } else { strFlags += "| NO_INTL ONLY "; } if( ((flags) & (1<<(2))) ) { strFlags += "| DIRC&INTL "; } else { strFlags += "| NO_DIRC&INTL "; } return strFlags; } /* * main functions */ // Loop through disk sectors sequentially void _sequentialAproach() { local int blockNumber = 0; for ( blockNumber = 0; blockNumber < BCOUNT; blockNumber++ ) { _detectBlockType( blockNumber ); } } void _checkRootblockChecksum(int blockNumber, BlockType blockType) { local int64 blockAddr = blockNumber * BSIZE; local uint32 checksum = ReadUInt(blockAddr + (4 * kRootblockChecksumMemberLongwordIndex)); local uint32 expectedChecksum = calculateRootblockChecksum(blockAddr); if (checksum != expectedChecksum) { local string filename = getFileHeaderBlockName(typedatablock.header_key); Printf("Invalid %s checksum. Block index %03Xh Address: %05Xh Checksum: %08Xh Expected: %08Xh File: %s\n", EnumToString(blockType), blockNumber, blockAddr, typedatablock.chksum, expectedChecksum, filename); // TODO: Try to add a bookmark. See https://forum.sweetscape.com/t/typetostring-function/138 // local string bookmarkName = Str("Invalid %s index %03Xh", EnumToString(DataBlock), blockNumber); // local string typename = "DATABLOCKOFS"; // TODO: Does a TypeToString function exist? // AddBookmark(blockAddr, bookmarkName, typename, -1, cNone, cRed); } } // get block type void _detectBlockType( int blockNumber ){ //check if block was already detected if( _blockInfo[blockNumber] == KnownBlockType ) { return; } local int64 blockAddr = blockNumber * BSIZE; //prepare vars local uchar blockArray[BSIZE]; //detect boot block //blockArray = _readBlock( blockNumber ); FSeek( blockAddr ); ReadBytes( blockArray, blockNumber * BSIZE, BSIZE ); if ( blockArray[0] == 'D' && blockArray[1] == 'O' && blockArray[2] == 'S' && blockArray[4] != 'D' && blockArray[5] != 'O' && blockArray[6] != 'S' ) { //detect root block BOOTBLOCK typebootblock; _blockInfo[0] = KnownBlockType; _blockInfo[1] = KnownBlockType; // boot block consists of 2 blocks on floppy disks (not on hard disks, though) } //get type ulongs of block local int type = ReadInt( blockAddr ); local int sec_type = ReadInt( blockAddr + 508 ); //detect different block types if ( type == 2 && sec_type == 1 ) { //detect root block ROOTBLOCK typerootblock; _blockInfo[blockNumber] = KnownBlockType; _checkRootblockChecksum(blockNumber, Rootblock); //follow bitmap blocks _followBitmapBlocks( typerootblock.bm_pages ); } if ( type == 2 && sec_type == -3 ) { //detect file header FILEHEADERBLOCK typefileheaderblock; _blockInfo[blockNumber] = KnownBlockType; _checkRootblockChecksum(blockNumber, FileHeaderBlock); if( isFFS == 1 ) { _followDataBlocks( typefileheaderblock.data_blocks ); } } if ( type == 16 && sec_type == -3 ) { //detect file extension block FILEEXTENSIONBLOCK typefileextensionblock; _blockInfo[blockNumber] = KnownBlockType; _checkRootblockChecksum(blockNumber, FileExtensionBlock); if( isFFS == 1 ) { _followDataBlocks( typefileheaderblock.data_blocks ); } } if ( isFFS == 0 && type == 8 ) { //detect data block DATABLOCKOFS typedatablock; _blockInfo[blockNumber] = KnownBlockType; _checkRootblockChecksum(blockNumber, DataBlock); } if ( type == 2 && sec_type == 2 ) { //detect user directory block USERDIRECTORYBLOCK typeuserdirectoryblock; _blockInfo[blockNumber] = KnownBlockType; _checkRootblockChecksum(blockNumber, UserDirectoryBlock); } // no known block type if ( _blockInfo[blockNumber] != KnownBlockType ) { _blockInfo[blockNumber] = UnknownBlockType; } } // follow block chains void _followBitmapBlocks( ulong bm_pages[] ){ //prepare vars local uchar blockArray[BSIZE]; local int i; for ( i = 0; i < sizeof(bm_pages)/4; i++ ){ // skip iteration or even break out of the loop if ( bm_pages[i] == 0 || bm_pages[i] > BCOUNT -1 ) { continue; } // mark bitmap block FSeek( bm_pages[i] * BSIZE ); ReadBytes( blockArray, bm_pages[i] * BSIZE, BSIZE ); BITMAPBLOCK typebitmapblock; } // TODO: here should follow a possible bitmap extension block(s) analysis for hard disk files } void _followDataBlocks( ulong data_blocks[] ){ //prepare vars local uchar blockArray[BSIZE]; local int i; for ( i = 0; i < sizeof(data_blocks)/4; i++ ){ // skip iteration or even break out of the loop if ( data_blocks[i] == 0 || data_blocks[i] > BCOUNT -1 ) { continue; } // mark data block FSeek( data_blocks[i] * BSIZE ); ReadBytes( blockArray, data_blocks[i] * BSIZE, BSIZE ); DATABLOCKFFS typedatablockffs; _blockInfo[data_blocks[i]] = KnownBlockType; } } /* * template action */ Printf( "Filesize : %d (%s)\n", FSIZE, FSIZE == 901120 ? "DD" : "HD" ); Printf( "Block Count: %d\n", BCOUNT ); Printf( "isFFS : %d\n\n", isFFS ); Printf( "Start template\n" ); _sequentialAproach(); //_printBlockInfoArray(); Printf( "Template done\n\n" );