Talk:DS Store File Format: Difference between revisions

From MozillaWiki
Jump to navigation Jump to search
(Added minor clarification about 'pict' struct.)
 
(Added some quick and dirty code to parse .DS_Store files)
Line 1: Line 1:
The 'pict' structure mentioned in the article is in fact an AliasRecord to the background picture. [[User:Dhcmrlchtdj|Dhcmrlchtdj]] 08:12, 11 October 2007 (PDT)
The 'pict' structure mentioned in the article is in fact an AliasRecord to the background picture. [[User:Dhcmrlchtdj|Dhcmrlchtdj]] 08:12, 11 October 2007 (PDT)
What follows is a stab at a command-line utility to parse .DS_Store files, partially based on the reverse-engineered information from the article. [[User:Dhcmrlchtdj|Dhcmrlchtdj]] 02:48, 16 October 2007 (PDT)
<pre>
/*
* dsdump.c
*
* Written by Marco Piovanelli, October 2007
* <mailto:marco.piovanelli@pobox.com>
*
* compile with:
* cc -framework Carbon -o dsdump dsdump.c
*/
#include <stdio.h>
#include <inttypes.h>
#include <Carbon/Carbon.h>
inline uint16_t flip_short(uint16_t x)
{
#if TARGET_RT_LITTLE_ENDIAN
return ((x & 0xff00) >> 8) |
  ((x & 0x00ff) << 8);
#else
return x;
#endif
}
inline uint32_t flip_long(uint32_t x)
{
#if TARGET_RT_LITTLE_ENDIAN
return ((x & 0xff000000) >> 24) |
  ((x & 0x00ff0000) >> 8 ) |
  ((x & 0x0000ff00) << 8 ) |
  ((x & 0x000000ff) << 24);
#else
return x;
#endif
}
static void flip_short_array(uint16_t * buf, uint32_t len)
{
#if TARGET_RT_LITTLE_ENDIAN
int index;
for(index = 0; index < len; ++index)
{
buf[index] = flip_short(buf[index]);
}
#endif
}
static void flip_rect(Rect * r)
{
#if TARGET_RT_LITTLE_ENDIAN
r->top = flip_short(r->top);
r->left = flip_short(r->left);
r->bottom = flip_short(r->bottom);
r->right = flip_short(r->right);
#endif
}
// type codes
#define kDSTypeBoolean 'bool' // Boolean (1 byte)
#define kDSTypeShort 'shor' // SInt16 (4 bytes, but highest two are always zero)
#define kDSTypeLong 'long' // SInt32 (4 bytes)
#define kDSTypeUnicodeString 'ustr' // UTF-16 encoded string (4-byte length follows type code)
#define kDSTypeBlob 'blob' // arbitrary structure (4-byte length follows type code)
// data tags
#define kDSTagWindowInfo 'fwi0' // blob -- 16 bytes
#define kDSTagWindowHeight 'fwvh' // shor
#define kDSTagWindowSidebarWidth 'fwsw' // long
#define kDSTagICSP 'icsp' // blob -- 8 bytes
#define kDSTagIconViewOptions 'icvo' // blob -- 26 bytes
#define kDSTagIconViewTextSize 'icvt' // shor
#define kDSTagIconUnknown1 'icgo' // blob -- 8 bytes
#define kDSTagBackgroundType 'BKGD' // blob -- 12 bytes
#define kDSTagPicture 'pict' // blob
#define kDSTagIconLocation 'Iloc' // blob -- 16 bytes
#define kDSTagDesktopIconLocation 'dilc' // blob -- 32 bytes
#define kDSTagListViewOptions 'lsvo' // blob -- 76 bytes
#define kDSTagListViewTextSize 'lsvt' // shor
#define kDSTagComment 'cmmt' // ustr
#define kDSTagClippingRect 'clip' // blob -- 8 bytes
// background types
#define kDSBackgroundDefault 'DefB' // default background (white)
#define kDSBackgroundColor 'ClrB' // color background
#define kDSBackgroundPicture 'PctB' // picture background
// arrangements
#define kDSArrangementNone 'none'
#define kDSArrangementByCreationDate 'ascd'
#define kDSArrangementByModificationDate 'modd'
#define kDSArrangementBySize 'phys'
#define kDSArrangementByKind 'kind'
#define kDSArrangementByLabel 'labl'
// label positions
#define kDSLabelPositionBottom 'botm'
#define kDSLabelPositionRight 'rght'
#pragma options align=mac68k
typedef struct _DSStructHeader
{
uint32_t mTag;
uint32_t mType;
} DSStructHeader;
typedef struct _DSIconLocation
{
uint32_t mLocationX;
uint32_t mLocationY;
} DSIconLocation;
typedef struct _DSWindowInfo
{
Rect mWindowRect;
} DSWindowInfo;
typedef struct _DSIconViewOptions
{
uint32_t mVersion; // always 'icv4' in Tiger?
uint16_t mIconSize; // icon size in pixels
uint32_t mArrangement; // one of the kDSArrangement* constants
uint32_t mLabelPosition; // one of the kDSLabelPosition* constants ('botm' or 'rght')
} DSIconViewOptions;
typedef struct _DSBackground
{
uint32_t mBackgroundType;
} DSBackground;
#pragma options align=reset
static char * ostype_to_string(uint32_t tag, char string[5])
{
string[0] = (tag & 0xff000000) >> 24;
string[1] = (tag & 0x00ff0000) >> 16;
string[2] = (tag & 0x0000ff00) >> 8;
string[3] = (tag & 0x000000ff);
string[4] = 0;
return string;
}
static void print_cfstring(const char * format, CFStringRef cfstring)
{
UInt8 nameBuf[32]; // truncate strings to 31 characters
CFIndex usedBufLen = 0;
// convert name to ASCII for printf
CFStringGetBytes(cfstring, CFRangeMake(0, CFStringGetLength(cfstring)), kCFStringEncodingASCII, '*', false, nameBuf, sizeof(nameBuf) - 1, &usedBufLen);
nameBuf[usedBufLen] = 0;
printf(format, nameBuf);
}
static void print_rect(const char * format, const Rect * r)
{
char rectString[256];
sprintf(rectString, "[t = %d, l = %d, b = %d, r = %d]", r->top, r->left, r->bottom, r->right);
printf(format, rectString);
}
static void parse_ds_struct(uint32_t offset, CFStringRef name, uint32_t tag, uint32_t type, const void * dataPtr, uint32_t dataSize)
{
char tagString[5];
char typeString[5];
printf("%08x\t", offset);
print_cfstring("%-32s", name);
printf("\t%s\t%s\t%5d\t", ostype_to_string(tag, tagString), ostype_to_string(type, typeString), (int)dataSize);
switch(tag)
{
case kDSTagWindowInfo:
{
DSWindowInfo windowInfo = *(const DSWindowInfo*)dataPtr;
flip_rect(&windowInfo.mWindowRect);
print_rect("(window_rect = %s)", &windowInfo.mWindowRect);
break;
}
case kDSTagIconViewOptions:
{
DSIconViewOptions iconViewOptions = *(const DSIconViewOptions*)dataPtr;
const char * labelPosition = "unknown";
const char * arrangement = "unknown";
iconViewOptions.mIconSize = flip_short(iconViewOptions.mIconSize);
iconViewOptions.mArrangement = flip_long(iconViewOptions.mArrangement);
iconViewOptions.mLabelPosition = flip_long(iconViewOptions.mLabelPosition);
switch(iconViewOptions.mLabelPosition)
{
case kDSLabelPositionBottom: labelPosition = "bottom"; break;
case kDSLabelPositionRight: labelPosition = "right"; break;
}
switch(iconViewOptions.mArrangement)
{
case kDSArrangementNone: arrangement = "none"; break;
case kDSArrangementByModificationDate: arrangement = "by modification date"; break;
case kDSArrangementByCreationDate: arrangement = "by creation date"; break;
case kDSArrangementBySize: arrangement = "by size"; break;
case kDSArrangementByKind: arrangement = "by kind"; break;
case kDSArrangementByLabel: arrangement = "by label"; break;
}
printf("(icon_size = %d; label_position = %s; arrangement = %s)", iconViewOptions.mIconSize, labelPosition, arrangement);
break;
}
case kDSTagIconViewTextSize:
{
printf("(icon_view_text_size = %d)", flip_long(*(const uint32_t*)dataPtr));
break;
}
case kDSTagWindowSidebarWidth:
{
printf("(sidebar_width = %d)", flip_long(*(const uint32_t*)dataPtr));
break;
}
case kDSTagWindowHeight:
{
printf("(window_height = %d)", flip_long(*(const uint32_t*)dataPtr));
break;
}
case kDSTagBackgroundType:
{
if(dataSize == 12)
{
DSBackground background = *(const DSBackground *)dataPtr;
background.mBackgroundType = flip_long(background.mBackgroundType);
switch(background.mBackgroundType)
{
case kDSBackgroundDefault:
{
printf("(type = default)");
break;
}
case kDSBackgroundColor:
{
printf("(type = color)");
break;
}
case kDSBackgroundPicture:
{
printf("(type = picture)");
break;
}
}
}
break;
}
case kDSTagPicture:
{
Handle aliasHandle = 0;
FSRef fsRef;
Boolean wasChanged = false;
char path[1024];
if(PtrToHand(dataPtr, &aliasHandle, dataSize) == noErr)
{
if (FSResolveAliasWithMountFlags(0, (AliasHandle)aliasHandle, &fsRef, &wasChanged, kResolveAliasFileNoUI) == noErr)
{
if (FSRefMakePath(&fsRef, (UInt8*)path, sizeof(path)) == noErr)
{
printf("(alias = %s)", path);
}
}
DisposeHandle(aliasHandle);
}
break;
}
case kDSTagIconLocation:
{
if(dataSize == 16)
{
DSIconLocation iconLocation = *(const DSIconLocation*) dataPtr;
iconLocation.mLocationX = flip_long(iconLocation.mLocationX);
iconLocation.mLocationY = flip_long(iconLocation.mLocationY);
printf("(x = %d; y = %d)", iconLocation.mLocationX, iconLocation.mLocationY);
}
break;
}
case kDSTagListViewTextSize:
{
printf("(list_view_text_size = %d)", flip_long(*(const uint32_t*)dataPtr));
break;
}
case kDSTagComment:
{
if(type == kDSTypeUnicodeString)
{
uint16_t * commentBuf = (uint16_t*)dataPtr;
uint32_t commentLength = dataSize / 2;
CFStringRef comment = 0;
flip_short_array(commentBuf, commentLength);
comment = CFStringCreateWithCharacters(kCFAllocatorDefault, commentBuf, commentLength);
print_cfstring("(comment = %s)", comment);
CFRelease(comment);
}
break;
}
case kDSTagClippingRect:
{
Rect clippingRect = * (const Rect*)dataPtr;
flip_rect(&clippingRect);
print_rect("(clipping_rect = %s)", &clippingRect);
break;
}
}
printf("\n");
}
static int parse_ds_block(FILE * f, uint32_t blockOffset)
{
uint32_t structCount = 0;
int structIndex;
DSStructHeader dsStruct;
fseek(f, blockOffset + 8, SEEK_SET);
if (fread(&structCount, sizeof(structCount), 1, f) != 1)
{
return 0;
}
structCount = flip_long(structCount);
if(structCount == 0)
{
return 0;
}
printf("...block @ %08x contains %d structures...\n", blockOffset, structCount);
for(structIndex = 0; structIndex < structCount; ++structIndex)
{
uint32_t structOffset = ftell(f);
uint32_t objectNameLength = 0;
uint16_t * objectNameBuf = 0;
CFStringRef objectName = 0;
uint32_t dataSize = 0;
void * dataBuf = 0;
// read object name length
if(fread(&objectNameLength, sizeof(objectNameLength), 1, f) != 1)
{
return;
}
objectNameLength = flip_long(objectNameLength);
objectNameBuf = (uint16_t*)malloc(2 * objectNameLength);
// read object name (this is UTF-16 encoded)
if(fread(objectNameBuf, 2 * objectNameLength, 1, f) != 1)
{
return;
}
flip_short_array(objectNameBuf, objectNameLength);
// create CFString from object name
objectName = CFStringCreateWithCharacters(kCFAllocatorDefault, objectNameBuf, objectNameLength);
free(objectNameBuf);
// read tag and type
if(fread(&dsStruct, sizeof(dsStruct), 1, f) != 1)
{
return;
}
dsStruct.mTag = flip_long(dsStruct.mTag);
dsStruct.mType = flip_long(dsStruct.mType);
// calculate payload size
switch(dsStruct.mType)
{
case kDSTypeBoolean:
{
dataSize = 1;
break;
}
case kDSTypeShort:
case kDSTypeLong:
{
dataSize = 4;
break;
}
case kDSTypeUnicodeString:
{
if(fread(&dataSize, sizeof(dataSize), 1, f) != 1)
{
return;
}
dataSize = flip_long(dataSize);
dataSize *= 2;
break;
}
case kDSTypeBlob:
{
if(fread(&dataSize, sizeof(dataSize), 1, f) != 1)
{
return;
}
dataSize = flip_long(dataSize);
break;
}
default:
{
// unrecognized data type
break;
}
}
if(dataSize > 0)
{
dataBuf = malloc(dataSize);
if(fread(dataBuf, dataSize, 1, f) != 1)
{
return;
}
parse_ds_struct(structOffset, objectName, dsStruct.mTag, dsStruct.mType, dataBuf, dataSize);
free(dataBuf);
}
CFRelease(objectName);
}
return structCount;
}
static void parse_ds_store_file(const char * inDSStorePath)
{
FILE * f = fopen(inDSStorePath, "r");
uint32_t blockOffset;
if(f)
{
printf("%-8s\t%-32s\t%-4s\t%-4s\t%s\t%s\n\n", "offset", "object name", "tag", "type", "length", "description");
fseek(f, 0x14, SEEK_SET);
if(fread(&blockOffset, sizeof(blockOffset), 1, f) != 1)
{
goto cleanup;
}
blockOffset = flip_long(blockOffset);
blockOffset &= ~15;
for( ; ; blockOffset = (blockOffset & 0xfffff000) + 0x1000)
{
if (parse_ds_block(f, blockOffset) == 0)
{
break;
}
}
cleanup:
fclose(f);
}
}
int main ( int argc, char * argv[] )
{
parse_ds_store_file(argv[1]);
return 0 ;
}
</pre>

