Howto Extract PNG from binary file @ 0x80?

Discussion in 'Python' started by flamesrock, Dec 11, 2004.

  1. flamesrock

    flamesrock Guest

    Hi,

    As a newbie to the language, I have no idea where to start..please bare
    with me..

    The simcity 4 savegame file has a png image stored at the hex location
    0x80. What I want to extract it and create a file with the .png
    extension in that directory.

    Can somebody explain with a snippet of code how I would accomplish
    this? I've looked around but the information is too vague and I don't
    know how to start.

    -thanks in advance
    flamesrock, Dec 11, 2004
    #1
    1. Advertising

  2. "flamesrock" <> wrote:

    > As a newbie to the language, I have no idea where to start..please bare
    > with me..
    >
    > The simcity 4 savegame file has a png image stored at the hex location
    > 0x80. What I want to extract it and create a file with the .png
    > extension in that directory.
    >
    > Can somebody explain with a snippet of code how I would accomplish
    > this? I've looked around but the information is too vague and I don't
    > know how to start.


    there are *many* ways to ignore the first 128 bytes when you read a file
    (you can seek to the right location, you can read 128 bytes and throw them
    a way, you can read one byte 128 times and throw each one of them away,
    you can read all data and remove the first 128 bytes, etc).

    here's a snippet that understands the structure of the PNG, and stops copying
    when it reaches the end of the PNG:

    import struct

    def pngcopy(infile, outfile):

    # copy header
    header = infile.read(8)
    if header != "\211PNG\r\n\032\n":
    raise IOError("not a valid PNG file")
    outfile.write(header)

    # copy chunks, until IEND
    while 1:
    chunk = infile.read(8)
    size, cid = struct.unpack("!l4s", chunk)
    outfile.write(chunk)
    outfile.write(infile.read(size))
    outfile.write(infile.read(4)) # checksum
    if cid == "IEND":
    break

    to use this, open the input file (the simcity file) and the output file
    (the png file you want to create) in binary mode, use the "seek"
    method to move to the right place in the simcity file, and call "pngcopy"
    with the two file objects.

    infile = open("mysimcityfile", "rb")
    infile.seek(0x80)
    outfile = open("myimage.png", "wb")
    pngcopy(infile, outfile)
    outfile.close()
    infile.close()

    hope this helps!

    </F>
    Fredrik Lundh, Dec 11, 2004
    #2
    1. Advertising

  3. flamesrock

    Aaron Guest

    On Sat, 11 Dec 2004 10:53:19 +0100, Fredrik Lundh wrote:

    > "flamesrock" <> wrote:
    >
    >> As a newbie to the language, I have no idea where to start..please bare
    >> with me..
    >>
    >> The simcity 4 savegame file has a png image stored at the hex location
    >> 0x80. What I want to extract it and create a file with the .png
    >> extension in that directory.
    >>
    >> Can somebody explain with a snippet of code how I would accomplish
    >> this? I've looked around but the information is too vague and I don't
    >> know how to start.

    >
    > there are *many* ways to ignore the first 128 bytes when you read a file
    > (you can seek to the right location, you can read 128 bytes and throw them
    > a way, you can read one byte 128 times and throw each one of them away,
    > you can read all data and remove the first 128 bytes, etc).
    >
    > here's a snippet that understands the structure of the PNG, and stops copying
    > when it reaches the end of the PNG:
    >
    > import struct
    >
    > def pngcopy(infile, outfile):
    >
    > # copy header
    > header = infile.read(8)
    > if header != "\211PNG\r\n\032\n":
    > raise IOError("not a valid PNG file")
    > outfile.write(header)
    >
    > # copy chunks, until IEND
    > while 1:
    > chunk = infile.read(8)
    > size, cid = struct.unpack("!l4s", chunk)
    > outfile.write(chunk)
    > outfile.write(infile.read(size))
    > outfile.write(infile.read(4)) # checksum
    > if cid == "IEND":
    > break
    >
    > to use this, open the input file (the simcity file) and the output file
    > (the png file you want to create) in binary mode, use the "seek"
    > method to move to the right place in the simcity file, and call "pngcopy"
    > with the two file objects.
    >
    > infile = open("mysimcityfile", "rb")
    > infile.seek(0x80)
    > outfile = open("myimage.png", "wb")
    > pngcopy(infile, outfile)
    > outfile.close()
    > infile.close()
    >
    > hope this helps!
    >
    > </F>


    Thanks! And what a pleasant surprise! I just noticed you authored the book
    I've been studying - Python Standard Library. Its awesome ;)

    Oh- and Sorry for the late response. Google groups wouldn't allow me to
    followup. I'm using pan now.
    Aaron, Dec 23, 2004
    #3
  4. flamesrock

    flamesrock Guest

    Well, I've been working on getting this code to work, and I think I
    *almost* have it...

    First, according to ghex, it seems the PNG starts at the 97th byte of
    the file
    infile = open("mysimcityfile.sc4", "rb")
    infile.seek(97)
    print (infile.read(4))
    output:
    flamesrock@flames:~/score$ python sc4png.py
    PNG
    (for 5 bytes it outputs an extra line, and for 6- 
    (the biology symbol for a male)) So to debug further, I took out the
    exception and modified the code like this:

    #import struct
    #
    #def pngcopy(infile, outfile):
    #
    # # copy header
    # header = infile.read(4)
    # #if header != "\211PNG\r\n\032\n":
    # # raise IOError("not a valid PNG file")
    # outfile.write(header)
    #
    # # copy chunks, until IEND
    # while 1:
    # chunk = infile.read(8)
    # size, cid = struct.unpack("!l4s", chunk)
    # outfile.write(chunk)
    # outfile.write(infile.read(size))
    # outfile.write(infile.read(4)) # checksum
    # if cid == "IEND":
    # break
    #
    #
    #infile = open("mysimcityfile.sc4", "rb")
    #infile.seek(97)
    #outfile = open("myimage.png", "wb")
    #pngcopy(infile, outfile)
    #outfile.close()
    #infile.close()

    returning the following output:
    flamesrock@flames:~/score$ python sc4png.py
    Traceback (most recent call last):
    File "sc4png.py", line 26, in ?
    pngcopy(infile, outfile)
    File "sc4png.py", line 14, in pngcopy
    size, cid = struct.unpack("!l4s", chunk)
    struct.error: unpack str size does not match format

    Any ideas on how to fix it? If I understand this page correctly,
    http://www.python.org/doc/current/lib/module-struct.html
    a png is basically a 'big endian string of 14 chars'? Changing it to
    !14b" gives a"ValueError: unpack tuple of wrong size"
    -thanks in advance for any help
    flamesrock, Jan 4, 2005
    #4
  5. flamesrock

    Robert Kern Guest

    flamesrock wrote:

    [snip]

    > flamesrock@flames:~/score$ python sc4png.py
    > Traceback (most recent call last):
    > File "sc4png.py", line 26, in ?
    > pngcopy(infile, outfile)
    > File "sc4png.py", line 14, in pngcopy
    > size, cid = struct.unpack("!l4s", chunk)
    > struct.error: unpack str size does not match format
    >
    > Any ideas on how to fix it? If I understand this page correctly,
    > http://www.python.org/doc/current/lib/module-struct.html
    > a png is basically a 'big endian string of 14 chars'? Changing it to
    > !14b" gives a"ValueError: unpack tuple of wrong size"
    > -thanks in advance for any help


    No, the chunk header is specified as "!l4s" where that second character
    is a lower-case "L", not one "1". The struct is "a 4-byte long integer
    in big-endian format followed by a 4-character string".

    I think the error you are running into is that the chunk you are passing
    in does not have 8 characters. Is your input truncated somehow?

    --
    Robert Kern


    "In the fields of hell where the grass grows high
    Are the graves of dreams allowed to die."
    -- Richard Harter
    Robert Kern, Jan 4, 2005
    #5
  6. flamesrock

    flamesrock Guest

    Hmm...I'm not sure exactly. One of the things I've read is that
    sometimes stuff is compressed in a savegame file, but I don't know why
    they'd do that with a png..

    I have some code that does the exact same thing in php only I don't
    understand php syntax and how they did it. Does it give any clues as to
    whether its truncated or not? (assuming you understand php)
    http://simcitysphere.com/sc4Extractor.php.txt (original, only extracts
    png)
    http://simcitysphere.com/regionFileDecode_inc.php.txt (extracts png and
    some fancy stuff like image map position)

    -thanks
    flamesrock, Jan 4, 2005
    #6
  7. flamesrock

    Robert Kern Guest

    Don't seek to position 97. Seek to 96. You're off by one. That's why
    there's the test for the header (which you removed).

    Files (and Python strings for that matter) are 0-indexed. The first
    character is 0, the second 1, and so on. If you want the 97th character,
    you have to seek to position 96.

    --
    Robert Kern


    "In the fields of hell where the grass grows high
    Are the graves of dreams allowed to die."
    -- Richard Harter
    Robert Kern, Jan 4, 2005
    #7
  8. flamesrock

    flamesrock Guest

    omg, it works!! thankyou!!!!!
    and thankyou again, Frederick for the original code!

    I've discovered a weird thing when changing header = infile.read(8) to
    header = infile.read(4):
    For some reason, the extracted png image is the exact same size as the
    savegame archive (10.1 megs.) When reading 8bytes, its closer to
    ~200kb. Any clue as to why that is?

    -thanks

    #import struct
    #
    #def pngcopy(infile, outfile):
    #
    # # copy header
    # header = infile.read(8)
    # if header != "\211PNG\r\n\032\n":
    # raise IOError("not a valid PNG file")
    # outfile.write(header)
    #
    # # copy chunks, until IEND
    # while 1:
    # chunk = infile.read(8)
    # size, cid = struct.unpack("!l4s", chunk)
    # outfile.write(chunk)
    # outfile.write(infile.read(size))
    # outfile.write(infile.read(4)) # checksum
    # if cid == "IEND":
    # break
    #
    #
    #infile = open("mysimcityfile.sc4", "rb")
    #infile.seek(96)
    ###print (infile.read(4))
    #outfile = open("myimage.png", "wb")
    #pngcopy(infile, outfile)
    #outfile.close()
    #infile.close()

    (http://simcitysphere.com/peachville.sc4)
    flamesrock, Jan 4, 2005
    #8
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.

Share This Page