LBR Format
Format type | Archive |
---|---|
Max files | 65535 |
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 | Platform | Extract files? | Decompress on extract? | Create new? | Modify? | Compress on insert? | Access hidden data? | Edit metadata? | Notes |
---|---|---|---|---|---|---|---|---|---|
Camoto | Linux/Windows | Yes | N/A | Yes | Yes | N/A | N/A | N/A | |
Camoto/gamearchive.js | Any | Yes | N/A | Yes | Yes | N/A | N/A | N/A | |
Vgfmext | DOS | Yes | N/A | No | No | N/A | N/A | N/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!)