******************************************
               * Duke Nukem 3D Group (.GRP) File Format *
               ******************************************
RTCM v11-22-99
----------------------------------------------------------------------------

DISCLAIMER


This document is intended as a small guide to the .GRP file specifications.
All information was gathered by the author without the aid of Ken Silverman 
or 3D Realms.  All trademark names are hereby acknowledged to the respective
owners.  This guide is intended to help programmers who wish to program the
.GRP format.  The author is not liable for the accuracy of this document nor
liable to any damages incurred through the use of the information contained
in this document.

----------------------------------------------------------------------------

Group (.GRP) File Format

* Note: Numbers are in decimal format.

--
Header
--------+----------+-------+---------------------
 Offset | Datatype | Bytes | Description
--------+----------+-------+---------------------
   0    | 12 byte  | 0-11  | 12 byte ID
        | array    | (12)  | KenSilverman
--------+----------+-------+---------------------
   12   | Long     | 12-15 | Number of files
        | Integer  | (4)   | contained in 
        |          |       | .GRP file
        |          |       | (X number of files)
--------+----------+-------+---------------------

--
X number of records in the following format.
--------+----------+-------+---------------------
   16   | 12 byte  | 16-27 | Filename 
        | array    | (12)  |
--------+----------+-------+---------------------
   28   | Long     | 28-31 | Length of the file
        | Integer  | (4)   |
--------+----------+-------+---------------------
Etc.

--
X number of file data stored in respect to the order of the filenames.

----------------------------------------------------------------------------

It is just an uncompressed archive of data!

----------------------------------------------------------------------------

{ An example of how to read a .GRP file (with limited error checking In Pascal) }

