<- 1  -   of 75 ->
^^
vv
List results:
Search options:
Use \ before commas in usernames
Are you sure that's correct? On a save game that I already had, it caused the room to be completely empty- the geometry was there, but the lava, grapple points, crates, and doors were all missing. I haven't tried it on a fresh save yet, though.
Yes, it's correct. Do it from a fresh save. That value only changes which layers are on by default, and the save file stores which layers are active on your current file; the game'll read from there instead of from the MLVL file.
Starting from a fresh save doesn't seem to have worked quite right either. The toxic water is there, along with some butterflies and a nice dripping water effect, but the fish are absent.
Edit history:
Aruki: 2015-01-13 01:05:27 am
Aruki: 2015-01-13 12:57:47 am
Aruki: 2015-01-13 12:52:40 am
Um, that's correct. :P The layer is enabled.

I couldn't tell you why the fish isn't there without a better understanding of what FishCloud's properties do, but it's probably not supposed to be there.

EDIT: Gonna check real quick and see if I can enable the fish

EDIT 2: Yeah, got it. It's just what I was saying on the last page; the fish has a boolean property that enables/disables it, and it's disabled in Magma Pool. To enable it, open 491bfaba.MREA, and change the byte at 0x1117C7 from 0 to 1.
Success!
I tried making a flowchart of the Babygoth's AFSM to try to visualize the data. Along the way, I learned I am terrible at making flowcharts.



I'm wondering if anyone's got any ideas what it would take to generate a chart like this (except a lot cleaner) with the AFSM files as input for use in an editor. I can't exactly wrap my head around how this could be done. Also welcome if anyone else just wants to try to manually make a cleaner version of this, because mine sucks.
Anyone want to mess about with the button mappings? Tweaks.pak -> F1ED8FD7
MP2 also a bit MP1 speedrunning
Those fishes are in the ice from Gravity chamber near the Gravity suit.
Ok, so I think I'm looking at doing a complete rewrite of PakTool sometime soon; the source code is really ugly and I don't really want to touch it as it is currently. Does anyone have any feature requests/general improvement suggestions?

The main performance improvement that comes to mind is skipping duplicate files when unpacking; the current build doesn't do this and it probably wastes a lot of time. As far as new features go, the main stuff that comes to mind is just the obvious; adding more support for the MP3/TF pak formats, as well as actually parsing files for dependencies instead of using the lists, which would MASSIVELY simplify repacking, especially when adding or removing files from paks. (As far as that's concerned, the biggest obstacle to being able to do that was ANCS, which is now mostly cracked. It would require a little more research into other unknown formats that have dependencies (like PART) and some research into the disassembly to find out how the game locates files within paks in order to optimize file placement, but it should be doable now.)

Anyone got anything else?
Edit history:
Antidote: 2015-01-18 04:08:05 am
I just wrote a new MREA decompression prog, currently source only but I've asked parax to build a windows version. However anyone is welcome to build it and provide a link. As with all of my source it's been released under GPLv3 (the license Athena [my personal library] uses). Please do not redistribute without credit!

http://dl.dropboxusercontent.com/u/21757902/mreadcmp.zip

Special thanks to Parax for this:
http://dl.dropboxusercontent.com/u/21757902/mreadcmp_win32.rar
Thanks for the decompressor and the build, Antidote and Paraxade!

As soon as I'm done with work today, I'm going to try and do a flowchart of an AFSM file...I can't guarentee if I can finish it but I'll give it a shot.
heh, thanks Sinistar, on an MREA related note:
Why are you posting faked pictures? This is a hacking thread, not a mockup thread.
darn, i've been caught!
Ok here's something really cool. I decided to shoot an email over to Mike Sneath, who worked on Metroid Prime as a Senior Artist, to ask about the unused green Phazon Suit, and he actually replied! Here's what he said about it:

Quote:
Hey there!  Thank you for the nice comments about my work and Metroid Prime.  Yes you are right that is what was another version of the Phazon Suit which was done by another artist Gene Kohler.  Gene made all the suits with the only exception being the black phazon suit.  That suit you mentioned was cut because if I remember the Art Lead Todd Keller didn't feel the suit felt evil enough.  That was the whole idea about liquid Phazon is that it transforms things into being aggressive and destructive and Todd wanted that to be reflected in how the suit look.  That is all I can remember with regards to that suit you mentioned and I hope I got the story right on that as it was a long time ago but I believe that is why it got cut. 

I hope that helps and good luck with your project.
That's awesome, thanks for sharing!  I remember trying to contact some of the sound designers back in like 2006 about the sound effects and AGSC files but they never got back with me. 

Thinking out loud here, but I wonder if Mr. Sneath has any documentation on the file formats like the skeleton or animation files.
If he did, he'd still be under the NDA :/
haha, yeah, even if he did he wouldn't share it. And frankly we don't need help on formats anyway... skeletons have been cracked for a while, other uncracked formats are mostly because none of us have really tried to figure them out yet.

I might reply and try asking Mike a couple more questions about other unused stuff he might know about, though. My understanding was he did most of the creature models so he'd probably remember some things related to them.
I realized it was a long shot; again, just thinking out loud there, lol. 

I wonder if Mike knows anything about that unused morphball.
Quote from MrSinistar:
I wonder if Mike knows anything about that unused morphball.


Yeah, Id like to know whats its purpose was, still baffles me as to its purpose, given its texture and how it looks like it's made of stone.
Edit history:
Aruki: 2015-01-19 04:59:21 pm
Aruki: 2015-01-19 01:43:02 pm
On an only-somewhat-on-topic note, I recently got a full dump of Donkey Kong Country: Tropical Freeze so I've been researching its formats a bit.







I think I posted about this game before, but I never tried seriously researching it until now. They've changed a LOT of things...

- pak: There's a new pak format, and for some reason Retro has decided to extend the asset IDs to 128 bits, double the 64-bit ones from Prime 3 and DKCR. WHY. I released a version of PakTool a while ago that was meant to support Tropical Freeze, but I only had one pak to test on. Now that I have more, I've been able to verify it does work on every pak in the game, and I've also been able to verify that the level of support I put in is completely inadequate. Tropical Freeze paks have a new data section labeled "META"; I didn't know what this was before, so I didn't add support for it. Now I've cracked it; it's actually some extra data for certain file types, associated with files through their file IDs.

The problem is, with some formats, this is extremely important extra data, like the compressed/decompressed buffer sizes for compressed data, which means some of the unpacked files are completely useless without this info. Needless to say I started on my rewrite of PakTool and wrote a better DKCTF unpacker. It currently embeds a file's metadata at the end of the file, and I'm debating whether to include an automatic decompression function.

- Models: The model format is completely different. The old one was heavily tied to GX, so now that GX is gone it's not too surprising the format has been revamped. It's a lot closer to modern OpenGL; vertices are organized into multiple vertex buffers with different attributes, strides, etc. and they're accessed through an index buffer. I'm not sure if this format can use multiple primitive types, because every model I've imported so far only uses triangles.

The most annoying thing to deal with on the models is the GPU buffer data at the end of the file is compressed with a custom LZSS implementation. Each vertex/index buffer is compressed separately, and the metadata (compressed/uncompressed sizes) are located in the pak metadata instead of the file itself. I was able to crack the compression format, though, by disassembling the game's executable, which is unstripped (thanks to a tool by crediar and some help from Ninji). This was the most frustrating thing to deal with because I needed to come up with a way to unpack and decompress them while preserving the buffer offsets and sizes, and was what made me decide to redo Tropical Freeze pak dumping.

Something interesting is they've actually created three different model formats - CMDL, SMDL, and WMDL. CMDL is a generic model format. SMDL is for skinned models. WMDL is for world models (levels, terrain) - they finally split this away from the room format, so we don't have to deal with one absolutely massive format anymore. Luckily, it's easy to accomodate all three of these, because they all have the exact same geometry format. The only difference between them is SMDL and WMDL each have an extra data section at the beginning. In SMDL, it's only one extra value; my best guess is a bone count, but I haven't looked into it yet. WMDL contains an AROT section, though; probably the same as the MREA AROT section from other Retro games.

- Textures: This is also completely different, since it's another case of a format where the old one was heavily tied to GX stuff. Unfortunately, GX2's texture setup is a hell of a lot more complicated than the first GX. The new TXTR format has a lot more metadata, and more complicated swizzling. I'm trying to look into it to see if I can figure out how to decode them properly, but I'm not super hopeful. My understanding is this stuff is common to a lot of Wii U games, though, so hopefully if I don't figure it out someone else will and there will be some documentation around soon.

Also, the texture GPU buffer data is LZSS-compressed the same way the model one is.

- Materials: Again, completely different and now more in-line with what you would expect to see in a modern game. The nicest change is materials actually have names now. They also use actual shaders now. The shaders are pre-compiled and they're stored in zlib-compressed MTRL files (which, again, store their metadata for decompression in the pak instead of the file). The materials themselves seem to more explicitly assign textures in different slots - diffuse, normal, specular, reflection, etc - and most of it seems like it's setting up textures and passing parameters to the shader. I've cracked the structure of the materials, but it's currently hard to say what most of it actually does. Here's a dump of the data from a material:

Quote:
MATERIAL #7
Name: rock_stoneEdges_01
Shader: 9b2a53e95d4246f2a279421d09d5fa8b.MTRL
Shading type: PHNG
Unknown: 0x0
Number of subsections: 8
1. DIFT - d54190af0a1741e1b5caed9a93121f3c.TXTR, 0x0, 0xffffffff, 0x1, 0x1, 0xffffffff
2. NMAP - 4fed9744b41443c7ae1fa69d457629b7.TXTR, 0x0, 0xffffffff, 0x1, 0x1, 0xffffffff
3. SPCT - 13a224d1bf0a440c9025e28b4d727782.TXTR, 0x0, 0xffffffff, 0x1, 0x1, 0xffffffff
4. SPCP - 13.7925
5. DIFC - 1.0, 1.0, 1.0, 1.0
6. ICNC - 0.0, 0.0, 0.0, 1.0
7. SPCC - 1.0, 1.0, 1.0, 1.0
8. ICMC - 0.0, 0.0, 0.0, 1.0


- Levels: The old MREA and MLVL formats are gone, and they seem to have mixed the functionality from both of them together into a new format called ROOM; smart idea, if you ask me, since MLVL was pretty redundant in DKCR, though I wouldn't be surprised to see it come back if Retro does another Metroid. I haven't looked into this one too much yet, so I'm hazy on the exact details, but ROOM seems to largely track object layout data. Like I mentioned before, terrain geometry has been decoupled from it and is stored in external WMDL files now; there seems to be a new Art object that loads in and positions WMDLs to form the terrain.

Still lots to look into with this game, but that's most of what I've found over the last few days; still doing lots of work with it. I'm also not going to release the new version of PakTool yet because it only supports Tropical Freeze right now; I'll have to redo the support for the other games. I might try looking into adding some repacking support for Metroid Prime 3 while I'm at it, since I might want to start experimenting with it a little further soon, and being able to repack will make that a lot easier. I still want to add dependency parsing for Prime 1 soon, but that would be a large enough task that I'm not going to start working on it just yet.
Hi,
Quote from Paraxade:
I'm wondering if anyone's got any ideas what it would take to generate a chart like this (except a lot cleaner) with the AFSM files as input for use in an editor. I can't exactly wrap my head around how this could be done. Also welcome if anyone else just wants to try to manually make a cleaner version of this, because mine sucks.


The program called "yEd" takes as input a  GraphML file and has a number of tools to plot it in a nice way. I can look into converting AFSM files to GraphML if you want (though my programming skills are a bit rusty, so if you want something quickly you might prefer to do it yourself :p )
Edit history:
Aruki: 2015-01-19 04:31:02 pm
Will look into it, but the point isn't just to create graphs, it's to be able to create an editor for the AI that can generate a graph based on the FSM data for the user to edit. Sounds like it'll be good for some ideas to see how it can be implemented, but not so useful to actually make any tools.

Also, I decided to ask Mike about the unused Morph Ball (specifically, whether it was the corresponding Morph Ball for the early Phazon Suit), and Torobyte:

Quote:
The morph ball question.  I am not 100% on that but I think you're right about it being the "Phazon version" of the ball.

Torobyte-  sounds familiar but I do not remember anything about that one.


He's also okayed me to ask him a few more questions, not necessarily related to cut content. I don't want to bombard him with stuff so I'd like to just come up with a few good ones and then leave him alone for now. If anyone's got any suggestions what I could ask about, feel free to share, though remember he's an artist, so he's probably not going to know too many details about other areas of the game's development (like formats). I'm leaning towards asking for some info on the general development process; could be some nice insight into how exactly Retro had their tools set up.
Edit history:
DJGrenola: 2015-01-19 05:01:08 pm
Quote from Paraxade:
it probably wastes a lot of time


+30-40% when unpacking a full game.
I didn't release my utility because it contains a design flaw (metafile format didn't preserve name table order correctly) which I couldn't be bothered to fix (and I largely lost interest in this a while ago). which meant that a handful of PAKs didn't recompress satisfactorily. but it has a few handy features, like superior decompression speed.

also, I suspect PakTool right now violates the GPL by inlining minilzo code without itself being GPL licenced, so you might want to avoid that in the rewrite.

I might as well give you the source code to my utility, since I don't currently plan on doing any more work on it. released under zlib licence. implementation is plain C. compiles under MSVC and gcc and probably just about any other C compiler out there. tested on win32, Mac OS X and Linux. requires external LZO and ZLIB. have fun.

Code:
// --------------------------------------------------------------------------
// Retropak v0.1.
// An unpacker/repacker for Metroid Prime 1 and 2 PAK files.
// Copyright (c) 2014 Ali Campbell

// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.

// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:

// 1. The origin of this software must not be misrepresented; you must not
//    claim that you wrote the original software. If you use this software
//    in a product, an acknowledgment in the product documentation would be
//    appreciated but is not required.
// 2. Altered source versions must be plainly marked as such, and must not be
//    misrepresented as being the original software.
// 3. This notice may not be removed or altered from any source distribution.
// --------------------------------------------------------------------------

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// for varargs (slice_printf())
#include <stdarg.h>

#ifdef _WIN32
#define RPAK_WINDOZE
#include <Windows.h>
#include <sys/types.h>
#ifndef RPAK_NOIOCTL
#define RPAK_NOIOCTL // no ioctl() for terminals on this arch
#endif
#endif // def _WIN32

#ifndef RPAK_WINDOZE      /* for architectures with ioctl */
#include <sys/ioctl.h>    /* for getting terminal size */
#endif

// for stat():
#ifndef RPAK_WINDOZE
#include <unistd.h>
#include <sys/stat.h>
#endif

#ifdef RPAK_WINDOZE
// for _mkdir()
#include <direct.h>
#endif

// for errors:
#include <errno.h>
#include <ctype.h>

// zlib
#include "zlib.h"

// LZO
#include "lzo/lzoconf.h"
#include "lzo/lzo1x.h"

#include <stdio.h>

#define RPAK_VERSION_S      "0.1"
#define RPAK_VERSION_I      0x00000001
#define RPAK_VERSION_SHORT  "retropak v" RPAK_VERSION_S
#define RPAK_VERSION_FULL   RPAK_VERSION_SHORT ", (c) Ali Campbell 2014"

// errors
#define RPAK_E_OK                    0
#define RPAK_E_NULL                  1  // generic NULL pointer on function call
#define RPAK_E_CLI                   2  // bad command line
#define RPAK_E_CLI_HELP              3  // help displayed
#define RPAK_E_MALLOC                4  // malloc() failure
#define RPAK_E_BUG                   5  // generic bug
#define RPAK_E_FOPEN_PAK             6  // fopen(PAK) failed
#define RPAK_E_PAK_EXISTS            7  // refused to overwrite existing PAK
#define RPAK_E_MALLOC_0              8  // bug: malloc(0)
#define RPAK_E_ZEROWIDTH             9  // lineslicer
#define RPAK_E_BADINDENT             10 // lineslicer
#define RPAK_E_MKDIR                 11 // could not create output dir
#define RPAK_E_FREAD                 12 // fread() errors
#define RPAK_E_FREAD_EOF             13 // input file unexpectedly truncated
#define RPAK_E_BAD_DUP               14 // store_entry: non-matching duplicate found
//#define RPAK_E_GOOD_DUP           15 // store_entry: matching duplicate found (and ignored)
#define RPAK_E_ZERO_ID               16 // store_entry: ID to store was 0
#define RPAK_E_FILE_IN_PATH          17 // mkdir: path fragment was a file, not a directory
#define RPAK_E_FWRITE                18 // fwrite() errors
#define RPAK_E_FWRITE_EOF            19 // output file unexpectedly truncated
#define RPAK_E_AUTODIR_NOEXT         20 // could not derive output dir from PAK, no extension
#define RPAK_E_AUTODIR_NONAME        21 // could not derive output dir from PAK, no name
#define RPAK_E_DIR_NOT_DIR           22 // output directory was actually a file or symlink
//#define RPAK_E_STAT_PAK           23 // could not stat() open PAK file
#define RPAK_E_PAK_TOOBIG            24 // PAK size exceeded built-in sane limit
#define RPAK_E_BAD_2WORD             25 // second 32-word of PAK file was not NULL
#define RPAK_E_TOOMANY_NAMES         26 // reported number of names in PAK exceeded sane limit
#define RPAK_E_TOOMANY_ENTRIES       27 // reported number of entries in PAK exceeded sane limit
#define RPAK_E_NAME_VOODOO           28 // bad magic (MLVL etc) in name table
#define RPAK_E_NAMELEN_TOOBIG        29 // name length from name table exceeded sane limit
//#define RPAK_E_NO_ENTRIES            30 // reported number of entries in PAK was zero
#define RPAK_E_BAD_COMPRESS          31 // bad compress value
#define RPAK_E_FSEEK                 32 // fseek() errors
#define RPAK_E_FOPEN_OPFILE          33 // fopen(some_unpacked_file_for_writing) failed
#define RPAK_E_BAD_SRC_LEN           34 // bad source (compressed) payload length
#define RPAK_E_BAD_SRC_OFF           35 // bad source offset to payload
#define RPAK_E_FILE_NOT_FILE         36 // could not read or write file as it was a dir or symlink
#define RPAK_E_FILE_EXISTS           37 // could not overwrite op file (no +o)
#define RPAK_E_ZLIB_INIT             38
#define RPAK_E_ZLIB_STREAM_ERROR     39 // Z_STREAM_ERROR
#define RPAK_E_ZLIB_NEED_DICT        40 // Z_NEED_DICT
#define RPAK_E_ZLIB_DATA_ERROR       41 // Z_DATA_ERROR
#define RPAK_E_ZLIB_MEM_ERROR        42 // Z_MEM_ERROR
#define RPAK_E_ZLIB_COMPRESS         43 // generic zlib compression error
#define RPAK_E_GETPATH_ZL            44 // rpak_getpath called with zero length string
#define RPAK_E_GETPATH_NOSEP         45 // rpak_getpath no slashes => not a path, just a filename
#define RPAK_E_STAT                  46 // stat() or GetFileAttributes() failure
#define RPAK_E_FOPEN_READ_FILE       47 // fopen() in rpak_read_file() failed
#define RPAK_E_METAFILE_LINE_LEN     48 // line in metafile exceeded pre-parse sane limit
#define RPAK_E_METAFILE_LINE_1_LEN   49 // length of first line in metafile was incorrect
#define RPAK_E_METAFILE_LINE_N_LEN   50 // length of nth line in metafile was incorrect
#define RPAK_E_METAFILE_BAD_1ST_LINE 51 // first line in metafile was bad
#define RPAK_E_METAFILE_BAD_NTH_LINE 52 // nth line in metafile was bad
#define RPAK_E_ENTRY_TOO_LARGE       53 // individual file to include in PAK exceeded sane size limit
#define RPAK_E_BAD_VERSION           54 // version not 0x00030005
#define RPAK_E_METAFILE_VERSION      55 // incompatible metafile version number
//#define RPAK_E_LZO_SET_METHOD        56 // lzo_set_method() failed
#define RPAK_E_METAFILE_PAKTYPE      57 // PAK type in metafile was unrecognised
//#define RPAK_E_LZO_BAD_SEGLEN        58 // lzo reported segment size was bigger than source data
#define RPAK_E_LZO_INIT              59 // lzo_init() failed
#define RPAK_E_LZO_DECOMPRESS        60 // lzo1x_decompress_safe() failed
#define RPAK_E_LZO_COMPRESS_NODATA   61 // LZO compress function returned no data
#define RPAK_E_LZO_COMPRESS_OVERRUN  62 // LZO-compressed data would overrun output buffer
#define RPAK_E_LZO_COMPRESS          63 // other LZO error
#define RPAK_E_LZO_COMPRESS_SEGSIZE  64 // compressed LZO segment size exceeded 64K
#define RPAK_E_LZO_COMPRESS_NOINPUT  65 // rpak_lzo_compress() called with <2 bytes of source
#define RPAK_E_METAFILE_BAD_COMPRESS 66 // bad compression field character in metafile
#define RPAK_E_FTELLO                67 // ftello() fail
#define RPAK_E_BAD_HEADER_END        68 // ftello() returned insane end-of-top position for CRC
#define RPAK_E_ALLOCS_FREES          69 // with RPAK_DBGMEM, exits with error if allocs != frees
#define RPAK_E_PAK_WAS_ACTUALLY_DIR  70 // PAK file was actually a directory
#define RPAK_E_ZL_ENTITY_NAME        71 // name in name table was zero length (not allowed)
#define RPAK_E_BAD_ENTITY_NAME       72 // name in name table contained evil characters
#define RPAK_E_UNC_LEN_MISMATCH      73 // uncompressed block len from PAK didn't match real one
#define RPAK_E_LZO_ZERO_SEGLEN       74
#define RPAK_E_ENTRY_VOODOO          75 // bad magic in entries table (unpack)
#define RPAK_E_METAFILE_UNK          76 // unknown (NULL) value at end of metafile header was wrong

#ifdef RPAK_WINDOZE
#define RPAK_BPRINTF(a,b, ...)     rpak_slice_printf((a),(b),__VA_ARGS__) // bug printf
#define RPAK_EPRINTF(a,b, ...)     rpak_slice_printf((a),(b),__VA_ARGS__) // error printf
#define RPAK_WPRINTF(a,b, ...)     rpak_slice_printf((a),(b),__VA_ARGS__) // warning printf
#define RPAK_MPRINTF(a,b, ...)     rpak_slice_printf((a),(b),__VA_ARGS__) // message printf
#else
#define RPAK_BPRINTF(a,b,c...)     rpak_slice_printf((a),(b),c) // bug printf
#define RPAK_EPRINTF(a,b,c...)     rpak_slice_printf((a),(b),c) // error printf
#define RPAK_WPRINTF(a,b,c...)     rpak_slice_printf((a),(b),c) // warning printf
#define RPAK_MPRINTF(a,b,c...)     rpak_slice_printf((a),(b),c) // message printf
#endif // ndef RPAK_WINDOZE

#ifdef RPAK_WINDOZE
#define RPAK_FUNC_M                            __FUNCTION__
#define RPAK_SNPRINTF(A,B,C,D,...)              _snprintf_s((A),(B),(C),(D),__VA_ARGS__)
#define RPAK_SCPRINTF(A,...)                    _scprintf((A),__VA_ARGS__)
#define RPAK_STRNCPY(A,B,C,D)                  _mbsncpy_s((A), (B), (C), (D))
#else
#define RPAK_FUNC_M                            __func__
#define RPAK_SNPRINTF(A,B,C,D...)              snprintf((A),(C),D)
#define RPAK_SCPRINTF(A...)                    snprintf(&rpak_scprintf_dummy, 0, A)
#define RPAK_STRNCPY(A,B,C,D)                  strncpy((A), (C), (D))
#endif /* end ndef RPAK_WINDOZE */

#ifndef XYZZY_TYPES
#define XYZZY_TYPES
#ifdef RPAK_WINDOZE
typedef UINT8 u8_t;
typedef INT8 s8_t;
typedef UINT16 u16_t;
typedef INT16 s16_t;
typedef UINT32 u32_t;
typedef INT32 s32_t;
#else // ndef RPAK_WINDOZE
typedef unsigned char u8_t;
typedef signed char s8_t;
typedef unsigned short u16_t;
typedef signed short s16_t;
typedef unsigned int u32_t;
typedef signed int s32_t;
#endif // ndef RPAK_WINDOZE
#endif // ndef XYZZY_TYPES

typedef u32_t rpak_err_t;

#ifdef RPAK_WINDOZE
#define RPAK_UOFF_T      u64_t
#define RPAK_UOFF_T_CAST u64_t
#define RPAK_FMT_UOFF_T  "llu"
#define RPAK_OFF_T       s64_t
#define RPAK_FMT_OFF_T   "lld"
#else // ndef RPAK_WINDOZE
#define RPAK_UOFF_T      off_t
#define RPAK_UOFF_T_CAST long long unsigned int
#define RPAK_FMT_UOFF_T  "llu"
#define RPAK_OFF_T       off_t
#define RPAK_FMT_OFF_T   "lld"
#endif

