Author Topic: Pascal strings in struct for reading a resource  (Read 480 times)

Offline tenfifty2

  • Newcomer
  • *
  • Posts: 5
  • New Member
Pascal strings in struct for reading a resource
« on: May 08, 2024, 10:11:27 AM »
Hi guys!
   I'm not a super great C programmer, but I'm trying to knock out a "simple" MPW Tool to print the data contained in the 'ckid' resource that gets created by Projector when you check out a file.  Unfortunately, most of the data I want is in Pascal strings.

   The format of the 'ckid' resource is documented in Appendix A in the "Bldg & Mng Progs in MPW 2ed.pdf" file that comes with MPW.  But the data structure is only described in Pascal format.  Here is a C struct that I made that seems to me like it should be equivalent:

Code: [Select]
#include <time.h>

typedef unsigned long uLong;
typedef unsigned short uShort;
typedef struct{
uLong checkSum; // checkSum
long LOC; // location identifier
short version; // ckid version number
short readOnly; // checkout state; 0=modifiable
char branch; // if modifiable & byte not 0, then branch was made on checkout
Boolean modifyReadOnly; // Did user execute "ModifyReadOnly"?
uShort history; //  1 if history present, 0 if not
uShort commentLength; // length of current comment if history is present
time_t checkoutDate; // date and time of checkout
time_t modificationDate; // modification date of file
uLong PIDa; // PID.a
uLong PIDb; // PID.b
short userID; // user ID
short fileID; // file ID
short revisionID; // revision ID
Str255 projectPath; // project path
char pad1;
Str255 userName; // user name
char pad2;
Str255 revisionNum; // revision number
char pad3;
Str255 fileName; // filename
char pad4;
Str255 taskDesc; // task
char pad5;
Str255 comment; // comment
char pad6;
} CKIDRec, *CKIDPtr, **CKIDHandle;

In the program, I do something like this (Again, not sure this is the best, but it sort of works):

Code: [Select]
Handle ResHandle;
CKIDRec CkidRecord;

refnum = openresfile(argv[1]);

ResHandle = Get1Resource('ckid',128);

HLock(ResHandle);

CkidRecord = **(CKIDRec **)ResHandle;

This works pretty well, and I can access everything up to projectPath using CkidRecord.checkSum, etc.  But once I try to get to the Str255's, it starts returning garbage or crashing.

So I found another example for dealing with Pascal strings, and added this:

Code: [Select]
StringPtr projectPathPtr, userNamePtr;
Str255 projectPath, userName;
Size bytes;

projectPathPtr = (**(CKIDHandle)ResHandle).projectPath;
bytes = (**(CKIDHandle)ResHandle).projectPath[0] + 1;
BlockMove(projectPathPtr, projectPath, bytes);

userNamePtr = (**(CKIDHandle)ResHandle).userName;
bytes = (**(CKIDHandle)ResHandle).userName[0] + 1;
BlockMove(userNamePtr, userName, bytes);

After that, 'projectPath' contains the Project Path, but there's garbage in front of and behind it.  'userName' always contains garbage.  I can view what should be the correct data using ResEdit, however.

I wonder if it would be easier to take everything in the struct after revisionID as one big chunk and then manually parse through it, but I've tried that a few times and haven't come up with a scheme that works and doesn't crash my machine. 

Does all that make sense?  Does anyone have any suggestions or examples of code that uses a similar stucture and how to deal with it?  Or am I looking at this all wrong?

Many thanks in advance!

Offline Daniel

  • Gold Member
  • *****
  • Posts: 302
  • Programmer, Hacker, Thinker
Re: Pascal strings in struct for reading a resource
« Reply #1 on: May 08, 2024, 10:40:33 AM »
Here's all 4 possible string moves. Pick whichever one makes sense for your situation (and maybe add bounds checks or null pointer checks if needed). BlockMoveData is backward compatible with BlockMove (it sets a flag in the A-Line trap which is ignored by BlockMove implementations which don't check it) but is only for data guaranteed to not be 68k code. This means it doesn't have to flush caches (for later 680x0 CPUs) or invalidate the 68k Emulator's JIT cache (for PowerMacs). Which can speed things up.