Revision as of 09:48, 16 October 2007

The 'pict' structure mentioned in the article is in fact an AliasRecord to the background picture. Dhcmrlchtdj 08:12, 11 October 2007 (PDT)

What follows is a stab at a command-line utility to parse .DS_Store files, partially based on the reverse-engineered information from the article. Dhcmrlchtdj 02:48, 16 October 2007 (PDT)

/*
 *			dsdump.c
 *
 *			Written by Marco Piovanelli, October 2007
 *			<mailto:marco.piovanelli@pobox.com>
 *
 *			compile with:
 *			cc -framework Carbon -o dsdump dsdump.c
 */

#include <stdio.h>
#include <inttypes.h>
#include <Carbon/Carbon.h>

inline uint16_t flip_short(uint16_t x)
{
#if TARGET_RT_LITTLE_ENDIAN
	return ((x & 0xff00) >> 8) |
		   ((x & 0x00ff) << 8);
#else
	return x;
#endif
}

inline uint32_t flip_long(uint32_t x)
{
#if TARGET_RT_LITTLE_ENDIAN
	return ((x & 0xff000000) >> 24) |
		   ((x & 0x00ff0000) >> 8 ) |
		   ((x & 0x0000ff00) << 8 ) |
		   ((x & 0x000000ff) << 24);
#else
	return x;
#endif
}

