CocoaDev

Edit AllPages

Here is a function to parse .DS_Store files, for those who want to dig into the internals of the Finder. It’s written entirely based on observation of .DS_Store files, and as such is really just guesswork. However, it does seem to parse all the .DS_Store files I’ve found.

It returns a dictionary with filenames as keys, and further dictionaries as values. These dictionaries have four-character OStype strings as keys, and various kinds of values: NSNumber, NSString and NSData, depending on the type of the data.

I have not yet begun to work out what the different attributes actually mean. If anyone wants to contribute some information on that, feel free.

-WAHa

(Update on December 8, 2006: Fixed some minor bugs. - WAHa)

static inline uint16_t CSGetUInt16(const uint8_t *b) { return ((uint16_t)b[0]«8) (uint16_t)b[1]; }    
static inline uint32_t CSGetUInt32(const uint8_t *b) { return ((uint32_t)b[0]«24) ((uint32_t)b[1]«16) ((uint32_t)b[2]«8) (uint32_t)b[3]; }
static inline int16_t CSGetInt16(const uint8_t *b) { return ((int16_t)b[0]«8) (int16_t)b[1]; }    
static inline int32_t CSGetInt32(const uint8_t *b) { return ((int32_t)b[0]«24) ((int32_t)b[1]«16) ((int32_t)b[2]«8) (int32_t)b[3]; }

NSDictionary *CSParseDSStore(NSString *filename) { NSData *data=[NSData dataWithContentsOfFile:filename]; if(!data) return nil;

const unsigned char *bytes=[data bytes];
int length=[data length];
if(length<20) return nil; // Too short for the header.

int ver=CSGetUInt32(bytes);
if(ver!=1) return nil; // Unsupported version (probably).

int type=CSGetUInt32(bytes+4);
if(type!='Bud1') return nil; // Unsupported filetype.

NSMutableDictionary *dict=[NSMutableDictionary dictionary];

for(int n=0;;n++)
{
	if(length<24+n*4) return nil; // Truncated file.

	int offs=CSGetUInt32(bytes+20+n*4);
	if(offs==0) return dict; // No more chunk sections, parsing is done.

	offs&=~0x0f; // Chunk sections are 16-byte aligned, but the offsets are not for some reason.

	if(length<offs+12) return nil; // Truncated file.

	int val1=CSGetUInt32(bytes+offs);
	int val2=CSGetUInt32(bytes+offs+4);
	int numchunks=CSGetUInt32(bytes+offs+8);
	int chunk=offs+12;

	for(int i=0;i<numchunks;i++)
	{
		if(val2&2) chunk+=4; // Extra four-byte value before each chunk.

		if(length<chunk+4) goto end; // Truncated file.

		int namelen=CSGetUInt32(bytes+chunk);
		if(length<chunk+12+namelen*2) goto end; // Truncated file.

		NSString *filename=[[[NSString alloc] initWithBytes:bytes+chunk+4 length:namelen*2
		encoding:CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingUTF16BE)] autorelease];
		chunk+=4+namelen*2;

		NSString *attrname=[[[NSString alloc] initWithBytes:bytes+chunk length:4
		encoding:NSISOLatin1StringEncoding] autorelease];
		uint32_t type=CSGetUInt32(bytes+chunk+4);
		chunk+=8;

		id value;
		switch(type)
		{
			case 'bool': // One-byte boolean.
				if(length<chunk+1) goto end; // Truncated file.
				value=[NSNumber numberWithBool:bytes[chunk]];
				chunk+=1;
			break;

			case 'long': // Four-byte long.
			case 'shor': // Shorts seem to be 4 bytes too.
				if(length<chunk+4) goto end; // Truncated file.
				value=[NSNumber numberWithLong:CSGetInt32(bytes+chunk)];
				chunk+=4;
			break;

			case 'blob': // Binary data.
			{
				if(length<chunk+4) goto end; // Truncated file.
				int len=CSGetUInt32(bytes+chunk);
				if(length<chunk+4+len) goto end; // Truncated file.
				value=[NSData dataWithBytes:bytes+chunk+4 length:len];
				chunk+=4+len;
			}
			break;

			case 'ustr': // UTF16BE string
			{
				if(length<chunk+4) goto end; // Truncated file.
				int len=CSGetUInt32(bytes+chunk);
				if(length<chunk+4+len*2) goto end; // Truncated file.
				value=[[[NSString alloc] initWithBytes:bytes+chunk+2 length:len*2
				encoding:CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingUTF16BE)] autorelease];
				chunk+=4+len*2;
			}
			break;

			default: goto end; // Unknown chunk type, give up.
		}

		NSMutableDictionary *filedict=[dict objectForKey:filename];
		if(!filedict)
		{
			filedict=[NSMutableDictionary dictionary];
			[dict setObject:filedict forKey:filename];
		}

		[filedict setObject:value forKey:attrname];
	}
	end: ;
} }

Interesting… In term of what some of the attributes are see http://developer.apple.com/documentation/Carbon/Reference/Finder_Interface/Reference/reference.html – RbrtPntn


The OStypes I’m seeing don’t really match anything listed on that page, so I don’t think that’s quite it… Although a lot of this is very obvious, or should become obvious with a little bit of experimentation. - WAHa