Code: [Select]
void CStrToCStr(char * src, char * dst) {
 int len = strlen(src);
 BlockMoveData(src, dst, len+1);
}

void PStrToPStr(Str255 src, Str255 dst) {
 int len = src[0];
 BlockMoveData(src, dst, len+1);
}

void CStrToPStr(char * src, Str255 dst) {
 int len = strlen(src);
 BlockMoveData(src, dst+1, len);
 dst[0] = (char)len;
}

void PStrToCStr(Str255 src, char * dst) {
 int len = src[0];
 BlockMoveData(src+1, dst, len);
 dst[len] = 0;
}

Offline tenfifty2

  • Newcomer
  • *
  • Posts: 5
  • New Member
Re: Pascal strings in struct for reading a resource
« Reply #2 on: May 08, 2024, 11:12:19 AM »
Hi Daniel!
   Thank you very much for the reply!

   I'm not certain how to incorporate those. 

   Looking at the struct I posted, I'm first of all not quite certain it's right, but let's assume it is for the purposes of this question.  In C, a Str255 is really just a 256-byte string, right?  Ok, so let's say we have some data in projectPath that's not quite that long.  Now we want to access the next relevant field, userName.  How does C know where to get it from, because according to the struct that should be 257 bytes later, right?  But it's not, because projectPath is a Pascal string and starts with the length, then the data, then the \0, and then the pad, right?

   Is it even possible to use a C struct to handle this data?

Thank you so much for the help!

Offline joevt

  • Enthusiast Member
  • ***
  • Posts: 89
  • New Member
Re: Pascal strings in struct for reading a resource
« Reply #3 on: May 08, 2024, 05:48:26 PM »
Is a Str255 defined as char[256] in C? (char should be unsigned)

The first byte is the length. The length is followed by room for 255 characters.

The total length is 256 bytes, so you don't need padding bytes.

Offline tenfifty2

  • Newcomer
  • *
  • Posts: 5
  • New Member
Re: Pascal strings in struct for reading a resource
« Reply #4 on: May 08, 2024, 08:33:56 PM »
Str255 is defined in MacTypes.h as:

Code: [Select]
typedef unsigned char        Str255[256];

That being said, I based the struct in my first post on the Pascal structure defined in the PDF I referenced.  It definitely has pad bytes, and you can see them in ResEdit too, where there appears to be two ^^'s between each data field.

Still not sure what to do here... should I be able to get this to work with C structures, or should I try and parse through it one character at a time?

Thank you!

Offline joevt

  • Enthusiast Member
  • ***
  • Posts: 89
  • New Member
Re: Pascal strings in struct for reading a resource
« Reply #5 on: May 08, 2024, 10:36:52 PM »
The Resorcerer template says the strings are variable length, not fixed length.

Offline joevt

  • Enthusiast Member
  • ***
  • Posts: 89
  • New Member
Re: Pascal strings in struct for reading a resource
« Reply #6 on: May 08, 2024, 10:38:53 PM »
It appears the filler bytes between strings allow the strings to be treated as C strings.

The comment string has a two byte length and is therefore not a pascal string.

Since the ckid is variable length, I would maybe copy only the non-variable part from the resource to CkidRecord (everthing before projectPath).

C in classic Mac OS may have an option to print pascal strings. Is it "%p" instead of "%s"? I forget. That's not going to work for the comment which as a two-byte length.

Since the strings always have a terminating null character, you can treat them as C strings (unless you think someone might try putting a null character inside the string).

If you need to modify a string, you need to remember to update the length. If you update the length, you need to move all the following strings. If you make a string longer, then you need to move all the strings before making it longer.

« Last Edit: May 08, 2024, 10:56:36 PM by joevt »

Offline tenfifty2

  • Newcomer
  • *
  • Posts: 5
  • New Member