static void flip_short_array(uint16_t * buf, uint32_t len)
{
#if TARGET_RT_LITTLE_ENDIAN
	int	index;
	for(index = 0; index < len; ++index)
	{
		buf[index] = flip_short(buf[index]);
	}
#endif
}

static void flip_rect(Rect * r)
{
#if TARGET_RT_LITTLE_ENDIAN
	r->top = flip_short(r->top);
	r->left = flip_short(r->left);
	r->bottom = flip_short(r->bottom);
	r->right = flip_short(r->right);
#endif
}

//	type codes

#define			kDSTypeBoolean						'bool'		//	Boolean (1 byte)
#define			kDSTypeShort						'shor'		//	SInt16 (4 bytes, but highest two are always zero)
#define			kDSTypeLong							'long'		//	SInt32 (4 bytes)
#define			kDSTypeUnicodeString				'ustr'		//	UTF-16 encoded string (4-byte length follows type code)
#define			kDSTypeBlob							'blob'		//	arbitrary structure (4-byte length follows type code)

//	data tags

#define			kDSTagWindowInfo					'fwi0'		//	blob -- 16 bytes
#define			kDSTagWindowHeight					'fwvh'		//	shor
#define			kDSTagWindowSidebarWidth			'fwsw'		//	long
#define			kDSTagICSP							'icsp'		//	blob -- 8 bytes
#define			kDSTagIconViewOptions				'icvo'		//	blob -- 26 bytes
#define			kDSTagIconViewTextSize				'icvt'		//	shor
#define			kDSTagIconUnknown1					'icgo'		//	blob -- 8 bytes
#define			kDSTagBackgroundType				'BKGD'		//	blob -- 12 bytes
#define			kDSTagPicture						'pict'		//	blob
#define			kDSTagIconLocation					'Iloc'		//	blob -- 16 bytes
#define			kDSTagDesktopIconLocation			'dilc'		//	blob -- 32 bytes
#define			kDSTagListViewOptions				'lsvo'		//	blob -- 76 bytes
#define			kDSTagListViewTextSize				'lsvt'		//	shor
#define			kDSTagComment						'cmmt'		//	ustr
#define			kDSTagClippingRect					'clip'		//	blob -- 8 bytes