//#ifdef RPAK_64
#ifdef RPAK_WINDOZE
typedef UINT64 u64_t;
typedef INT64 s64_t;
// NB: these are actually the same as the non-windoze
// versions below but in a separate section in case
// I need to change them later for whatever reason
#ifndef RPAK_FMT_U64
#define RPAK_FMT_U64    "llu"
#endif
#ifndef RPAK_FMT_U64HEX
#define RPAK_FMT_U64HEX "llx"
#endif
#ifndef RPAK_FMT_S64
#define RPAK_FMT_S64    "lld"
#endif
#ifndef RPAK_FMT_S64HEX
#define RPAK_FMT_S64HEX "llx"
#endif
#else // ndef RPAK_WINDOZE
typedef unsigned long long u64_t;
typedef long long s64_t;
#ifndef RPAK_FMT_U64
#define RPAK_FMT_U64    "llu"
#endif
#ifndef RPAK_FMT_U64HEX
#define RPAK_FMT_U64HEX "llx"
#endif
#ifndef RPAK_FMT_S64
#define RPAK_FMT_S64    "lld"
#endif
#ifndef RPAK_FMT_S64HEX
#define RPAK_FMT_S64HEX "llx"
#endif
#endif // ndef RPAK_WINDOZE
//#endif // def RPAK_64

#ifndef RPAK_FMT_SIZET
#ifdef RPAK_WINDOZE
#define RPAK_FMT_SIZET   "Iu"
#define RPAK_FMT_SIZET_X "Ix"
#else
#define RPAK_FMT_SIZET   "zu"
#define RPAK_FMT_SIZET_X "zx"
#endif
#endif

#ifdef RPAK_DBGMEM2 // all just to suppress a compiler warning
#define RPAK_MALLOCMSG(X,M)           rpak_realloc(NULL, (X), 1, M)
#define RPAK_REALLOCMSG(PTR,SIZE,MSG) rpak_realloc((PTR), (SIZE), 0, (MSG))
#define RPAK_FREEMSG(X,M)             rpak_free((X), M)
#else // ndef RPAK_DBGMEM2
#define RPAK_MALLOCMSG(X,M)           rpak_realloc(NULL, (X), 1)
#define RPAK_REALLOCMSG(PTR,SIZE,MSG) rpak_realloc((PTR), (SIZE), 0)
#define RPAK_FREEMSG(X,M)             rpak_free((X))
#endif

#ifdef RPAK_DBGMEM2
void *rpak_realloc (void *p, size_t size, u8_t zero, const char *label);
void rpak_free (void *p, const char *label);
#else
void *rpak_realloc (void *p, size_t size, u8_t zero);
void rpak_free (void *p);
#endif // ! RPAK_DBGMEM2

// FIXME: look properly into win32 MSYS pathsep class of bugs
#ifndef RPAK_PATHSEP
#ifdef RPAK_WINDOZE
#define RPAK_PATHSEP '\\'
#else
#define RPAK_PATHSEP '/'
#endif
#endif

#ifdef RPAK_WINDOZE
#define RPAK_DOZE_STRERROR_LEN 80
#endif

#ifdef RPAK_DBGMEM
s32_t rpak_dbgmem_get_allocs(void);
s32_t rpak_dbgmem_get_frees(void);
#endif

typedef struct rpak_stat {
  u8_t is_dir;
  u8_t is_link;
  off_t fsize;
} rpak_stat_t;

typedef u32_t rpak_checksum_t; // for now until I get something better

typedef struct rpak_entry {
  char *filename;
  u32_t entry_table_seq;
  u8_t compress;
  u32_t payload_len_src;
  u32_t payload_offset;
  size_t payload_len_dest;
  rpak_checksum_t payload_checksum;
  u32_t magic_u32;
  u8_t magic_s[5];
  u32_t id;
  char *name;
  u8_t copied;
} rpak_entry_t;

// hashtable:
// 64 bit CPU: 152 bytes per node; ~3 MB for 20,000 PAK entries
// 32 bit CPU: 76 bytes per node; ~1.5 MB for 20,000 PAK entries
typedef struct rpak_hash {
  struct rpak_hash *buckets[16];
  rpak_entry_t *entries;
  size_t entries_alloced;
  size_t entries_fill;
} rpak_hash_t;

typedef struct rpak_pak {
  u16_t version_major;
  u16_t version_minor;
  u32_t num_entries;
  u32_t unk32_1;
  //u32_t type;
  rpak_hash_t *entries_by_id;   // hashtable used for collision checks
  rpak_entry_t *entries_by_seq; // linear list including duplicates
} rpak_pak_t;

typedef struct rpak_cfg {
  u8_t pack_or_unpack;
  char *pakfile;
  char *dir;
  char *metafile;
  u8_t short_filenames;
  u8_t overwrite;
  u16_t tw; // terminal width, short because it's used in every printf
  u8_t crc_duplicates;
  u8_t vrb; // verbose
  u8_t lower_case_filenames;
  u8_t metafile_only;
  //u8_t no_decompress;
  u8_t raw_payload;
#ifdef RPAK_WINDOZE
  char errmsg[RPAK_DOZE_STRERROR_LEN]; // static buffer for strerror_s() on doze
#endif
} rpak_cfg_t;

#ifdef RPAK_DBGMEM
static u32_t mallocs=0;
static u32_t frees=0;
#endif

// command line switches
#define RPAK_CLI_M_PACK              "pack"
#define RPAK_CLI_M_UNPACK            "unpack"

// for both packing and unpacking:
#define RPAK_CLI_O_PAKFILE_S         "-p"
#define RPAK_CLI_O_PAKFILE_L         "--pakfile"
#define RPAK_CLI_O_DIR_S             "-d"
#define RPAK_CLI_O_DIR_L             "--directory"
#define RPAK_CLI_O_METAFILE_S        "-m"
#define RPAK_CLI_O_METAFILE_L        "--metafile"
#define RPAK_CLI_O_OVERWRITE_S       "-o"
#define RPAK_CLI_O_OVERWRITE_L       "--overwrite"
#define RPAK_CLI_O_VERBOSE_S         "-v"
#define RPAK_CLI_O_VERBOSE_L         "--verbose"

#define RPAK_CLI_O_SHORT_FILENAMES_S "-s"
#define RPAK_CLI_O_SHORT_FILENAMES_L "--short-filenames"
#define RPAK_CLI_O_HELP_S            "-h"
#define RPAK_CLI_O_HELP_L            "--help"
#define RPAK_CLI_O_LC_FILENAMES_S    "-l"
#define RPAK_CLI_O_LC_FILENAMES_L    "--lower-case-filenames"
// for packing only:
#define RPAK_CLI_O_CRC_DUPLICATES_S  "-c"
#define RPAK_CLI_O_CRC_DUPLICATES_L  "--crc-duplicates"
// for unpacking only:
#define RPAK_CLI_O_METAFILE_ONLY_S   "-x"
#define RPAK_CLI_O_METAFILE_ONLY_L   "--no-extract"
#define RPAK_CLI_O_RAW_PAYLOAD_S     "-r"
#define RPAK_CLI_O_RAW_PAYLOAD_L     "--raw-payload"

// arbitrary sanity limits:
#define RPAK_MAX_ENTRIES             1000000
#define RPAK_NAMELEN_MAX             1024
#define RPAK_PAK_SIZE_MAX            0x7fffffff
#define RPAK_METAFILE_MAX_LINE_LEN   (RPAK_NAMELEN_MAX + 25)
#define RPAK_ENTRY_FILESIZE_MAX      1024 * 1024 * 1024 // 1 GiB
#define RPAK_TOP_LEN_MAX             100000000

// default metafile (list file) name
#define RPAK_METAFILE_DEFAULT        "retropak.txt"

#define RPAK_MODE_PACK               1
#define RPAK_MODE_UNPACK             2

// compression methods
#define RPAK_Z_SCHEME_ZLIB           1
#define RPAK_Z_SCHEME_LZO            2

#define RPAK_ESTREAM                 stdout

#define RPAK_ENTRY_LEN               20

#define RPAK_PAKTYPE_32_ZLIB         1 // metroid prime 1
#define RPAK_PAKTYPE_32_LZO          2 // metroid prime 2
#define RPAK_PAKTYPE_64_ZLIB         3 // others (not supported)

#define RPAK_METAFILE_CHAR_COMPRESSED   'Z'
#define RPAK_METAFILE_CHAR_UNCOMPRESSED '.'

static rpak_err_t parse_cli (int argc, char **argv, rpak_cfg_t *cfg);
static void rpak_usage(rpak_cfg_t *cfg);
static void rpak_banner(rpak_cfg_t *cfg);
static rpak_err_t rpak_pack(rpak_cfg_t *cfg);
static rpak_err_t rpak_unpack(rpak_cfg_t *cfg);
static rpak_err_t rpak_mkdir(rpak_cfg_t *cfg, char *path);
static u32_t rpak_slice_printf(u16_t cols,          /* wrap width */
                               size_t wrap_indent,  /* how much to indent new lines */
                               char *fmt,           /* fmtstg */
                               ...);                /* etc. */
static rpak_err_t rpak_mkdir_2(rpak_cfg_t *cfg, char *path);
static rpak_err_t stream_u16_bigendian (rpak_cfg_t *cfg, FILE *fp, u16_t *u16);
static rpak_err_t stream_u32_bigendian (rpak_cfg_t *cfg, FILE *fp, u32_t *u32);
static rpak_err_t fread_fully(rpak_cfg_t *cfg,
                              u8_t *buf,
                              size_t size,
                              FILE *f);
static rpak_err_t fwrite_fully(rpak_cfg_t *cfg,
                               u8_t *buf,
                               size_t size,
                               char *filename,
                               FILE *f);
static rpak_err_t rpak_store_entry (rpak_cfg_t *cfg,
                                    rpak_hash_t *hash,
                                    u32_t id,
                                    rpak_entry_t *entry);
static rpak_err_t rpak_fetch_entries (rpak_cfg_t *cfg,
                                      rpak_hash_t *hash,
                                      u32_t id,
                                      rpak_entry_t **entries,
                                      size_t *num_entries);
static void rpak_hash_free(rpak_hash_t *hash, u8_t free_entries);
static u32_t parse_u32_bigendian(u8_t *b);
static void rpak_free_entry(rpak_entry_t *entry);
static void rpak_hash_size(rpak_hash_t *hash, u32_t *size);
static rpak_checksum_t rpak_checksum (u8_t *buf, size_t len);
static rpak_err_t rpak_zlib_decompress(rpak_cfg_t *cfg,
                                       u8_t *source,
                                       size_t srclen,
                                       u8_t **dest,
                                       size_t *destlen,
                                       char *entry_name,
                                       u32_t entry_id,
                                       u8_t suppress_errors);
static rpak_err_t rpak_lzo_decompress(rpak_cfg_t *cfg,
                                       u8_t *source,
                                       size_t srclen,
                                       u8_t **dest,
                                       size_t *destlen,
                                       char *entry_name,
                                       u32_t entry_id);
static rpak_err_t rpak_zlib_compress(rpak_cfg_t *cfg,
                                     u8_t *source,
                                     size_t srclen,
                                     u8_t **dest,
                                     size_t *destlen);
static rpak_err_t rpak_lzo_compress(rpak_cfg_t *cfg,
                                    u8_t *source,
                                    size_t srclen,
                                    u8_t **dest,
                                    size_t *destlen);
static rpak_err_t rpak_getpath (rpak_cfg_t *cfg, char *path, char **output);
static rpak_err_t rpak_write_file (rpak_cfg_t *cfg, char *filename,
                            u8_t *payload, size_t len, u8_t mkpath);
static FILE *rpak_fopen (rpak_cfg_t *cfg, char *filename, char *mode);
static rpak_err_t rpak_stat(rpak_cfg_t *cfg, char *filename, rpak_stat_t *stat_out);
static rpak_err_t rpak_read_file (rpak_cfg_t *cfg, char *filename,
                                  u8_t **payload_out, size_t *len_out);
static rpak_err_t rpak_parse_metafile (rpak_cfg_t *cfg,
                                       /*char *path, */
                                       rpak_entry_t **entries_out,
                                       u32_t *num_entries_out,
                                       u16_t *version_major_out,
                                       u16_t *version_minor_out,
                                       u32_t *retropak_version_out,
                                       u32_t *pak_type_out);
static u8_t is_valid_entity_name(char *name, size_t len);
static u8_t is_valid_magic_s(char *magic);
static int rpak_compare_entries(const void *e1, const void *e2);
static void to_u32_bigendian(u32_t x, u8_t *out);
static u32_t calculate_padding_len (u32_t len, u8_t round_down);
static rpak_err_t rpak_lzo_init(rpak_cfg_t *cfg);
static rpak_err_t mkdir_for_file(rpak_cfg_t *cfg, char *filename);
static char *pakname_by_checksum(rpak_checksum_t cksum);
static s32_t rpak_get_term_width (void);

#ifdef RPAK_WINDOZE
static char *rpak_windoze_strerror(rpak_cfg_t *cfg);
#endif

static char rpak_scprintf_dummy; // yeah. don't worry about it

#ifdef RPAK_WINDOZE
#define RPAK_STRERROR(c) rpak_windoze_strerror(c)
#else
#define RPAK_STRERROR(c) strerror(errno)
#endif

#ifdef RPAK_WINDOZE
static char *rpak_windoze_strerror(rpak_cfg_t *cfg) {
  memset(cfg->errmsg, 0, RPAK_DOZE_STRERROR_LEN);
  _strerror_s(cfg->errmsg, RPAK_DOZE_STRERROR_LEN, NULL);
  return cfg->errmsg;
}
#endif

// FIXME: add follow_link parameter, choose between stat() and lstat()
static rpak_err_t rpak_stat(rpak_cfg_t *cfg, char *filename, rpak_stat_t *stat_out) {
#ifdef RPAK_WINDOZE
  WIN32_FILE_ATTRIBUTE_DATA fad;
  u64_t tmpu64;
#else
  struct stat sb;
#endif
  if (!filename) {
    RPAK_BPRINTF(cfg->tw, 5, "BUG: rpak_stat(filename==NULL)\n");
    return RPAK_E_NULL;
  }
  if (!stat_out) {
    RPAK_BPRINTF(cfg->tw, 5, "BUG: rpak_stat(stat_out==NULL)\n");
    return RPAK_E_NULL;
  }

#ifdef RPAK_WINDOZE
  memset(&fad, 0, sizeof(WIN32_FILE_ATTRIBUTE_DATA));
#else
  memset(&sb, 0, sizeof(struct stat));
#endif

  memset(stat_out, 0, sizeof(rpak_stat_t));

#ifdef RPAK_WINDOZE
  if (!GetFileAttributesEx(filename, GetFileExInfoStandard, &fad))
    return RPAK_E_STAT;
//printf("file attributes(%s) = 0x%x\n", filename, fad.dwFileAttributes);
  if (fad.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
//printf("is_dir=1\n");
    stat_out->is_dir = 1;
  }
  tmpu64 = fad.nFileSizeHigh;
  tmpu64 <<= 32;
  tmpu64 &= 0xffffffff00000000UL;
  tmpu64 |= 0xffffffff & fad.nFileSizeLow;
  stat_out->fsize = (off_t) 0x7fffffffffffffff & tmpu64;
#else // ndef RPAK_WINDOZE
  if (lstat(filename, &sb))
    return RPAK_E_STAT;
  if ((sb.st_mode & S_IFMT) == S_IFLNK)
    stat_out->is_link = 1;
  if (stat(filename, &sb))
    return RPAK_E_STAT;
  if ((sb.st_mode & S_IFMT) == S_IFDIR)
    stat_out->is_dir = 1;

  stat_out->fsize = sb.st_size;
#endif
  return RPAK_E_OK;
}

static s32_t rpak_get_term_width (void) {
  /* get terminal width if the system supports ioctls */
#ifdef RPAK_WINDOZE
  // ??? FIXME 
  return 80;
#else // not windows
#ifdef TIOCGWINSZ
  struct winsize wsize;
  if (!(ioctl (0, TIOCGWINSZ, &wsize)))
    return wsize.ws_col;
#endif
#endif
  return 80;
}

#define MP1NA000 "MP1 US 0-00"
#define MP1PAL   "MP1 PAL"
#define MP2NA    "MP2 US"
#define MP2PAL   "MP2 PAL"
static char *pakname_by_checksum (rpak_checksum_t cksum) {

  switch (cksum) {
  
    // mp1
    case 0x599f62d9: return "AudioGrp, "      MP1NA000;
    case 0x24faba9b: return "AudioGrp, "      MP1PAL;
    case 0x9715a862: return "GGuiSys, "       MP1NA000;
    case 0x0643b7a5: return "GGuiSys, "       MP1PAL;
    case 0x82b75cdf: return "Metroid1, "      MP1NA000;
    case 0xbf4892a5: return "Metroid1, "      MP1PAL;
    case 0x5cca900b: return "Metroid2, "      MP1NA000;
    case 0xd50bb89d: return "Metroid2, "      MP1PAL;
    case 0xed97b87e: return "Metroid3, "      MP1NA000;
    case 0x158577f1: return "Metroid3, "      MP1PAL;
    case 0xe4a67dd2: return "Metroid4, "      MP1NA000;
    case 0x08c5ec35: return "Metroid4, "      MP1PAL;
    case 0x6d9f5270: return "metroid5, "      MP1NA000;
    case 0x12c46850: return "metroid5, "      MP1PAL;
    case 0x479f4bbf: return "Metroid6, "      MP1NA000;
    case 0xd79fc3dd: return "Metroid6, "      MP1PAL;
    case 0xbaac3a0a: return "Metroid7, "      MP1NA000;
    case 0xf461a418: return "Metroid7, "      MP1PAL;
    case 0xa4996852: return "Metroid8, "      MP1NA000;
    case 0xf01c868d: return "Metroid8, "      MP1PAL;
    case 0xdefeb29f: return "MidiData, "      MP1NA000;
    case 0xcceaa877: return "MidiData, "      MP1PAL;
    case 0x007ec854: return "MiscData, "      MP1NA000;
    case 0x8f744f87: return "MiscData, "      MP1PAL;
    case 0xf1180d02: return "NoARAM, "        MP1NA000;
    case 0x47eab168: return "NoARAM, "        MP1PAL;
    case 0x26c7a8ff: return "SamGunFx, "      MP1NA000;
    case 0xa3fba426: return "SamGunFx, "      MP1PAL;
    case 0xf2030b89: return "SamusGun, "      MP1NA000 " or " MP1PAL;
    case 0x575f1615: return "TestAnim, "      MP1NA000 " or " MP1PAL;
    case 0xbcdc1a47: return "Tweaks, "        MP1NA000;
    case 0x9ee73643: return "Tweaks, "        MP1PAL;
    case 0xe11c9f6b: return "SlideShow, "     MP1NA000 " or " MP1PAL;

    // mp2
    case 0xee3d3e4b: return "AudioGrp, "      MP2NA  " or " MP2PAL;
    case 0xcf9a6318: return "FrontEnd, "      MP2NA;
    case 0x5df3f1f2: return "FrontEnd, "      MP2PAL;
    case 0xb006c47c: return "GGuiSys, "       MP2NA;
    case 0x8fd8f3b0: return "GGuiSys, "       MP2PAL;
    case 0xace5ad7a: return "LogBook, "       MP2NA;
    case 0x2e1e9511: return "LogBook, "       MP2PAL;
    case 0xd329f767: return "Metroid1, "      MP2NA  " or " MP2PAL;
    case 0x2a153f6b: return "Metroid2, "      MP2NA;
    case 0x4568dcaf: return "Metroid2, "      MP2PAL;
    case 0xc24bf5eb: return "Metroid3, "      MP2NA;
    case 0xd86a1099: return "Metroid3, "      MP2PAL;
    case 0x447d7432: return "Metroid4, "      MP2NA;
    case 0x0ce3c448: return "Metroid4, "      MP2PAL;
    case 0x9c126f84: return "Metroid5, "      MP2NA  " or " MP2PAL;
    case 0x5c64a0f3: return "Metroid6, "      MP2NA;
    case 0x605c462e: return "Metroid6, "      MP2PAL;
    case 0x230b691f: return "MidiData, "      MP2NA  " or " MP2PAL;
    case 0x1d07ea79: return "MiscData, "      MP2NA  " or " MP2PAL;
    case 0xd5a2ce33: return "NoARAM, "        MP2NA;
    case 0x7e52b571: return "NoARAM, "        MP2PAL;
    case 0x269291e0: return "SamGunFxLow, "   MP2NA  " or " MP2PAL;
    case 0x68ac4be2: return "SamGunFxMulti, " MP2NA  " or " MP2PAL;
    case 0xc5a83764: return "SamGunFx, "      MP2NA  " or " MP2PAL;
    case 0xb9dc4def: return "SamusGunLow, "   MP2NA  " or " MP2PAL;
    case 0x75aff537: return "SamusGun, "      MP2NA  " or " MP2PAL;
    case 0x8f9f1375: return "SlideShow, "     MP2NA  " or " MP2PAL;
    case 0x2c67be61: return "TestAnim, "      MP2NA  " or " MP2PAL;
    
    // others
    default: return "UNKNOWN";
  }
}

FILE *rpak_fopen (rpak_cfg_t *cfg, char *filename, char *mode) {
  FILE *f = NULL;
  u8_t failed=0;
  char *errmsg = NULL;
  if (!filename) {
    RPAK_BPRINTF(cfg->tw, 5,
                 "BUG: rpak_fopen(filename==NULL)\n");
    return NULL;
  }
  if (!mode) {
    RPAK_BPRINTF(cfg->tw, 5,
                 "BUG: rpak_fopen(mode==NULL)\n");
    return NULL;
  }

//printf("fopen(%s)\n", filename);

#ifdef RPAK_WINDOZE
  if (fopen_s(&f, filename, mode))
    failed = 1;
#else
  if (NULL == (f = fopen(filename, mode)))
    failed = 1;
#endif
  if (failed) {
    errmsg = RPAK_STRERROR(cfg);
    RPAK_EPRINTF(cfg->tw, 7,
                 "ERROR: Could not open file \"%s\" (%s).\n",
                 filename, errmsg);
  }
  return f;
}

// sorting function for qsort
static int rpak_compare_entries(const void *e1, const void *e2) {
  const rpak_entry_t *e3, *e4;
  e3 = e1;
  e4 = e2;
  if (e3->payload_offset < e4->payload_offset)
    return -1;
  if (e3->payload_offset == e4->payload_offset)
    return 0;
  if (e3->payload_offset > e4->payload_offset)
    return 1;
  return 0;
}

static void rpak_sort_entries (rpak_entry_t *entries, u32_t num_entries) {
  qsort(entries, num_entries, sizeof(rpak_entry_t), rpak_compare_entries);
}

