Ambrosia Garden Archive
    • .REZ Reading Source Code


      Wheefun

      Hi all!

      I was reading Mehrunes' information on the .REZ file format and thought it'd be fun to write some code that read the file.

      Its pretty limited since it currently only loads ship resources but it'd be easy to extend that to all the resources.

      I don't claim this is the best way of going about this but I tried to be as clear as possible.

      I had fun with it and the only reason I'm spamming the board with this is I thought maybe someone else would find it useful. So be kind! ๐Ÿ˜‰ If this type of post is unwelcome I'll happily remove it.

      Anyway, here is the Source Code! ๐Ÿ™‚

      Cheers!

    • Interesting. You mention Adam (Rosenfield, I suppose, maker of EVNEW) so I suppose you know about the EVNEW source code; let me also mention the fact the source code for the .bin to .rez converter included with Nova is available, too.

      Anyway, a few notes. First, there are portability issues. WHY THE HECK DO YOU ASSUME A LITTLE ENDIAN HOST? It makes sense for your code to be able to compile on Macs as well (PowerPC Macs, admittedly), we may need to have to convert from .rez; even if there is no shortage of such converters it never harms to consider your code may be used somewhere else than the place you're currently using. Also, try to use explicit-sized integers, C99 defines int32_t and uint32_t, now I know that there are very few environments where int is 16 bits, but they do exist (short is less of a problem as it's always 16 bits, though I know some embedded environments where they don't care much about ISO conformance and have 8 bits shorts to save on space as much as possible, when the ISO standard mandates that shorts be at least 16 bits, and very few people on their right mind would make shorts longer than that). Of course, you cannot avoid some Visual C stupidities (_tmain? wtf?), but as long as they are contained they should be easier to fix, should they need to be.

      Then, why name your source file with extension cpp? I only see standard C features (apart from a few Visual C-isms), you might as well rename it with extension c for it to be compiled as plain old C, to avoid a good deal of headaches, sometimes C++ can have surprising effects (say, name mangling and function overloading).

      Also, it's only a matter of seconds to replace the hard-coded test.rez with argv(1) so that the name of the file is given on the command line.

      I think I know where the 12 is coming from. I think it's because, just after having read IndexTyp.offset, you're already 12 bytes into the file, so you just have to fseek(file, indexTyp.offset, SEEK_CUR) to be at the beginning of the resource entries. It just so happens that it's the offset from the beginning of the file to the first 'e' of "resource.map", but if it was something else than resource.map it would no longer be the case, especially, substracting 1 from it to get the beginning of this string is not a good idea: this offset exists to easily get the beginning of the resource entries even if the string is something else (and especially, something else with a different length) than "resource.map".

      I suppose you might wish to know the binary format of the various Nova resources, not just shฤp. Unfortunately, the Holy Nova Bible doesn't document such low-level details, it doesn't indicate you which fields are 32 bits and where there is unused space, if any. However, this info is given in the ResEdit Nova templates, available in the Mac distribution of Nova (that you should be able to expand, since it's a .sit archive); this file is itself a resource file (I think you can user Burger's convertor to convert it to .rez if you prefer to rad such files) containing resources of type TMPL, whose ID does not matter (as long as they're different from each other) but whose resource name indicates which resource type they specify the format of. The format of the TMPL resource itself is simple, really:

      List running to the end of the resource (no explicit count in the resource):
      {
      BYTE number of following chars
      CHARs label of the field (used when editing the resource, it's also that way that you can relate them to the description given in the Bible)
      (note: there is no padding to 256 or 255 or any NUL terminating byte or whatever, the following comes immediately after the last char of the label string)
      CHAR(4) field type, a code made of four characters indicating the kind of field, its size, and how it should be edited.
      }

      Full documentation for the field types is available at (damn, Bernard's site seems to be down at the moment) and ResEdit Reference, in the chapter ResEdit Templates at page 79 (93 for the pdf doc). For instance, DWRD is just a two-byte integer, Cxxx is a C string that takes up xxx bytes (in hex), counting the terminating NUL. For instance, the template for bรถรถm is:

      FrameAdvance DWRD
      SoundIndex DWRD
      GraphicIndex DWRD

      So in Mehrunes' syntax, this would be:
      SHORT FrameAdvance;
      SHORT SoundIndex;
      SHORT GraphicIndex;

      By the way, it is indeed better all around to fill the structure one field by one field, instead of trying to fill it entirely at once, because besides byte swapping there are also issues of padding and stuff; however, I think you should byte swap each item of the struct just after you've read it; also, I don't think it's necessary to prefix every single struct item name with "ship", since you already know it's a ship structure and they are only valid within its context so there is no namespace collosion problem (but it's more a matter of personal taste).

    • Quote

      .bin to .rez converter included with Nova is available, too.

      Very nice, I wasn't aware of that source availibility.

      Quote

      Anyway, a few notes. First, there are portability issues. WHY THE HECK DO YOU ASSUME A LITTLE ENDIAN HOST?

      Yeah yeah yeah ๐Ÿ˜‰ This code is very much a hack. I just sat down and pounded it out in one go. That is pretty easy to fix at least. Just need to check for endian-ness and swap accordingly. And as you pointed out I'm also assuming 32-bit ints and 16-bit shorts.

      Quote

      Then, why name your source file with extension cpp? I only see standard C features (apart from a few Visual C-isms), you might as well rename it with extension c for it to be compiled as plain old C, to avoid a good deal of headaches, sometimes C++ can have surprising effects (say, name mangling and function overloading).

      Well when I first started writing this I meant to do it in C++ but then it just sort of munged itself into C. The project file actually tells VS to compile the code as straight C. Just me being lazy. <_<

      Quote

      Also, it's only a matter of seconds to replace the hard-coded test.rez with argv(1) so that the name of the file is given on the command line.

      Yeah at the start though the way it was written would it make it crash out if it hit a resource other than a ship heh. That was before I got to the resource map hunk of the file and was doing everything with the resource index. Agreed though, a simple change.

      Quote

      I think I know where the 12 is coming from. I think it's because, just after having read IndexTyp.offset, you're already 12 bytes into the file, so you just have to fseek(file, indexTyp.offset, SEEK_CUR) to be at the beginning of the resource entries. It just so happens that it's the offset from the beginning of the file to the first 'e' of "resource.map", but if it was something else than resource.map it would no longer be the case, especially, substracting 1 from it to get the beginning of this string is not a good idea: this offset exists to easily get the beginning of the resource entries even if the string is something else (and especially, something else with a different length) than "resource.map".

      Ahhh. I see said the blind man. That makes much more sense. I couldn't figure out what the heck was going on there.

      Quote

      The format of the TMPL resource itself is simple, really:

      Excellent information.

      Quote

      however, I think you should byte swap each item of the struct just after you've read it;

      Is this a stylistic concern or is there a technical reason that this is preferable?

      Quote

      don't think it's necessary to prefix every single struct item name with "ship", since you already know it's a ship structure and they are only valid within its context so there is no namespace collosion problem (but it's more a matter of personal taste).

      The magic of copy and paste coding at work ๐Ÿ˜‰ Yeah its ugly. It needs to be changed.

      Well thank you in total for your post I wasn't really expecting any feedback. Not sure what exactly I'm going to do to with this mind you. I just sort of did it on a lark. I would like to see a plugin editor with some gui features included within it so perhaps after I rewrite this code to something a bit less odious I'll work on that.

      Thank you for time Zacha!

    • You're welcome, I strive to spread the gospel of Plain Old C and Portability and Good Style. Don't worry, I'm also the kind of guy who writes a piece of code on a whim without having any idea of where it'll be going. As it happens, it eventually went into ViewRLE (sorry, Mac only, I don't know anything of the Win32 API)

      Quote

      Is this a stylistic concern or is there a technical reason that this is preferable?

      Well, for one, you don't have data living in the struct in the wrong byte order for longer than it needs to be. In particular (say in resources where there is a count of something, then a list of things with the number of items in the count) you may have to use the value you read before reading other stuff, so it might as well be in native byte order as soon as possible (it obviously needs to be in native byte order to be used). Also, this is better to check for consistency: you read a 32-bit int then swap a 32-bit int, the read and the swap are next so it's easier; in fact you could concievably group these two operations in a macro or a function.

    • I have a hack to make fread and fwrite endian-neutral in case you're interested.