//	background types

#define			kDSBackgroundDefault				'DefB'		//	default background (white)
#define			kDSBackgroundColor					'ClrB'		//	color background
#define			kDSBackgroundPicture				'PctB'		//	picture background

//	arrangements

#define			kDSArrangementNone					'none'
#define			kDSArrangementByCreationDate		'ascd'
#define			kDSArrangementByModificationDate	'modd'
#define			kDSArrangementBySize				'phys'
#define			kDSArrangementByKind				'kind'
#define			kDSArrangementByLabel				'labl'

//	label positions

#define			kDSLabelPositionBottom				'botm'
#define			kDSLabelPositionRight				'rght'

#pragma options align=mac68k

typedef struct _DSStructHeader
{
	uint32_t	mTag;
	uint32_t	mType;
} DSStructHeader;

typedef struct _DSIconLocation
{
	uint32_t	mLocationX;
	uint32_t	mLocationY;
} DSIconLocation;

typedef struct _DSWindowInfo
{
	Rect		mWindowRect;
} DSWindowInfo;

typedef struct _DSIconViewOptions
{
	uint32_t	mVersion;				//	always 'icv4' in Tiger?
	uint16_t	mIconSize;				//	icon size in pixels
	uint32_t	mArrangement;			//	one of the kDSArrangement* constants
	uint32_t	mLabelPosition;			//	one of the kDSLabelPosition* constants ('botm' or 'rght')
} DSIconViewOptions;