CONST
  ID='By Samiel - samiel@fastlane.net'; {That's me}

TYPE
  Str12=array [1..12] of byte; {12 byte array}

  Header=
    record
      KS:Str12; {KenSilverman}
      NumGrp:longint; {Number of files contained in the .GRP file}
    end;

  GrpRec=
    record
      FN:Str12; {FILENAME}
      Len:longint; {Length of the file}
    end;

VAR
  count:longint; {Counter}
  Head:Header;   {Header}
  Grp:GrpRec;    {Record}
  F:File;        {File variable}
  IsGrp:boolean; {.GRP file verification flag}
  Res:word;      {Result of BlockRead}
  Exist:boolean; {File exist flag}
  tmp:string;    {String}

Procedure Error(s:string;var F:File); {Halts the program on an error}
Begin
  writeln;
  writeln('Error : ',s);
  writeln;
  {$I-}
  close(F); {If file F is open, then close it}
  {$I+}
  Halt;     {Halt the program}
End;

Function Str12ToStr(s:str12):string; {Convert a 12 byte array to a string}
Var
  j:byte;
  tmp:string;
Begin
  tmp:='';
  for j:=1 to 12 do
    tmp:=tmp+chr(s[j]); {Add the character}
  Str12ToStr:=tmp;
End;

BEGIN
  writeln;
  writeln(ID); {Write an ID}
  if paramcount=1 then {Make sure there is a parameter}
    begin
      tmp:=paramstr(1); {Get the parameter}
      for count:=1 to length(tmp) do
        tmp[count]:=Upcase(tmp[count]); {Convert it to uppercase}

      {$I-}
      assign(F,tmp);
      reset(F);
      close(F);
      {$I+}
      Exist:=(IOResult=0) and (tmp<>''); {Check to see if the file exists}

      if Not Exist then
        Error('File '+tmp+' Not Found',F) {If not, halt the program}
      else 
        begin
          assign(F,tmp);
          reset(F,1); {Read BYTE sized records}

          BlockRead(F,Head,SizeOf(Head),Res); {Read the header}
          if Res<>SizeOf(Head) then
            Error('Unexpected End Of File',F); {Make sure it's good}

          IsGrp:=true;
          tmp:='KenSilverman';
          for count:=1 to 12 do {Determine if it is a .GRP file}
            if tmp[count]<>chr(Head.KS[count]) then
              IsGrp:=false;

          if not IsGrp then
            Error(tmp+' Not A Group File',F); {If not, halt the program}

          count:=0;

          writeln;

          repeat
            inc(count); {Increment the file number}

            BlockRead(F,Grp,SizeOf(Grp),Res); {Read the record}
            if Res<>SizeOf(Head) then
              Error('Unexpected End Of File',F); {Make sure it's good}

            writeln(Str12ToStr(Grp.FN),' - ',Grp.Len:1,' bytes'); {Write it}
          until count=Head.NumGrp; {Until we reach the number specified}

          writeln;

          close(F); {Close the file}
        end;
    end
  else
    begin
      writeln;
      writeln('USAGE:'); {How to use GRPINFO}
      writeln;
      writeln('  GRPINFO <GROUPFILE.GRP>');
    end;
END.

/*

   GRP.H

   Oliver Kraus
   kraus@lrs.e-technik.uni-erlangen.de

*/

#ifndef _GRP_H
#define _GRP_H

#include <stdio.h>

struct _grp_dir_struct
{
   char     label[12];
   long     size;
};
typedef struct _grp_dir_struct grp_dir_struct;
typedef struct _grp_dir_struct *grp_dir_type;

struct _grp_struct
{
   char *fname;
   FILE *fp;

   int is_id_ok;

   long dir_list_len;
   grp_dir_type dir_list_ptr;
   long *dir_list_idx;
   int is_dir_ok;

};
typedef struct _grp_struct grp_struct;
typedef struct _grp_struct *grp_type;

int is_grp_file(char *name);
grp_type grp_Open(char *fname);
void grp_Close(grp_type grp);
int grp_LoadDirectory(grp_type grp);
int grp_SeekFile(grp_type grp, long n);


#endif

 

 

/*

	GRP.C

   Oliver Kraus
   kraus@lrs.e-technik.uni-erlangen.de

*/


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

int map_name_compare(char *n1, char *n2);


/* global vars */

char *grp_id_str = "KenSilverman";
#define GRP_ID_SIZE 12

grp_type qsort_grp;

int grp_dir_cmp( void *elem1, void *elem2 )
{
   int i1 = *(int *)(elem1);
   int i2 = *(int *)(elem2);
   return map_name_compare(qsort_grp->dir_list_ptr[i1].label,
                           qsort_grp->dir_list_ptr[i2].label);
}

int is_grp_file(char *name)
{
	FILE *fp;
	char s[GRP_ID_SIZE];

	fp = fopen(name, "rb");
	if ( fp == NULL )
		return 0;

	if ( fread(s, GRP_ID_SIZE, 1, fp) != 1 )
	{
		fclose(fp);
		return 0;
	}
	if ( strncmp(s, grp_id_str, GRP_ID_SIZE) != 0 )
	{
		fclose(fp);
		return 0;
	}
	fclose(fp);
	return 1;
}

grp_type grp_Open(char *fname)
{
	grp_type grp;
	grp = (grp_type)malloc(sizeof(grp_struct));
	if ( grp != NULL )
	{
		grp->fname = (char *)malloc(strlen(fname)+1);
		if ( grp->fname != NULL )
		{
			strcpy(grp->fname, fname);
			grp->fp = fopen(fname, "rb");
			if ( grp->fp != NULL )
			{
				grp->is_id_ok = 0;
				grp->dir_list_len = 0L;
				grp->dir_list_ptr = NULL;
            grp->dir_list_idx = NULL;
				grp->is_dir_ok = 0;
				return grp;
			}
			free(grp->fname);
		}
		free(grp);
	}
	return NULL;
}

void grp_Close(grp_type grp)
{
	if ( grp != NULL )
	{
		if ( grp->dir_list_ptr != NULL )
			free(grp->dir_list_ptr);
      if ( grp->dir_list_idx != NULL )
         free(grp->dir_list_idx);
		fclose(grp->fp);
		free(grp->fname);
		free(grp);
	}
}

int grp_SeekFile(grp_type grp, long n)
{
   long pos, i;
   if ( n >= grp->dir_list_len )
      return 0;
   pos = GRP_ID_SIZE;
   pos += sizeof(long);
   pos += (long)sizeof(grp_dir_struct)*grp->dir_list_len;
   for( i = 0; i < n; i++ )
   {
      pos += grp->dir_list_ptr[i].size;
   }
   if ( fseek(grp->fp, pos, SEEK_SET) != 0 )
   {
      perror("seek within grp file");
		return 0;
   }
   return 1;
}

int grp_LoadId(grp_type grp)
{
	char *task = "grp file identifier";
	char s[GRP_ID_SIZE];

	if ( fseek(grp->fp, 0L, SEEK_SET) != 0 )
	{
		perror(task);
		return 0;
	}
	if ( fread(s, GRP_ID_SIZE, 1, grp->fp) != 1 )
	{
		perror(task);
		return 0;
	}
	if ( strncmp(s, grp_id_str, GRP_ID_SIZE) != 0 )
	{
		fprintf(stderr, "%s: wrong identifier in file %s\n", task, grp->fname);
		return 0;
	}
	grp->is_id_ok = 1;
	return 1;
}

int grp_LoadDirectory(grp_type grp)
{
	char *task = "grp directory";

	if ( grp->is_id_ok == 0 )
		if ( grp_LoadId(grp) == 0 )
			return 0;

	if ( fseek(grp->fp, GRP_ID_SIZE, SEEK_SET) != 0 )
	{
		perror(task);
		return 0;
	}

	if (fread(&(grp->dir_list_len), sizeof (long), 1, grp->fp) != 1)
	{
		perror(task);
		return 0;
	}

	if ( grp->dir_list_ptr != NULL )
		free(grp->dir_list_ptr);
	grp->dir_list_ptr =
		(grp_dir_struct *)malloc(grp->dir_list_len*sizeof(grp_dir_struct));
	if ( grp->dir_list_ptr == NULL )
	{
		fprintf(stderr, "%s: out of memory for directory of %s\n", task, grp->fname);
		return 0;
   }
   grp->dir_list_idx =
      (long *)malloc(grp->dir_list_len*sizeof(long));
   if ( grp->dir_list_idx == NULL )
	{
      fprintf(stderr, "%s: out of memory for directory-index of %s\n", task, grp->fname);
		return 0;
   }

	{
		long i;
		for ( i = 0; i < grp->dir_list_len; i++ )
      {
         grp->dir_list_idx[i] = i;
			if (fread(grp->dir_list_ptr+i, sizeof(grp_dir_struct), 1, grp->fp) != 1)
			{
				perror(task);
				return 0;
			}
		}
   }

   qsort_grp = grp;
   qsort( grp->dir_list_idx, grp->dir_list_len, sizeof(long),
            grp_dir_cmp);

	grp->is_dir_ok = 1;

	return 1;
}