We just moved to a different server. Please be patient until all files and pages are restored and the MediaWiki software has been updated. Thank you

Operation Flashpoint

From REWiki
Jump to: navigation, search


This game uses a custom .PBO file format for storing multiple file/directory trees in one file.

.BPO file format

The format has a header with file entries delimited by an entry with an empty filename and then the contents of every file in order of appearance in the header.

A header entry has the following structure (note that off is the offset from the byte after the end of the terminating null byte in the file path; all numbers are little-endian):

Header entry size: 14h + full_path_length + 1 
╔═════╤═══════════╤════════╤═════════════════════════════════════════════════════════╗
║ off │ name      │ length │ description                                             ║
╟─────┼───────────┼────────┼─────────────────────────────────────────────────────────╢
║ --  │ full path │ --     │ full path of the file, eg. "dirone\dirtwo\filename.txt" ║
║     │           │        │ as a null-terminated string                             ║
║ 00h │ unknown   │ 0Ch    │ unknown, seems unused                                   ║
║ 0Ch │ date      │ 04h    │ creation date as returned by time(2)                    ║
║ 10h │ size      │ 04h    │ file size in bytes                                      ║
╚═════╧═══════════╧════════╧═════════════════════════════════════════════════════════╝

Extractor

A simple extractor follows (maybe this should be placed in a separate article or as a file). Note that it runs on GNU/Linux, but could be easily ported to Windows.

/* This code is public domain */
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <stdlib.h>
#include <libgen.h>
#include <time.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>

struct pbo_entry
{
  uint32_t unknown[3];
  uint32_t date;
  uint32_t size;
} __attribute__((packed));

struct pbo_entry_list
{
  const char *file_name;
  struct pbo_entry entry;
  struct pbo_entry_list *next;
};

static void unpack_file(const char *fname);
static int extract_file(FILE *f, const char *filename, int len);
static int mkdirs(const char *path);
static int read_cstring(FILE *f, char *buf, int buflen);
static int dump_file(FILE *f, const char *filename, int size);

int
main(int argc, char **argv)
{
  int x;

  if (argc < 2)
  {
    printf("Usage: unpbo FILENAME(s)\n");
    return 1;
  }

  for (x = 1; x < argc; x++)
    unpack_file(argv[x]);

  return 0;
}

static void
unpack_file(const char *fname)
{
  FILE *file;
  char *p;
  struct pbo_entry_list *entries = NULL;
  struct pbo_entry_list *node;
  struct pbo_entry_list *tmp_list, *tmp;
  struct pbo_entry ent;
  char buf[512];

  if ((file = fopen(fname, "r")) == NULL)
    return;

  for (;;)
  {
    if (read_cstring(file, buf, sizeof(buf)) < 0)
    {
      perror("read_cstring");
      goto end;
    }

    for (p = buf; *p != 0; p++)
    {
      if (*p == '\\')
	*p = '/';
    }

    if (fread(&ent, sizeof(ent), 1, file) != 1)
    {
      perror("fread");
      goto end;
    }

    if (strlen(buf) == 0)
      break;

    node = calloc(1, sizeof(*node));
    node->file_name = strdup(buf);
    node->entry = ent;
    node->next = entries;
    entries = node;
  }

  tmp_list = entries;
  entries = NULL;

  for (node = tmp_list; node != NULL;)
  {
    tmp = node;
    node = node->next;
    tmp->next = entries;
    entries = tmp;
  }

  for (node = entries; node != NULL; node = node->next)
  {
    struct tm *tm;

    tm = localtime(((time_t *)&node->entry.date));
    printf("unknowns: {%.8x, %.8x, %.8x}, size: %i\n",
	   node->entry.unknown[0], node->entry.unknown[1],
	   node->entry.unknown[2], node->entry.size);
    printf("time: %s", asctime(tm));
    
    if (extract_file(file, node->file_name, node->entry.size) != 0)
      goto end;
  }

end:
  fclose(file);
}

static int
read_cstring(FILE *f, char *buf, int buflen)
{
  int c;
  int pos = 0;

  do
  {
    if ((c = fgetc(f)) < 0)
      return -1;

    if (pos < buflen)
      buf[pos] = c;

    pos += 1;
  }
  while (c != 0);

  return pos;
}

static int
extract_file(FILE *f, const char *filename, int len)
{
  printf("%s\n", filename);

  if (mkdirs(filename) != 0)
    return -1;

  if (dump_file(f, filename, len) != 0)
    return -1;

  return 0;
}

static int
mkdirs(const char *path)
{
  char buffer[512];
  char dir[512];
  char *p, *e;
  struct stat st;
  
  strncpy(buffer, path, sizeof(buffer) - 1);
  buffer[sizeof(buffer) - 1] = 0;

  p = buffer;
  dir[0] = 0;
  dir[sizeof(dir) - 1] = 0;

  while ((e = strchr(p, '/')) != NULL)
  {
    *e = 0;
    
    if (dir[0] != 0)
      strncat(dir, "/", sizeof(dir) - 1);

    strncat(dir, p, sizeof(dir) - 1);

    printf("dir: %s\n", dir);

    if (stat(dir, &st) == 0)
    {
      if (!S_ISDIR(st.st_mode))
      {
	fprintf(stderr, "Error: %s is in the way\n", dir);
	return -1;
      }

      if (access(dir, R_OK | W_OK | X_OK) != 0)
      {
	perror(dir);
	return -1;
      }
    }
    else if (errno == ENOENT)
    {
      if (mkdir(dir, 0777) != 0)
      {
	perror(dir);
	return -1;
      }
    }
    else
    {
      perror(dir);
      return -1;
    }

    p = e + 1;
  }

  return 0;
}

static int
dump_file(FILE *f, const char *fname, int size)
{
  char buffer[8192];
  int x;
  FILE *out = NULL;

  if ((out = fopen(fname, "w")) == NULL)
    return -1;
  
  while (size > 0)
  {
    x = (size < sizeof(buffer)) ? size : sizeof(buffer);

    if ((x = fread(buffer, 1, x, f)) <= 0)
    {
      fprintf(stderr, "fread failed\n");
      goto err;
    }

    size -= x;

    if ((x = fwrite(buffer, 1, x, out)) != x)
    {
      fprintf(stderr, "fwrite failed\n");
      goto err;
    }
  }

  fclose(out);
  return 0;

err:
  if (out != NULL)
    fclose(out);

  return -1;
}
Personal tools