3
edits
Dhcmrlchtdj (talk | contribs) (Added minor clarification about 'pict' struct.) |
Dhcmrlchtdj (talk | contribs) (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> | |||
edits