Home ยป How can I store game metadata in a .png file?

How can I store game metadata in a .png file?


If all you truly needed was the PNG file, chances are they just simply added the information into the file. This is actually a practice of Steganography. A lot of the times, this is used to hide payloads or secret messages in things that are seemingly public facing. However, it is likely in this case that this method is what was used. Typical Stegongraphy will go out of the way to hide the contents, but there is no reason why one could not simply append the data out of the image in the end of the file and retrieve it.

A PNG has the byte signature $89 at the start, so it is possible that the information was inserted after the PNG structure itself and simply parsed by the SPORE game.

However, further research given by the other answers and a search on google reveals that Spore was actually using just a version of Stegongraphy to hide the information in the alpha bits. With this in mind, we can rule out the possibility of appended data or meta-data.

It should be noted that meta data is still a very viable choice, if the data is being parsed locally. If that information might be shared around the web or re-encoded, the export is not guaranteed to keep all your information. When pixel data is used, it can survive losless conversions without an issue.

The PNG format has support for more-or-less arbitrary metadata. The PNG standard defines a PNG file, essentially a series of chunks, some of which are required (and contain the image data). Others, however, are optional. For example, there’s a chunk for storing gamma information or histogram data.

In particular, there is a tEXt chunk that can be used to store arbitrary key/value text pairs. This can be used to ship around any kind of arbitrary data you want, provided you can represent that data as text (which is fairly likely).

You will need a PNG library that allows you access and manipulate these additional chunks (such as the reference library), or you’ll need to write one yourself. Then it’s just a matter of choosing how to encode the data you want as key/value pairs. I’d suggest the following:

  • choose key names that are prefixed with your project’s name or code name as a way of creating a crude “namespace” system and avoid potential conflicts with other application’s uses of the data
  • don’t try to store actual textures this way, store references to those textures that point within your game’s own asset database
  • data such as creature or object size, weight, et cetera — simple scalars, basically — can be trivially stored

In the interests of making a more complete answer, I’ll also point out that there is another approach (previously documented by the @Vaughn and @Alexis’s answers): encode the additional data you want directly in your image pixels, distributing your data across the low-order bits of the color channels. This approach doesn’t require the use of extra metadata, which means you can implement it entirely without relying on it or worrying about external programs incorrectly handling that metadata. It’s also got a very high “cool” factor, and because you only use low-order bits the image will still look correct to the human eye. However, it does mean your image size is a primary controlling factor for the amount of data you can store; if you need more storage you’d need to allocate more pixels to the image.

As others have pointed out, this process is known as steganography.

The developer of Monaco actually made an excellent article on how both they and Spore accomplished this.

The basic summary of what they do is fairly simple:

  • Convert your data into binary
  • Convert your target image into a raw bitmap
  • Walk along the pixels of the image in some predictable pattern (they simply do left-to-right from the top-left corner).
  • Write one bit to the lowest order bit of each color channel of each pixel
  • Export the modified bitmap to png again

Simply do this in reverse to retrieve your data.

The basic idea behind the process is that there are a lot of pixels in an image, and the lowest order bits of each color channel don’t make a big difference. In addition, about half of the bits you write will just be what the bit in the image already was. What you get back is essentially the right image, but with weird artifacts. He takes time to note that these artifacts are only really noticeable if you really crank the contrast/saturation and zoom in. He does have source images with lots of initial noise, though.

From the article:

Notice in the last image how there is a barely discernible horizontal line in the noise. That’s the end of the level data. What this means is that I can actually fit all level data into a 265×120 pixel image, only using the least significant bit.


Something I could do, and I believe that the Spore folks did as well, is actually use ALL of the color bits in pixels that are 100% transparent. Since those pixels are transparent, it doesn’t matter what color you set them to.

I can’t do this, however, since I am using the entire image, which means that I have no transparent pixels to work with.

Why favor this technique over just storing it in the metadata?

  • It’s funner! ๐Ÿ™‚
  • Services may mangle metadata (possibly as a privacy/security feature), but shouldn’t mangle your png’s pixels unless they have aggressive image-hosting requirements (looking at you, facebook). But if they’re completely re-exporting your image there’s nothing you reliably can do.

Extra credit: to reduce the noticeability of noise, you could use a PRNG with a fixed seed to select the pixels to modify. You could also only modify some of the color channels in a similar manner.