Re: Pascal strings in struct for reading a resource
« Reply #7 on: May 09, 2024, 08:27:10 AM »
Ah, thank you joevt, that was really enlightening.  I didn't think to look at it with Resourcerer. 

Yes, the strings can be variable length, that's what I meant when I said the doc says they're Pascal strings, which the Mac OS includes represent as Str255.  So when you say the pad can be used to treat it like a C string, how does that deal with the first part where the string length is coded?  Can we still do this with the struct?

Another option I had was something like this:

Code: [Select]
#include <time.h>

typedef unsigned long uLong;
typedef unsigned short uShort;
typedef struct{
uLong checkSum; // checkSum
long LOC; // location identifier
short version; // ckid version number
short readOnly; // checkout state; 0=modifiable
char branch; // if modifiable & byte not 0, then branch was made on checkout
Boolean modifyReadOnly; // Did user execute "ModifyReadOnly"?
uShort history; //  1 if history present, 0 if not
uShort commentLength; // length of current comment if history is present
time_t checkoutDate; // date and time of checkout
time_t modificationDate; // modification date of file
uLong PIDa; // PID.a
uLong PIDb; // PID.b
short userID; // user ID
short fileID; // file ID
short revisionID; // revision ID
char theRest[1536] // We have to parse this out manually due to pstrings
} CKIDRec, *CKIDPtr, **CKIDHandle;

Then taking CkidRecord.theRest and parsing it out.  Not entirely sure how to do that though... I've taken a few stabs at it and I usually end up crashing the whole machine.  Have a suggestion?

Thank you much!

Offline Daniel

  • Gold Member
  • *****
  • Posts: 302
  • Programmer, Hacker, Thinker
Re: Pascal strings in struct for reading a resource
« Reply #8 on: May 09, 2024, 08:47:55 PM »
As it seems you have found out, C doesn't support having variable-length data in the middle of a struct. You have to use pointer arithmetic.

Fortunately, none of the fixed-size data is broken up by variable-length data. So you don't have to have multiple structs (or go with the extreme solution of having a separate pointer variable for every single value you want to extract from that resource). You just need a buffer big enough to hold the max length of everything, plus a bunch of pointer variables (one for the struct at the front, and then one per string).