static rpak_err_t rpak_pack (rpak_cfg_t *cfg) {
  
  rpak_stat_t sb;
  u32_t ecode = RPAK_E_OK;
  FILE *fp_pak = NULL;
  rpak_entry_t *entries = NULL;
  rpak_entry_t *entries_by_offset_seq = NULL;
  u32_t num_entries=0;
  size_t top_len=0;
  u32_t num_names=0;
  u32_t i=0;
  u8_t *top_buf = NULL;
  u16_t version_major = 0;
  u16_t version_minor = 0;
  size_t total_names_len=0;
  size_t cur_off=0;
  rpak_hash_t *hashroot = NULL;
  u8_t *dummybuf = NULL;
  size_t entries_table_start_offset=0;
  size_t k=0;
  u32_t metafile_version=0;
  u32_t pak_type=0;
  char *tmp_pakname=NULL;
  
  memset(&sb, 0, sizeof(rpak_stat_t));
  
  if (RPAK_E_OK != (ecode = rpak_parse_metafile (cfg,
                                                 //cfg->metafile, // FIXME: add this for clarity
                                                 &entries,
                                                 &num_entries,
                                                 &version_major,
                                                 &version_minor,
                                                 &metafile_version,
                                                 &pak_type)))
    goto L_rpak_pack_end;
    
//printf("pak_type=%u\n", pak_type);
  
  // deep-clone entries to make another array that will be sorted by offset sequence
  if (num_entries) {
    entries_by_offset_seq = RPAK_MALLOCMSG(sizeof(rpak_entry_t) * num_entries,
                                           "entries by offset seq");
    memcpy(entries_by_offset_seq, entries, sizeof(rpak_entry_t) * num_entries);
  }
  for (k=0;k<num_entries;k++) {
    rpak_entry_t *entry_src = NULL;
    rpak_entry_t *entry_dest = NULL;
    entry_dest = entries_by_offset_seq + k;
    entry_src = entries + k;
    if (entry_src->name) {
      entry_dest->name = RPAK_MALLOCMSG(strlen(entry_src->name) + 1, "entry name");
      memcpy(entry_dest->name, entry_src->name, strlen(entry_src->name) + 1);
    }
    if (entry_src->filename) {
      entry_dest->filename = RPAK_MALLOCMSG(strlen(entry_src->filename) + 1,
                                            "entry filename");
      memcpy(entry_dest->filename,
             entry_src->filename,
             strlen(entry_src->filename) + 1);
    }
    // also, scribble the original offsets with a failvalue to make sure
    // that we correctly replace them later
    entry_src->payload_offset = ~0;
  }
  
//printf("%x\n", rpak_checksum((u8_t *) entries_by_offset_seq, num_entries * sizeof(rpak_entry_t)));
  // sort clone by offset sequence
  rpak_sort_entries(entries_by_offset_seq, num_entries);
//printf("%x\n", rpak_checksum((u8_t *) entries_by_offset_seq, num_entries * sizeof(rpak_entry_t)));
  
  if (!(cfg->pakfile)) {
    // must derive PAK file name
    cfg->pakfile = RPAK_MALLOCMSG(strlen(cfg->dir)+4+1, "derived PAK file name");
    memcpy(cfg->pakfile, cfg->dir, strlen(cfg->dir));
    memcpy(cfg->pakfile + strlen(cfg->dir), ".pak", 5);
    tmp_pakname = cfg->pakfile;
    RPAK_MPRINTF(cfg->tw, 2, "- No output PAK file name was provided. Using \"%s\", "
                             "as derived from the input directory name.\n", cfg->pakfile);
  }
  
  if (RPAK_E_OK != rpak_stat(cfg, cfg->pakfile, &sb)) {
    // PAK file does not exist; attempt to create it
    
    // but first, we need to create the directory to contain it
    if (RPAK_E_OK != (ecode = mkdir_for_file (cfg, cfg->pakfile)))
      goto L_rpak_pack_end;
    
    // now create the file
    if (NULL == (fp_pak = rpak_fopen (cfg, cfg->pakfile, "wb"))) {
      RPAK_EPRINTF(cfg->tw, 7,
                   "ERROR: Could not create PAK file \"%s\" for writing.\n",
                   cfg->pakfile);
      ecode = RPAK_E_FOPEN_PAK;
      goto L_rpak_pack_end;
    }
    RPAK_MPRINTF(cfg->tw, 2, "- Created PAK file \"%s\".\n", cfg->pakfile);
  } else {
    // stat succeeded: file exists
    if (cfg->overwrite) {
      // but we are cleared to overwrite it
      if (NULL == (fp_pak = rpak_fopen (cfg, cfg->pakfile, "wb"))) {
        RPAK_EPRINTF(cfg->tw, 7,
                     "ERROR: Failed to overwite PAK file \"%s\".\n",
                     cfg->pakfile);
        ecode = RPAK_E_FOPEN_PAK;
        goto L_rpak_pack_end;
      }
      RPAK_MPRINTF(cfg->tw, 2, "- Scribbled existing PAK file \"%s\".\n", cfg->pakfile);
    } else {
      RPAK_EPRINTF(cfg->tw, 7,
                   "ERROR: Refusing to overwrite file \"%s\" (use %s to override).\n",
                   cfg->pakfile, RPAK_CLI_O_OVERWRITE_S);
      ecode = RPAK_E_PAK_EXISTS;
      goto L_rpak_pack_end;
    }
  }
  
  RPAK_MPRINTF(cfg->tw, 2, "- Loaded metadata file with %u entries.\n", num_entries);
  
  // we only want each ID to appear once in the name table,
  // so we'll need a hashtable to keep track
  hashroot = RPAK_MALLOCMSG(sizeof(rpak_hash_t), "pack hashroot");
  memset(hashroot, 0, sizeof(rpak_hash_t));
  
  for (i=0;i<num_entries;i++) {
  
    rpak_entry_t *entries_from_hash = NULL;
    size_t num_hash_entries=0;
    
    if (entries[i].name) {
      num_hash_entries=0;
      ecode = rpak_fetch_entries (cfg,
                                  hashroot,
                                  entries[i].id,
                                  &entries_from_hash,
                                  &num_hash_entries);
      if (ecode != RPAK_E_OK) {
        rpak_hash_free(hashroot, 1);
        goto L_rpak_pack_end;
      }
      
      if (num_hash_entries) {
        // seen it before, ignore it
        continue;
      }
      
      // otherwise add it to the hashtable
      ecode = rpak_store_entry(cfg, hashroot, entries[i].id, entries+i);
      if (ecode != RPAK_E_OK) {
        rpak_hash_free(hashroot, 1);
        goto L_rpak_pack_end;
      }

      num_names++;
      total_names_len += strlen(entries[i].name);
      
    }
  }
  
  top_len = 4 // header
            + 4 // NULL
            + 4 // number of names
            + num_names * 4  // magic
            + num_names * 4  // entry ID
            + num_names * 4  // name length
            + total_names_len // summed names
            + 4; // total entries
            
  //top_padding = calculate_padding_len(top_len, 0);
  top_buf = RPAK_MALLOCMSG(top_len, "top buffer");
  
  // header
  top_buf[0] = (version_major >> 8) & 0xff;
  top_buf[1] = (version_major & 0xff);
  top_buf[2] = (version_minor >> 8) & 0xff;
  top_buf[3] = (version_minor & 0xff);
  // NULL 32
  memset(top_buf + 4, 0, 4);
  // num names
  to_u32_bigendian(num_names, top_buf+8);
  cur_off = 12;
  // name table

  for (i=0;i<num_entries;i++) {
    size_t namelen=0;
    rpak_entry_t *entries_from_hash = NULL;
    size_t num_hash_entries=0;

    if (entries[i].name) {
      num_hash_entries=0;

      ecode = rpak_fetch_entries (cfg,
                                  hashroot,
                                  entries[i].id,
                                  &entries_from_hash,
                                  &num_hash_entries);
      if (ecode != RPAK_E_OK) {
        rpak_hash_free(hashroot, 1);
        goto L_rpak_pack_end;
      }
      
      if (num_hash_entries) {
        if (entries_from_hash[0].copied) {
          // seen it before, ignore it
          continue;
        } else {
          entries_from_hash[0].copied = 1;
        }
      }
      
      namelen = strlen(entries[i].name);
      memcpy(top_buf + cur_off, entries[i].magic_s, 4);
      to_u32_bigendian(entries[i].id, top_buf + cur_off + 4);
      to_u32_bigendian(0x7fffffff & namelen, top_buf + cur_off + 8);
      memcpy(top_buf + cur_off + 12, entries[i].name, namelen);
      cur_off += 12 + (0x7fffffff & namelen);
    }
  }
  rpak_hash_free(hashroot, 1);
  hashroot = NULL;
  
  // number of entries
  to_u32_bigendian(num_entries, top_buf + cur_off);
  cur_off+=4;
  
//rpak_hexdump (top_buf, top_len, 0, 32, stdout);

  // write file top to file
  ecode = fwrite_fully(cfg, top_buf, top_len, cfg->pakfile, fp_pak);
  RPAK_FREEMSG(top_buf, "top buffer");
  if (ecode != RPAK_E_OK)
    goto L_rpak_pack_end;
    
  hashroot = RPAK_MALLOCMSG(sizeof(rpak_hash_t), "pack hashroot");
  memset(hashroot, 0, sizeof(rpak_hash_t));
  
  // also write a dummy entries table, since we don't yet have the
  // file sizes (from the filesystem) and we don't want to read them
  // twice, but we know how big the table needs to be
  entries_table_start_offset = cur_off;
  if (num_entries) {
    dummybuf = RPAK_MALLOCMSG(RPAK_ENTRY_LEN * num_entries, "dummy entries dummybuf");
    memset(dummybuf, 0xac, RPAK_ENTRY_LEN * num_entries);
    ecode = fwrite_fully(cfg, dummybuf, RPAK_ENTRY_LEN * num_entries, cfg->pakfile, fp_pak);
    RPAK_FREEMSG(dummybuf, "dummy entries dummybuf");
    dummybuf = NULL;
    if (ecode != RPAK_E_OK)
      goto L_rpak_pack_end;
  }
  cur_off += RPAK_ENTRY_LEN * num_entries;
  
//printf("num_entries=%u\n", num_entries);

  RPAK_MPRINTF(cfg->tw, 2, "- Packing files. Please wait.\n");
  
  // - go through list-by-offset, compute new offset for each file,
  //   starting from the known start offset
  // - these new offsets should be written into a hashtable, and also
  //   used to scribble the old offsets in list-by-offset
  //cur_off = top_len;
  for (i=0;i<num_entries;i++) {
  
    char id_s[9];
    u8_t uncompressed_length_buf[4];
  
    rpak_entry_t *entry = NULL;
    char *filename = NULL;
    size_t dirlen=0;
    size_t pad=0;
    size_t dummysizet=0;
    size_t filename_len=0;
    size_t name_len=0;
    u8_t *zbuf = NULL;
    
    size_t k=0;

    entry = entries_by_offset_seq + i;

//printf("cur_off=%u\n", cur_off);

    pad = calculate_padding_len(cur_off, 0);
    entry->payload_offset = cur_off + pad;
    
    if (entry->payload_offset & 0x1f) {
      RPAK_BPRINTF(cfg->tw, 5, "BUG: entry->payload_offset is misaligned.\n");
      ecode = RPAK_E_BUG;
      goto L_rpak_pack_end;
    }    
    
    cur_off += pad;
    
    // write prepadding (only needed for the first entry
    // as the postpadding will align all other entries, no need for prepadding)
    if (pad) {
//printf("%u bytes prepadding, cur_off=%u\n", pad & 0xffffffff, cur_off);
      dummybuf = RPAK_MALLOCMSG(pad, "prepad dummybuf");
      memset(dummybuf, 0x0, pad);
      ecode = fwrite_fully(cfg, dummybuf, pad, cfg->pakfile, fp_pak);
      RPAK_FREEMSG(dummybuf, "prepad dummybuf");
      dummybuf = NULL;
      if (ecode != RPAK_E_OK)
        goto L_rpak_pack_end;
    }
      
    // build filename
    dirlen = strlen(cfg->dir);
    if (cfg->short_filenames || !(entry->name)) {
      filename_len = dirlen + 1 + 8 + 1 + 4;
    } else {
      name_len = strlen(entry->name);
      filename_len = dirlen + 1 + 8 + 1 + name_len + 1 + 4;
    }
    memset(id_s, 0, 9);
    RPAK_SNPRINTF(id_s, 9, 9, "%08x", entry->id);
    filename = RPAK_MALLOCMSG(filename_len + 1, "filename to load");
    filename[filename_len] = 0;
    memcpy(filename, cfg->dir, dirlen);
    filename[dirlen] = RPAK_PATHSEP;
    memcpy(filename + dirlen + 1, id_s, 8);
    if (cfg->short_filenames || !(entry->name)) {
      filename[dirlen + 1 + 8] = '.';
      for (k=0;k<5;k++) {
        char c;
        c = (cfg->lower_case_filenames) ? tolower(entry->magic_s[k]) : entry->magic_s[k];
        filename[dirlen + 1 + 8 + 1 + k] = c;
      }
    } else {
      filename[dirlen + 1 + 8] = '_';
      for (k=0;k<name_len;k++) {
        char c;
        c = (cfg->lower_case_filenames) ? tolower(entry->name[k]) : entry->name[k];
        filename[dirlen + 1 + 8 + 1 + k] = c;
      }
      filename[dirlen + 1 + 8 + 1 + name_len] = '.';
      for (k=0;k<5;k++) {
        char c;
        c = (cfg->lower_case_filenames) ? tolower(entry->magic_s[k]) : entry->magic_s[k];
        filename[dirlen + 1 + 8 + 1 + name_len + 1 + k] = c;
      }
    }
        
    // load file
//printf("[1]\n");
    ecode = rpak_read_file (cfg,
                            filename,
                            &dummybuf,
                            &dummysizet);
//printf("[2]\n");

    RPAK_FREEMSG(filename, "filename to load");
    if (RPAK_E_OK != ecode) {
//printf("[3]\n");
      goto L_rpak_pack_end;
    }
//printf("[4]\n");
    if (dummysizet > RPAK_ENTRY_FILESIZE_MAX) {
      RPAK_EPRINTF(cfg->tw, 7, "ERROR: File \"%s\" was too large to include in the PAK file "
                               "(size was %lu, maximum size is %lu).\n", entry->filename, 
                               (u64_t) dummysizet, (u64_t) RPAK_ENTRY_FILESIZE_MAX);
      RPAK_FREEMSG(dummybuf, "rpak_read_file payload");
      ecode = RPAK_E_ENTRY_TOO_LARGE;
      goto L_rpak_pack_end;
    }
    
    entry->payload_len_src = 0xffffffff & dummysizet;
    
    // compress
    if (entry->compress) {
    
      if (pak_type == RPAK_PAKTYPE_32_ZLIB) { // || pak_type == ...
        // zlib
//printf("zlib\n");
        ecode = rpak_zlib_compress(cfg, 
                                   dummybuf,
                                   dummysizet,
                                   &zbuf,
                                   &(entry->payload_len_dest));
      } else {
//printf("lzo\n");
        // lzo
        ecode = rpak_lzo_compress(cfg, 
                                  dummybuf,
                                  dummysizet,
                                  &zbuf,
                                  &(entry->payload_len_dest));
      }
      
      // write uncompressed length
      to_u32_bigendian (entry->payload_len_src, uncompressed_length_buf);
      if (ecode == RPAK_E_OK)
        ecode = fwrite_fully(cfg,
                             uncompressed_length_buf,
                             4,
                             cfg->pakfile,
                             fp_pak);
      //cur_off += 4;
    } else {
      entry->payload_len_dest = entry->payload_len_src;
    }
    
    // copy payload to PAK
    if (ecode == RPAK_E_OK)
      ecode = fwrite_fully(cfg,
                           entry->compress ? zbuf : dummybuf,
                           entry->payload_len_dest,
                           cfg->pakfile,
                           fp_pak);

    // clean up payload buffers
    if (entry->compress)
      RPAK_FREEMSG(zbuf, "zbuf out");
    RPAK_FREEMSG(dummybuf, "rpak_read_file payload");
    dummybuf = NULL;
    if (RPAK_E_OK != ecode)
      goto L_rpak_pack_end;
    
    if (cfg->vrb) {
      if (entry->compress) {
        RPAK_MPRINTF(cfg->tw, 0,
                     "Added 0x%x (compressed %u"
                     " -> %lu"
                     " bytes).\n",
                     entry->id,
                     entry->payload_len_src,
                     (u64_t) entry->payload_len_dest);
      } else {
        RPAK_MPRINTF(cfg->tw, 0,
                     "Added 0x%x (%lu"
                     " bytes).\n",
                     entry->id,
                     entry->payload_len_src,
                     (u64_t) entry->payload_len_dest);
      }
    }
    
    cur_off += entry->payload_len_dest;
    if (entry->compress) {
      // for compressed entries we had to write the uncompressed
      // payload size to the PAK first which is not yet reflected in the
      // offsets, so we need to advance everything four bytes,
      // including the value that will get written into the entries
      // table (later)
      cur_off += 4;
      entry->payload_len_dest += 4;
    }
    
    if (cur_off & 0x1f) {
//printf("prepad off=%x, ", cur_off);
      dummysizet = calculate_padding_len(cur_off, 1);
//printf("%u bytes postpadding, cur_off=%u\n", dummysizet & 0xffffffff, );
      dummybuf = RPAK_MALLOCMSG(dummysizet, "postpadding buf");
      memset(dummybuf, 0xff, dummysizet);
      ecode = fwrite_fully(cfg,
                           dummybuf,
                           dummysizet,
                           cfg->pakfile,
                           fp_pak);
      RPAK_FREEMSG(dummybuf, "postpadding buf");
      if (RPAK_E_OK != ecode)
        goto L_rpak_pack_end;
      cur_off += dummysizet;
      entry->payload_len_dest += dummysizet;
      //entry->payload_len_dest += dummysizet;
//printf("postpad off=%x\n", cur_off);
    }
      
    // store in hashtable for looking up offset and payload length later on
    if (RPAK_E_OK != (ecode = rpak_store_entry (cfg,
                                                hashroot,
                                                entry->id,
                                                entry)))
      goto L_rpak_pack_end;
  }
  
  // - now go through list by entry table, get the id and look up the offsets from the hashtable
  for (i=0;i<num_entries;i++) {
  
    size_t j;
    size_t num_dup_entries=0;
    u32_t payload_offset=0;
    rpak_entry_t *dup_entries = NULL;
    
    rpak_fetch_entries(cfg, hashroot, entries[i].id, &dup_entries, &num_dup_entries);
    
    if (!num_dup_entries) {
      RPAK_BPRINTF(cfg->tw, 5, "BUG: entry %x not found in hashtable.\n", entries[i].id);
      goto L_rpak_pack_end;
    }

    for (j=0;j<num_dup_entries;j++) {
//printf("%p.copied is %u\n", dup_entries+j, dup_entries[j].copied);
      if (!dup_entries[j].copied) {
        payload_offset = dup_entries[j].payload_offset;
        entries[i].payload_offset = payload_offset;
        dup_entries[j].copied = 1;
//printf("setting %p.copied = 1\n", dup_entries+j);
        break;
      }
    }
    if (payload_offset == 0) {
      RPAK_BPRINTF(cfg->tw, 5, "BUG: no payload offset found in hashtable for entry %x\n", entries[i].id);
      ecode = RPAK_E_BUG;
      goto L_rpak_pack_end;
    }
//printf ("off %x -> %x\n", entries[i].payload_offset, payload_offset);
  }
  
//printf("etso=%u\n", entries_table_start_offset);
  
  // finally, overwrite the dummy entries table we wrote out before
  if (fseek (fp_pak, entries_table_start_offset, SEEK_SET) < 0) {
    RPAK_EPRINTF(cfg->tw, 7, "ERROR: Could not seek to start of dummy entries table to overwrite "
                             "it with real entry data. The generated PAK will not be valid.\n");
    ecode = RPAK_E_FSEEK;
    goto L_rpak_pack_end;
  }
 
  if (num_entries) {
    dummybuf = RPAK_MALLOCMSG(RPAK_ENTRY_LEN * num_entries, "entry table");
    memset(dummybuf, 0, RPAK_ENTRY_LEN * num_entries);
  }
  for (i=0;i<num_entries;i++) {
    size_t dummy_num_entries;
    rpak_entry_t *entries_from_hash = NULL;
    u8_t *p = dummybuf + (i * RPAK_ENTRY_LEN);
    
    to_u32_bigendian(entries[i].compress, p);
    memcpy(p+4, entries[i].magic_s, 4);
    to_u32_bigendian(entries[i].id, p+8);
    // need to get the file length from the hashtable
    if (RPAK_E_OK != (ecode = rpak_fetch_entries (cfg,
                                                  hashroot,
                                                  entries[i].id,
                                                  &entries_from_hash,
                                                  &dummy_num_entries)))
      goto L_rpak_pack_end;
    // can always use the 0th element because the payload length is always the same for dups
    to_u32_bigendian(entries_from_hash[0].payload_len_dest, p+12);
    // offsets on entries[x] were rewritten by hashtable search earlier
    // so are now correct values
    to_u32_bigendian(entries[i].payload_offset, p+16);
  }
  // overwrite dummy entries table from earlier
  if (num_entries) {
    ecode = fwrite_fully(cfg, dummybuf, RPAK_ENTRY_LEN * num_entries, cfg->pakfile, fp_pak);
    RPAK_FREEMSG(dummybuf, "entry table");
    dummybuf = NULL;
    if (ecode != RPAK_E_OK)
      goto L_rpak_pack_end;
  }
    
  RPAK_MPRINTF(cfg->tw, 2, "- The PAK file was written successfully.\n");

L_rpak_pack_end:
  if (tmp_pakname) {
    RPAK_FREEMSG(cfg->pakfile, "derived PAK file name");
    cfg->pakfile = NULL;
  }
  if (hashroot)
    rpak_hash_free(hashroot, 1);
  if (entries_by_offset_seq) {
    for (i=0;i<num_entries;i++) {
      rpak_free_entry(entries_by_offset_seq + i);
    }
    RPAK_FREEMSG(entries_by_offset_seq, "entries by offset seq");
  }
  if (entries) {
    for (i=0;i<num_entries;i++) {
      rpak_free_entry(entries + i);
    }
    RPAK_FREEMSG(entries, "pack entries list");
  }
  if (fp_pak)
    fclose(fp_pak);
  return ecode;
}

static u32_t calculate_padding_len (u32_t len, u8_t round_down) {
  if (round_down || (len & 0x1f))
    return 0x20 - (len & 0x1f);
  return 0; // FIXME: ???
}

#define RPAK_METAFILE_1ST_LINE_LEN     40
#define RPAK_METAFILE_NTH_LINE_LEN_MIN 24

