LBR Format

From ModdingWiki
Jump to navigation Jump to search
LBR Format
Format typeArchive
Max files65535
File Allocation Table (FAT)Beginning
Filenames?Yes, hashed
Metadata?None
Supports compression?No
Supports encryption?No
Supports subdirectories?No
Hidden data?No
Games

The LBR Format is used by Vinyl Goddess From Mars to store most of the game data.

File format

Signature

There is no known signature for this format. One method to identify files is to read in the file entries and ensure all the offsets are within range (and perhaps increasing in value.) It is somewhat unlikely that this method would incorrectly identify a file.

Header

The header begins at the start of the file (offset 0.)

Data type Description
UINT16LE numFiles Number of files stored within

File entry

After the header, the following structure is repeated once for each file.

Data type Description
UINT16LE hash Hash of the filename
UINT32LE offset Offset into LBR where file data begins

Each file's data starts at the offset indicated. File sizes must be calculated by comparing adjacent offsets, or in the case of the last file, comparing its offset to the total size of the LBR.

Filename hash

Rather than storing filenames, LBR files store a hash calculated from the original filename. If you have a filename it is very easy to calculate the hash, but because hashes are one-way functions, if all you have is a hash it is impossible to calculate the filename (this is because each hash matches at least 56 billion different filenames. Malvineous wrote a tool to list all possible filenames for a hash matching a given prefix and suffix, but the many thousands of names produced makes this a very tedious and error-prone way of discovering filenames.)

Some example code to calculate the hashes follows.

int calcHash(const std::string& data)
{
  int hash = 0;
  for (std::string::const_iterator i = data.begin(); i != data.end(); i++) {
    hash ^= *i << 8;
    for (int j = 0; j < 8; j++) {
      hash <<= 1;
      if (hash & 0x10000) hash ^= 0x1021;
    }
  }
  return hash & 0xffff;
}

Optimized C code:

/* original code in GODDESS.EXE at offset 0x00016622
self test: lbr_hash("Casplat1.cmp") == 0xBDCB */
uint16_t lbr_hash(char *s) {
uint16_t hash;
uint8_t b, i;
  hash = 0;
  if (s) {
    for (; *s; s++) {
      /* a..z => A..Z */
      b = toupper(*s);
      hash ^= (b << 8);
      for (i = 0; i < 8; i++) {
        /* b = (hash & 0x8000) ? 1 : 0; */
        b = hash >> 15;
        hash <<= 1;
        /* hash ^= b ? 0x1021 : 0; */
        hash ^= (b << 12) | (b << 5) | b;
      }
    }
  }
  return(hash);
}

Vgfmext also contains BASIC code to calculate hashes, using a slight variation.

List of known filenames

Here is a list of known filenames inside the archive. They can be passed to the hash function to calculate the hash, then matched against an entry in the archive to give that entry a filename.

