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 ;
}