Talk:DS Store File Format

From MozillaWiki
Jump to: navigation, search

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			kDSTypeOSType						'type'		//	OSType (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
#define			kDSTagViewStyle						'vstl'		//	type -- 4 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'

//	view styles (Leopard) -- possible values for kDSTagViewStyle

#define			kDSViewStyleIcons					'icnv'
#define			kDSViewStyleList					'Nlsv'
#define			kDSViewStyleColumns					'clmv'
#define			kDSViewStyleCoverFlow				'Flwv'

#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;
				const char *	typeName = 0;

				background.mBackgroundType = flip_long(background.mBackgroundType);
				switch(background.mBackgroundType)
				{
					case kDSBackgroundDefault:		typeName = "default";	break;
					case kDSBackgroundColor:		typeName = "color";		break;
					case kDSBackgroundPicture:		typeName = "picture";	break;
				}

				if(typeName)
				{
					printf("(type = %s)", typeName);
				}
			}
			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;
		}

		case kDSTagViewStyle:
		{
			if(type == kDSTypeOSType)
			{
				uint32_t		viewStyle = flip_long(*(const uint32_t*)dataPtr);
				const char *	styleName = 0;

				switch(viewStyle)
				{
					case kDSViewStyleIcons:			styleName = "icons";		break;
					case kDSViewStyleList:			styleName = "list";			break;
					case kDSViewStyleColumns:		styleName = "columns";		break;
					case kDSViewStyleCoverFlow:		styleName = "Cover Flow";	break;
				}

				if(styleName)
				{
					printf("(view style = %s)", styleName);
				}
			}
			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:
			case kDSTypeOSType:
			{
				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 ;
}