LGP Format - niemasd/PyFF7 GitHub Wiki

LGP is an archive file format and is used in many file packages in Final Fantasy VII. Files are not compressed when they are packed into an LGP archive.

Section 1: File Header

An LGP archive's File Header gives general information about the LGP archive.

12 bytes: File Creator

  • This is a "right-aligned" string: strings shorter than 12 characters are left-padded with <NULL> (i.e., 0x0)
  • In FF7, it's always <NULL><NULL>SQUARESOFT
  • In some mods, it's FICEDULA-LGP to indicate that the file is an "patch" (i.e., not a complete archive)

4 bytes: Number of Files

  • This is a 4-byte integer denoting the number of files in the archive

Section 2: Table of Contents

The Table of Contents stores information about the files stored in the LGP archive. There is one entry for each file in the archive (see Number of Files). Each entry is 27 bytes and contains the following sections:

20 bytes: Filename

  • This is a "left-aligned" string: strings shorter than 20 characters are right-padded with <NULL> (i.e., 0x0)

4 bytes: Data Start Position

  • This is a 4-byte integer denoting the position in this LGP archive where the data for this file starts

1 byte: Check Code

  • It's unclear what this is, but it seems to be some form of a "check code" (e.g. file attribute?)
  • It normally seems to be 14, but it varies

2 bytes: Conflict Table Index

  • This is a 2-byte short indicating the (1-based) index in the Conflict Table that corresponds to this filename
    • If this filename is unique, the Conflict Table Index is 0, and it doesn't show up in the Conflict Table

Section 3: Lookup Table

The Lookup Table is always 3,600 bytes. It is a hash table with 900 entries, where each entry is composed of two 2-byte integers (ToC index followed by file count). Every filename can be hashed to a Lookup Table index using just its first two characters. For the i-th slot of the Lookup Table, the first 2-byte integer denotes the earliest ToC index of all files hashing to slot i, and the second 2-byte integer denotes the total number of files hashing to slot i.

Given a filename name, the corresponding (0-based) Lookup Table index is computed as follows:

MAX_LOOKUP_HASH = 30

def hash(c):
    if c == '.':
        return -1
    elif c == '-':
        return 10        # 'k' - 'a'
    elif c == '_':
        return 11        # 'l' - 'a'
    elif str.isdigit(c): # '0' <= c <= '9'
        return ord(c) - ord('0')
    elif str.isalpha(c): # 'a' <= c <= 'z' or 'A' <= c <= 'Z'
        return ord(c.lower()) - ord('a')
    else:                # invalid character
        raise ValueError

def index(name): # filename does not include folders: just the actual file
    hash(name[0]) * MAX_LOOKUP_HASH + hash(filename[1]) + 1

Section 4: Conflict Table

The Conflict Table stores directory information for files that have conflicting filenames but are in different directories. The first 2 bytes are an integer designating the number of conflicts (i.e., the number of entries in the Conflict Table).

Each entry starts with a 2 byte integer designating the number locations this entry corresponds to (i.e., how many files have this exact filename, but in different folders). For each of these locations, we have 128 bytes designating the full path to the corresponding folder as a "left-aligned" string (right-padded with <NULL>, i.e., 0x0), followed by a 2 byte integer designating the corresponding 0-based index in the Table of Contents.

Section 5: File Data

There is one entry for each file in the archive (see Number of Files). Note that there can be dummy space between the entries, so it is safer to go directly off the positions listed in the Table of Contents instead of iteratively going byte-by-byte. Each entry is 24+n bytes and contains the following sections:

20 bytes: Filename

  • This is a "left-aligned" string: strings shorter than 20 characters are right-padded with <NULL> (i.e., 0x0)

4 bytes: File Size

  • This is a 4-byte integer denoting the length of this file

n bytes: File Data

  • This is the actual data of this file, and its length is specified in the previous section of the entry (File Size)
  • NOTE: In some LGP archives, you can have filename conflicts on a case-insensitive filesystem (e.g. smab and SMAB in battle.lgp)
    • In this case, my LGP Info script will work fine regardless, but my LGP Unpacker may not work on your machine
  • NOTE: In some LGP archives, there are extra files that do not appear in the Table of Contents (e.g. battle.lgp)
    • My LGP Unpacker now handles this properly, assuming you don't run into the case-sensitivity issue described above
    • However, it seems like this only happens with the case-insensitive filename conflicts (e.g. smab and SMAB)
  • NOTE: The above two issues (case-insensitive filename conflicts and not appearing in the Table of Contents) seem to appear much more frequently than originally thought (definitely not just in battle.lgp)

Section 6: Terminator

The terminator is the remaining content following the end of the last data entry. It's a string that is terminated by EOF (as opposed to NULL). For all official LGP archives, it's FINAL FANTASY7.

Summary

  • File Header
    • 12 bytes (File Creator)
      • Default: <NULL><NULL>SQUARESOFT
    • 4 bytes (Number of Files, nF)
  • Table of Contents (nF entries)
    • File 1
      • 20 bytes (Filename)
      • 4 bytes (Data Start Position, 0-based)
      • 1 byte (Check Code?)
      • 2 bytes (Conflict Table Index)
    • ...
    • File nF
      • ...
  • Lookup Table (900 entries)
    • Entry 1
      • 2 bytes (Table of Contents Index)
      • 2 bytes (File Count)
    • ...
    • Entry 900
      • ...
  • Conflict Table (nC entries)
    • 2 bytes (Number of Conflicts, nC)
      • Nothing else in Conflict Table if nC = 0
    • Conflict 1
      • 2 bytes (Number of Locations, nL)
      • Location 1
        • 128 bytes (Full Path to Folder)
        • 2 bytes (Table of Contents Index)
      • ...
      • Location nL
        • ...
    • ...
    • Conflict nC
      • ...
  • File Data (nF entries)
    • File 1
      • 20 bytes (Filename)
      • 4 bytes (File Size, s)
      • s bytes (File Data)
    • ...
    • File nF
      • ...
    • Potentially extra files not in the Table of Contents
  • File Terminator
    • Remaining bytes until EOF
    • Default: FINAL FANTASY7
⚠️ **GitHub.com Fallback** ⚠️