Skip to content

Instantly share code, notes, and snippets.

@jojonas
Last active February 21, 2026 14:39
Show Gist options
  • Select an option

  • Save jojonas/8a49555f479030b358ec to your computer and use it in GitHub Desktop.

Select an option

Save jojonas/8a49555f479030b358ec to your computer and use it in GitHub Desktop.

Revisions

  1. jojonas revised this gist Mar 26, 2015. 1 changed file with 12 additions and 4 deletions.
    16 changes: 12 additions & 4 deletions love2d-unpacker.py
    Original file line number Diff line number Diff line change
    @@ -69,10 +69,18 @@ def unpack(executablename, unzipdestination=None, lovefilename=True):


    if __name__=="__main__":
    parser = argparse.ArgumentParser(description='Unpack a love executable.')
    parser.add_argument('-x', '--extract', metavar="DESTINATION", type=str, help="Unzip files to this folder")
    parser.add_argument('-l', '--love', metavar="DESTINATION", type=str, help="Filename of the .love file to write.")
    parser.add_argument('executable', type=str, help='Executable to unpack')
    parser = argparse.ArgumentParser(description="Unpack a love game executable which has been " \
    "fused by appending the .love file to a love binary. Choose one of two modes: either " \
    "just separate the .love file from the binary, using the '--love' (or '-l') option, or " \
    "extract the contained files into a directory specified with '--extract' (or '-x'). You " \
    "can also specify both options.")
    parser.add_argument('-x', '--extract', metavar="UNZIPDESTINATION", type=str, help="Unzip " \
    "files to this folder (it will be created if it doesn't exist, otherwise the contents " \
    "will be overwritten!)")
    parser.add_argument('-l', '--love', metavar="LOVEFILE", type=str, help="Split off the .love " \
    "file and save it to this file (it will be overwritten if it already exists!)")
    parser.add_argument('executable', metavar="EXECUTABLE", type=str, help="The love game " \
    "executable to unpack.")

    args = parser.parse_args()

  2. jojonas revised this gist Mar 26, 2015. 1 changed file with 17 additions and 4 deletions.
    21 changes: 17 additions & 4 deletions love2d-unpacker.py
    Original file line number Diff line number Diff line change
    @@ -11,9 +11,10 @@ def readui32(file):
    number += bytes[3] << 24
    return number

    def gotozipstart(file):
    def skip2zip(file):
    SIGNATURE = b'PK\x05\x06'

    # retrieve the file size
    file.seek(0, 2)
    filesize = file.tell()

    @@ -28,18 +29,30 @@ def gotozipstart(file):
    else:
    raise ValueError("Corrupted zip archive.")

    file.seek(8, 1) # skip 8 bytes
    # skip 8 bytes
    file.seek(8, 1)

    # read size and offset of central directory
    size = readui32(file)
    offset = readui32(file)

    start = (signature_position - offset) - size
    # Calculate beginning of the zip file:
    # There is a "central directory" with the size 'size' located at 'offset' (relative to the zip
    # file). The signature is appended directly after the central directory. We have already found
    # the signature start and know the size of the central directory, so we can calculate the
    # beginning of the central directory via 'signature_position - size'. The result is the "real"
    # offset inside the packed executable. The supposed offset inside the zip file is stored at
    # 'offset', so we can calculate the beginning of the zip-file.
    start = (signature_position - size) - offset

    # seek to the beginning position
    file.seek(start, 0)


    def unpack(executablename, unzipdestination=None, lovefilename=True):
    with open(executablename, 'rb') as executable:
    gotozipstart(executable)
    skip2zip(executable)

    data = executable.read()

    if lovefilename:
  3. jojonas revised this gist Mar 26, 2015. 1 changed file with 46 additions and 27 deletions.
    73 changes: 46 additions & 27 deletions love2d-unpacker.py
    Original file line number Diff line number Diff line change
    @@ -2,38 +2,57 @@
    import os, os.path
    import zipfile
    import io

    def readui32(file):
    bytes = file.read(4)
    number = bytes[0]
    number += bytes[1] << 8
    number += bytes[2] << 16
    number += bytes[3] << 24
    return number

    def gotozipstart(file):
    SIGNATURE = b'PK\x05\x06'

    def iterchunks(file, chunksize):
    while True:
    chunk = file.read(chunksize)
    if not chunk:
    file.seek(0, 2)
    filesize = file.tell()

    # scan the last 65k (2^16) for the zip signature
    signature_position = filesize
    while signature_position > filesize - (2 << 16):
    file.seek(signature_position, 0)
    data = file.read(len(SIGNATURE))
    if data == SIGNATURE:
    break
    else:
    yield chunk

    def unpack(executablename, unzip=None, lovefilename=True):
    MAGIC = b'PK\x03\x04'
    signature_position -= 1
    else:
    raise ValueError("Corrupted zip archive.")

    file.seek(8, 1) # skip 8 bytes
    size = readui32(file)
    offset = readui32(file)

    start = (signature_position - offset) - size

    file.seek(start, 0)


    def unpack(executablename, unzipdestination=None, lovefilename=True):
    with open(executablename, 'rb') as executable:
    for chunk in iterchunks(executable, 32):
    if chunk.startswith(MAGIC):
    break
    else: # nobreak
    raise ValueError("Magic bytes not found.")

    data = chunk + executable.read()
    gotozipstart(executable)
    data = executable.read()

    if lovefilename:
    with open(lovefilename, 'wb') as lovefile:
    lovefile.write(data)

    if lovefilename:
    with open(lovefilename, 'wb') as lovefile:
    lovefile.write(data)
    if unzipdestination:
    if not os.path.isdir(unzipdestination):
    os.makedirs(unzipdestination)

    if unzip:
    if not os.path.isdir(unzip):
    os.makedirs(unzip)

    zipdata = io.BytesIO(data)
    with zipfile.ZipFile(zipdata, 'r') as zip:
    zip.extractall(unzip)
    zipdata = io.BytesIO(data)
    with zipfile.ZipFile(zipdata, 'r') as zip:
    zip.extractall(unzipdestination)


    if __name__=="__main__":
    @@ -44,5 +63,5 @@ def unpack(executablename, unzip=None, lovefilename=True):

    args = parser.parse_args()

    unpack(args.executable, unzip=args.extract, lovefilename=args.love)
    unpack(args.executable, unzipdestination=args.extract, lovefilename=args.love)

  4. jojonas revised this gist Mar 25, 2015. 1 changed file with 5 additions and 2 deletions.
    7 changes: 5 additions & 2 deletions love2d-unpacker.py
    Original file line number Diff line number Diff line change
    @@ -28,15 +28,18 @@ def unpack(executablename, unzip=None, lovefilename=True):
    lovefile.write(data)

    if unzip:
    if not os.path.isdir(unzip):
    os.makedirs(unzip)

    zipdata = io.BytesIO(data)
    with zipfile.ZipFile(zipdata, 'r') as zip:
    zip.extractall(unzip)


    if __name__=="__main__":
    parser = argparse.ArgumentParser(description='Unpack a love executable.')
    parser.add_argument('-x', '--extract', type=str, help="Unzip files to this folder")
    parser.add_argument('-l', '--love', type=str, help="Filename of the .love file to write.")
    parser.add_argument('-x', '--extract', metavar="DESTINATION", type=str, help="Unzip files to this folder")
    parser.add_argument('-l', '--love', metavar="DESTINATION", type=str, help="Filename of the .love file to write.")
    parser.add_argument('executable', type=str, help='Executable to unpack')

    args = parser.parse_args()
  5. jojonas created this gist Mar 25, 2015.
    45 changes: 45 additions & 0 deletions love2d-unpacker.py
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,45 @@
    import argparse
    import os, os.path
    import zipfile
    import io

    def iterchunks(file, chunksize):
    while True:
    chunk = file.read(chunksize)
    if not chunk:
    break
    else:
    yield chunk

    def unpack(executablename, unzip=None, lovefilename=True):
    MAGIC = b'PK\x03\x04'

    with open(executablename, 'rb') as executable:
    for chunk in iterchunks(executable, 32):
    if chunk.startswith(MAGIC):
    break
    else: # nobreak
    raise ValueError("Magic bytes not found.")

    data = chunk + executable.read()

    if lovefilename:
    with open(lovefilename, 'wb') as lovefile:
    lovefile.write(data)

    if unzip:
    zipdata = io.BytesIO(data)
    with zipfile.ZipFile(zipdata, 'r') as zip:
    zip.extractall(unzip)


    if __name__=="__main__":
    parser = argparse.ArgumentParser(description='Unpack a love executable.')
    parser.add_argument('-x', '--extract', type=str, help="Unzip files to this folder")
    parser.add_argument('-l', '--love', type=str, help="Filename of the .love file to write.")
    parser.add_argument('executable', type=str, help='Executable to unpack')

    args = parser.parse_args()

    unpack(args.executable, unzip=args.extract, lovefilename=args.love)