XADMaster/XADCFBFParser.m
2018-03-21 14:24:50 +02:00

321 lines
8.7 KiB
Objective-C

/*
* XADCFBFParser.m
*
* Copyright (c) 2017-present, MacPaw Inc. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
#import "XADCFBFParser.h"
#import "XADBlockHandle.h"
#import "NSDateXAD.h"
@implementation XADCFBFParser
-(id)init
{
if((self=[super init]))
{
sectable=NULL;
minisectable=NULL;
}
return self;
}
-(void)dealloc
{
free(sectable);
free(minisectable);
[super dealloc];
}
+(int)requiredHeaderSize { return 512; }
+(BOOL)recognizeFileWithHandle:(CSHandle *)handle firstBytes:(NSData *)data name:(NSString *)name
{
const uint8_t *bytes=[data bytes];
int length=[data length];
return length>=512&&bytes[0]==0xd0&&bytes[1]==0xcf&&bytes[2]==0x11&&bytes[3]==0xe0&&
bytes[4]==0xa1&&bytes[5]==0xb1&&bytes[6]==0x1a&&bytes[7]==0xe1&&bytes[28]==0xfe&&bytes[29]==0xff;
}
-(void)parse
{
CSHandle *fh=[self handle];
// Read header
[fh skipBytes:30];
int secshift=[fh readUInt16LE];
int minisecshift=[fh readUInt16LE];
[fh skipBytes:10];
uint32_t numtablesecs=[fh readUInt32LE];
uint32_t firstdirsec=[fh readUInt32LE];
[fh skipBytes:4];
minsize=[fh readUInt32LE];
uint32_t firstminitablesec=[fh readUInt32LE];
uint32_t numminitablesecs=[fh readUInt32LE];
uint32_t firstmastersec=[fh readUInt32LE];
/*uint32_t nummastersecs=*/[fh readUInt32LE];
secsize=1<<secshift;
minisecsize=1<<minisecshift;
// Read allocation table through the master allocation table
int idspersec=secsize/4;
numsectors=numtablesecs*idspersec;
sectable=malloc(numsectors*sizeof(uint32_t));
secvisitedtable=calloc(numsectors*sizeof(bool),1);
for(int i=0;i<numtablesecs;i++)
{
if(i==109)
{
[self seekToSector:firstmastersec];
}
else if(i>109 && (i-109)%(idspersec-1)==0)
{
uint32_t nextsec=[fh readUInt32LE];
[self seekToSector:nextsec];
}
int sector=[fh readUInt32LE];
off_t currpos=[fh offsetInFile];
[self seekToSector:sector];
for(int j=0;j<idspersec;j++) sectable[i*idspersec+j]=[fh readUInt32LE];
[fh seekToFileOffset:currpos];
}
// Read short-sector allocation table
numminisectors=numminitablesecs*idspersec;
minisectable=malloc(numminisectors*sizeof(uint32_t));
uint32_t minitablesec=firstminitablesec;
for(int i=0;i<numminitablesecs;i++)
{
[self seekToSector:minitablesec];
for(int j=0;j<idspersec;j++) minisectable[i*idspersec+j]=[fh readUInt32LE];
minitablesec=[self nextSectorAfter:minitablesec];
}
// Read directory entries
NSMutableArray *entries=[NSMutableArray array];
BOOL firstentry=YES;
uint32_t dirsec=firstdirsec;
while(dirsec!=0xfffffffe)
{
if(secvisitedtable[dirsec]) [XADException raiseIllegalDataException];
secvisitedtable[dirsec]=true;
[self seekToSector:dirsec];
for(int i=0;i<secsize;i+=128)
{
uint8_t name[64];
[fh readBytes:64 toBuffer:name];
int numnamebytes=[fh readUInt16LE];
int type=[fh readUInt8];
int black=[fh readUInt8];
uint32_t leftchild=[fh readUInt32LE];
uint32_t rightchild=[fh readUInt32LE];
uint32_t rootnode=[fh readUInt32LE];
[fh skipBytes:16];
uint32_t flags=[fh readUInt32LE];
uint64_t created=[fh readUInt64LE];
uint64_t modified=[fh readUInt64LE];
uint32_t firstsec=[fh readUInt32LE];
off_t size;
if(secshift>=12)
{
size=[fh readUInt64LE];
}
else
{
size=[fh readUInt32LE];
[fh skipBytes:4];
}
if(firstentry)
{
if(type!=5 && type!=0) [XADException raiseIllegalDataException];
firstentry=NO;
}
if(type==0) // empty entry
{
[entries addObject:[NSNull null]];
}
else if(type==5) // root entry
{
rootdirectorynode=rootnode;
firstminisector=firstsec;
[entries addObject:[NSNull null]];
}
else
{
NSMutableDictionary *entry=[NSMutableDictionary dictionaryWithObjectsAndKeys:
[self decodeFileNameWithBytes:name length:numnamebytes],@"CFBFFileName",
[NSNumber numberWithInt:type],@"CFBFType",
[NSNumber numberWithInt:black],@"CFBFRedOrBlack",
[NSNumber numberWithUnsignedInt:leftchild],@"CFBFLeftChild",
[NSNumber numberWithUnsignedInt:rightchild],@"CFBFRightChild",
[NSNumber numberWithUnsignedInt:flags],@"CFBFFlags",
nil];
if(type==1)
{
[entry setObject:[NSNumber numberWithBool:YES] forKey:XADIsDirectoryKey];
[entry setObject:[NSNumber numberWithUnsignedInt:rootnode] forKey:@"CFBFRootNode"];
}
else if(type==2)
{
[entry setObject:[NSNumber numberWithLongLong:size] forKey:XADFileSizeKey];
[entry setObject:[NSNumber numberWithLongLong:size] forKey:XADCompressedSizeKey];
[entry setObject:[NSNumber numberWithUnsignedLongLong:firstsec] forKey:@"CFBFFirstSector"];
}
if(created) [entry setObject:[NSDate XADDateWithWindowsFileTime:created] forKey:XADCreationDateKey];
if(modified) [entry setObject:[NSDate XADDateWithWindowsFileTime:modified] forKey:XADLastModificationDateKey];
[entries addObject:entry];
}
}
dirsec=[self nextSectorAfter:dirsec];
}
// Resolve directory structure
[self processEntry:rootdirectorynode atPath:[self XADPath] entries:entries];
}
-(XADString *)decodeFileNameWithBytes:(uint8_t *)bytes length:(int)length
{
static const int LowChar=0x3800;
static const int HighChar=0x3800+64*65;
static const char Chars[64]="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz._";
for(int i=0;i<length-2;i+=2)
{
uint16_t c=CSUInt16LE(&bytes[i]);
if(c<LowChar||c>HighChar)
{
NSMutableString *filename=[NSMutableString stringWithCapacity:length/2-2];
for(int i=0;i<length-2;i+=2) [filename appendFormat:@"%C",CSUInt16LE(&bytes[i])];
return [self XADStringWithString:filename];
}
}
NSMutableString *filename=[NSMutableString stringWithCapacity:length];
for(int i=0;i<length-2;i+=2)
{
uint16_t code=CSUInt16LE(&bytes[i])-LowChar;
if(code==HighChar) [filename appendString:@"!"];
else
{
int c1=code&0x3f;
int c2=code>>6;
[filename appendFormat:@"%c",Chars[c1]];
if(c2<64) [filename appendFormat:@"%c",Chars[c2]];
}
}
return [self XADStringWithString:filename];
}
-(void)processEntry:(uint32_t)n atPath:(XADPath *)path entries:(NSArray *)entries
{
NSMutableDictionary *entry=[entries objectAtIndex:n];
uint32_t left=[[entry objectForKey:@"CFBFLeftChild"] unsignedIntValue];
if(left!=0xffffffff) [self processEntry:left atPath:path entries:entries];
XADPath *filename=[path pathByAppendingXADStringComponent:[entry objectForKey:@"CFBFFileName"]];
[entry setObject:filename forKey:XADFileNameKey];
[self addEntryWithDictionary:entry];
int type=[[entry objectForKey:@"CFBFType"] intValue];
if(type==1)
{
uint32_t root=[[entry objectForKey:@"CFBFRootNode"] unsignedIntValue];
if(root!=0xffffffff) [self processEntry:root atPath:filename entries:entries];
}
uint32_t right=[[entry objectForKey:@"CFBFRightChild"] unsignedIntValue];
if(right!=0xffffffff) [self processEntry:right atPath:path entries:entries];
}
-(void)seekToSector:(uint32_t)sector
{
if(sector>=numsectors) [XADException raiseIllegalDataException];
[[self handle] seekToFileOffset:(sector+1)*secsize];
}
-(uint32_t)nextSectorAfter:(uint32_t)sector
{
if(sector>=numsectors) [XADException raiseIllegalDataException];
return sectable[sector];
}
-(CSHandle *)handleForEntryWithDictionary:(NSDictionary *)dict wantChecksum:(BOOL)checksum
{
CSHandle *handle=[self handle];
off_t size=[[dict objectForKey:XADFileSizeKey] longLongValue];
uint32_t first=[[dict objectForKey:@"CFBFFirstSector"] unsignedIntValue];
if(size>=minsize)
{
XADBlockHandle *bh=[[[XADBlockHandle alloc] initWithHandle:handle length:size blockSize:secsize] autorelease];
[bh setBlockChain:sectable numberOfBlocks:numsectors firstBlock:first headerSize:512];
return bh;
}
else
{
XADBlockHandle *bh=[[[XADBlockHandle alloc] initWithHandle:handle blockSize:secsize] autorelease];
[bh setBlockChain:sectable numberOfBlocks:numsectors firstBlock:firstminisector headerSize:512];
XADBlockHandle *mbh=[[[XADBlockHandle alloc] initWithHandle:bh length:size blockSize:minisecsize] autorelease];
[mbh setBlockChain:minisectable numberOfBlocks:numminisectors firstBlock:first headerSize:0];
return mbh;
}
}
-(NSString *)formatName
{
return @"CFBF";
}
@end