Reading a packfile

I have had a structure with defines an x and y coordinate, as well as a 20-character name.

When reading in from the packfile, I would simply use:

1struct itemType
3 int x;
4 int y;
5 char name[20];
8itemType loadData(PACKFILE *f)
10 itemType newItem;
12 pack_fread(, sizeof(, f);
13 newItem.x = pack_igetw(f);
14 newItem.y = pack_igetw(f);
16 return newItem;

However, I want to change the name from a character to a std::string. If I try to use the same routine, I'll have problems, since the size of the name will be different for each name.

Is there an alternate method I could use to read in an unknown-sized string variable with pack_fread?

One thought I had considered was something similar to std::cin.get(char c). It would only grab a single letter at a time, until it hit a '\0'. Or, if I specify a string-type to pack_fread, will it be able to keep reading in data until it finds the '\0' itself?

Unfortunately, pack_fread didn't seem to have any "Similar topics" on the page that I could check out...

Thanks if anyone knows!

Jakub Wasilewski

One option is to write/read the strings using the length-content string format, like:

// write
pack_iputw(str.size(), f);
pack_fputs(str.c_str(), f);

// read
pack_igetw(stringLength, f);
char *buffer = new char[stringLength+1];
pack_fread(buffer, stringLength, f);
buffer[stringLength] = '\0';
string sth = buffer;

Of course, you can also store them as zero-delimited, and use something like:

// write
pack_fputs(str.c_str(), f);

// read
char c;
string buffer = "";
while (c = pack_getc(f))
    buffer += c;

There is no easy built-in way, just wrap one of the above in two functions and use everywhere you need to save/load strings.

Also, to clear some stuff up, concerning the variable-sized string you mention. Actually, std::string is always the same size in memory, just like any other object in C++. The problem stems from the fact that a std::string internally stores a pointer to a character array, which makes no sense after writing it to a file and reading it back from it.


Jakub, does the packfile save the number of bytes of data we are passing into it when saving, then? That would tremendously simplify things for me!

If so, I may just have skipped that part ;D

So I can write to the packfile, telling it the size of my next string of text. Then, when I read it again, I first pull out the size of the data, and then the actual data?

/me slaps forehead


Jakub Wasilewski

Jakub, does the packfile save the number of bytes of data we are passing into it when saving, then? That would tremendously simplify things for me!

Unfortunately, all packfiles provide is raw input/output, and the fputs/fgets pair. Almost everything works like the functions from stdio, so just look those up if you need reference for pack_xxx. The differences are documented in the Allegro manual.

Actually, there is one pitfall with my code snippets above. Allegro's pack_fputs always saves stuff as UTF-8, which could cause various annoying errors with the code above. If you stick to ASCII you'll be okay, but if you need Unicode or Extended ASCII characters then you'll have to take care of this problem.


I think I've got it now. Let me know if this looks correct:

1/*! \brief Load the data in from a file
2 *
3 * The (open) packfile needs to be specified.
4 */
5void data::load(PACKFILE *thePackfile)
7 itemType theItem;
8 const uInt numItems;
9 uInt stringLen;
11 pItem.clear();
13 numItems = pack_igetw(thePackfile);
14 for (int i = 0; i < numItems; i++)
15 {
16 stringLen = pack_igetw(thePackfile);
18 pack_fread(, stringLen, thePackfile); // name is a string
19 theItem.x = pack_igetw(thePackfile);
20 theItem.y = pack_igetw(thePackfile);
22 pItem.push_back(theItem); // pItem is a vector
23 }
27/*! \brief Save all the data out to a file
28 *
29 * The (open) packfile needs to be specified.
30 *
31 * \returns Number of items found in the packfile
32 */
33uInt data::save(PACKFILE *thePackfile)
35 const uInt numItems;
36 uInt stringLen;
38 numItems = pItem.size();
39 pack_iputw(numItems, thePackfile);
40 for (int i = 0; i < pItem.size(); i++)
41 {
42 stringLen = pItem<i>.name.size();
44 pack_iputw(stringLen, thePackfile);
45 pack_fwrite(pItem<i>.name.c_str(), stringLen, thePackfile);
46 pack_iputw(pItem<i>.x, thePackfile);
47 pack_iputw(pItem<i>.y, thePackfile);
48 }
50 return pItem.size();

If so, thanks for the push in the right direction, Jakub!

EDIT: Changed the code a little (forgot to save the size to the packfile in the previous revision).

Jakub Wasilewski

pack_fread(, stringLen, thePackfile);

This part won't work. Those buffers I used up there were declared for a reason. You try to read literal characters as the std::string object, which is bound to screw things up badly. Std::strings are quite different from char arrays - you can't treat them as such, you have to use appropriate constructors or operators to set the string.

When writing, you can use c_str() to get a C-like character array. However, this can't be used when reading, because the c_str() array is not modifiable.


I understand allegro is written in C, and not for C++. Does that mean that allegro will never be able to use std::strings instead of char arrays? :-/

Steve Terry

You can use C++ with allegro, there is nothing stopping you from doing that. You however need to be careful when using strings with allegro and know the limitations of going from a char array to a string and vice versa.


What I meant, was that there's not much chance that pack_fread() will ever utilize the std::string, is there? Unless they wanted to rewrite allegro in C++ (or can C call the std template?)... It's too bad. It's about as annoying as std::iostream not being able to use strings either on " myString)" :)


You can always write wrapper functions...
Write the code once, use it many times.


Okay, now my next question:

I don't see a way to save a double/float to a packfile (I'm looking at PACKFILE to see what to do). I see the following, but nothing which clearly explains what I need to use here:

pack_getc // Returns the next character from a stream.
pack_putc // Puts a character in the stream.
pack_igetw // Like pack_getc(), but using 16-bit Intel byte ordering words.
pack_iputw // Like pack_putc(), but using 16-bit Intel byte ordering words.
pack_igetl // Like pack_getc(), but using 32-bit Intel byte ordering words.
pack_iputl // Like pack_putc(), but using 32-bit Intel byte ordering words.
pack_mgetw // Like pack_getc(), but using 16-bit Motorola byte ordering words.
pack_mputw // Like pack_putc(), but using 16-bit Motorola byte ordering words.
pack_mgetl // Like pack_getc(), but using 32-bit Motorola byte ordering words.
pack_mputl // Like pack_putc(), but using 32-bit Motorola byte ordering words.

Would I want to use the 32-bit ones, since double/float may need that precision?


I use my own method that just came natural (and somewhat from working on sockets). I'm not claiming it to be the best, it's just easy for me.

For each item stored in my packfile, I write two chars (that's two bytes) to the beginning. Bytes can store from 0-255. These two bytes represent the size of the following data in bytes. It kind of looks like this:

ctransfer = new char[strlen(ctext) + 2];
ctransfer[0] = strlen(ctext) / 255;
ctransfer[1] = strlen(ctext) % 255;

Then you'd pack_fwrite as normal using the ctransfer. This way, you are storing the size specifically within the file. I wrote a program to convert text files into this format, so I just use notepad to fill in the data and then use the converter to have it stored in a packfile.

It's just what I do. :-/

Thread #589285. Printed from