If my guess is right and the strings in the resource have both length bytes and null terminators (making them both C and Pascal strings), then this code will work. Though it might need stylistic tweaking (the typedefs don't have Ptr & Handle varients, for instance).

Code: [Select]

typedef struct {
uLong checkSum; // checkSum
long LOC; // location identifier
short version; // ckid version number
short readOnly; // checkout state; 0=modifiable
char branch; // if modifiable & byte not 0, then branch was made on checkout
Boolean modifyReadOnly; // Did user execute "ModifyReadOnly"?
uShort history; //  1 if history present, 0 if not
uShort commentLength; // length of current comment if history is present
time_t checkoutDate; // date and time of checkout
time_t modificationDate; // modification date of file
uLong PIDa; // PID.a
uLong PIDb; // PID.b
short userID; // user ID
short fileID; // file ID
short revisionID; // revision ID
} FixedSizeData;

typedef struct {
FixedSizedData  * fixedSizeData;
Str255 * projectPath;
Str255 * userName;
Str255 * revisionNum;
Str255 * fileName;
Str255 * taskDesc;
Str255 * comment;
} CKIDRecPStr;

typedef struct {
FixedSizedData  * fixedSizeData;
char * projectPath;
char * userName;
char * revisionNum;
char * fileName;
char * taskDesc;
char * comment;
} CKIDRecCStr;

void parseCKIDPStr(CKIDRecPstr * ckidRec, void * ckidBuf) {
  unsigned char * buf = (char *) ckidBuf;
  ckidRec->fixedSizeData = (FixedSizedData *) buf; buf +=sizeof FixedSizedData;
  ckidRec->projectPath = (Str255 *) buf; buf += 1 + buf[0] + 1;//deal with the len byte + string + null term sandwich
  ckidRec->userName = (Str255 *) buf; buf += 1 + buf[0] + 1;
  ckidRec->revisionNum = (Str255 *) buf; buf += 1 + buf[0] + 1;
  ckidRec->fileName = (Str255 *) buf; buf += 1 + buf[0] + 1;
  ckidRec->taskDesc = (Str255 *) buf; buf += 1 + buf[0] + 1;
  ckidRec->comment = (Str255 *) buf; buf += 1 + buf[0] + 1;
}

void parseCKIDCStr(CKIDRecCstr * ckidRec, void * ckidBuf) {
  unsigned char * buf = (char *) ckidBuf;
  ckidRec->fixedSizeData = (FixedSizedData *) buf; buf +=sizeof FixedSizedData;
  ckidRec->projectPath = (char *) buf+1; buf += 1 + buf[0] + 1;//deal with the len byte + string + null term sandwich
  ckidRec->userName = (char *) buf+1; buf += 1 + buf[0] + 1;
  ckidRec->revisionNum = (char *) buf+1; buf += 1 + buf[0] + 1;
  ckidRec->fileName = (char *) buf+1; buf += 1 + buf[0] + 1;
  ckidRec->taskDesc = (char *) buf+1; buf += 1 + buf[0] + 1;
  ckidRec->comment = (char *) buf+1; buf += 1 + buf[0] + 1;
}
« Last Edit: May 10, 2024, 05:57:46 AM by Daniel »

Offline joevt

  • Enthusiast Member
  • ***
  • Posts: 89
  • New Member
Re: Pascal strings in struct for reading a resource
« Reply #9 on: May 09, 2024, 09:16:21 PM »
A Str255 is a fixed size field of 256 bytes. The string inside the Str255 is variable length but a Str255 is not variable length.

TheRest could be up to 64K since the length for the comment is two bytes. I think in C you would define it like this:
Code: [Select]
char theRest[];

or this (if the compiler doesn't like flexible arrays):
Code: [Select]
char theRest[0];

or this (if the compiler doesn't like zero sized arrays):
Code: [Select]
char theRest[1];

With an array size of 1, You would have to use sizeof(CKIDRec) - sizeof(CKIDRec.theRest) to calculate the size of the non-string data.
Or use offsetof(CKIDRec, theRest) to calculate the size of the non-string data.
If the compiler doesn't have offsetof, then #define it. https://en.wikipedia.org/wiki/Offsetof

If the data is in a locked resource, then you don't need to copy it to a local or global variable.
Code: [Select]
HLock(ResHandle);
CKIDPtr ckid = *(CKIDHandle)ResHandle;
char* p = &ckid->theRest;
char* projectPath = p; p += *p + 2;
char* userName    = p; p += *p + 2;
char* revisionNum = p; p += *p + 2;
char* fileName    = p; p += *p + 2;
char* taskDesc   = p; p += *p + 2;
char* comment   = p; p += *(unsigned short *)p + 3;
long size = p - (char*)ckid;
printf("size:%ld handlesize:%ld\n", size, GetHandleSize(ResHandle));
printf("username:\"%s\"\n", username+1);
printf("revisionNum:\"%s\"\n", revisionNum+1);
printf("fileName:\"%s\"\n", fileName+1);
printf("taskDesc:\"%s\"\n", taskDesc+1);
printf("comment:\"%s\"\n", comment+2);

A char pointer is defined for each string. Since the strings have one or two size bytes, you need to add 1 or 2 when passing it to functions that expect a C string.

p needs to be changed to unsigned char * if char is not unsigned.

You have to recalculate the pointers if you unlock the handle. You need to unlock the handle if you want to increase the size.

Offline tenfifty2

  • Newcomer
  • *
  • Posts: 5
  • New Member
Re: Pascal strings in struct for reading a resource
« Reply #10 on: May 15, 2024, 06:54:59 AM »
Thanks for the help guys!  I got it!  I pieced together a few bits from both of your replies, but took most of it from joevt's last reply. 

Thanks again to both of you for the help, it's much appreciated!!