Related Solutions

Default value for UUID column in Postgres

tl;dr Call DEFAULT when defining a column to invoke one of the OSSP uuid functions. The Postgres server will automatically invoke the function every time a row is inserted. CREATE TABLE tbl ( pkey UUID NOT NULL DEFAULT uuid_generate_v1() , CONSTRAINT pkey_tbl...

comparing five integers with if , else if statement

try this : int main () { int n1, n2, n3, n4, n5, biggest,smallest; cout << "Enter the five numbers: "; cin >> n1 >> n2 >> n3 >> n4 >> n5 ; smallest=biggest=n1; if(n2>biggest){ biggest=n2; } if(n2<smallest){ smallest=n2;...

How to play YouTube audio in background/minimised?

Here's a solution using entirely free and open source software. The basic idea is that although YouTube can't play clips in the background, VLC for Android can play clips in the background, so all we need to do is pipe the clip to VLC where we can listen to it...

Why not use “which”? What to use then?

Here is all you never thought you would ever not want to know about it: Summary To get the pathname of an executable in a Bourne-like shell script (there are a few caveats; see below): ls=$(command -v ls) To find out if a given command exists: if command -v...

Split string into Array of Arrays [closed]

If I got correct what you want to receive as a result, then this code would make what you want: extension Array { func chunked(into size: Int) -> [[Element]] { return stride(from: 0, to: self.count, by: size).map { Array(self[$0 ..< Swift.min($0 + size,...

Retrieving n rows per group

Let's start with the basic scenario. If I want to get some number of rows out of a table, I have two main options: ranking functions; or TOP. First, let's consider the whole set from Production.TransactionHistory for a particular ProductID: SELECT...

Don’t understand how my mum’s Gmail account was hacked

IMPORTANT: this is based on data I got from your link, but the server might implement some protection. For example, once it has sent its "silver bullet" against a victim, it might answer with a faked "silver bullet" to the same request, so that anyone...

What is /storage/emulated/0/?

/storage/emulated/0/Download is the actual path to the files. /sdcard/Download is a symlink to the actual path of /storage/emulated/0/Download However, the actual files are located in the filesystem in /data/media, which is then mounted to /storage/emulated/0...

How can I pass a command line argument into a shell script?

The shell command and any arguments to that command appear as numbered shell variables: $0 has the string value of the command itself, something like script, ./script, /home/user/bin/script or whatever. Any arguments appear as "$1", "$2", "$3" and so on. The...

What is pointer to string in C?

argv is an array of pointers pointing to zero terminated c-strings. I painted the following pretty picture to help you visualize something about the pointers. And here is a code example that shows you how an operating system would pass arguments to your...

How do mobile carriers know video resolution over HTTPS connections?

This is an active area of research. I happen to have done some work in this area, so I'll share what I can about the basic idea (this work was with industry partners and I can't share the secret details ๐Ÿ™‚ ). The tl;dr is that it's often possible to identify an...

How do I change the name of my Android device?

To change the hostname (device name) you have to use the terminal (as root): For Eclair (2.1): echo MYNAME > /proc/sys/kernel/hostname For Froyo (2.2): (works also on most 2.3) setprop net.hostname MYNAME Then restart your wi-fi. To see the change, type...

How does reverse SSH tunneling work?

I love explaining this kind of thing through visualization. ๐Ÿ™‚ Think of your SSH connections as tubes. Big tubes. Normally, you'll reach through these tubes to run a shell on a remote computer. The shell runs in a virtual terminal (tty). But you know this part...

Difference between database vs user vs schema

In Oracle, users and schemas are essentially the same thing. You can consider that a user is the account you use to connect to a database, and a schema is the set of objects (tables, views, etc.) that belong to that account. See this post on Stack Overflow:...

What’s the output of this code written in java?

//if you're using Eclipse, press ctrl-shift-f to "beautify" your code and make it easier to read int arr[] = new int[3]; //create a new array containing 3 elements for (int i = 0; i < 3; i++) { arr[i] = i;//assign each successive value of i to an entry in...

How safe are password managers like LastPass?

We should distinguish between offline password managers (like Password Safe) and online password managers (like LastPass). Offline password managers carry relatively little risk. It is true that the saved passwords are a single point of failure. But then, your...