typedef struct _DSBackground
{
	uint32_t	mBackgroundType;
} DSBackground;

#pragma options align=reset

static char * ostype_to_string(uint32_t tag, char string[5])
{
	string[0] = (tag & 0xff000000) >> 24;
	string[1] = (tag & 0x00ff0000) >> 16;
	string[2] = (tag & 0x0000ff00) >> 8;
	string[3] = (tag & 0x000000ff);
	string[4] = 0;

	return string;
}

static void print_cfstring(const char * format, CFStringRef cfstring)
{
	UInt8						nameBuf[32];			//	truncate strings to 31 characters
	CFIndex						usedBufLen = 0;

	//	convert name to ASCII for printf
	CFStringGetBytes(cfstring, CFRangeMake(0, CFStringGetLength(cfstring)), kCFStringEncodingASCII, '*', false, nameBuf, sizeof(nameBuf) - 1, &usedBufLen);
	nameBuf[usedBufLen] = 0;

	printf(format, nameBuf);
}

static void print_rect(const char * format, const Rect * r)
{
	char						rectString[256];

	sprintf(rectString, "[t = %d, l = %d, b = %d, r = %d]", r->top, r->left, r->bottom, r->right);
	printf(format, rectString);
}

static void parse_ds_struct(uint32_t offset, CFStringRef name, uint32_t tag, uint32_t type, const void * dataPtr, uint32_t dataSize)
{
	char						tagString[5];
	char						typeString[5];

	printf("%08x\t", offset);
	print_cfstring("%-32s", name);
	printf("\t%s\t%s\t%5d\t", ostype_to_string(tag, tagString), ostype_to_string(type, typeString), (int)dataSize);

	switch(tag)
	{
		case kDSTagWindowInfo:
		{
			DSWindowInfo		windowInfo = *(const DSWindowInfo*)dataPtr;

			flip_rect(&windowInfo.mWindowRect);
			print_rect("(window_rect = %s)", &windowInfo.mWindowRect);
			break;
		}

		case kDSTagIconViewOptions:
		{
			DSIconViewOptions	iconViewOptions = *(const DSIconViewOptions*)dataPtr;
			const char *		labelPosition = "unknown";
			const char *		arrangement = "unknown";

			iconViewOptions.mIconSize = flip_short(iconViewOptions.mIconSize);
			iconViewOptions.mArrangement = flip_long(iconViewOptions.mArrangement);
			iconViewOptions.mLabelPosition = flip_long(iconViewOptions.mLabelPosition);

			switch(iconViewOptions.mLabelPosition)
			{
				case kDSLabelPositionBottom:		labelPosition = "bottom";	break;
				case kDSLabelPositionRight:			labelPosition = "right";	break;
			}

			switch(iconViewOptions.mArrangement)
			{
				case kDSArrangementNone:				arrangement = "none";					break;
				case kDSArrangementByModificationDate:	arrangement = "by modification date";	break;
				case kDSArrangementByCreationDate:		arrangement = "by creation date";		break;
				case kDSArrangementBySize:				arrangement = "by size";				break;
				case kDSArrangementByKind:				arrangement = "by kind";				break;
				case kDSArrangementByLabel:				arrangement = "by label";				break;
			}

			printf("(icon_size = %d; label_position = %s; arrangement = %s)", iconViewOptions.mIconSize, labelPosition, arrangement);
			break;
		}

		case kDSTagIconViewTextSize:
		{
			printf("(icon_view_text_size = %d)", flip_long(*(const uint32_t*)dataPtr));
			break;
		}
		
		case kDSTagWindowSidebarWidth:
		{
			printf("(sidebar_width = %d)", flip_long(*(const uint32_t*)dataPtr));
			break;
		}

		case kDSTagWindowHeight:
		{
			printf("(window_height = %d)", flip_long(*(const uint32_t*)dataPtr));
			break;
		}

		case kDSTagBackgroundType:
		{
			if(dataSize == 12)
			{
				DSBackground	background = *(const DSBackground *)dataPtr;

				background.mBackgroundType = flip_long(background.mBackgroundType);
				switch(background.mBackgroundType)
				{
					case kDSBackgroundDefault:
					{
						printf("(type = default)");
						break;
					}

					case kDSBackgroundColor:
					{
						printf("(type = color)");
						break;
					}

					case kDSBackgroundPicture:
					{
						printf("(type = picture)");
						break;
					}
				}
			}
			break;
		}

		case kDSTagPicture:
		{
			Handle				aliasHandle = 0;
			FSRef				fsRef;
			Boolean				wasChanged = false;
			char				path[1024];

			if(PtrToHand(dataPtr, &aliasHandle, dataSize) == noErr)
			{
				if (FSResolveAliasWithMountFlags(0, (AliasHandle)aliasHandle, &fsRef, &wasChanged, kResolveAliasFileNoUI) == noErr)
				{
					if (FSRefMakePath(&fsRef, (UInt8*)path, sizeof(path)) == noErr)
					{
						printf("(alias = %s)", path);
					}
				}
				DisposeHandle(aliasHandle);
			}
			break;
		}

		case kDSTagIconLocation:
		{
			if(dataSize == 16)
			{
				DSIconLocation	iconLocation = *(const DSIconLocation*) dataPtr;

				iconLocation.mLocationX = flip_long(iconLocation.mLocationX);
				iconLocation.mLocationY = flip_long(iconLocation.mLocationY);

				printf("(x = %d; y = %d)", iconLocation.mLocationX, iconLocation.mLocationY);
			}
			break;
		}

		case kDSTagListViewTextSize:
		{
			printf("(list_view_text_size = %d)", flip_long(*(const uint32_t*)dataPtr));
			break;
		}

		case kDSTagComment:
		{
			if(type == kDSTypeUnicodeString)
			{
				uint16_t *		commentBuf = (uint16_t*)dataPtr;
				uint32_t		commentLength = dataSize / 2;
				CFStringRef		comment = 0;

				flip_short_array(commentBuf, commentLength);
				comment = CFStringCreateWithCharacters(kCFAllocatorDefault, commentBuf, commentLength);
				print_cfstring("(comment = %s)", comment);
				CFRelease(comment);
			}
			break;
		}

		case kDSTagClippingRect:
		{
			Rect				clippingRect = * (const Rect*)dataPtr;

			flip_rect(&clippingRect);
			print_rect("(clipping_rect = %s)", &clippingRect);
			break;
		}
	}

	printf("\n");
}