static rpak_err_t rpak_parse_metafile (rpak_cfg_t *cfg,
                                       /*char *path, */
                                       rpak_entry_t **entries_out,
                                       u32_t *num_entries_out,
                                       u16_t *version_major_out,
                                       u16_t *version_minor_out,
                                       u32_t *retropak_version_out,
                                       u32_t *pak_type_out) {

  rpak_err_t ecode = RPAK_E_OK;
  char *buf = NULL;
  size_t len = 0;
  size_t i=0;
  size_t last_line_start=0;
  u32_t curline=1;
  char *line = NULL;
  size_t line_len=0;
  char *filename = NULL;
  char *filename_p = NULL;
  size_t dirnamelen = 0;
  u8_t on_first_line = 1;
  size_t entries_alloced=0;
  size_t entries_fill=0;
  u32_t retropak_version=0;
  u32_t pak_type=0;
  u32_t pak_magic=0;
  
  *entries_out = NULL;
  *version_major_out = 0;
  *version_minor_out = 0;
  *num_entries_out = 0;
  
  if (NULL == cfg->metafile) {
    // must construct filename from directory name ...
    dirnamelen = strlen(cfg->dir);
    filename = RPAK_MALLOCMSG(dirnamelen + 1 + strlen(RPAK_METAFILE_DEFAULT) + 1,
                              "metafile name");
    memcpy(filename, cfg->dir, dirnamelen);
    filename[dirnamelen] = RPAK_PATHSEP;
    memcpy(filename + dirnamelen + 1, RPAK_METAFILE_DEFAULT, strlen(RPAK_METAFILE_DEFAULT)+1);
    RPAK_MPRINTF(cfg->tw, 2,
                 "- No metafile name was provided -- \"%s\" will be used, as derived from the "
                 "input directory name.\n",
                 filename);
  }
  
  filename_p = (filename?filename:(cfg->metafile));
  
  if (RPAK_E_OK != (ecode = rpak_read_file (cfg,
                                            filename_p,
                                            (u8_t **) &buf,
                                            &len)))
    goto L_rpak_parse_metafile_fail;
    
//printf("%s\n", buf);
//printf("%llu\n", len);

  if (len == 0) {
    RPAK_EPRINTF(cfg->tw, 7, "ERROR: Metafile \"%s\" was zero bytes in size.\n", filename_p);
    ecode = RPAK_E_METAFILE_BAD_1ST_LINE;
    goto L_rpak_parse_metafile_fail;
  }
  
  for (i=0;i<len;i++) {
  
    rpak_entry_t entry;
    char magic_s[5];

    u32_t unk=0;
    u32_t id=0;
    u32_t offset=0;
    char cflag = 0;
    char *name = NULL;
    size_t name_len=0;
    int num_scanf_items=0;
    u8_t code=0; // a hack
    u8_t j=0;
    u8_t field_count=0;
    //u8_t fieldlens[5];
    size_t z=0;
    size_t prev_z=0;
    u8_t fail=0;
  
    if (i-last_line_start >= RPAK_METAFILE_MAX_LINE_LEN) {
//printf ("i=%u,lls=%u\n",i,last_line_start);
      RPAK_EPRINTF(cfg->tw, 7,
                   "ERROR: Metadata file \"%s\" line %u exceeded maximum line length (%u).\n",
                   filename_p, curline, (u32_t) RPAK_METAFILE_MAX_LINE_LEN);
      ecode = RPAK_E_METAFILE_LINE_LEN;
      goto L_rpak_parse_metafile_fail;
    }
    
    if (buf[i] == '\n') {
      // EOL
      
      buf[i] = 0; // NT
      line = buf + last_line_start;
      line_len = i - (last_line_start);
      
      // skip leading spaces
      while (line[0]==' ' && i<len) {
        i++;
        line++;
        line_len--;
      }

      last_line_start = i+1;
      curline++;
      
      // ignore hashed-out lines
      if (line[0]=='#')
        continue;
        
      if (on_first_line) {

        if (line_len != RPAK_METAFILE_1ST_LINE_LEN) {
          RPAK_EPRINTF(cfg->tw, 7,
                       "ERROR: Metadata file \"%s\" first line had incorrect length "
                       "(%lu, should be %lu).\n",
                       filename_p, line_len, RPAK_METAFILE_1ST_LINE_LEN);
          ecode = RPAK_E_METAFILE_LINE_1_LEN;
          goto L_rpak_parse_metafile_fail;
        }
        
        // <0x00030005> <unk>
        // scanf() doesn't respect the field width
        // specifier for %x, so we need to check the field
        // lengths manually
        j=0;
        for (z=0;z<line_len;z++) {
          if (line[z] == ' ') {
            //z++; // skip space
            if (j>4) {
              fail=1;
              break;
            }
//printf ("z-prev_z=%u\n", z - prev_z);
            switch (j) {
              case 0:
                if ((z - prev_z) != 4)
                  fail=1;
                break;
              case 1: case 2: case 3: case 4:
                if ((z - prev_z) != 8)
                  fail=1;
                break;
            }
            prev_z = z+1;
            j++;
          }
        }
       
#ifdef RPAK_WINDOZE
        if (fail || (4 != sscanf_s(line,
                                   "RPAK %8x %8x %8x %8x",
                                   &retropak_version,
                                   &pak_type,
                                   &pak_magic,
                                   &unk))) {
#else
        if (fail || (4 != sscanf(line,
                                 "RPAK %8x %8x %8x %8x",
                                 &retropak_version,
                                 &pak_type,
                                 &pak_magic,
                                 &unk))) {
#endif
          RPAK_EPRINTF(cfg->tw, 7,
                       "ERROR: Metadata file \"%s\" first line was "
                       "not parsed successfully: %s\n",
                       filename_p, line);
          ecode = RPAK_E_METAFILE_BAD_1ST_LINE;
          goto L_rpak_parse_metafile_fail;
        }
        if (retropak_version != RPAK_VERSION_I) {
          RPAK_EPRINTF(cfg->tw, 7, "ERROR: The metafile version (%x) is not compatible with this "
                                   "version of retropak.\n", retropak_version);
          ecode = RPAK_E_METAFILE_VERSION;
          goto L_rpak_parse_metafile_fail;
        }
        if ((pak_type != RPAK_PAKTYPE_32_ZLIB)
            && (pak_type != RPAK_PAKTYPE_32_LZO)
            /*&& (pak_type != RPAK_PAKTYPE_64_ZLIB)*/) {
          RPAK_EPRINTF(cfg->tw, 7, "ERROR: The PAK type in the metafile header was not "
                                   "recognised (%x).\n", pak_type);
          ecode = RPAK_E_METAFILE_PAKTYPE;
          goto L_rpak_parse_metafile_fail;
        }
        if (unk != 0) {
          RPAK_EPRINTF(cfg->tw, 7, "ERROR: Bad \"null\" value in metafile header: (%x), "
                                   "should be 0.\n", unk);
          ecode = RPAK_E_METAFILE_UNK;
          goto L_rpak_parse_metafile_fail;
        }
        if (pak_magic != 0x00030005) {
          RPAK_EPRINTF(cfg->tw, 7, "ERROR: The PAK version in the metafile header was not "
                                   "recognised (%x).\n", pak_magic);
          ecode = RPAK_E_BAD_VERSION;
          goto L_rpak_parse_metafile_fail;
        }

        on_first_line=0;
        
      } else {
      
        if (line_len < RPAK_METAFILE_NTH_LINE_LEN_MIN
            || line_len == RPAK_METAFILE_NTH_LINE_LEN_MIN+1) {
          RPAK_EPRINTF(cfg->tw, 7,
                       "ERROR: Metadata file \"%s\" line had bad length "
                       "(length was %lu, wanted <= %lu or > %lu): %s\n",
                       filename_p, line_len, RPAK_METAFILE_NTH_LINE_LEN_MIN,
                       RPAK_METAFILE_NTH_LINE_LEN_MIN+1, line);
          ecode = RPAK_E_METAFILE_LINE_N_LEN;
          goto L_rpak_parse_metafile_fail;
        }
        
        memset(magic_s, 0, 5);
        
        if (line_len > RPAK_METAFILE_NTH_LINE_LEN_MIN+1) {
          name_len = line_len - (RPAK_METAFILE_NTH_LINE_LEN_MIN+1); // remainder of line
          if (name_len) {
            name = RPAK_MALLOCMSG(1 + name_len, "entry name [from metafile]");
            //memset(name, 0, 1 + name_len);
            memcpy(name, line + RPAK_METAFILE_NTH_LINE_LEN_MIN + 1, name_len+1);
          }
        }
        
        // again, scanf() doesn't respect the field width
        // specifier for %x, so we need to check the field
        // lengths manually
        j=0;
        fail=0;
        field_count=4;
        if (name)
          field_count=5;
        for (z=0;z<line_len;z++) {
          if (line[z] == ' ') {
            //z++; // skip space
            if (j>=field_count) {
              fail=1;
              break;
            }
//printf ("z-prev_z=%u\n", z - prev_z);
            switch (j) {
              case 0:
                if ((z - prev_z) != 8)
                  fail=1;
                break;
              case 1:
                if ((z - prev_z) != 4)
                  fail=1;
                break;
              case 2:
                if ((z - prev_z) != 8)
                  fail=1;
                break;
              case 3:
                if ((z - prev_z) != 1)
                  fail=1;
                break;
              case 4:
                if ((z - prev_z) > RPAK_NAMELEN_MAX)
                  fail=1;
                break;
            }
            prev_z = z+1;
            j++;
          }
        }

        if (!fail) {
		      if (name) {
#ifdef RPAK_WINDOZE
	          num_scanf_items = sscanf_s(line, "%x %4s %x %c %s",
										                   &id, magic_s, 5, &offset, &cflag, 1, name, 1+strlen(name));
#else
	          num_scanf_items = sscanf(line, "%8x %4s %8x %c %s",
										                 &id, magic_s, &offset, &cflag, name);
#endif
		      } else {
#ifdef RPAK_WINDOZE
	          num_scanf_items = sscanf_s(line, "%8x %4s %8x %c",
										                   &id, magic_s, 5, &offset, &cflag, 1);
#else
	          num_scanf_items = sscanf(line, "%8x %4s %8x %c",
										                 &id, magic_s, &offset, &cflag);
#endif
          }
          
          if (    (RPAK_METAFILE_CHAR_COMPRESSED   != cflag)
               && (RPAK_METAFILE_CHAR_UNCOMPRESSED != cflag)) {
            RPAK_EPRINTF(cfg->tw, 7,
                         "ERROR: Metadata file \"%s\" line was "
                         "not parsed successfully (the compression field was bad): %s.\n",
                         filename_p, line);
            ecode = RPAK_E_METAFILE_BAD_COMPRESS;
            if (name_len)
              RPAK_FREEMSG(name, "entry name");
            goto L_rpak_parse_metafile_fail;
          }
        }

        // evil fail code hackola:
        if (   (fail && (code=1))
            || (num_scanf_items != 5 && num_scanf_items != 4 && (code=2))
            || (num_scanf_items == 5 && !name_len && (code=3))
            || (num_scanf_items == 4 && name_len && (code=4))
            || (!is_valid_magic_s(magic_s) && (code=5))
            || ((offset > RPAK_PAK_SIZE_MAX) && (code=6))
            || ((name && !is_valid_entity_name(name, name_len) && (code=7)))) {
          RPAK_EPRINTF(cfg->tw, 7,
                       "ERROR: Metadata file \"%s\" line was "
                       "not parsed successfully (code %u): (%s).\n",
                       filename_p, (u32_t) code, line);
          ecode = RPAK_E_METAFILE_BAD_NTH_LINE;
          if (name_len)
            RPAK_FREEMSG(name, "entry name");
          goto L_rpak_parse_metafile_fail;
        }
        
#ifdef RPAK_DBG
        printf("meta: id %08x, magic %s, offset %08x, cflag %c, name %s\n",
               id, magic_s, offset, cflag, (name?name:"NULL"));
#endif

        memset(&entry, 0, sizeof(rpak_entry_t));
        entry.id = id;
        //memcpy(entry.magic_s, magic_s, 5);
        for (j=0;j<4;j++)
          entry.magic_s[j] = toupper(magic_s[j]);
        entry.magic_s[j]=0;
        entry.magic_u32 = parse_u32_bigendian((u8_t *) magic_s);
        entry.payload_offset = offset;
        //entry.compress = ((cflag=='Z') ? 1 : 0);
        if (RPAK_METAFILE_CHAR_COMPRESSED == cflag)
          entry.compress = 1;
        if (name)
          entry.name = name;
        //entry.index_into_table_list = entries_fill;

        if (entries_fill >= entries_alloced) {
          entries_alloced = (entries_alloced ? (entries_alloced * 2) : 1000);
          *entries_out = RPAK_REALLOCMSG(*entries_out,
                                         entries_alloced * sizeof(rpak_entry_t),
                                         "pack entries list");
        }
        
        memcpy(*entries_out + entries_fill, &entry, sizeof(rpak_entry_t));
        
        entries_fill++;
        
        //if (name)
        //  RPAK_FREEMSG(name, "entry name");
        
      }
        
#ifdef RPAK_DBG
      printf("metadata line %u: %s\n", curline, line);
#endif
      
    }
    
  }
  
  *num_entries_out = entries_fill;
  *version_major_out = (pak_magic >> 16) & 0xffff;
  *version_minor_out = (pak_magic & 0xffff);
  *pak_type_out = pak_type;
  
L_rpak_parse_metafile_end:
  if (filename)
    RPAK_FREEMSG(filename, "metafile name");
  if (buf)
    RPAK_FREEMSG(buf, "rpak_read_file payload");
  return ecode;

L_rpak_parse_metafile_fail:
  if (*entries_out) {
    for (i=0;i<entries_fill;i++) {
      if ((*entries_out)[i].name)
        RPAK_FREEMSG((*entries_out)[i].name, "entry name");
    }
    RPAK_FREEMSG(*entries_out, "pack entries list");
  }
  *entries_out = NULL;
  goto L_rpak_parse_metafile_end;

}

static u8_t is_valid_magic_s(char *magic) {
  size_t i;
  for (i=0;i<4;i++) {
    if (magic[i] & 0x80 || !isalnum(magic[i]) || magic[i] > 0x5a) // upper case only
      return 0;
  }
  return 1;
}

static u8_t is_valid_entity_name(char *name, size_t len) {
  size_t i;
  u8_t c;
  //printf("len=%u\n", len);
  if (len > RPAK_NAMELEN_MAX)
    return 0;
  for (i=0;i<len;i++) {
    c = name[i];
    //printf("%c",c);
    if ((c < 0x20) || (c > 0x7e))
      return 0;
  }
  return 1;
}

static rpak_err_t rpak_mkdir(rpak_cfg_t *cfg, char *path) {

  // FIXME: probably not secure

  // recursively build a directory path
  // start at the beginning of the path and for each fragment:
  // - does the fragment exist on disc? if it does, make sure
  //   it is a directory and not a file
  // - if it doesn't, create the directory and carry on
  size_t i;
  size_t len;
  rpak_stat_t sb;
  size_t last_pathsep_index=0;
  
  char *path_clone = NULL;
  rpak_err_t ecode = RPAK_E_OK;
  
  if (!path) {
    RPAK_EPRINTF(cfg->tw, 7, "ERROR: rpak_mkdir(NULL)\n");
    return RPAK_E_NULL;
  }

#ifdef RPAK_DBG
  printf("rpak_mkdir(%s)\n", path);
#endif
  
  len = strlen(path);
  path_clone = RPAK_MALLOCMSG(len+1, "rpak_mkdir path clone");
  memcpy(path_clone, path, len+1);
  
  // get rid of any multiple slashes (UNIX only, don't bother on windows)
#ifndef RPAK_WINDOZE
  for (i=0;i<len+1;i++) {
    size_t j,k;
    j=i;
    while (j<len && (path_clone[j] == RPAK_PATHSEP))
      j++;
    if (j >= len)
      j = len - 1;
    k=i;
    if (k >= len - 1)
      k = len - 2;
    if (j!=k) {
      memmove(path_clone+k+1, path_clone+j, len+1-j);
      path_clone[k+1+len-j] = 0; // null-terminate
    }
  }
  
#ifdef RPAK_DBG
  printf("rpak_mkdir path after demultislash: %s\n", path_clone);
#endif
#endif
  
  for (i=0;i<len+1;i++) {

    //size_t j=0;
    u8_t is_pathsep=0;
    char *not_a_directory_fmtstg = "";
    
    //if (path_clone[i] == RPAK_PATHSEP || !path_clone[i]) {
    is_pathsep = (path_clone[i] == '/');
#ifdef RPAK_WINDOZE
    if (path_clone[i] == '\\')
      is_pathsep = 1;
#endif
    if (is_pathsep || !path_clone[i]) {

#ifdef RPAK_WINDOZE
      // is it the drive letter?
      if (i == 2 && isalpha(path_clone[0]) && path_clone[1] == ':') {
        last_pathsep_index = i;
        continue;
      }
#endif
      
      if (!i) // leading slash, don't try to create the root directory
        continue;
      
      path_clone[i] = 0; // edit string: insert null terminator

      // OK so this is complicated slightly by the fact that
      // we can have paths like ./../../././beans/../oats/./ etc
      // but basically we never try to create a '.' or a '..' so
      // we need to detect those.

      if ((last_pathsep_index+1 < len)
          && ((!strcmp(path_clone+last_pathsep_index+1, "."))
              || !strcmp(path_clone+last_pathsep_index+1, ".."))) {
        // don't try to create these
        last_pathsep_index = i;
#ifdef RPAK_DBG
        printf("rpak_mkdir: will not create %s\n", path_clone);
#endif
        path_clone[i] = RPAK_PATHSEP; // remove null terminator again
        continue;
      }
        
      // check whether it exists
      //memset(&sb, 0, sizeof(rpak_stat_t));
      if (RPAK_E_OK == rpak_stat (cfg, path_clone, &sb)) {

//printf("rpak_stat(): path_clone=%s\n", path_clone);

        // it exists
        //if (!(statbuf.st_mode & S_IFDIR) && !(statbuf.st_mode & symlink_bit)) {
        if ((!sb.is_dir) && (!sb.is_link)) {
          // uh oh, it's not a directory or symlink
#ifdef RPAK_WINDOZE
          not_a_directory_fmtstg = "ERROR: Trying to create path, but \"%s\" is not a directory "
                                   "or symbolic link.\n";
#else
          not_a_directory_fmtstg = "ERROR: Trying to create path, but \"%s\" is not a directory.\n";
#endif
          RPAK_EPRINTF(cfg->tw, 7,
                       not_a_directory_fmtstg,
                       path_clone);
          ecode = RPAK_E_FILE_IN_PATH;
          goto L_rpak_mkdir_end;
        }
      } else {
        if (RPAK_E_OK != (ecode = rpak_mkdir_2 (cfg, path_clone)))
          goto L_rpak_mkdir_end;
#ifdef RPAK_DBG
        printf("Created directory: %s\n", path_clone);
#endif
      }
      
      path_clone[i] = RPAK_PATHSEP; // remove null terminator again
      last_pathsep_index = i;
    }
  }
L_rpak_mkdir_end:
  if (path_clone)
    RPAK_FREEMSG(path_clone, "rpak_mkdir path clone");
  return ecode;
}

static rpak_err_t rpak_mkdir_2 (rpak_cfg_t *cfg, char *path) {
  char *errmsg = NULL;
#ifdef RPAK_DBG
  printf("rpak_mkdir_2(%s)\n", path);
#endif
#ifdef RPAK_WINDOZE
  if (_mkdir (path)) {
#else
  if (mkdir (path, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH)) { // 755
#endif
    errmsg = RPAK_STRERROR(cfg);
    RPAK_EPRINTF(cfg->tw, 7,
                 "ERROR: Could not create directory \"%s\" (%s). This may be because one "
                 "of the path fragments is actually a file, or it may be a permissions "
                 "problem.\n",
                 path, errmsg);
    return RPAK_E_MKDIR;
  }
  return RPAK_E_OK;
}

static rpak_err_t rpak_getpath (rpak_cfg_t *cfg, char *path, char **output) {
  // *output must be freed by the caller if there is no error
  size_t j;
  size_t pathlen=0;
  u8_t nuke=0;
  if (!output) {
    RPAK_BPRINTF(cfg->tw, 5, "BUG: rpak_getpath(output==NULL)\n");
    return RPAK_E_NULL;
  }
  *output = NULL;
  if (!path) {
    RPAK_BPRINTF(cfg->tw, 5, "BUG: rpak_getpath(path==NULL)\n");
    return RPAK_E_NULL;
  }
  pathlen = strlen(path);
  if (!pathlen) {
    RPAK_BPRINTF(cfg->tw, 5, "BUG: rpak_getpath(\"\")\n");
    return RPAK_E_GETPATH_ZL;
  }
  *output = RPAK_MALLOCMSG(1 + pathlen, "getpath buf");
  memcpy(*output, path, 1 + pathlen);
  // find last slash
  for (j=pathlen;j>0;j--) { // goes from L to 1, not (L-1) to 0
    nuke = ((*output)[j-1] == '/'); // is it a pathsep?
#ifdef RPAK_WINDOZE
    if ((*output)[j-1] == '\\')
      nuke = 1;
#endif
    //if ((*output)[j-1] == RPAK_PATHSEP) {
    if (nuke) {
      // got it, nuke it!
      (*output)[j-1] = 0; // null terminator
      break;
    }
  }
  if (!j) {
    // no slashes found -- fail
    RPAK_FREEMSG(*output, "getpath buf");
    *output = NULL;
    return RPAK_E_GETPATH_NOSEP;
  }
  return RPAK_E_OK;
}

static rpak_err_t rpak_unpack (rpak_cfg_t *cfg) {
  
  rpak_stat_t sb;
  size_t i;
  char *errmsg;
  rpak_pak_t pak;
  u32_t tmpu32;
  char *pakname; // derived by CRC
  
  u32_t ecode = RPAK_E_OK;
  FILE *fp_pak = NULL;
  char *dirname = NULL;
  size_t tmplen = 0;
  size_t tmppos = 0;
  u8_t found_dot = 0;
  u32_t num_names = 0;
  off_t pak_file_size = 0;
  u32_t num_dups=0;
  size_t meta_line_len = 0;
  size_t meta_fill = 0;
  size_t meta_alloced = 0;
  char *metabuf_head = NULL;
  char *metabuf_body = NULL;
  char *metabuf_full = NULL;
  char *metafile_name = NULL;
  u32_t num_compressed_files = 0;
  u8_t compression_scheme = RPAK_Z_SCHEME_ZLIB; // try this first
  u8_t first_compressed_file=1;
  u32_t pak_type = 0;
  off_t header_end = 0;
  u8_t *header_crc_buf = NULL;
  rpak_checksum_t cksum = 0;
  size_t metabuf_body_len=0;
  char *not_dir_fmtstg="";
  
  errmsg = NULL;

  memset (&pak, 0, sizeof(rpak_pak_t));
  
  if (NULL == cfg->dir) {
    // no target directory specified
    // try to use the PAK file name with the extension removed
    tmplen = strlen(cfg->pakfile);
    for (i=0;i<tmplen;i++) {
      if (cfg->pakfile[i] == '.') {
        tmppos = i;
        found_dot = 1;
      }
    }
    if (!found_dot) {
      RPAK_EPRINTF(cfg->tw, 7,
                   "ERROR: Could not derive output directory name from PAK file name "
                   "\"%s\" because the PAK file lacks an extension.\n", cfg->pakfile);
      ecode = RPAK_E_AUTODIR_NOEXT;
      goto L_rpak_unpack_end;
    } else if (tmppos == 0) {
      RPAK_EPRINTF(cfg->tw, 7,
                   "ERROR: Could not derive output directory name from PAK file name "
                   "\"%s\" -- PAK file is all extension and no name.\n", cfg->pakfile);
      ecode = RPAK_E_AUTODIR_NONAME;
      goto L_rpak_unpack_end;
    }
    dirname = RPAK_MALLOCMSG(1+tmplen, "rpak_unpack dirname");
    memcpy(dirname, cfg->pakfile, tmppos);
    RPAK_MPRINTF(cfg->tw, 2,
                 "- No output directory was specified. Using \"%s\", "
                 "as derived from the PAK filename.\n",
                 dirname);
  } else {
    dirname = RPAK_MALLOCMSG(1+strlen(cfg->dir), "rpak_unpack dirname");
    memcpy(dirname, cfg->dir, 1+strlen(cfg->dir));
  }
  
  if (!(cfg->metafile_only)) { // we only need the op dir if we are actually unpacking files
    if (RPAK_E_OK != rpak_stat(cfg, dirname, &sb)) {
      // directory does not exist; attempt to create it
      ecode = rpak_mkdir(cfg, dirname);
      if (RPAK_E_OK != ecode)
        goto L_rpak_unpack_end;
      RPAK_MPRINTF(cfg->tw, 2, "- Created output directory \"%s\".\n", dirname);
    } else {
      // it exists -- check it's actually a directory
      //if (!(statbuf.st_mode & S_IFDIR) && !(statbuf.st_mode & symlink_bit)) {
      if (!sb.is_dir && !sb.is_link) {
        // uh oh, it's not a directory or symlink
  #ifdef RPAK_WINDOZE
        not_dir_fmtstg = "ERROR: Unpack output directory \"%s\" exists, but "
                         "is not actually a directory.\n";
  #else
        not_dir_fmtstg = "ERROR: Unpack output directory \"%s\" exists, but "
                         "is not actually a directory or symbolic link.\n";
  #endif
        RPAK_EPRINTF(cfg->tw, 7, not_dir_fmtstg,
                     dirname);
        ecode = RPAK_E_DIR_NOT_DIR;
        goto L_rpak_unpack_end;
      }
    }
  }

  // stat it beforehand to make sure it's not a directory
  // (ignore errors, fopen() will catch them)
  if (RPAK_E_OK == (ecode = rpak_stat (cfg, cfg->pakfile, &sb))) {
    if (sb.is_dir) { //  || sb.is_link) {
      RPAK_EPRINTF(cfg->tw, 7,
                   "ERROR: PAK \"%s\" could not be opened because it was actually "
                   "a directory, not a file.\n",
                   cfg->pakfile);
      ecode = RPAK_E_PAK_WAS_ACTUALLY_DIR;
      goto L_rpak_unpack_end;
    }
  }
  
  // open the PAK file to be unpacked
  if (NULL == (fp_pak = rpak_fopen(cfg, cfg->pakfile, "rb"))) {
    //errmsg = RPAK_STRERROR(cfg);
    //RPAK_EPRINTF(cfg->tw, 7,
    //             "ERROR: Could not open the source PAK file \"%s\" for unpacking (%s).\n",
    //             cfg->pakfile, errmsg);
    ecode = RPAK_E_FOPEN_PAK;
    goto L_rpak_unpack_end;
  }
  
  // and then stat it again, now we have it open
  // get PAK file size
  if (RPAK_E_OK != (ecode = rpak_stat (cfg, cfg->pakfile, &sb))) {
    //errmsg = RPAK_STRERROR(cfg);
    RPAK_EPRINTF(cfg->tw, 7,
                 "ERROR: Could not determine size of PAK file \"%s\" (%s).\n",
                 cfg->pakfile, errmsg);
    //ecode = RPAK_E_STAT_PAK;
    goto L_rpak_unpack_end;
  }
  if (sb.fsize > RPAK_PAK_SIZE_MAX) {
    RPAK_EPRINTF(cfg->tw, 7,
                 "ERROR: PAK file \"%s\" is %li bytes. This software will refuse "
                 "to process PAK files having a size greater than %u bytes.\n",
                 cfg->pakfile, sb.fsize, RPAK_PAK_SIZE_MAX);
    ecode = RPAK_E_PAK_TOOBIG;
    goto L_rpak_unpack_end;
  }

  // symlinked PAKs are okay?
  pak_file_size = sb.fsize;
  RPAK_MPRINTF(cfg->tw, 2,
               "- PAK file is %li bytes in size.\n", (s64_t) pak_file_size); 
  
  // BEGIN READING PAK FILE HERE  
  
  // get version
  if (RPAK_E_OK != (ecode = stream_u16_bigendian (cfg, fp_pak, &(pak.version_major))))
    goto L_rpak_unpack_end;
  if (RPAK_E_OK != (ecode = stream_u16_bigendian (cfg, fp_pak, &(pak.version_minor))))
    goto L_rpak_unpack_end;
  
  RPAK_MPRINTF(cfg->tw, 2,
               "- PAK file is version %u.%u.\n",
               (u32_t) pak.version_major, (u32_t) pak.version_minor);
               
  if (pak.version_major != 3 && pak.version_minor != 5) {
    RPAK_EPRINTF(cfg->tw, 7,
                 "ERROR: PAK file had unknown version number (%u.%u). "
                 "Cannot continue.\n", (u32_t) pak.version_major, (u32_t) pak.version_minor);
    ecode = RPAK_E_BAD_VERSION;
    goto L_rpak_unpack_end;
  }
  
  if (RPAK_E_OK != (ecode = stream_u32_bigendian (cfg, fp_pak, &(tmpu32))))
    goto L_rpak_unpack_end;
  if (tmpu32) {
    RPAK_EPRINTF(cfg->tw, 7,
                 "ERROR: The second 32-bit word of the PAK file is bad (it should be all-"
                 "zero, but it is actually 0x%x).\n", tmpu32);
    ecode = RPAK_E_BAD_2WORD;
    goto L_rpak_unpack_end;
  }
  
  if (RPAK_E_OK != (ecode = stream_u32_bigendian (cfg, fp_pak, &(num_names))))
    goto L_rpak_unpack_end;
  if (num_names > RPAK_MAX_ENTRIES) {
    RPAK_EPRINTF(cfg->tw, 7,
                 "ERROR: This PAK has an insane reported number of names in the name table "
                 "at the top of the file (%u). Cannot continue.\n", num_names);
    ecode = RPAK_E_TOOMANY_NAMES;
    goto L_rpak_unpack_end;
  }
  RPAK_MPRINTF(cfg->tw, 2,
               "- There %s %i entr%s in the name table.\n",
               (num_names==1?"is":"are"),
               num_names,
               (num_names==1?"y":"ies"));

  // set up the top-level hashtable
  pak.entries_by_id = RPAK_MALLOCMSG(sizeof(rpak_hash_t), "hash buckets");
  memset(pak.entries_by_id, 0, sizeof(rpak_hash_t));
  
  /*
  if (pak_file_size < 13) {
    RPAK_MPRINTF(cfg->tw, 2, "- The PAK file contained no entries. There is nothing to do.\n");
    ecode = RPAK_E_OK;
    goto L_rpak_unpack_end;
  }
  */  
  
  // load name table
  // we load the name table into the hashtable, because we need
  // to be able to marry up the IDs from the name table to the IDs
  // from the entries table later.
  for (i=0;i<num_names;i++) {
    
    size_t j;
    rpak_entry_t *enp;
    rpak_entry_t *dummy;
    u8_t quit=0; // nasty
    
    enp = RPAK_MALLOCMSG(sizeof(rpak_entry_t), "entry [from name table]");
    memset(enp, 0, sizeof(rpak_entry_t));
    
    // magic value
    if (RPAK_E_OK != (ecode = fread_fully(cfg, enp->magic_s, 4, fp_pak)))
      goto L_rpak_unpack_nameload_fail;
    enp->magic_u32 = parse_u32_bigendian(enp->magic_s);
//printf ("m%c%c%c%c", en.magic_s[0], en.magic_s[1], en.magic_s[2], en.magic_s[3]);
/*
    for (j=0;j<4;j++) {
      if (!isupper(enp->magic_s[j]) && !isdigit() {
        RPAK_EPRINTF(cfg->tw, 7,
                    "ERROR: Entry %u in the name table has a bad magic value (%x). "
                    "Cannot continue.\n",
                    i, enp->magic_u32);
        ecode = RPAK_E_NAME_VOODOO;
        goto L_rpak_unpack_nameload_fail;
      }
    }
*/
//printf("lolmagic = %x, %s\n", enp->magic_u32, enp->magic_s);
    if (!is_valid_magic_s((char *) enp->magic_s)) {
    //if (!is_valid_magic(enp->magic_u32)) {
      RPAK_EPRINTF(cfg->tw, 7,
                  "ERROR: Entry %u in the name table has a bad magic value (0x%x). "
                  "Cannot continue.\n",
                  i, enp->magic_u32);
      ecode = RPAK_E_NAME_VOODOO;
      goto L_rpak_unpack_nameload_fail;
    }
    // ID
    if (RPAK_E_OK != (ecode = stream_u32_bigendian (cfg, fp_pak, &(enp->id))))
      goto L_rpak_unpack_nameload_fail;
    // namelen
    if (RPAK_E_OK != (ecode = stream_u32_bigendian (cfg, fp_pak, &tmpu32))) // name len
      goto L_rpak_unpack_nameload_fail;
    if (tmpu32 > RPAK_NAMELEN_MAX) { // sanity
      RPAK_EPRINTF(cfg->tw, 7,
                   "ERROR: Entry %u in the name table has an insane name length value "
                   "(0x%x, %u). Cannot continue.\n",
                   i, tmpu32, tmpu32);
      ecode = RPAK_E_NAMELEN_TOOBIG;
      goto L_rpak_unpack_nameload_fail;
    }
    if (tmpu32 == 0) {
      RPAK_EPRINTF(cfg->tw, 7,
                   "ERROR: Entry %u in the name table has a zero-length name. "
                   "These are not allowed; cannot continue.\n",
                   i);
      ecode = RPAK_E_ZL_ENTITY_NAME;
      goto L_rpak_unpack_nameload_fail;
    }
    // name
    enp->name = RPAK_MALLOCMSG(tmpu32+1, "entry name [from name table]");
    enp->name[tmpu32] = 0; // NT
    if (RPAK_E_OK != (ecode = fread_fully(cfg, (u8_t *) enp->name, tmpu32, fp_pak)))
      goto L_rpak_unpack_nameload_fail;
      
    if (!is_valid_entity_name(enp->name, tmpu32)) {
      RPAK_EPRINTF(cfg->tw, 7,
                   "ERROR: Entry %u in the name table has a name that contains "
                   "illegal characters.\n",
                   i);
      ecode = RPAK_E_BAD_ENTITY_NAME;
      goto L_rpak_unpack_nameload_fail;
    }
    
    j=0;
    // are there multiple names for the same ID? (it happens sometimes!)
    if (!(RPAK_E_OK == (ecode = rpak_fetch_entries (cfg,
                                                    pak.entries_by_id,
                                                    enp->id,
                                                    &dummy, // dummy
                                                    &j)))) {
      goto L_rpak_unpack_nameload_fail;
    }
    
//printf("J=%u\n", j);
   
    if (j>0)
      RPAK_MPRINTF(cfg->tw, 2, "- NOTE: There are multiple name table entries for ID %x.\n", enp->id);
    
    // store entry
    if (RPAK_E_OK != (ecode = rpak_store_entry (cfg,
                                                pak.entries_by_id,
                                                enp->id,
                                                enp))) { // enp is copied
      goto L_rpak_unpack_nameload_fail;
    }
    
    goto L_rpak_unpack_nameload_end;
    
L_rpak_unpack_nameload_fail:
    quit=1;
L_rpak_unpack_nameload_end:
    if (enp->name)
      RPAK_FREEMSG(enp->name, "entry name");
    enp->name = NULL;
    RPAK_FREEMSG(enp, "entry");
    enp = NULL;
    if (quit)
      goto L_rpak_unpack_end;
    
  }
  
  tmpu32=0;
  rpak_hash_size(pak.entries_by_id, &tmpu32);
  RPAK_MPRINTF(cfg->tw, 2, "- %u name%s %s loaded successfully into the hashtable.\n",
               tmpu32, (tmpu32>1)?"s":"", (tmpu32>1)?"were":"was");
  // num_entries
  if (RPAK_E_OK != (ecode = stream_u32_bigendian (cfg, fp_pak, &(pak.num_entries))))
    goto L_rpak_unpack_end;
  if (pak.num_entries > RPAK_MAX_ENTRIES) {
    RPAK_EPRINTF(cfg->tw, 7,
                 "ERROR: This PAK has an insane number of entries (0x%x, %u).\n",
                 pak.num_entries, pak.num_entries);
    ecode = RPAK_E_TOOMANY_ENTRIES;
    goto L_rpak_unpack_end;
  }
  if (pak.num_entries == 0) {
    RPAK_MPRINTF(cfg->tw, 9,
                 "WARNING: This PAK has no entries. There is literally nothing that "
                 "can be done about this, but a zero-entries metafile has been "
                 "created anyway.\n");
    // decided this was no longer an error
    //ecode = RPAK_E_NO_ENTRIES;
    //goto L_rpak_unpack_end;
  }
  RPAK_MPRINTF(cfg->tw, 2, "- The PAK claims to contain %u entries.\n",
               pak.num_entries);
 
  // have num_entries. the entries table we will extract to a linear
  // array (so not the hashtable this time) to preserve its sequence and
  // duplicated blocks for the listfile, so we need to alloc that array:
  if (pak.num_entries) {
    pak.entries_by_seq = RPAK_MALLOCMSG(pak.num_entries * sizeof(rpak_hash_t),
                                        "entries_by_seq");
    memset(pak.entries_by_seq, 0, pak.num_entries * sizeof(rpak_hash_t));
  }
  
//printf("[1]\n");

  // predict start of payload section
  errno=0;
#ifdef RPAK_WINDOZE
  header_end = ftell(fp_pak);
#else
  header_end = ftello(fp_pak);
#endif
  if (errno) {
    errmsg = RPAK_STRERROR(cfg);   
    RPAK_EPRINTF(cfg->tw, 7,
                "ERROR: Could not determine current file offset before loading "
                "entries table (%s).\n", errmsg);
    ecode = RPAK_E_FTELLO;
    goto L_rpak_unpack_end;
  }
  header_end += (pak.num_entries * 20);
  header_end += calculate_padding_len(header_end, 0);
 
  // entries
  for (i=0;i<pak.num_entries;i++) {
  
    u8_t magic[4];
    u32_t compress_flag=0;
    u32_t id=0;
    u32_t payload_len_src=0;
    u32_t payload_offset=0;
    rpak_entry_t *enp = NULL;
    size_t num_hash_entries=0;

    if (RPAK_E_OK != (ecode = stream_u32_bigendian (cfg, fp_pak, &compress_flag)))
      goto L_rpak_unpack_end;
    if (compress_flag != 0 && compress_flag != 1) {
      RPAK_EPRINTF(cfg->tw, 7,
                  "ERROR: The PAK has a bad compression value (0x%x) in entry number %u.\n",
                  compress_flag, i);
      ecode = RPAK_E_BAD_COMPRESS;
      goto L_rpak_unpack_end;
    }
    if (RPAK_E_OK != (ecode = fread_fully (cfg, magic, 4, fp_pak)))
      goto L_rpak_unpack_end;
    if (!is_valid_magic_s((char *) magic)) {
      RPAK_EPRINTF(cfg->tw, 7,
                  "ERROR: The PAK had an invalid magic value for entry number %u.\n",
                  i);
      ecode = RPAK_E_ENTRY_VOODOO;
      goto L_rpak_unpack_end;
    }
    if (RPAK_E_OK != (ecode = stream_u32_bigendian (cfg, fp_pak, &id)))
      goto L_rpak_unpack_end;
    if (RPAK_E_OK != (ecode = stream_u32_bigendian (cfg, fp_pak, &payload_len_src)))
      goto L_rpak_unpack_end;
    if (payload_len_src > pak_file_size) {
      //printf("%u\n", pak_file_size);
      RPAK_EPRINTF(cfg->tw, 7,
                   "ERROR: The PAK has a bad source payload length value (0x%x) "
                   "for entry number %lu (id 0x%x) which is larger than the PAK file "
                   "itself (%li bytes).\n",
                   payload_len_src, (u64_t) i, id, (s64_t) pak_file_size);
      ecode = RPAK_E_BAD_SRC_LEN;
      goto L_rpak_unpack_end;
    }
    if (payload_len_src == 0) {
      RPAK_EPRINTF(cfg->tw, 7,
                   "ERROR: The PAK has a null source payload length value "
                   "for entry number %lu (id 0x%x). Zero-length files are not allowed in PAKs.\n",
                   (u64_t) i, id);
      ecode = RPAK_E_BAD_SRC_LEN;
      goto L_rpak_unpack_end;
    }
    if (payload_len_src & 0x1f) {
      RPAK_EPRINTF(cfg->tw, 7,
                   "ERROR: The PAK has a bad source payload length value (0x%x) "
                   "for entry number %lu (id 0x%x). It should be a multiple of 0x20.\n",
                   payload_len_src, (u64_t) i, id);
      ecode = RPAK_E_BAD_SRC_LEN;
      goto L_rpak_unpack_end;
    }
    if (RPAK_E_OK != (ecode = stream_u32_bigendian (cfg, fp_pak, &payload_offset)))
      goto L_rpak_unpack_end;
    if (payload_offset < header_end) {
      RPAK_EPRINTF(cfg->tw, 7,
                   "ERROR: The PAK has a payload offset value (0x%x) "
                   "for entry number %lu (id 0x%x) which lies within the PAK file "
                   "header (ending at 0x%x) rather than the payload section.\n",
                   payload_offset, (u64_t) i, id, header_end);
      ecode = RPAK_E_BAD_SRC_OFF;
      goto L_rpak_unpack_end;
    }
    if (payload_offset & 0x1f) {
      RPAK_EPRINTF(cfg->tw, 7,
                   "ERROR: The PAK has a bad payload offset value (0x%x) "
                   "for entry number %lu (id 0x%x) -- it should be a multiple "
                   "of 0x20.\n",
                   payload_offset, (u64_t) i, id);
      ecode = RPAK_E_BAD_SRC_OFF;
      goto L_rpak_unpack_end;
    }
//printf("payload_offset=%u\n", payload_offset);
//printf("payload_len_src=%u\n", payload_len_src);
//printf("pak_file_size=%u\n", pak_file_size);
    if (payload_offset + payload_len_src > pak_file_size) {
      RPAK_EPRINTF(cfg->tw, 7,
                   "ERROR: The PAK reports a payload length "
                   "for entry number %lu (id 0x%x) which would run off the end of the PAK file "
                   ".\n",
                   (u64_t) i, id);
      ecode = RPAK_E_BAD_SRC_OFF;
      goto L_rpak_unpack_end;
    }

//printf("%x\n", id);
    
    // transfer the name over from the hashtable
    if (RPAK_E_OK == (ecode = rpak_fetch_entries (cfg,
                                                  pak.entries_by_id,
                                                  id,
                                                  &enp,
                                                  &num_hash_entries))) {
      if (enp && enp[0].name) {
        pak.entries_by_seq[i].name = RPAK_MALLOCMSG(strlen(enp[0].name)+1, "entry name");
        memcpy(pak.entries_by_seq[i].name, enp[0].name, strlen(enp[0].name)+1);
      }
    }
    
    pak.entries_by_seq[i].compress = compress_flag;
    pak.entries_by_seq[i].id = id;
    memcpy(pak.entries_by_seq[i].magic_s, magic, 4);
    pak.entries_by_seq[i].magic_u32 = parse_u32_bigendian(pak.entries_by_seq[i].magic_s);
    pak.entries_by_seq[i].payload_len_src = payload_len_src;
    pak.entries_by_seq[i].payload_offset = payload_offset;
    
    if (compress_flag)
      num_compressed_files++;
    
  }
  
//printf("[2]\n");
  
  rpak_hash_free(pak.entries_by_id, 1); // 1 = free the entries as well as the hash
  
//printf("[3]\n");
  
  // make a fresh hashtable
  pak.entries_by_id = RPAK_MALLOCMSG(sizeof(rpak_hash_t), "hash buckets");
  memset(pak.entries_by_id, 0, sizeof(rpak_hash_t));
  
//printf("[4]\n");

  //header_end=0;
  
  // OK, do the CRC thing
  if (pak.num_entries) {
    errno=0;
#ifdef RPAK_WINDOZE
    header_end = ftell(fp_pak);
#else
    header_end = ftello(fp_pak);
#endif
    if (errno) {
      errmsg = RPAK_STRERROR(cfg);   
      RPAK_EPRINTF(cfg->tw, 7, "ERROR: Could not get end of header file position (%s).\n",
                   errmsg);
      ecode = RPAK_E_FTELLO;
      goto L_rpak_unpack_end;
    }
#ifdef RPAK_WINDOZE
    if (fseek(fp_pak, header_end, SEEK_SET)) {
#else
    if (fseeko(fp_pak, header_end, SEEK_SET)) {
#endif
      errmsg = RPAK_STRERROR(cfg);   
      RPAK_EPRINTF(cfg->tw, 7, "ERROR: Could not seek to start of PAK file (%s).\n",
                   errmsg);
      ecode = RPAK_E_FSEEK;
      goto L_rpak_unpack_end;
    }
    if (header_end > RPAK_TOP_LEN_MAX) {
      RPAK_EPRINTF(cfg->tw, 7, "ERROR: PAK end-of-top position exceeded sane limit.\n");
      ecode = RPAK_E_BAD_HEADER_END;
      goto L_rpak_unpack_end;
    }
    header_crc_buf = RPAK_MALLOCMSG(header_end & 0x7fffffff, "header crc buf");
    if (RPAK_E_OK != (ecode = fread_fully(cfg,
                                          header_crc_buf,
                                          header_end & 0x7fffffff,
                                          fp_pak))) {
      RPAK_EPRINTF(cfg->tw, 7, "ERROR: Attempt to re-read file top in order to checksum it "
                               "failed.\n");
      RPAK_FREEMSG(header_crc_buf, "header crc buf");
      goto L_rpak_unpack_end;
    }
  //printf("[5]\n");
    cksum = rpak_checksum(header_crc_buf, header_end & 0x7fffffff);
    pakname = pakname_by_checksum(cksum);
    RPAK_FREEMSG(header_crc_buf, "header crc buf");
    RPAK_MPRINTF(cfg->tw, 2, "- Header checksum is %x%s%s%s.\n",
                 (u32_t) cksum,
                 ((pakname!=NULL)?(" ("):""),
                 ((pakname!=NULL)?pakname:""),
                 ((pakname!=NULL)?(")"):""));
  }
               
  if (!cfg->metafile_only && pak.num_entries)
    RPAK_MPRINTF(cfg->tw, 2, "- Extracting files to \"%s\". Please wait.\n", dirname);
  
  // now, we go back through the linear list to extract the files.
  for (i=0;i<pak.num_entries;i++) {
  
    u32_t reported_decompressed_payload_len;
    u8_t magic_s[5];
    //size_t sublen;
    
    rpak_entry_t *entry = NULL;
    rpak_entry_t *entries_from_hash = NULL;
    u8_t *payload = NULL;
    u8_t *z_payload = NULL;
    u8_t *file_payload = NULL;
    rpak_checksum_t checksum=0;
    u8_t dup=0;
    size_t filenamelen=0;
    size_t num_entries_from_hash=0;
    size_t j=0;
    size_t entry_name_len=0;
    size_t file_len=0;
    u8_t *entry_name = NULL;
    char *meta_line = NULL;
    char z = '#';
    u8_t payload_needed=1;
    u8_t actually_write_file=1;
    //char *opfile_fmtstg = NULL;
    
    entry = pak.entries_by_seq + i;
    entry->filename = NULL;

    // is this a duplicate?
    if (RPAK_E_OK != rpak_fetch_entries(cfg,
                                        pak.entries_by_id,
                                        entry->id,
                                        &entries_from_hash,
                                        &num_entries_from_hash))
      goto L_rpak_unpack_end;
      
    if (num_entries_from_hash) { // yes it is
      dup=1;
      num_dups++;
      if (!cfg->crc_duplicates) { // are we CRCing duplicate payloads?
        payload_needed=0; // if we're not checking dups then we can just ignore them
      } else {
        // if we are checking dups then we need the PL but don't write the file
        actually_write_file=0; 
      }
    }
    
    if (cfg->metafile_only) {
      payload_needed=0;
      actually_write_file=0;
    }

    // seek to payload
    if (payload_needed) {
      if (fseek(fp_pak, entry->payload_offset, SEEK_SET)) {
        errmsg = RPAK_STRERROR(cfg);
        RPAK_EPRINTF(cfg->tw, 7,
                     "ERROR: Was unable to seek to offset 0x%x to get the payload of the "
                     "file%s%s%s with ID %x (%s).\n",
                     entry->payload_offset,
                     ( (entry->name) ? " \"" : "" ),
                     ( (entry->name) ? ( entry->name ) : "" ),
                     ( (entry->name) ? "\"" : "" ),
                     entry->id,
                     errmsg);
        ecode = RPAK_E_FSEEK;
        goto L_rpak_unpack_end;
      }

      // alloc payload
      payload = RPAK_MALLOCMSG(entry->payload_len_src, "payload");

      // load payload
      if (RPAK_E_OK != (ecode = fread_fully(cfg, payload, entry->payload_len_src, fp_pak))) {
        RPAK_FREEMSG(payload, "payload");
        goto L_rpak_unpack_end;
      }

      // compute checksum
      if (cfg->crc_duplicates) {
        checksum = rpak_checksum(payload, entry->payload_len_src);
        entry->payload_checksum = checksum;
      }

      // for dups, compare against old checksum
      if (dup) {
  //printf("dup sums: %08x | %08x\n", entry_from_hash->payload_checksum, checksum);
        if (checksum != entries_from_hash[0].payload_checksum) {
          RPAK_EPRINTF(cfg->tw, 7,
                       "ERROR: Duplicate entry in PAK for the file%s%s%s with ID 0x%x "
                       "has a payload that does not match the one that was previously "
                       "encountered.\n",
                       ( (entry->name) ? " \"" : "" ),
                       ( (entry->name) ? ( entry->name ) : "" ),
                       ( (entry->name) ? "\"" : "" ),
                       entry->id);
          ecode = RPAK_E_BAD_DUP;
          RPAK_FREEMSG(payload, "payload");
          goto L_rpak_unpack_end;
        }
        RPAK_FREEMSG(payload, "payload");
        //continue;
        actually_write_file=0;
      }
      
    }
    
    if (entry->compress)
      z = RPAK_METAFILE_CHAR_COMPRESSED;
    else
      z = RPAK_METAFILE_CHAR_UNCOMPRESSED;

    // create metafile line
#define META_FMTSTG_MAIN "%08x %4s %08x %c%s%s\n"
    meta_line_len = RPAK_SCPRINTF(META_FMTSTG_MAIN,
                                  entry->id,
                                  entry->magic_s,
                                  entry->payload_offset,
                                  z,
                                  (entry->name)?" ":"",
                                  (entry->name)?(entry->name):"");
    meta_line = RPAK_MALLOCMSG(meta_line_len + 1, "meta line");
    memset(meta_line, 0, meta_line_len + 1);
    RPAK_SNPRINTF(meta_line,
                  meta_line_len+1,
                  meta_line_len+1,
                  META_FMTSTG_MAIN,
                  entry->id,
                  entry->magic_s,
                  entry->payload_offset,
                  z,
                  (entry->name)?" ":"",
                  (entry->name)?(entry->name):"");
                     
    // copy metafile line to main buffer
    if (meta_fill + meta_line_len + 1 > meta_alloced) {
//printf ("old meta_alloced = %llu\n", meta_alloced);
      metabuf_body = RPAK_REALLOCMSG (metabuf_body,
                                      meta_alloced = (meta_fill + meta_line_len + 1),
                                      "meta body");
    }
    memcpy(metabuf_body + meta_fill, meta_line, meta_line_len + 1);
//printf("%s\n", meta_line);
    meta_fill += meta_line_len;
    // would be better to keep using this buffer and expand it,
    // but then it would be yet another thing to free on failure
    RPAK_FREEMSG(meta_line, "meta line");
    meta_line = NULL;
    
    if (!actually_write_file || !payload_needed)
      continue;

    if (entry->compress)
      reported_decompressed_payload_len = parse_u32_bigendian(payload); // 4 bytes
        
    if (entry->compress && !(cfg->raw_payload)) {

      if (RPAK_Z_SCHEME_ZLIB == compression_scheme) {
      
        ecode = rpak_zlib_decompress(cfg,
                                     payload+4, // skip first four bytes
                                     entry->payload_len_src-4,
                                     &z_payload,
                                     &(entry->payload_len_dest),
                                     entry->name,
                                     entry->id,
                                     first_compressed_file); // suppress errors on first try 
                                                       
        if (RPAK_E_OK != ecode) {
          if (first_compressed_file) {
            // if this is the first try and zlib decompression fails, then we'll
            // try LZO instead. If that succeeds, we switch to LZO mode.

            RPAK_MPRINTF(cfg->tw, 2, "- No luck decompressing PAK file using zlib. "
                         "Trying LZO1X instead.\n");

            z_payload = NULL;
            
            ecode = rpak_lzo_decompress (cfg,
                                         payload+0x4, // skip first four bytes
                                         entry->payload_len_src-0x4,
                                         &z_payload,
                                         &(entry->payload_len_dest),
                                         entry->name,
                                         entry->id);
                                         
            if (RPAK_E_OK != ecode) {
              // decompression failed
              RPAK_EPRINTF(cfg->tw, 7,
                           "ERROR: Tried both zlib and LZO1X compression schemes "
                           "for the file%s%s%s with ID 0x%x. Both schemes failed. "
                           "Giving up.\n", 
                           ( (entry->name) ? " \"" : "" ),
                           ( (entry->name) ? ( entry->name ) : "" ),
                           ( (entry->name) ? "\"" : "" ),
                           entry->id);
              RPAK_FREEMSG(payload, "payload");
              goto L_rpak_unpack_end;
            }

            // we were successful with LZO, so switch over to that
            // for future entries
            compression_scheme = RPAK_Z_SCHEME_LZO;

            RPAK_MPRINTF(cfg->tw, 2, "- First file decompressed successfully using LZO1X, "
                         "so continuing with this compression scheme. Please wait.\n");

            
          } else {
            // zlib failed but not on the first compressed file
            // so this is fatal
            RPAK_FREEMSG(payload, "payload");
            goto L_rpak_unpack_end;
          }
        }

        first_compressed_file=0;

      } else if (RPAK_Z_SCHEME_LZO == compression_scheme) {

//printf("reported_decompressed_payload_len=%u\n", reported_decompressed_payload_len);

        if (RPAK_E_OK != (ecode = rpak_lzo_decompress(cfg,
                                                      payload+4, // skip first four bytes
                                                      entry->payload_len_src-4,
                                                      &z_payload,
                                                      &(entry->payload_len_dest),
                                                      entry->name,
                                                      entry->id))) {
          RPAK_EPRINTF(cfg->tw, 7, "ERROR: LZO decompression failed "
                                   "for the file%s%s%s with ID 0x%x.\n",
                                   ( (entry->name) ? " \"" : "" ),
                                   ( (entry->name) ? ( entry->name ) : "" ),
                                   ( (entry->name) ? "\"" : "" ),
                                   entry->id);
          RPAK_FREEMSG(payload, "payload");
          goto L_rpak_unpack_end;
        }

      }
      
      if (entry->payload_len_dest != reported_decompressed_payload_len) {
        RPAK_EPRINTF(cfg->tw, 7, "ERROR: The uncompressed payload size (%u) did not match "
                                 "the value in the PAK (%u) for the file%s%s%s with ID 0x%x.\n",
                                 entry->payload_len_dest,
                                 reported_decompressed_payload_len,
                                 ( (entry->name) ? " \"" : "" ),
                                 ( (entry->name) ? ( entry->name ) : "" ),
                                 ( (entry->name) ? "\"" : "" ),
                                 entry->id);
        RPAK_FREEMSG(payload, "payload");
        if (z_payload)
          RPAK_FREEMSG(z_payload, "zbuf out");
        ecode = RPAK_E_UNC_LEN_MISMATCH;
        goto L_rpak_unpack_end;
      }
      
        
    } else
      entry->payload_len_dest = entry->payload_len_src;
        
    // build the filename
    
    if (entry->name) {
      entry_name_len = strlen(entry->name);
      entry_name = RPAK_MALLOCMSG(1+entry_name_len, "entry name copy");
      memcpy(entry_name, entry->name, 1+entry_name_len);
    }

    memcpy(magic_s, entry->magic_s, 5);

    // maybe convert filename fragments to lower case if (cfg->lower_case_filenames)
    if (cfg->lower_case_filenames) {
      //if (entry->name) {
      if (entry_name) {
        for (j=0;j<entry_name_len;j++)
          entry_name[j] = tolower(entry_name[j]);
        entry_name[j] = 0;
      }
      for (j=0;j<4;j++)
        magic_s[j] = tolower(magic_s[j]);
      magic_s[j] = 0;
    }

    filenamelen=0;

    // messy code almost-duplication
    if (entry->name && *(entry->name) && !cfg->short_filenames) {
      filenamelen = RPAK_SCPRINTF("%s%c%08x_%s.%s", dirname, RPAK_PATHSEP, entry->id, entry_name, magic_s);
      // will be freed by the entry cleanup code, we don't do it here:
      entry->filename = RPAK_MALLOCMSG(filenamelen+1, "entry filename");
      memset(entry->filename, 0, filenamelen+1);
      RPAK_SNPRINTF(entry->filename, filenamelen+1, filenamelen+1, "%s%c%08x_%s.%s",
                         dirname, RPAK_PATHSEP, entry->id, entry_name, magic_s);
    } else {
      filenamelen = RPAK_SCPRINTF("%s%c%08x.%s", dirname, RPAK_PATHSEP, entry->id, magic_s);
      // will be freed by the entry cleanup code, we don't do it here:
      entry->filename = RPAK_MALLOCMSG(filenamelen+1, "entry filename");
      memset(entry->filename, 0, filenamelen+1);
      RPAK_SNPRINTF(entry->filename, filenamelen+1, filenamelen+1, "%s%c%08x.%s",
                         dirname, RPAK_PATHSEP, entry->id, magic_s);
    }

//printf("[%s%c%0x_%s.%s]\n", dirname, RPAK_PATHSEP, entry->id, entry_name, magic_s);
//printf("entry->filename = %s\n", entry->filename);
//printf("filenamelen = %u\n", filenamelen);
    
    RPAK_FREEMSG(entry_name, "entry name copy");

    file_len = entry->payload_len_dest;
    if (entry->compress) {
      //if (cfg->no_decompress) {
      if (cfg->raw_payload) {
        file_len = entry->payload_len_src - 4;
        file_payload = payload + 4; // skip uncompressed block length for raw dump
      } else
        file_payload = z_payload;
    } else
      file_payload = payload;
    
    ecode = rpak_write_file (cfg,
                             entry->filename,
                             file_payload,
                             file_len,
                             0); // no mkpath as it should already have been done

    if (payload)
      RPAK_FREEMSG (payload, "payload");
    if (z_payload)
      RPAK_FREEMSG(z_payload, "zbuf out");

    if (RPAK_E_OK != ecode)
      goto L_rpak_unpack_end;
    
    if (cfg->vrb) {
      if (entry->compress)
        RPAK_MPRINTF(cfg->tw, 2,
                     "- Wrote \"%s\" (decompressed %u"
                     " -> %lu"
                     " bytes).\n",
                     entry->filename,
                     entry->payload_len_src,
                     (u64_t) entry->payload_len_dest);
      else
        RPAK_MPRINTF(cfg->tw, 2,
                     "- Wrote \"%s\" (%u"
                     " bytes).\n",
                     entry->filename,
                     entry->payload_len_src);
    }
    
    
    // add to hashtable
    if (!dup) {
      if (RPAK_E_OK != (ecode = rpak_store_entry (cfg,
                                                  pak.entries_by_id,
                                                  entry->id,
                                                  entry))) {
        goto L_rpak_unpack_end;
      }
    }
    

    
  }
  
  // now we can build the metafile top, now we know the compression scheme
  
#define META_FMTSTG_HEAD "# PAK metadata. Generated by retropak v" RPAK_VERSION_S \
                         ".\n" \
                         "# File format:\n" \
                         "#   first line:  RPAK <retropak version> <PAK type> <PAK version> <NULL>\n" \
                         "#   other lines: <id> <magic> <original PAK offset> <Z|.> [name ...]\n" \
                         "#\n" \
                         "RPAK %08x %08x %04x%04x %08x\n"                         
                         
  if (RPAK_Z_SCHEME_ZLIB == compression_scheme)
    pak_type = RPAK_PAKTYPE_32_ZLIB;
  else if (RPAK_Z_SCHEME_LZO == compression_scheme)
    pak_type = RPAK_PAKTYPE_32_LZO;
  meta_line_len = RPAK_SCPRINTF(META_FMTSTG_HEAD,
                                RPAK_VERSION_I,
                                pak_type,
                                pak.version_major,
                                pak.version_minor,
                                pak.unk32_1);

  metabuf_head = RPAK_MALLOCMSG(meta_line_len + 1, "metabuf head");
  memset(metabuf_head, 0, meta_line_len + 1);
  RPAK_SNPRINTF(metabuf_head,
                 meta_line_len + 1,
                 meta_line_len + 1,
                 META_FMTSTG_HEAD,
                 RPAK_VERSION_I,
                 pak_type,
                 pak.version_major,
                 pak.version_minor,
                 pak.unk32_1);

  // OK. Copy header and body to metabuf.
  metabuf_body_len = 0;
  if (pak.num_entries)
    metabuf_body_len = strlen(metabuf_body);
  metabuf_full = RPAK_MALLOCMSG(meta_line_len + metabuf_body_len + 1, "metabuf full");
  memcpy(metabuf_full, metabuf_head, meta_line_len);
  if (pak.num_entries)
    memcpy(metabuf_full + meta_line_len, metabuf_body, strlen(metabuf_body));

  RPAK_FREEMSG(metabuf_head, "metabuf head");
  metabuf_head = NULL;
  
  // we have built the metafile, so save that to disc
  if (NULL == cfg->metafile) {
    tmplen = strlen(dirname) + 1 + strlen(RPAK_METAFILE_DEFAULT);
    metafile_name = RPAK_MALLOCMSG(tmplen+1, "metafilename");
    memset(metafile_name, 0, tmplen+1);
    
    tmplen = strlen(RPAK_METAFILE_DEFAULT);
    memcpy(metafile_name, dirname, tmppos=strlen(dirname));
    metafile_name[tmppos]=RPAK_PATHSEP;
    memcpy(metafile_name + tmppos + 1, RPAK_METAFILE_DEFAULT, tmplen);
  } else
    metafile_name = cfg->metafile;
  ecode = rpak_write_file (cfg,
                           metafile_name,
                           (u8_t *) metabuf_full,
                           strlen(metabuf_full),
                           1); // 1 = create path if it doesn't exist

  if (RPAK_E_OK != ecode) {
    if (!cfg->metafile)
      RPAK_FREEMSG(metafile_name, "metafilename");
    goto L_rpak_unpack_end;
  }
  
  if (!cfg->metafile_only) {
    RPAK_MPRINTF(cfg->tw, 2, "- %u files were written successfully. "
                             "%u duplicate entries were ignored.\n",
                             pak.num_entries-num_dups,
                             num_dups);
  }
  RPAK_MPRINTF(cfg->tw, 2, "- Metadata was written to \"%s\".\n",
                           metafile_name);
                           
  if (!cfg->metafile)
    RPAK_FREEMSG(metafile_name, "metafilename");
  
L_rpak_unpack_end:
  if (NULL != metabuf_full)
    RPAK_FREEMSG(metabuf_full, "metabuf full");
  if (NULL != metabuf_head)
    RPAK_FREEMSG(metabuf_head, "metabuf head");
  if (NULL != metabuf_body)
    RPAK_FREEMSG(metabuf_body, "metabuf body");
  if (NULL != pak.entries_by_seq) {
    for (i=0;i<pak.num_entries;i++)
      rpak_free_entry(pak.entries_by_seq + i);
    RPAK_FREEMSG(pak.entries_by_seq, "entries_by_seq");
  }
  if (NULL != pak.entries_by_id)
    rpak_hash_free(pak.entries_by_id, 1);
  if (NULL != dirname)
    RPAK_FREEMSG(dirname, "rpak_unpack dirname");
  if (NULL != fp_pak)
    fclose(fp_pak);
  
  return ecode;
  
}

static rpak_err_t rpak_read_file (rpak_cfg_t *cfg, char *filename,
                                  u8_t **payload_out, size_t *len_out) {
  // *payload_out must be freed by the caller if the result is RPAK_E_OK
  
  rpak_stat_t sb;
  
  rpak_err_t ecode = RPAK_E_OK;
  FILE *fp = NULL;
  u64_t mask = 0;
  
  *payload_out = NULL;
  *len_out = 0;
  
  if (!filename) {
    RPAK_BPRINTF(cfg->tw, 5, "BUG: rpak_read_file(filename==NULL)\n");
    return RPAK_E_NULL;
  }
  if (!payload_out) {
    RPAK_BPRINTF(cfg->tw, 5, "BUG: rpak_read_file(payload_out==NULL)\n");
    return RPAK_E_NULL;
  }
  if (!len_out) {
    RPAK_BPRINTF(cfg->tw, 5, "BUG: rpak_read_file(len_out==NULL)\n");
    return RPAK_E_NULL;
  }

  // stat the file once before opening it,
  // to check it isn't a directory or something
  memset(&sb, 0, sizeof(rpak_stat_t));
  if (RPAK_E_OK != (ecode = rpak_stat (cfg, filename, &sb))) {
    RPAK_EPRINTF(cfg->tw, 7, "ERROR: File to be loaded is missing: \"%s\".\n", filename);
    return ecode;
  }

  if (sb.is_dir) {
    RPAK_EPRINTF(cfg->tw, 7,
                 "ERROR: Could not load file \"%s\", because it is "
                 "actually a directory.\n", filename);
    return RPAK_E_FILE_NOT_FILE;
  }
  
  // then, open it
  if (NULL == (fp = rpak_fopen (cfg, filename, "rb")))
    return RPAK_E_FOPEN_READ_FILE;

  // and now we stat it again
  memset(&sb, 0, sizeof(rpak_stat_t));
  if (RPAK_E_OK != (ecode = rpak_stat (cfg, filename, &sb))) {
    fclose(fp);
    return ecode;
  }

  if (sb.is_dir) {
    RPAK_EPRINTF(cfg->tw, 7,
                 "ERROR: Could not load file \"%s\", because it is "
                 "actually a directory.\n", filename);
    fclose(fp);
    return RPAK_E_FILE_NOT_FILE;
  }
  
  if (sb.fsize==0) {
    fclose(fp);
    return RPAK_E_OK;
  }
  
  *payload_out = RPAK_MALLOCMSG(sb.fsize, "rpak_read_file payload");
  
  if (sizeof(off_t) == 4 || sizeof(size_t) == 4)
    mask = 0x7fffffff;
  else
    mask = 0x7fffffffffffffff;

  ecode = fread_fully (cfg, *payload_out, sb.fsize & mask, fp);
  fclose(fp);
  
  if (RPAK_E_OK != ecode) {
    RPAK_FREEMSG(*payload_out, "rpak_read_file payload");
    *payload_out = NULL;
    return ecode;
  }
  
  *len_out = sb.fsize;

  return ecode;
}

#define RPAK_LZO_ZBUF_LEN 65536
static rpak_err_t rpak_lzo_compress(rpak_cfg_t *cfg,
                                    u8_t *source,
                                    size_t srclen,
                                    u8_t **dest,
                                    size_t *destlen) {
  
  size_t srcpos=0;
  int lzo_ecode = LZO_E_OK;
  rpak_err_t ecode = RPAK_E_OK;
  char *wkmem = NULL;
  u8_t *zbuf = NULL;
  size_t zbuf_len = 0;
  u16_t zbuf_len_16=0;
  size_t alloced=0;
  size_t fill=0;
  u32_t j=0;
  
  *dest = NULL;
  *destlen = 0;
  
  wkmem = RPAK_MALLOCMSG(LZO1X_999_MEM_COMPRESS, "LZO1X_999_MEM_COMPRESS");
  zbuf = RPAK_MALLOCMSG(RPAK_LZO_ZBUF_LEN, "lzo compress segment");
  alloced = 1024;
  *dest = RPAK_MALLOCMSG(alloced, "lzo compress main");
  
  // process data in 16K chunks
  for (srcpos=0;srcpos < srclen;srcpos+=16384) {
  
    //u8_t verbatim=0;
  
    zbuf_len = RPAK_LZO_ZBUF_LEN;
  
    lzo_ecode = lzo1x_999_compress(source + srcpos,
                                   (srclen-srcpos > 16384) ? 16384 : (srclen-srcpos),
                                   zbuf,
                                   &zbuf_len,
                                   wkmem);
                                   
    // removed these checks because it seems like lzo1x_999_compress doesn't
    // return LZO_E_OUTPUT_OVERRUN or anything if the destination buffer you
    // provide is too small. it just crashes, which sadly we can't check for
                                   
    /*if ((zbuf_len > RPAK_LZO_ZBUF_LEN) || (lzo_ecode == LZO_E_OUTPUT_OVERRUN)) {
      RPAK_EPRINTF(cfg->tw, 7, "ERROR: The output buffer length was exceeded while "
                               "LZO-compressing segment %u. This should not happen. "
                               "Try increasing RPAK_LZO_ZBUF_LEN and recompiling "
                               "retropak.\n", j);
      ecode = RPAK_E_LZO_COMPRESS_OVERRUN;
      goto L_rpak_lzo_compress_fail;
    } else*/
    
    
    if (lzo_ecode != LZO_E_OK) {
      RPAK_EPRINTF(cfg->tw, 7, "ERROR: There was an error while LZO-compressing segment %u.\n", j);
      ecode = RPAK_E_LZO_COMPRESS;
      goto L_rpak_lzo_compress_fail;
    } else if (!zbuf_len) {
      RPAK_EPRINTF(cfg->tw, 7, "ERROR: While LZO-compressing segment %u, no compressed data "
                               "was produced.\n", j);
      ecode = RPAK_E_LZO_COMPRESS_NODATA;
      goto L_rpak_lzo_compress_fail;
    } else if (zbuf_len > 0xfffe) {
      RPAK_EPRINTF(cfg->tw, 7, "ERROR: While LZO-compressing segment %u, more than "
                               "0xfffe bytes of compressed data was produced. This will not "
                               "fit into the segment size field in the PAK.\n", j);
      ecode = RPAK_E_LZO_COMPRESS_SEGSIZE;
      goto L_rpak_lzo_compress_fail;
    }    
    
    // segment was compressed
    zbuf_len_16 = 0xffff & zbuf_len;
        
    // OK. Segment is ready.
    
//printf("zbuf_len_16=%u\n", zbuf_len_16);
    
    // write segment size to main buffer
    while (fill + 2 + zbuf_len >= alloced)
      (*dest) = RPAK_REALLOCMSG((*dest), alloced = (alloced * 2), "lzo compress main");
      
    (*dest)[fill] = (zbuf_len_16 >> 8) & 0xff;
    (*dest)[fill+1] = zbuf_len_16 & 0xff;
    
    // write compressed segment
    memcpy((*dest)+fill+2, zbuf, zbuf_len);
    
    fill += 2 + zbuf_len;
    j++;
    
  }
  
  *destlen = fill;
  
L_rpak_lzo_compress_end:
  if (zbuf)
    RPAK_FREEMSG(zbuf, "lzo compress segment");
  if (wkmem)
    RPAK_FREEMSG(wkmem, "LZO1X_999_MEM_COMPRESS");
    
  return ecode;
    
L_rpak_lzo_compress_fail:
  if (*dest)
    RPAK_FREEMSG(*dest, "lzo compress main");
  goto L_rpak_lzo_compress_end;
  
}

static rpak_err_t rpak_lzo_decompress(rpak_cfg_t *cfg,
                                      u8_t *source,
                                      size_t srclen,
                                      u8_t **dest,
                                      size_t *destlen,
                                      char *entry_name,
                                      u32_t entry_id) {
  int lzo_ecode = 0;
  size_t alloced=0;
  lzo_uint out_len = 0;
  size_t srcpos=0;
  size_t destpos=0;
  u16_t seglen = 0;
  

  *dest = NULL;
  *destlen = 0;
  
//printf("lzo_decompress, id %x\n", entry_id);
  
  if (srclen < 2) {
    RPAK_EPRINTF(cfg->tw, 7, "ERROR: rpak_lzo_decompress(): srclen<2.\n");
    return RPAK_E_LZO_COMPRESS_NOINPUT;
  }

  do { // loop for each segment

//printf("outer loop\n");

    // read the 16-bit segment length
    seglen = (0xff00 & (((u16_t) source[srcpos]) << 8)) | source[srcpos+1];
    srcpos += 2;

    if (!seglen) {
      RPAK_EPRINTF(cfg->tw, 7, "ERROR: rpak_lzo_decompress(): seglen==0.\n");
      if (*dest)
        RPAK_FREEMSG(*dest, "zbuf out");
      return RPAK_E_LZO_ZERO_SEGLEN;
    }

    if (seglen == 0xffff) {
      // done?
      return RPAK_E_OK;
    }
    
//printf("seglen = 0x%x\n", seglen);
/*
    if (seglen + srcpos > srclen) {
      // segment would extend beyond the end of the buffer, so we're done
      return RPAK_E_OK;
    }
*/
    lzo_ecode = LZO_E_OK;
  
    do { // loop, keep reallocing until we manage to decompress the whole segment

//printf("inner loop (alloced = %" RPAK_FMT_SIZET ")\n", alloced);

      if (!alloced
          || (LZO_E_INPUT_NOT_CONSUMED == lzo_ecode)
          || (LZO_E_OUTPUT_OVERRUN == lzo_ecode)) {
        *dest = RPAK_REALLOCMSG(*dest,
                                alloced = (alloced ? (alloced * 2) : (srclen * 2)),
                                "zbuf out");
//printf("alloced %u\n", alloced);
      }

      out_len = alloced - destpos; // remaining space in buffer

//printf("begin lzo1x\n");
      lzo_ecode = lzo1x_decompress_safe (source + srcpos,
                                         seglen,
                                         *dest + destpos,
                                         &out_len, // modified by call
                                         NULL);
//printf("end lzo1x\n");
//printf("out_len=%u\n", out_len);

//rpak_hexdump(*dest, out_len, 0, 32, stdout);
    } while (LZO_E_INPUT_NOT_CONSUMED == lzo_ecode || LZO_E_OUTPUT_OVERRUN == lzo_ecode);

    destpos += out_len;
    *destlen += out_len;
    srcpos += seglen;

    if (LZO_E_OK != lzo_ecode) {
      RPAK_EPRINTF(cfg->tw, 7, "ERROR: Could not decompress payload -- "
                   "lzo1x_decompress_safe() failed (code %i).\n", lzo_ecode);
      RPAK_FREEMSG(*dest, "zbuf out");
      return RPAK_E_LZO_DECOMPRESS;
    }

//printf("out_len=%u\n", out_len);

  } while (srcpos + 2 < srclen);

//printf("end lzo_decompress\n");

  return RPAK_E_OK;
}

static rpak_err_t rpak_write_file (rpak_cfg_t *cfg, char *filename,
                                   u8_t *payload, size_t len, u8_t mkpath) {

  rpak_stat_t sb;

  FILE *fp = NULL;
  rpak_err_t ecode = RPAK_E_OK;
  char *symlink_text = " ";

  // general file writing scheme
  // does the file exist? if so, do we have permission to overwrite it?
  memset(&sb, 0, sizeof(rpak_stat_t));
  if (RPAK_E_OK == rpak_stat(cfg, filename, &sb)) {
    // stat succeeded: file exists
    if (cfg->overwrite) {
      // but we are cleared to overwrite it
      if (sb.is_dir || sb.is_link) {
        // but we can't, because it's a directory or symlink
#ifndef RPAK_WINDOZE
        symlink_text = " a symbolic link or ";
#endif
        RPAK_EPRINTF(cfg->tw, 7,
                    "ERROR: Could not overwrite existing file \"%s\" because it is "
                    "not actually a file. It is"
                    "%s"
                    "a directory.\n",
                    filename, symlink_text);
        return RPAK_E_FILE_NOT_FILE;
      }
    } else {
      RPAK_EPRINTF(cfg->tw, 7,
                  "ERROR: Refusing to overwrite file \"%s\" (use %s to override).\n",
                  filename, RPAK_CLI_O_OVERWRITE_S);
      return RPAK_E_FILE_EXISTS;
    }
  }

  if (mkpath) {
    if (RPAK_E_OK != (ecode = mkdir_for_file(cfg, filename))) {
      return ecode;
    }
  }
  

  // write the payload
  if (NULL == (fp = rpak_fopen(cfg, filename, "wb"))) {
    /*
    errmsg = RPAK_STRERROR(cfg);
    RPAK_EPRINTF(cfg->tw, 7,
                 "ERROR: Could not write file \"%s\" (%s).\n",
                 filename, errmsg);
    */
    return RPAK_E_FOPEN_OPFILE;
  }
  ecode = fwrite_fully (cfg, payload, len, filename, fp);
  fclose(fp);
  return ecode;
}

// pass in a filename and it will make a directory
// to contain that file
static rpak_err_t mkdir_for_file (rpak_cfg_t *cfg, char *filename) {
  char *dirname = NULL;
  rpak_err_t ecode = RPAK_E_OK;
  // strip filename off path
  ecode = rpak_getpath (cfg, filename, &dirname);
//printf("[%u] %s -> %s\n", ecode, filename, dirname);
  switch (ecode) {
    case RPAK_E_OK:
      // have path, make directory
      ecode = rpak_mkdir (cfg, dirname);
      RPAK_FREEMSG(dirname, "getpath buf");
      break;
    case RPAK_E_GETPATH_NOSEP:
      // no path, just filename, no mkdir needed
      ecode = RPAK_E_OK;
      break;
    default:
      // error
      return ecode;
  }
  return ecode;
}

static rpak_err_t rpak_lzo_init(rpak_cfg_t *cfg) {
  if (lzo_init() != LZO_E_OK) {
    RPAK_EPRINTF(cfg->tw, 7, "ERROR: The LZO compression library was not "
                 "successfully initialised.\n");
    return RPAK_E_LZO_INIT;
  }
  return RPAK_E_OK;
}

// adapted from the public domain zpipe.c:
static rpak_err_t rpak_zlib_compress(rpak_cfg_t *cfg,
                                     u8_t *source,
                                     size_t srclen,
                                     u8_t **dest,
                                     size_t *destlen) {
                                     
  int ret, flush;
  z_stream strm;
  size_t srcpos=0;
  size_t alloced=0;
  
  // allocate deflate state
  strm.zalloc = Z_NULL;
  strm.zfree = Z_NULL;
  strm.opaque = Z_NULL;
  
  ret = deflateInit(&strm, Z_BEST_COMPRESSION);
  if (ret != Z_OK)
      return RPAK_E_ZLIB_INIT;

  // compress until end of file
  strm.avail_in = srclen;
  flush = Z_FINISH;
  
  strm.next_in = source;

  // run deflate() on input until output buffer not full, finish
  // compression if all of source has been read in 
  do {
    if (alloced == *destlen) // realloc if no space left
      *dest = RPAK_REALLOCMSG(*dest,
                              alloced = (*destlen ? (*destlen * 2) : srclen),
                              "zbuf out");
    strm.avail_out = alloced - *destlen; // bytes available in output buffer
    strm.next_out = *dest + *destlen;    // current offset in output buffer
    ret = deflate(&strm, flush);    // no bad return value
    srcpos += srclen - strm.avail_in;
    *destlen += (alloced - *destlen) - strm.avail_out;

  } while (strm.avail_out == 0);
  
  // clean up
  deflateEnd(&strm);
  
  if (strm.avail_in != 0 || ret != Z_STREAM_END) {     // all input will be used
    if (strm.avail_in != 0)
      RPAK_EPRINTF(cfg->tw, 7, "ERROR: Zlib compression failed -- strm.avail_in != 0.\n");
    else
      RPAK_EPRINTF(cfg->tw, 7, "ERROR: Zlib compression failed (code %u).\n", ret);
    RPAK_FREEMSG(*dest, "zbuf out");
    *dest = NULL;
    return RPAK_E_ZLIB_COMPRESS;
  }
  
  return RPAK_E_OK;
  
}



// adapted from the public domain zpipe.c:
static rpak_err_t rpak_zlib_decompress(rpak_cfg_t *cfg,
                                       u8_t *source,
                                       size_t srclen,
                                       u8_t **dest,
                                       size_t *destlen,
                                       char *entry_name,
                                       u32_t entry_id,
                                       u8_t suppress_errors) {
  
  int ret;
  z_stream strm;
  size_t srcpos=0;
  size_t alloced=0;

  // allocate inflate state
  strm.zalloc = Z_NULL;
  strm.zfree = Z_NULL;
  strm.opaque = Z_NULL;
  strm.avail_in = 0;
  strm.next_in = Z_NULL;
  
  ret = inflateInit(&strm);
  if (ret != Z_OK)
    return RPAK_E_ZLIB_INIT;

  strm.next_in = source;
  strm.avail_in = srclen;
    
  // run inflate() on input until output buffer not full
  do {
    if (alloced == *destlen) // realloc if no space left
      *dest = RPAK_REALLOCMSG(*dest,
                              alloced = (*destlen ? (*destlen * 2) : srclen),
                              "zbuf out");
    strm.avail_out = alloced - *destlen; // bytes available in output buffer
    strm.next_out = *dest + *destlen;    // current offset in output buffer
    ret = inflate(&strm, Z_FINISH);
    srcpos += srclen - strm.avail_in;
    *destlen += (alloced - *destlen) - strm.avail_out;
    if (ret == Z_STREAM_ERROR) {
//printf("%i", ret);
      if (!suppress_errors) {
        RPAK_EPRINTF(cfg->tw, 7,
                     "ERROR: File%s%s%s with ID 0x%x in PAK "
                     "caused a zlib error (invalid compression level).\n",
                     ( (entry_name) ? " \"" : "" ),
                     ( (entry_name) ? ( entry_name ) : "" ),
                     ( (entry_name) ? "\"" : "" ),
                     entry_id);
      }
      inflateEnd(&strm);
      RPAK_FREEMSG(*dest, "zbuf out");
      return RPAK_E_ZLIB_STREAM_ERROR;
    }
    switch (ret) {
      case Z_NEED_DICT:
      case Z_DATA_ERROR:
      case Z_MEM_ERROR:
        inflateEnd(&strm);
        RPAK_FREEMSG(*dest, "zbuf out");
        switch (ret) {
          case Z_NEED_DICT:
          case Z_DATA_ERROR:
            if (!suppress_errors) {
              RPAK_EPRINTF(cfg->tw, 7,
                          "ERROR: File%s%s%s with ID 0x%x in PAK "
                          "caused a zlib error (invalid or incomplete deflate data) [%i].\n",
                          ( (entry_name) ? " \"" : "" ),
                          ( (entry_name) ? ( entry_name ) : "" ),
                          ( (entry_name) ? "\"" : "" ),
                          entry_id,
                          ret);
            }
            return RPAK_E_ZLIB_DATA_ERROR;
          case Z_MEM_ERROR:
            if (!suppress_errors) {
              RPAK_EPRINTF(cfg->tw, 7,
                          "ERROR: File%s%s%s with ID 0x%x in PAK "
                          "caused a zlib error (out of memory).\n",
                          ( (entry_name) ? " \"" : "" ),
                          ( (entry_name) ? ( entry_name ) : "" ),
                          ( (entry_name) ? "\"" : "" ),
                          entry_id);
            }
            return RPAK_E_ZLIB_MEM_ERROR;
          default: break; // bug
        }
      default: break;
    }
  } while (strm.avail_out == 0);
  
  //clean up and return
  inflateEnd(&strm);
  return (ret == Z_STREAM_END) ? RPAK_E_OK : RPAK_E_ZLIB_DATA_ERROR;
}

static rpak_checksum_t rpak_checksum (u8_t *buf, size_t len) {
  u32_t crc = crc32(0, Z_NULL, 0); // from zlib
  crc = crc32(crc, buf, len);
  return crc;
}

static void rpak_free_entry(rpak_entry_t *entry) {
  if (!entry)
    return;
//printf("rpak_free_entry(%p)\n", entry);
  if (entry->name)
    RPAK_FREEMSG(entry->name, "entry name");
  entry->name = NULL;
  //if (entry->payload)
  //  RPAK_FREEMSG(entry->payload, "entry payload");
  if (entry->filename)
    RPAK_FREEMSG(entry->filename, "entry filename");
  entry->filename = NULL;
  //RPAK_FREEMSG(entry, "entry");
}

static void rpak_hash_free(rpak_hash_t *hash, u8_t free_entries) { // recursively free the hashtable
  size_t i;
  if (!hash)
    return;
  for (i=0;i<16;i++)
    rpak_hash_free(hash->buckets[i], free_entries);
  if (hash->entries && free_entries) {
    for (i=0;i<hash->entries_fill;i++) {
      rpak_free_entry(hash->entries+i);
    }
    RPAK_FREEMSG(hash->entries, "hash multi entries list");
    hash->entries = NULL;
  }
  RPAK_FREEMSG(hash, "hash buckets");
}

static void rpak_hash_size(rpak_hash_t *hash, u32_t *size) { // recursive
  size_t i;
  if (!hash)
    return;
  for (i=0;i<16;i++)
    rpak_hash_size(hash->buckets[i], size);
  (*size) += hash->entries_fill;
}

static rpak_err_t rpak_store_entry (rpak_cfg_t *cfg,
                                    rpak_hash_t *hash,
                                    u32_t id,
                                    rpak_entry_t *entry) {
  size_t i;
  size_t new_size=0;
  
  if (!hash) {
    RPAK_BPRINTF(cfg->tw, 5,
                 "BUG: rpak_store_entry(hash==NULL)\n");
    return RPAK_E_NULL;
  }
  if (!id) {
    RPAK_EPRINTF(cfg->tw, 7,
                 "ERROR: rpak_store_entry(id==0)\n");
    return RPAK_E_ZERO_ID;
  }

//printf("rpak_store_entry %x ", id);
//if (entry->name)
//printf("(%s)", entry->name);
//printf(":\n");
//printf("%p ", hash);
  for (i=0;i<8;i++) {
  
    u8_t nybble = (id>>28) & 0xf;
    
    if (!hash->buckets[nybble])
      hash->buckets[nybble] = RPAK_MALLOCMSG(sizeof(rpak_hash_t), "hash buckets");
    hash = hash->buckets[nybble];
    
    //new_size = hash->entries_alloced;
    
//printf("[%x] %p ", nybble, hash);
    if (i==7) {
      // collision, so we need to keep a list of colliding entries
      if (hash->entries_fill >= hash->entries_alloced) {
        new_size = (hash->entries_alloced ? (hash->entries_alloced * 2) : 1);
//printf("new_size=%u\n", new_size);
        hash->entries = RPAK_REALLOCMSG(hash->entries, new_size * sizeof(rpak_entry_t), "hash multi entries list");
        memset(hash->entries + hash->entries_alloced, 0, new_size - hash->entries_alloced);
        hash->entries_alloced = new_size;
      }
//printf("multihash: storing entry %08x w/off %u at position %u\n", entry->id, entry->payload_offset, hash->entries_fill);
      memcpy(hash->entries + hash->entries_fill,
             entry,
             sizeof(rpak_entry_t));
      if (entry->name) {
        hash->entries[hash->entries_fill].name = RPAK_MALLOCMSG(strlen(entry->name)+1,
                                                                "entry name [on hash]");
        memcpy(hash->entries[hash->entries_fill].name, entry->name, strlen(entry->name)+1);
      }
      if (entry->filename) {
        hash->entries[hash->entries_fill].filename = RPAK_MALLOCMSG(strlen(entry->filename)+1,
                                                                    "entry filename");
        memcpy(hash->entries[hash->entries_fill].filename,
               entry->filename, 
               strlen(entry->filename)+1);
      }
      
      (hash->entries_fill)++;
    }
    id <<= 4;
    id &= 0xfffffff0;
  }
//printf("\n");
  return RPAK_E_OK;
}

static rpak_err_t rpak_fetch_entries (rpak_cfg_t *cfg,
                                      rpak_hash_t *hash,
                                      u32_t id,
                                      rpak_entry_t **entries,
                                      size_t *num_entries) {
  size_t i;
  *entries = NULL;
  if (!hash) {
    RPAK_BPRINTF(cfg->tw, 5,
                 "BUG: rpak_fetch_entries(hash==NULL)\n");
    return RPAK_E_NULL;
  }
  for (i=0;i<8;i++) {
    u8_t nybble = (id>>28) & 0xf;
    if (!hash->buckets[nybble])
      return RPAK_E_OK;
    hash = hash->buckets[nybble];
    if (i==7) {
      *entries = hash->entries;
      *num_entries = hash->entries_fill;
    }
    id <<= 4;
    id &= 0xfffffff0;
  }
  return RPAK_E_OK;
}

static rpak_err_t stream_u16_bigendian (rpak_cfg_t *cfg, FILE *fp, u16_t *u16) {
  u8_t b[2];
  rpak_err_t ecode = RPAK_E_OK;
  if (NULL == fp) {
    RPAK_BPRINTF(cfg->tw, 5,
                 "BUG: stream_u16_bigendian(fp==NULL)\n");
    return RPAK_E_NULL;
  }
  if (NULL == u16) {
    RPAK_BPRINTF(cfg->tw, 5,
                 "BUG: stream_u16_bigendian(u16==NULL)\n");
    return RPAK_E_NULL;
  }
  if (RPAK_E_OK != (ecode = fread_fully(cfg, b, 2, fp)))
    return ecode;
  *u16 = (((u16_t) b[0]) << 8) & 0xff;
  *u16 |= b[1];
  return RPAK_E_OK;
}

static rpak_err_t stream_u32_bigendian (rpak_cfg_t *cfg, FILE *fp, u32_t *u32) {
  u8_t b[4];
  rpak_err_t ecode = RPAK_E_OK;
  if (NULL == fp) {
    RPAK_BPRINTF(cfg->tw, 5,
                 "BUG: stream_u32_bigendian(fp==NULL)\n");
    return RPAK_E_NULL;
  }
  if (NULL == u32) {
    RPAK_BPRINTF(cfg->tw, 5,
                 "BUG: stream_u32_bigendian(u32==NULL)\n");
    return RPAK_E_NULL;
  }
  if (RPAK_E_OK != (ecode = fread_fully(cfg, b, 4, fp)))
    return ecode;
  *u32 = parse_u32_bigendian(b);
  return RPAK_E_OK;
}

static u32_t parse_u32_bigendian(u8_t *b) {
  u32_t u32;
  u32 = (((u32_t) b[0]) << 24) & 0xff000000;
  u32 |= (((u32_t) b[1]) << 16) & 0xff0000;
  u32 |= (((u32_t) b[2]) << 8) & 0xff00;
  u32 |= b[3] & 0xff;
  return u32;
}

static void to_u32_bigendian(u32_t x, u8_t *out) {
  out[0] = (x>>24) & 0xff;
  out[1] = (x>>16) & 0xff;
  out[2] = (x>>8) & 0xff;
  out[3] = x & 0xff;
}

static rpak_err_t fread_fully(rpak_cfg_t *cfg,
                              u8_t *buf,
                              size_t size,
                              FILE *f) {
  size_t size_read=0;
  char *errmsg = NULL;
  while (size_read<size) {
    size_t j;
    size_read+=(j=fread(buf+size_read,1,size-size_read,f));
    if (ferror(f)) {
      errmsg = RPAK_STRERROR(cfg);
      RPAK_EPRINTF(cfg->tw, 7,
                   "ERROR: Something went wrong reading the PAK file (%s).\n",
                   errmsg);
      return RPAK_E_FREAD;
    } else if (feof(f)) {
      return RPAK_E_FREAD_EOF;
    }
    if (!j) {
#ifndef RPAK_WINDOZE
      usleep(1000);
#else
      Sleep(1); /* 1 ms */
#endif
    }
  }
  return RPAK_E_OK;
}

static rpak_err_t fwrite_fully(rpak_cfg_t *cfg,
                               u8_t *buf,
                               size_t size,
                               char *filename,
                               FILE *f) {
  size_t size_written=0;
  char *errmsg = NULL;
  while (size_written<size) {
    size_t j;
//printf("buf=%p\n", buf);
//printf("fwrite(%p, 1, %zu, [%s])\n", buf+size_written, size-size_written, filename);
    size_written+=(j=fwrite(buf+size_written,1,size-size_written,f));
    
    if (ferror(f)) {
      errmsg = RPAK_STRERROR(cfg);
      RPAK_EPRINTF(cfg->tw, 7,
                   "ERROR: Something went wrong writing file %s (%s).\n",
                   filename, errmsg);
      return RPAK_E_FWRITE;
    } else if (feof(f)) {
      errmsg = RPAK_STRERROR(cfg);
      RPAK_EPRINTF(cfg->tw, 7,
                   "ERROR: An unexpected EOF was encountered while writing file %s (%s).\n",
                   filename, errmsg);
      return RPAK_E_FREAD_EOF;
    }
    if (!j) {
#ifndef RPAK_WINDOZE
      usleep(10);
#else
      Sleep(1); /* 1 ms */
#endif
    }
  }
  return RPAK_E_OK;
}

#ifdef RPAK_WINDOZE
int wmain(int argc, wchar_t *argv[], wchar_t *envp[]) {
  int j;
#else
int main (int argc, char *argv[]) {
#endif

  u32_t ecode;
  rpak_cfg_t cfg;
  char **my_argv = NULL;

  memset(&cfg, 0, sizeof(rpak_cfg_t));
  cfg.tw = rpak_get_term_width();

  rpak_banner(&cfg);


  
#ifdef RPAK_WINDOZE
  // convert argv[] from wide char to multibyte unicode
  my_argv = (char **) RPAK_MALLOCMSG(sizeof(char *) * argc, "multibyte argv[1]");
  for (j=0;j<argc;j++) {
	  size_t size=0;

//printf("argv[%d]: \n", j);
//rpak_hexdump(argv[j], 16, 0, 16, stdout);

	  size = WideCharToMultiByte (CP_UTF8, 0, argv[j], -1, NULL, 0, NULL, NULL);
	  my_argv[j] = (char *) RPAK_MALLOCMSG(size, "multibyte argv[2]");
	  WideCharToMultiByte (CP_UTF8, 0, argv[j], -1, my_argv[j], size, NULL, NULL);
//printf("my_argv[%d]: \n", j);
//rpak_hexdump(my_argv[j], strlen((const char *) my_argv[j]), 0, 16, stdout);

  }
  // so on windows we need to free argv, on other architectures we don't
#else
  my_argv = argv;
#endif

  ecode = parse_cli (argc, my_argv, &cfg);

//printf("%s", cfg.pakfile); exit(1);


  if (RPAK_E_OK == ecode) {
    ecode = rpak_lzo_init(&cfg);
  } else {
    if (RPAK_E_CLI != ecode)
      rpak_usage(&cfg);
    printf("\n");
    return 0x7fffffff & ecode;
  }

  if (RPAK_E_OK == ecode) {
    if (cfg.pack_or_unpack == RPAK_MODE_PACK)
      ecode = rpak_pack(&cfg);
    else
      ecode = rpak_unpack(&cfg);
  }

#ifdef RPAK_WINDOZE
  for (j=0;j<argc;j++) RPAK_FREEMSG(my_argv[j], "multibyte argv[2]");
  RPAK_FREEMSG(my_argv, "multibyte argv[1]");
#endif

#ifdef RPAK_DBGMEM
  if (/*ecode == RPAK_E_OK &&*/ (mallocs != frees))
    ecode = RPAK_E_ALLOCS_FREES;
  printf ("allocs=%u, frees=%u.\n", mallocs, frees);
#endif
//getchar();
  return 0x7fffffff & ecode;
}

static void rpak_usage(rpak_cfg_t *cfg) {
  char *usage;
  usage = "Usage: retropak (pack | unpack) [options ...]\n\n";
  RPAK_MPRINTF(cfg->tw, 0, usage);
  usage = "Unpacks or repacks a Metroid Prime or Metroid Prime 2: Echoes PAK file. "
          "You must specify either \"pack\" or \"unpack\" as the first command-line "
          "argument to retropak."
          "\n\n";
  RPAK_MPRINTF(cfg->tw, 0, usage);
  usage = "  Command-line options:\n\n";
  RPAK_MPRINTF(cfg->tw, 2, usage);

  RPAK_MPRINTF(cfg->tw, 4, "   (" RPAK_CLI_O_PAKFILE_S " | " RPAK_CLI_O_PAKFILE_L
                           ") [PAK file]\n");
  RPAK_MPRINTF(cfg->tw, 6, "      In unpack mode, this option is mandatory and specifies "
                           "the PAK file to unpack. In pack mode, this option is only "
                           "needed if you want to override the output PAK name -- otherwise "
                           "retropak will attempt to use the name of the source directory "
                           "with the extension \".pak\" appended to it.\n\n");
  RPAK_MPRINTF(cfg->tw, 4, "   (" RPAK_CLI_O_DIR_S " | " RPAK_CLI_O_DIR_L
                           ") [content directory]\n");
  RPAK_MPRINTF(cfg->tw, 6, "      In pack mode, this option is mandatory and specifies "
                           "the source directory containing the files to pack. "
                           "In unpack mode, this option is only "
                           "needed if you want to override the name of the extraction directory "
                           "-- otherwise retropak will attempt to use the name of the "
                           "PAK file with its \".pak\" extension stripped.\n\n");
  RPAK_MPRINTF(cfg->tw, 4, "   (" RPAK_CLI_O_METAFILE_S " | " RPAK_CLI_O_METAFILE_L
                           ") [metadata file]\n");
  RPAK_MPRINTF(cfg->tw, 6, "      When unpacking, retropak will by default create a metadata "
                           "list file "
                           "called \"retropak.txt\" in the extracted files directory, "
                           "and in pack mode will expect to find this file in the source "
                           "directory. This option will allow you to specify a different "
                           "path to this file, for either mode.\n\n");
  RPAK_MPRINTF(cfg->tw, 4, "   (" RPAK_CLI_O_SHORT_FILENAMES_S " | " RPAK_CLI_O_SHORT_FILENAMES_L
                           ")\n");
  RPAK_MPRINTF(cfg->tw, 6, "      This option will force retropak to use short filenames for the "
                           "extracted files (both when reading them in pack mode and writing them "
                           "in unpack mode). Usually, the object name within the PAK is appended "
                           "to the object ID to create the filename, but this option will disable "
                           "this behaviour.\n\n");
  RPAK_MPRINTF(cfg->tw, 4, "   (" RPAK_CLI_O_OVERWRITE_S " | " RPAK_CLI_O_OVERWRITE_L
                           ")\n");
  RPAK_MPRINTF(cfg->tw, 6, "      This option will allow retropak to overwrite files (both"
                           " the PAK file in pack mode, and the output files in unpack mode), "
                           "which is something it will normally refuse to do.\n\n");
  RPAK_MPRINTF(cfg->tw, 4, "   (" RPAK_CLI_O_CRC_DUPLICATES_S " | " RPAK_CLI_O_CRC_DUPLICATES_L
                           ")\n");
  RPAK_MPRINTF(cfg->tw, 6, "      Many PAKs contain duplicate copies of the same file. "
                           "This option makes retropak compare checksums of supposedly "
                           "duplicate files to ensure that they are in fact the same. "
                           "This option is valid for unpack mode only.\n\n");
  RPAK_MPRINTF(cfg->tw, 4, "   (" RPAK_CLI_O_HELP_S " | " RPAK_CLI_O_HELP_L
                           ")\n");
  RPAK_MPRINTF(cfg->tw, 6, "      This option will cause this help display to be shown.\n\n");
  RPAK_MPRINTF(cfg->tw, 4, "   (" RPAK_CLI_O_VERBOSE_S " | " RPAK_CLI_O_VERBOSE_L
                           ")\n");
  RPAK_MPRINTF(cfg->tw, 6, "      This option will cause retropak to print more details "
                           "about the operations it is performing.\n\n");
  RPAK_MPRINTF(cfg->tw, 4, "   (" RPAK_CLI_O_LC_FILENAMES_S " | " RPAK_CLI_O_LC_FILENAMES_L
                           ")\n");
  RPAK_MPRINTF(cfg->tw, 6, "      Use this option to make retropak use entirely lower case "
                           "filenames for the game files (in both pack and unpack modes). This "
                           "may be more convenient on architectures having case-sensitive "
                           "filesystems (for example Linux).\n\n");
  RPAK_MPRINTF(cfg->tw, 4, "   (" RPAK_CLI_O_METAFILE_ONLY_S " | " RPAK_CLI_O_METAFILE_ONLY_L
                           ")\n");
  RPAK_MPRINTF(cfg->tw, 6, "      This will prevent retropak from actually "
                           "extracting any game files -- instead, only the metadata file will "
                           "be written. This option is valid for unpack mode only.\n\n");
  RPAK_MPRINTF(cfg->tw, 4, "   (" RPAK_CLI_O_RAW_PAYLOAD_S " | " RPAK_CLI_O_RAW_PAYLOAD_L
                           ")\n");
  RPAK_MPRINTF(cfg->tw, 6, "      This option will cause retropak to extract the raw "
                           "payload with any compression intact. "
                           "This has no practical use and is mainly intended "
                           "to be helpful to others "
                           "developing or testing their own repacking solutions. "
                           "This option is valid for unpack mode only.\n");
}

static void rpak_banner(rpak_cfg_t *cfg) {
  RPAK_MPRINTF(cfg->tw, 0, "\n%s.\nAn unpacker and repacker for "
               "Metroid Prime and Metroid Prime 2: Echoes PAK files.\n\n",
               RPAK_VERSION_FULL);
}

static rpak_err_t parse_cli (int argc, char **argv, rpak_cfg_t *cfg) {
  
  u8_t machine_state=0;
  int i;

  //memset(cfg, 0, sizeof(rpak_cfg_t));
  
  if (argc<2) {
    RPAK_EPRINTF (cfg->tw, 7, "ERROR: Insufficient command-line options were specified.\n");
    rpak_usage(cfg);
    return RPAK_E_CLI;
  }
  if (!strcmp(argv[1], RPAK_CLI_M_PACK)) {
    cfg->pack_or_unpack = RPAK_MODE_PACK;
  } else if (!strcmp(argv[1], RPAK_CLI_M_UNPACK)) {
    cfg->pack_or_unpack = RPAK_MODE_UNPACK;
  } else if (!strcmp(argv[1], RPAK_CLI_O_HELP_S)
             || !strcmp(argv[1], RPAK_CLI_O_HELP_L)) {
    return RPAK_E_CLI_HELP;
  } else {
    RPAK_EPRINTF (cfg->tw, 7, "ERROR: The first command-line argument must be a mode -- either "
                  "\"%s\", or \"%s\".\n", RPAK_CLI_M_PACK, RPAK_CLI_M_UNPACK);
    return RPAK_E_CLI;
  }
//printf("argc=%u\n", argc);
  for (i=2;i<argc;i++) {
    char *arg = argv[i];
    if (!(arg[0])) {
      RPAK_EPRINTF (cfg->tw, 7, "ERROR: Empty command-line argument.\n");
      return RPAK_E_CLI;
    }
    switch (machine_state) {
      case 0:
        if (!strcmp(arg, RPAK_CLI_O_PAKFILE_S)
            || !strcmp(arg, RPAK_CLI_O_PAKFILE_L)) {
          machine_state = 1;
        } else if (!strcmp(arg, RPAK_CLI_O_DIR_S)
                   || !strcmp(arg, RPAK_CLI_O_DIR_L)) {
          machine_state = 2;
        } else if (!strcmp(arg, RPAK_CLI_O_METAFILE_S)
                   || !strcmp(arg, RPAK_CLI_O_METAFILE_L)) {
          machine_state = 3;
        } else if (!strcmp(arg, RPAK_CLI_O_SHORT_FILENAMES_S)
                   || !strcmp(arg, RPAK_CLI_O_SHORT_FILENAMES_L)) {
          cfg->short_filenames = 1; // no machine_state change
        } else if (!strcmp(arg, RPAK_CLI_O_HELP_S)
                   || !strcmp(arg, RPAK_CLI_O_HELP_L)) {
          return RPAK_E_CLI_HELP;
        } else if (!strcmp(arg, RPAK_CLI_O_OVERWRITE_S)
                   || !strcmp(arg, RPAK_CLI_O_OVERWRITE_L)) {
          cfg->overwrite = 1; // no machine_state change
        } else if (!strcmp(arg, RPAK_CLI_O_CRC_DUPLICATES_S)
                   || !strcmp(arg, RPAK_CLI_O_CRC_DUPLICATES_L)) {
          cfg->crc_duplicates = 1; // no machine_state change
        } else if (!strcmp(arg, RPAK_CLI_O_VERBOSE_S)
                   || !strcmp(arg, RPAK_CLI_O_VERBOSE_L)) {
          cfg->vrb = 1; // no machine_state change
        } else if (!strcmp(arg, RPAK_CLI_O_LC_FILENAMES_S)
                   || !strcmp(arg, RPAK_CLI_O_LC_FILENAMES_L)) {
          cfg->lower_case_filenames = 1;
        } else if (!strcmp(arg, RPAK_CLI_O_METAFILE_ONLY_S)
                   || !strcmp(arg, RPAK_CLI_O_METAFILE_ONLY_L)) {
          cfg->metafile_only = 1;
        } else if (!strcmp(arg, RPAK_CLI_O_RAW_PAYLOAD_S)
                   || !strcmp(arg, RPAK_CLI_O_RAW_PAYLOAD_L)) {
          //cfg->no_decompress=1;
          cfg->raw_payload=1;
        } else {
          RPAK_EPRINTF(cfg->tw, 7,
                       "ERROR: Unrecognised command-line argument \"%s\".\n", arg);
          return RPAK_E_CLI;
        }
        break;
      case 1:
        cfg->pakfile = arg;
        machine_state = 0;
        break;
      case 2:
        cfg->dir = arg;
        machine_state = 0;
        break;
      case 3:
        cfg->metafile = arg;
        machine_state = 0;
        break;
      default:
        RPAK_BPRINTF(cfg->tw, 5, "BUG: CLI state machine is in a bad way.\n");
        return RPAK_E_BUG;
    }
  }
  
  if (machine_state != 0) {
    RPAK_EPRINTF(cfg->tw, 7, "ERROR: The final command-line option (%s) was missing its "
                             "argument.\n", argv[argc-1]);
    return RPAK_E_CLI;
  }
  
  if (cfg->pack_or_unpack == RPAK_MODE_PACK) {
    if (!cfg->dir) {
      RPAK_EPRINTF(cfg->tw, 7, "ERROR: For pack mode, you need to provide at least a directory name"
                               " (with \"%s\" or \"%s\").\n", RPAK_CLI_O_DIR_S, RPAK_CLI_O_DIR_L);
      return RPAK_E_CLI;
    }
    if (cfg->crc_duplicates) {
      RPAK_EPRINTF(cfg->tw, 7, "ERROR: You cannot use \"%s\" or \"%s\" in pack mode.\n",
                   RPAK_CLI_O_CRC_DUPLICATES_S, RPAK_CLI_O_CRC_DUPLICATES_L);
      return RPAK_E_CLI;
    }
    if (cfg->metafile_only) {
      RPAK_EPRINTF(cfg->tw, 7, "ERROR: You cannot use \"%s\" or \"%s\" in pack mode.\n",
                   RPAK_CLI_O_METAFILE_ONLY_S, RPAK_CLI_O_METAFILE_ONLY_L);
      return RPAK_E_CLI;
    }
    //if (cfg->no_decompress) {
    if (cfg->raw_payload) {
      RPAK_EPRINTF(cfg->tw, 7, "ERROR: You cannot use \"%s\" or \"%s\" in pack mode.\n",
                   RPAK_CLI_O_RAW_PAYLOAD_S, RPAK_CLI_O_RAW_PAYLOAD_L);
      return RPAK_E_CLI;
    }
  } else { // unpack mode
    if (!cfg->pakfile) {
      RPAK_EPRINTF(cfg->tw, 7, "ERROR: For unpack mode, you need to provide "
                               "at least a PAK file name"
                               " (with \"%s\" or \"%s\").\n",
                               RPAK_CLI_O_PAKFILE_S, RPAK_CLI_O_PAKFILE_L);
      return RPAK_E_CLI;
    }
    /*
    if (!cfg->pakfile[0]) {
      RPAK_EPRINTF(cfg->tw, 7, "ERROR: The supplied PAK file name was empty.\n");
      return RPAK_E_CLI;
    }
    */
  }
  
  if (cfg->pack_or_unpack == RPAK_MODE_PACK) {
    RPAK_MPRINTF(cfg->tw, 2, "- Pack mode.\n");
  } else {
    RPAK_MPRINTF(cfg->tw, 2, "- Unpack mode.\n");
  }
  if (cfg->pakfile) {
    if (cfg->pack_or_unpack == RPAK_MODE_PACK)
      RPAK_MPRINTF(cfg->tw, 2, "- PAK file to be generated: %s\n", cfg->pakfile);
    else
      RPAK_MPRINTF(cfg->tw, 2, "- PAK file to unpack: %s\n", cfg->pakfile);
  }
  if (cfg->dir) {
    if (cfg->pack_or_unpack == RPAK_MODE_PACK)
      RPAK_MPRINTF(cfg->tw, 2, "- Source directory: %s\n", cfg->dir);
    else
      RPAK_MPRINTF(cfg->tw, 2, "- Target directory: %s\n", cfg->dir);
  }
  if (cfg->metafile) {
    RPAK_MPRINTF(cfg->tw, 2, "- Metafile name: %s\n", cfg->metafile);
  }
  if (cfg->short_filenames) {
    RPAK_MPRINTF(cfg->tw, 2, "- Using short filenames.\n");
  } else {
    RPAK_MPRINTF(cfg->tw, 2, "- Using long (retropak-style) filenames.\n");
  }
  if (cfg->crc_duplicates) {
    RPAK_MPRINTF(cfg->tw, 2, "- Will compare the checksums of payloads of any "
                             "duplicate files in the PAK to check they match.\n");
  }
  if (cfg->lower_case_filenames) {
    RPAK_MPRINTF(cfg->tw, 2, "- Will use lower case filenames for the game files.\n");
  }
  if (cfg->metafile_only) {
    RPAK_MPRINTF(cfg->tw, 2, "- Will only create a metadata file -- no game files "
                             "will actually be extracted.\n");
  }
  //if (cfg->no_decompress) {
  if (cfg->raw_payload) {
    RPAK_MPRINTF(cfg->tw, 2, "- Will not decompress game files. "
                             "Files from the PAK "
                             "will be written out "
                             "in raw form.\n");
  }
  return RPAK_E_OK;
}

#ifdef RPAK_DBGMEM2
void *rpak_realloc (void *p, size_t size, u8_t zero, const char *label) {
#else
void *rpak_realloc (void *p, size_t size, u8_t zero) {
#endif // all just to shut the compiler up
  void *q=NULL;
  if (!size) {
    printf("realloc(0)\n");
    exit(RPAK_E_MALLOC_0);
  }
  q=realloc(p, size);
//if (!p) {
//printf("realloc(%p, %" RPAK_FMT_SIZET ")\n", p, size);
//}
#ifdef RPAK_DBGMEM
  if (!p)
    mallocs++;
#endif
#ifdef RPAK_DBGMEM2
  printf("%p _A_ [%s] (%p, %" RPAK_FMT_SIZET ", zero=%u)\n",
               q, label, p, size, zero);
#endif
  if (!q) {
    printf("realloc(%p, %" RPAK_FMT_SIZET ") failed.\n", p, size);
    exit(RPAK_E_MALLOC);
  }
  if (zero)
    memset(q, 0, size);
  return q;
}

#ifdef RPAK_DBGMEM2
void rpak_free (void *p, const char *label) {
#else
void rpak_free (void *p) {
#endif
  if (p) {
//printf("%p freed\n", p);
#ifdef RPAK_DBGMEM 
    frees++;
#endif
#ifdef RPAK_DBGMEM2
    printf("%p _F_ [%s]\n", p, label);
#endif
    free(p);
  }
}

static rpak_err_t rpak_slice_printf_grow(char **buf,
                                         size_t *alloced) {
  if (!buf || !(*buf) || !alloced)
    return RPAK_E_NULL;
  (*alloced)<<=1;
  *buf = RPAK_REALLOCMSG(*buf, *alloced, "slice printf");
  return RPAK_E_OK;
}

static rpak_err_t rpak_free_lineslice(char *buf) {
  if (!buf)
    return RPAK_E_NULL;
  RPAK_FREEMSG(buf, "lineslice buffer");
  return RPAK_E_OK;
}



/*
#define RPAK_LINESLICE_DEBUG
*/
static rpak_err_t rpak_lineslice(u16_t cols,
                                 char *in,
                                 char **out,
                                 size_t *cursor_pos, /* both input and output */
                                 size_t wrap_indent) {
  u16_t chunklen;
  u8_t pass;
  size_t in_len=0;
  size_t in_off;
  size_t out_off=0;
  size_t dummy_cursor_pos=0;
  size_t cursor_pos_orig=0;
  s32_t i;
  if (!in) {
    fprintf(RPAK_ESTREAM, "rpak_lineslice: in==NULL\n");
    return RPAK_E_NULL;
  }
  if (!out) {
    fprintf(RPAK_ESTREAM, "rpak_lineslice: out==NULL\n");
    return RPAK_E_NULL;
  }
  if (!cols) {
    fprintf(RPAK_ESTREAM, "rpak_lineslice: cols=0\n");
    return RPAK_E_ZEROWIDTH;
  }
  if (wrap_indent >= cols) {
    fprintf(RPAK_ESTREAM,
            "rpak_lineslice: wrap_indent(%" RPAK_FMT_SIZET ") >= cols (%u)\n",
            wrap_indent, cols);
    return RPAK_E_BADINDENT;
  }
  
  if (cursor_pos && (*cursor_pos >= cols)) {
    fprintf(RPAK_ESTREAM,
            "rpak_lineslice: *cursor_pos(%" RPAK_FMT_SIZET ") >= cols (%u)\n",
            *cursor_pos, cols);
    return RPAK_E_BADINDENT;
  }
  in_len = strlen(in);
/*printf("%s\n", in);*/
  if (!cursor_pos)
    cursor_pos = &dummy_cursor_pos;
  cursor_pos_orig = *cursor_pos;
  /* two passes */
  for (pass=0;pass<2;pass++) {
    u8_t leftovers=0;
    in_off = 0;
    out_off = 0;
    *cursor_pos = cursor_pos_orig;
/*printf("[pass=%u]", pass);*/
    while (in_off < in_len) {
      /* apart from the very first time,
       * there has always been a newline just
       * copied to the output if the code is
       * at this point. */
      u8_t found_newline=0;

#ifdef RPAK_LINESLICE_DEBUG
fprintf(RPAK_ESTREAM, "[1 cp=%u]", *cursor_pos);
#endif

      if (in_off && wrap_indent) {
        /* copy the wrap_indent padding
         * to the output */
        if (pass)
          memset((*out)+out_off, ' ', wrap_indent);
        /* update output position */
        out_off += wrap_indent;
        /* update cursor position */
        *cursor_pos += wrap_indent;
#ifdef RPAK_LINESLICE_DEBUG
fprintf(RPAK_ESTREAM, "[2 cp=%u]", *cursor_pos);
#endif
      }
#ifdef RPAK_LINESLICE_DEBUG
fprintf(RPAK_ESTREAM, "[10 cp=%u]", *cursor_pos);
#endif
      /* consider a chunk: a line's worth of input */
      chunklen = cols - (cursor_pos?(*cursor_pos):0); /* the most we can display on one line */
#ifdef RPAK_LINESLICE_DEBUG
fprintf(RPAK_ESTREAM, "[11 cl=%u]", chunklen);
#endif
      if (in_off + chunklen >= in_len) {
        leftovers = 1; /* the final chunk, maybe */
        chunklen -= (in_off + chunklen) - in_len; /* truncate final chunk */
      }
#ifdef RPAK_LINESLICE_DEBUG
fprintf(RPAK_ESTREAM, "[9 cl=%u]", chunklen);
#endif
      /* scan chunk for newlines */
      for (i=0;i<chunklen;i++) {
        if (in[in_off+i] == '\n') {
          if (pass) {
            /* copy input to output */
            memcpy(*out+out_off, in+in_off, i+1);
          }
#ifdef RPAK_LINESLICE_DEBUG
fprintf(RPAK_ESTREAM, "[3]");
#endif
          in_off += i+1; /* +1 to skip the newline */
          out_off += i+1; /* +1 to copy newline */
          found_newline=1;
          *cursor_pos=0;
          break;
        }
      }
      if (found_newline)
        continue;
      /* no newline was found in the chunk,
       * so search backwards for a space (unless the chunk will fit
       * on a line) */
      if (!leftovers && chunklen) {
        for (i=chunklen-1;i>=0;i--) {
          if (in[in_off+i] == ' ') {
            /* OK, we'll break the line here */
            if (pass) {
              /* copy input to output */
              memcpy (*out+out_off, in+in_off, i);
              (*out)[out_off+i] = '\n';
            }
#ifdef RPAK_LINESLICE_DEBUG
fprintf(RPAK_ESTREAM, "[4]");
#endif
            in_off  += i+1; /* +1 to skip the space */
            out_off += i+1; /* +1 for newline */
            found_newline=1;
            *cursor_pos=0;
            break;
          }
        }
      } else {
        /* it's leftovers, and no newline was found in them,
         * so just copy them straight to the output */
#ifdef RPAK_LINESLICE_DEBUG
fprintf(RPAK_ESTREAM, "[5]");
#endif
        if (pass)
          memcpy(*out+out_off, in+in_off, chunklen);
#ifdef RPAK_LINESLICE_DEBUG
fprintf(RPAK_ESTREAM, "[6 in_off=%u, cl=%u]", in_off, chunklen);
#endif
        /* removed: values are never read (thanks, clang) */
        /* in_off += chunklen; */
        /* found_newline=1; */
        out_off += chunklen;
        *cursor_pos += chunklen;
#ifdef RPAK_LINESLICE_DEBUG
fprintf(RPAK_ESTREAM, "[7 cp=%u]", *cursor_pos);
#endif
        break;
      }
      if (found_newline)
        continue;
      /* no spaces were found in the chunk (or chunk is leftovers),
       * so we have two choices:
       *   i)  just break the line here mid-word, or
       *   ii) search forwards for the next space
       *       and break the line there, exceeding
       *       the line length limit.
       * option (ii) is more complicated so we'll
       * go with (i) for now */
      /* just copy the whole chunk to the output
       * and then insert a newline */
      if (pass) {
        memcpy(*out+out_off, in+in_off, chunklen);
        /* seems we don't need to do this because the terminal will auto linebreak when
           we hit terminal width */
        /*(*out)[out_off+chunklen] = '\n';*/
      }
#ifdef RPAK_LINESLICE_DEBUG
fprintf(RPAK_ESTREAM, ("[8]");
#endif
      *cursor_pos = 0;
      in_off += chunklen;
      out_off += chunklen; /*+1;*/ /* +1 for newline */
    }
    if (!pass) {
      *out = RPAK_MALLOCMSG(out_off+1, "lineslice buffer");
    } else {
      (*out)[out_off] = 0; /* terminator */
    }
  }
  return RPAK_E_OK;
}

static u32_t rpak_slice_printf(u16_t cols,          /* wrap width */
                               size_t wrap_indent,  /* how much to indent new lines */
                               char *fmt,           /* fmtstg */
                               ...) {               /* etc. */
  va_list argptr;
  void *val_ptr = NULL;
  char *val_stg = NULL;
  u32_t val_u32=0;
  s32_t val_s32=0;
//#ifdef RPAK_64
  u64_t val_u64=0;
  s64_t val_s64=0;
//#endif
#ifdef RPAK_FLT
  double val_dbl=0.0;
#endif
  size_t i;
  size_t alloced_len=64;
  u8_t mode=0;
  char *buf = NULL;
  char *buf2 = NULL;
  size_t fill=0;
  rpak_err_t e;
  u8_t broken_snprintf=0;
  char *fmtstg = NULL;
  size_t arglen=0;
  size_t cursor_pos=0;
  
  if (!fmt)
    return RPAK_E_NULL;

  //alloced_len = 1;
  if (NULL == (buf=RPAK_MALLOCMSG(alloced_len, "slice printf")))
    return RPAK_E_MALLOC;
  //if (RPAK_SCPRINTF_1ARG("%s", "test") <= 0) {
  if (RPAK_SCPRINTF("%s", "test") <= 0) {
    /* hooray, a broken snprintf */
#ifdef RPAK_VRB
    fprintf(RPAK_ESTREAM, "slice printf: broken _scprintf/snprintf\n");
#endif
    broken_snprintf=1;
  }
  
  /* modes:
   * 0: in normal text, expecting %
   * 1: after %, expecting l or d, i, s etc.
   * 2: after %l, expecting d, i, s etc.
   */

  va_start(argptr, fmt);
  
  /* fill will point to the null terminator. */
  for (i=0; fmt[i]; i++) {
    /*
    void *p = NULL;
    */
    u8_t skip=0;
    fmtstg = NULL;
#ifdef RPAK_VRB
    fprintf(RPAK_ESTREAM, "slice printf: i=%" RPAK_FMT_SIZET ", mode=%u\n", i, mode);
#endif
    switch (mode) {
      case 0: /* normal text, want % */
        if (fmt[i] == '%') {
          mode=1;
        } else {
          /* pass through */
          /* BEGIN CODEREP */
          while (fill+1 >= alloced_len) { /* grow if necessary */
            e = rpak_slice_printf_grow(&buf, &alloced_len);
            if (e != RPAK_E_OK) {
              va_end(argptr);
              return e;
            }
          }
          /* END CODEREP */
          buf[fill] = fmt[i];
          fill++;
        }
        break;
      case 1: /* have %, want l or something else */
//#ifdef RPAK_64
      case 2: /* have %l, want something else */
//#endif
        
        /* mode 1 or 2 -- for options with long modes: */
        switch (fmt[i]) {
          case 'l':
//#ifdef RPAK_64
            if (mode==1) {   /* % mode */
              mode=2; /* activate long mode */
              skip=1;
              break;
            }
//#endif
            /* So, this code is reached either for %l without
             * RPAK_64 or %ll with RPAK_64. */
            /* this is an error, so pass through */
            /* BEGIN CODEREP */
            while (fill+3 >= alloced_len) { /* grow if necessary */
              e = rpak_slice_printf_grow(&buf, &alloced_len);
              if (e != RPAK_E_OK) { /* buf already freed */
                va_end(argptr);
                return e;
              }
            }
            /* END CODEREP */
            memcpy (buf+fill, fmt+i-2, 3); /* 3 characters */
            mode = 0;
            fill += 3;
            skip = 1;
            break;
          case 'u':
          case 'i':
          case 'x':
          case 'd':
            switch (fmt[i]) {
              case 'u':
                fmtstg="%u";
//#ifdef RPAK_64
                if (mode==2)
                  fmtstg="%" RPAK_FMT_U64;
//#endif
                break;
              case 'i':
              case 'd':
                fmtstg="%d";
//#ifdef RPAK_64
                if (mode==2)
                  fmtstg="%" RPAK_FMT_S64;
//#endif
                break;
              case 'x':
                fmtstg="%08x";
//#ifdef RPAK_64
                if (mode==2)
                  fmtstg="%" RPAK_FMT_U64HEX;
//#endif
                break;
              default:
                RPAK_FREEMSG(buf, "slice printf");
                va_end(argptr);
                return RPAK_E_BUG;
            }
            break;
          default:
            break;
        } /* switch (fmt[i]) -- modes 1 & 2 */
        
        if (mode==1) {
          if (fmt[i] == '%') { /* literal % */
            /* CODEREP */
            while (fill+1 >= alloced_len) { /* grow if necessary */
              e = rpak_slice_printf_grow(&buf, &alloced_len);
              if (e != RPAK_E_OK) {
                va_end(argptr);
                return e;
              }
            }
            /* END CODEREP */
            buf[fill] = fmt[i];
            fill++;
            mode = 0;
          } else if (fmt[i] == 's') {
            fmtstg="%s";
          } else if (fmt[i] == 'p') {
            fmtstg = "%p";
#ifdef RPAK_FLT
          } else if (fmt[i] == 'f') {
            fmtstg="%lf";
#endif
          }
        }

        if (skip)
          continue;
        
        if (!fmtstg) { /* unrecognised format */
          if (mode==1) {
            /* pass through 2 chars */
            arglen=2;
          } else if (mode==2) {
            /* pass through 3 chars */
            arglen=3;
          }
        } else {

          switch (fmt[i]) {
            case 'u':
            case 'x':
//#ifdef RPAK_64
              if (mode==2) { /* long mode */
                val_u64 = va_arg(argptr, u64_t);
                //arglen = RPAK_SCPRINTF_1ARG(fmtstg, val_u64);
                arglen = RPAK_SCPRINTF(fmtstg, val_u64);
                break;
              }
//#endif
              val_u32 = va_arg(argptr, u32_t);
              //arglen = RPAK_SCPRINTF_1ARG(fmtstg, val_u32);
              arglen = RPAK_SCPRINTF(fmtstg, val_u32);
              break;
            case 'i':
            case 'd':
//#ifdef RPAK_64
              if (mode==2) { /* long mode */
                val_s64 = va_arg(argptr, s64_t);
                //arglen = RPAK_SCPRINTF_1ARG(fmtstg, val_s64);
                arglen = RPAK_SCPRINTF(fmtstg, val_s64);
                break;
              }
//#endif
              val_s32 = va_arg(argptr, s32_t);
              //arglen = RPAK_SCPRINTF_1ARG(fmtstg, val_s32);
              arglen = RPAK_SCPRINTF(fmtstg, val_s32);
              break;
            case 's':
              val_stg = va_arg(argptr, char *);
              //arglen = RPAK_SCPRINTF_1ARG(fmtstg, val_stg);
              arglen = RPAK_SCPRINTF(fmtstg, val_stg);
              break;
            case 'p':
              val_ptr = va_arg(argptr, void *);
              //arglen = RPAK_SCPRINTF_1ARG(fmtstg, val_ptr);
              arglen = RPAK_SCPRINTF(fmtstg, val_ptr);
              break;
#ifdef RPAK_FLT
            case 'f':
              val_dbl = va_arg(argptr, double);
              //arglen = RPAK_SCPRINTF_1ARG(fmtstg, val_dbl);
              arglen = RPAK_SCPRINTF(fmtstg, val_dbl);
              break;
#endif
          } /* end switch */
          if (broken_snprintf) {
            /* shrug, overwrite arglen with a guess */
            arglen = 512;
          }
        } /* end if valid format */
        
#ifdef RPAK_VRB
        fprintf(RPAK_ESTREAM,
                "slice printf: arglen[1]=%" RPAK_FMT_SIZET "\n",
                arglen);
#endif
        
        /* alloc */
        /* CODEREP */
        while (fill+arglen+1 >= alloced_len) { /* grow if necessary */
          e = rpak_slice_printf_grow(&buf, &alloced_len);
          if (e != RPAK_E_OK) { /* buf already freed */
            va_end(argptr);
            return e;
          }
        }
        /* END CODEREP */
        
        
        if (!fmtstg) {
          memcpy(buf+fill, fmt+i-(arglen-1), arglen);
          fill += arglen;
        } else {
          /* do the snprintf */

          switch (fmt[i]) {
            case 'u':
            case 'x':
//#ifdef RPAK_64
              if (mode==2) { /* long mode */
                //RPAK_SNPRINTF_1ARG(buf+fill, alloced_len-fill,
                //                    arglen+1, fmtstg, val_u64);
                RPAK_SNPRINTF(buf+fill, alloced_len-fill,
                                    arglen+1, fmtstg, val_u64);
                break;
              }
//#endif
              //RPAK_SNPRINTF_1ARG(buf+fill, alloced_len-fill,
              //                    arglen+1, fmtstg, val_u32);
              RPAK_SNPRINTF(buf+fill, alloced_len-fill,
                                  arglen+1, fmtstg, val_u32);
              break;
            case 'i':
            case 'd':
//#ifdef RPAK_64
              if (mode==2) { /* long mode */
                //RPAK_SNPRINTF_1ARG(buf+fill, alloced_len-fill,
                //                    arglen+1, fmtstg, val_s64);
                RPAK_SNPRINTF(buf+fill, alloced_len-fill,
                                    arglen+1, fmtstg, val_s64);
                break;
              }
//#endif
              //RPAK_SNPRINTF_1ARG(buf+fill, alloced_len-fill,
              //                    arglen+1, fmtstg, val_s32);
              RPAK_SNPRINTF(buf+fill, alloced_len-fill,
                                  arglen+1, fmtstg, val_s32);
              break;
            case 's':
              //RPAK_SNPRINTF_1ARG(buf+fill, alloced_len-fill,
              //                    arglen+1, fmtstg, val_stg);
              RPAK_SNPRINTF(buf+fill, alloced_len-fill,
                                  arglen+1, fmtstg, val_stg);
              break;
            case 'p':
              //RPAK_SNPRINTF_1ARG(buf+fill, alloced_len-fill,
              //                    arglen+1, fmtstg, val_ptr);
              RPAK_SNPRINTF(buf+fill, alloced_len-fill,
                                  arglen+1, fmtstg, val_ptr);
              break;
#ifdef RPAK_FLT
            case 'f':
              //RPAK_SNPRINTF_1ARG(buf+fill, alloced_len-fill,
              //                    arglen+1, fmtstg, val_dbl);
              RPAK_SNPRINTF(buf+fill, alloced_len-fill,
                                  arglen+1, fmtstg, val_dbl);
              break;
#endif
          }

#ifdef RPAK_VRB
          fprintf(RPAK_ESTREAM,
                  "slice printf: arglen[2]=%" RPAK_FMT_SIZET "\n",
                  arglen);
#endif
          
          if (!broken_snprintf) {
            fill += arglen;
          } else {
            fill = strlen((char *) buf); /* haha */
          }
        }
#ifdef RPAK_VRB
        fprintf(RPAK_ESTREAM, "slice printf: fill=%" RPAK_FMT_SIZET "\n", fill);
#endif
        
        mode=0;
        
        break;
        
      default:
        /* FIXME: bug */
        break;
        
    } /* end switch(mode) */
    
  } /* end for i loop */
  
  /* deal with leftovers */
  switch (mode) {
    case 0:
      arglen=0;
      break; /* should be no leftovers */
    case 1:
      arglen=1; /* "%" */
      break;
    case 2:
      arglen=2; /* "%l" */
      break;
    default:
      /* FIXME: bug */
      break;
  }
  
  /* CODEREP */
  while (fill+arglen+1 >= alloced_len) { /* grow if necessary */
    e = rpak_slice_printf_grow(&buf, &alloced_len);
    if (e != RPAK_E_OK) { /* buf already freed */
      va_end(argptr);
      return e;
    }
  }
  /* END CODEREP */
  
  if (arglen) {
    buf[fill] = '%';
    fill++;
    if (arglen==2) {
      buf[fill] = 'l';
      fill++;
    }
  }
  
  /* null-terminate output string */
  buf[fill] = 0;
  
  va_end(argptr);
  
  e=rpak_lineslice(cols, buf, &buf2, &cursor_pos, wrap_indent);
  RPAK_FREEMSG(buf, "slice printf");
  if (RPAK_E_OK != e)
    return e;
  
  fprintf(RPAK_ESTREAM, "%s", buf2);
  rpak_free_lineslice(buf2);
  
  return RPAK_E_OK;
  
} /* end function */