1000P.CMP 100P.CMP 250P.CMP 500P.CMP 50P.CMP APPLE.CMP APPLE.SND BAMBOOP.CMP BAPPLE0.OMP BETA.BIN BGRENSHT.CMP BLOOK.CMP BLUEBALL.CMP BLUEKEY.CMP BLUE.PAL BLUE.TLS BOTTLE.CMP BOUNCE.CMP BRAIN.CMP BREATH.CMP BRIDGE.CMP BSHOT.CMP BUTFLY.CMP CANNON.CMP CASPLAT1.CMP CASPLAT2.CMP CASPLAT3.CMP CASPLAT4.CMP CASTLE.PAL CASTLE.TLS COVERUP.MUS CREDITS.PAL CREDITS.SCR CRUSH.MUS CSTARS.CMP DATA.DAT DDARKBAR2.GRA DEATH.CMP DEMO_1.DTA DEMO_2.DTA DEMO_3.DTA DIFFBUTN.CMP DIFFMENU.CMP DOTS1.CMP DUNGEON.PAL DUNGEON.TLS DUNPLAT1.CMP DUSTCLUD.CMP ECHOT1.CMP EGYPPLAT.CMP EGYPT.PAL EGYPT.TLS ENDBOSSW.CMP ENDING.SCN ENTER2.SND EPISODE.PAL EPISODE.SCR EVILEYE.MUS EXIT.CMP EXPL1.SND FEVER.MUS FIRE231.CMP FRUIT.SND GAME1.PAL GAMEOPT.GRA GATEKEY.CMP GOLDKEY.CMP GRAVE.PAL GRAVE.TLS GREYKEY.CMP GRID.DTA HARDHEAD.CMP HEALJUG.CMP HEALPOT.CMP HEALPOTD.CMP HEALPOT.SND HELLO.T HORUS.MUS HURT.SND HUTS.PAL HUTS.TLS INBET.PAL INBETW.SCR INOUTP00.CMP INSURED.MUS INTRO.MUS JFIREB.CMP JILL.CMP JILLEXPB.CMP JILLEXP.CMP JILLFIRE.CMP JILL.SPR JUNGLE2.FON JUNGLE.FON KNIFE.CMP LAND.SND LC_CAPS.RAW LC_NUMS.RAW LEVEL1-1.M LEVEL1-2.M LEVEL1-3.M LEVEL1-4.M LEVEL1-5.M LEVEL1-6.M LEVEL1-7.M LEVEL1-8.M LEVEL1-9.M LEVEL2-1.M LEVEL2-2.M LEVEL2-3.M LEVEL2-4.M LEVEL2-5.M LEVEL2-6.M LEVEL2-7.M LEVEL2-8.M LEVEL2-9.M LEVEL3-1.M LEVEL3-2.M LEVEL3-3.M LEVEL3-4.M LEVEL3-5.M LEVEL3-6.M LEVEL3-7.M LEVEL3-8.M LEVEL3-9.M LGRENSHT.CMP LITSCROL.CMP MAINFONT.GRA MANEATPL.CMP MENU2.RAW MENUCH.GRA MENUCLIK.SND MENU.RAW MENUYSNO.GRA MIDLEVEL.CMP MIDPOST.SND MMREST.GRA MONDIE.SND MOUNT.TLS MPLAT211.CMP MPLAT212.CMP MPLAT221.CMP MPLAT311.CMP MPLAT331.CMP MPLAT332.CMP MUSHSHOT.CMP MYSTIC.MUS NEWBEH.CMP OLDBEH.CMP ORDER.RES OSIRIS.MUS OUTGATE.CMP OVERHEAD.PAL OVERHEAD.TLS OVERHED1.MAP OVERHED2.MAP OVERHED3.MAP PAN2.SND PRESENT.GRA PRESENT.PAL PROWLER.MUS PURPLE.PAL PURPLE.TLS PUZZ6.MUS RABBIT.CMP RABBITD.CMP REDKEY.CMP RETROJIL.MUS RING.CMP RUFEYE.CMP RUFEYES.CMP RUFEYSE.CMP SAVEBOXG.GRA SAVEBOXO.GRA SCORE.CMP SCROLLG.CMP SCROLLO.CMP SGREENE.CMP SHOTEXPL.CMP SHOTTEST.CMP SHWRREM.GRA SIXPS.GRA SIXPS.PAL SKELBONE.CMP SKELETON.CMP SKELETON.SND SKELFLY.CMP SMALLEX.CMP SMALNUM.CMP SPARE.SCR SPIKEBA.CMP SPLADY.CMP SPLAT211.CMP SPLAT223.CMP SPLAT231.CMP SPRING.SND SPROIN.CMP SQUARE.TLS STAR.CMP STARDUST.MUS STHORNSH.CMP STICKEYE.CMP STIKHORN.CMP STLSPIKE.CMP STORY.PAL STORY.SCR STRIKE.MUS STRYFNT1.GRA SVINYL.SPR TAFA.MUS T.CMP TEST0004.CMP THROW.SND TITLE.PAL TITLE.SCR TORNADO.CMP TRAMPLE.MUS TREEMPLA.CMP TREES.PAL TREES.TLS TWILIGHT.MUS UGH.CMP UNLOGIC1.GRA UNLOGIC1.PAL UNLOGIC.UNM VINE.CMP VINYLDIE.SND VINYL.GRA VINYL.PAL VINYL.SPR VSMALLE.CMP WEAPBLNK.OMP WEAPBLUE.OMP WEAPBOTL.OMP WEAPFIRE.OMP WEAPFSKF.OMP WEAPSLKF.OMP WEAPSTAR.OMP WFIREB.CMP WOODSPIK.CMP XHUTS.PAL YELLOW.PAL YELLOW.TLS YES.CMP

// These names were guessed by looking at others
ENDG1.PAL ENDG1.SCR ENDG2.PAL ENDG2.SCR ENDG3.PAL ENDG3.SCR MOUNT.PAL JUNGLE3.FON

// These names were brute-forced from the hashes against a dictionary so they
// could be wrong (each hash matches about 56 billion different filenames...)
BEGIN.PAL // Also ARCHIL.PAL. Before Bl so probably correct.
P.PAL // Also SANGGIL.PAL. Between O-P maybe correct.

// These names were guessed from the music filenames but with a different
// extension for the instruments.
COVERUP.TIM CRUSH.TIM EVILEYE.TIM FEVER.TIM HORUS.TIM INSURED.TIM INTRO.TIM MYSTIC.TIM OSIRIS.TIM PROWLER.TIM PUZZ6.TIM RETROJIL.TIM STARDUST.TIM STRIKE.TIM TAFA.TIM TRAMPLE.TIM TWILIGHT.TIM

Tools

The following tools are able to work with files in this format.

Name PlatformExtract files? Decompress on extract? Create new? Modify? Compress on insert? Access hidden data? Edit metadata? Notes
Camoto Linux/WindowsYesN/AYesYesN/AN/AN/A
Camoto/gamearchive.js AnyYesN/AYesYesN/AN/AN/A
Vgfmext DOSYesN/ANoNoN/AN/AN/A

Credits

This file format was reverse engineered by Frenkel Smeijers with the hash algorithm refined by Malvineous. If you find this information helpful in a project you're working on, please give credit where credit is due. (A link back to this wiki would be nice too!)