static int parse_ds_block(FILE * f, uint32_t blockOffset)
{
	uint32_t					structCount = 0;
	int							structIndex;
	DSStructHeader				dsStruct;

	fseek(f, blockOffset + 8, SEEK_SET);
	if (fread(&structCount, sizeof(structCount), 1, f) != 1)
	{
		return 0;
	}
	structCount = flip_long(structCount);
	if(structCount == 0)
	{
		return 0;
	}

	printf("...block @ %08x contains %d structures...\n", blockOffset, structCount);

	for(structIndex = 0; structIndex < structCount; ++structIndex)
	{
		uint32_t				structOffset = ftell(f);
		uint32_t				objectNameLength = 0;
		uint16_t *				objectNameBuf = 0;
		CFStringRef				objectName = 0;
		uint32_t				dataSize = 0;
		void *					dataBuf = 0;

		//	read object name length
		if(fread(&objectNameLength, sizeof(objectNameLength), 1, f) != 1)
		{
			return;
		}
		objectNameLength = flip_long(objectNameLength);
		objectNameBuf = (uint16_t*)malloc(2 * objectNameLength);

		//	read object name (this is UTF-16 encoded)
		if(fread(objectNameBuf, 2 * objectNameLength, 1, f) != 1)
		{
			return;
		}
		flip_short_array(objectNameBuf, objectNameLength);

		//	create CFString from object name
		objectName = CFStringCreateWithCharacters(kCFAllocatorDefault, objectNameBuf, objectNameLength);
		free(objectNameBuf);

		//	read tag and type
		if(fread(&dsStruct, sizeof(dsStruct), 1, f) != 1)
		{
			return;
		}
		dsStruct.mTag = flip_long(dsStruct.mTag);
		dsStruct.mType = flip_long(dsStruct.mType);

		//	calculate payload size
		switch(dsStruct.mType)
		{
			case kDSTypeBoolean:
			{
				dataSize = 1;
				break;
			}

			case kDSTypeShort:
			case kDSTypeLong:
			{
				dataSize = 4;
				break;
			}

			case kDSTypeUnicodeString:
			{
				if(fread(&dataSize, sizeof(dataSize), 1, f) != 1)
				{
					return;
				}
				dataSize = flip_long(dataSize);
				dataSize *= 2;
				break;
			}

			case kDSTypeBlob:
			{
				if(fread(&dataSize, sizeof(dataSize), 1, f) != 1)
				{
					return;
				}
				dataSize = flip_long(dataSize);
				break;
			}

			default:
			{
				//	unrecognized data type
				break;
			}
		}

		if(dataSize > 0)
		{
			dataBuf = malloc(dataSize);
			if(fread(dataBuf, dataSize, 1, f) != 1)
			{
				return;
			}
			parse_ds_struct(structOffset, objectName, dsStruct.mTag, dsStruct.mType, dataBuf, dataSize);
			free(dataBuf);
		}

		CFRelease(objectName);
	}

	return structCount;
}

static void parse_ds_store_file(const char * inDSStorePath)
{
	FILE *						f = fopen(inDSStorePath, "r");
	uint32_t					blockOffset;

	if(f)
	{
		printf("%-8s\t%-32s\t%-4s\t%-4s\t%s\t%s\n\n", "offset", "object name", "tag", "type", "length", "description");

		fseek(f, 0x14, SEEK_SET);
		if(fread(&blockOffset, sizeof(blockOffset), 1, f) != 1)
		{
			goto cleanup;
		}
		blockOffset = flip_long(blockOffset);
		blockOffset &= ~15;

		for( ; ; blockOffset = (blockOffset & 0xfffff000) + 0x1000)
		{
			if (parse_ds_block(f, blockOffset) == 0)
			{
				break;
			}
		}

cleanup:
		fclose(f);
	}
}

int main ( int argc, char * argv[] )
{
	parse_ds_store_file(argv[1]);
	return 0 ;
}