Basic Image Asset Protection
by Travis Evans · 07/17/2013 (11:53 pm) · 7 comments
I little while ago I was working on an image format to help protect my art assets in T2D before it became MIT. I had plans to, at some point, add it to T3D and I have finally gotten around to it.
I wanted a personal image format that no one else had, one that's simple but offers some level of protection. I would have loved to implement compression with this, but is way beyond my skill level.
The general idea is to use a format similar to a BMP image. The FX1 format is uncompressed and has 4 bytes per pixel, supporting ARGB. Really, the only difference between my FX1 format and a BMP is:
* One extra byte per pixel to support alpha
* Every byte of image data is "offset" or "skewed" using a simple XOR bitwise operation
The image header is only 12 bytes long and the structure is as follows:
U8: 0x88 "^"
U8: 0x46 "F"
U8: 0x58 "X"
U8: 0x31 "1"
U16: Width
U16: Height
U8: Blue Offset
U8: Green Offset
U8: Red Offset
U8: Alpha Offset
While the offsets are stored in the header, you could nix RGBA offsets and hard code them in the engine source to make it that much harder to decode the image.
Here's the C code for the new image format. Place this under: Engine > gfx > bitmap > loaders > bitmapFX1.cpp
You can also find the files here: www.torchive.com/custom/fx1/. Including a C# WinForms encoder application zipped up and ready to go (Used the .NET 4.5 Framework.)
And now, just need to alter 3 TorqueScript files:


I wanted a personal image format that no one else had, one that's simple but offers some level of protection. I would have loved to implement compression with this, but is way beyond my skill level.
The general idea is to use a format similar to a BMP image. The FX1 format is uncompressed and has 4 bytes per pixel, supporting ARGB. Really, the only difference between my FX1 format and a BMP is:
* One extra byte per pixel to support alpha
* Every byte of image data is "offset" or "skewed" using a simple XOR bitwise operation
The image header is only 12 bytes long and the structure is as follows:
U8: 0x88 "^"
U8: 0x46 "F"
U8: 0x58 "X"
U8: 0x31 "1"
U16: Width
U16: Height
U8: Blue Offset
U8: Green Offset
U8: Red Offset
U8: Alpha Offset
While the offsets are stored in the header, you could nix RGBA offsets and hard code them in the engine source to make it that much harder to decode the image.
Here's the C code for the new image format. Place this under: Engine > gfx > bitmap > loaders > bitmapFX1.cpp
You can also find the files here: www.torchive.com/custom/fx1/. Including a C# WinForms encoder application zipped up and ready to go (Used the .NET 4.5 Framework.)
#include "core/stream/stream.h"
#include "gfx/bitmap/gBitmap.h"
static bool sReadFX1(Stream &stream, GBitmap *bitmap);
static bool sWriteFX1(GBitmap *bitmap, Stream &stream, U32 compressionLevel);
static struct _privateRegisterBMP
{
_privateRegisterBMP()
{
GBitmap::Registration reg;
reg.extensions.push_back( "fx1" );
reg.readFunc = sReadFX1;
//reg.writeFunc = sWriteFX1; // No write function yet, though very similar to the read function, it can be added.
GBitmap::sRegisterFormat( reg );
}
} sStaticRegisterBMP;
// fx1 header format containing identifier and ARGB byte offset values
struct FX1FILEHEADER
{
U8 Identifier[4];
U16 Width;
U16 Height;
U8 B;
U8 G;
U8 R;
U8 A;
};
//------------------------------------------------------------------------------
//-------------------------------------- Supplementary I/O (Partially located in
// bitmapPng.cc)
//
static bool sReadFX1(Stream &stream, GBitmap *bitmap)
{
FX1FILEHEADER fx;
GFXFormat fmt = GFXFormatR8G8B8A8;
// Read the file header for the format identifier, the size of the image and the RGBA offsets
stream.read(4, &fx.Identifier);
stream.read(&fx.Width);
stream.read(&fx.Height);
stream.read(&fx.B);
stream.read(&fx.G);
stream.read(&fx.R);
stream.read(&fx.A);
bitmap->allocateBitmap(fx.Width, fx.Height, false, fmt);
U32 width = bitmap->getWidth();
U32 height = bitmap->getHeight();
U8 *ptr = bitmap->getAddress(0, 0);
// There maybe a better way to do this than a nested for loop
// such as for (U32 i = (height * width) yadda yadda, or _asm...
// but this works and is fast enough, at least for me
for (U32 y = 0; y < height; y++)
{
for (U32 x = 0; x < width; x++)
{
// FX1 uses 4 bytes per pixel, RGBA (GFXFormatR8G8B8A8)
U8 pixel[4];
// Read 4 bytes into an array for processing
stream.read(4, &pixel);
// Files read as BGRA
ptr[0] = (pixel[0] ^ fx.B); // Restore Blue value
ptr[1] = (pixel[1] ^ fx.G); // Restore Green value
ptr[2] = (pixel[2] ^ fx.R); // Restore Red value
ptr[3] = (pixel[3] ^ fx.A); // Restore Alpha value
// Add 4 bytes to move on to the next pixel
ptr += 4;
}
}
bitmap->setHasTransparency(true);
return true;
}
/*
// Empty write shell if you want to implement a write function
static bool sWriteFX1(GBitmap *bitmap, Stream &stream, U32 compressionLevel)
{
TORQUE_UNUSED( compressionLevel ); // FX1 does not use compression
}
*/And now, just need to alter 3 TorqueScript files:
FILE: /game/core/scripts/client/postFx/postFxManager.gui.cs
LINE: 382-ish
function ppColorCorrection_selectFile()
{
%filter = "Image Files (*.png, *.jpg, *.dds, *.bmp, *.gif, *.jng, *.tga, *.fx1)|*.png;*.jpg;*.dds;*.bmp;*.gif;*.jng;*.tga;*.fx1|All Files (*.*)|*.*|";
getLoadFilename( %filter, "ppColorCorrection_selectFileHandler");
}
==========================================================================================
FILE: /game/scripts/gui/chooseLevelDlg.cs
LINE: 121-ish
// Test against all of the different image formats
// This should probably be moved into an engine function
if (isFile(%levelPreview @ ".png") ||
isFile(%levelPreview @ ".jpg") ||
isFile(%levelPreview @ ".bmp") ||
isFile(%levelPreview @ ".gif") ||
isFile(%levelPreview @ ".jng") ||
isFile(%levelPreview @ ".mng") ||
isFile(%levelPreview @ ".tga") ||
isFile(%levelPreview @ ".fx1"))
{
%preview.setBitmap(%levelPreview);
}
==========================================================================================
FILE: /game/tools/materialEditor/main.cs
LINE: 90-ish
MaterialEditorGui.fileSpec = "Torque Material Files (materials.cs)|materials.cs|All Files (*.*)|*.*|";
MaterialEditorGui.textureFormats = "Image Files (*.png, *.jpg, *.dds, *.bmp, *.gif, *.jng, *.tga, *.fx1)|*.png;*.jpg;*.dds;*.bmp;*.gif;*.jng;*.tga;*.fx1|All Files (*.*)|*.*|";

#2
This was originally developed for a T2D game I was working on and I didn't want people to steal the game art, especially since I had paid an artist $250 for textures and spent several hours modeling and rendering backdrops. $250 for a hand full of textures may not sound like a lot, but that's enough to pay my utilities, water and trash for the month or two months worth of gas for my car. Basically trying to protect my investment.
A lot of big name players do this too. The newer Call of Duty and Battlefield games do this, and if I recall, though could be wrong, Crysis 2 have their assets encrypted (google "crysis 2 pak encrypted").
I also wanted to provide something to the community for those who want to do similar. There are a few topics around here about encrypting a zip file for asset protection. I am not sure if this is still officially unsupported.
07/18/2013 (11:27 am)
For me, it's a simple way to protect image assets; assets that either I create myself, commission from another artist, or textures I purchase, off say turbosquid.com, that require you have the media in a non-standard format, which some of my textures do in fact require.This was originally developed for a T2D game I was working on and I didn't want people to steal the game art, especially since I had paid an artist $250 for textures and spent several hours modeling and rendering backdrops. $250 for a hand full of textures may not sound like a lot, but that's enough to pay my utilities, water and trash for the month or two months worth of gas for my car. Basically trying to protect my investment.
A lot of big name players do this too. The newer Call of Duty and Battlefield games do this, and if I recall, though could be wrong, Crysis 2 have their assets encrypted (google "crysis 2 pak encrypted").
I also wanted to provide something to the community for those who want to do similar. There are a few topics around here about encrypting a zip file for asset protection. I am not sure if this is still officially unsupported.
#3
Search RLE (run length encoding). It is a very simple encoding scheme used in many formats including PCX. It would not work well for textures that are varied, but for textures that have lots of pixel runs (same color) it can be very effective for compression.
I can see doing this to comply with licenses, but protecting assets not so much. If you can view a texture you can steal it. So it won't stop screen capture theft. Obviously that would be more work for the thief, but it is still possible.
There are also middle ware solutions for this such as Bink. Of course that sort of protection is probably quite expensive.
Thanks for sharing this. If I license art that needs this sort of protection this will certainly help.
07/18/2013 (1:10 pm)
@Travis,Search RLE (run length encoding). It is a very simple encoding scheme used in many formats including PCX. It would not work well for textures that are varied, but for textures that have lots of pixel runs (same color) it can be very effective for compression.
I can see doing this to comply with licenses, but protecting assets not so much. If you can view a texture you can steal it. So it won't stop screen capture theft. Obviously that would be more work for the thief, but it is still possible.
There are also middle ware solutions for this such as Bink. Of course that sort of protection is probably quite expensive.
Thanks for sharing this. If I license art that needs this sort of protection this will certainly help.
#4
I was looking in to RLE earlier actually and came to a similar conclusion.
This is by no means meant as a full on solution; just a deterrent. It's like a lock, it keeps honest people honest. I know it's not possible to fully protect anything. It has to be decoded or decrypted to be used.
I've felt companies put too much effort into protection systems in attempt to win an already lost battle and often hurt the customers. Sony's little copy protection rootkit scandal, SecuRom issues, EA's EADM back in the day; worst piece of garbage several times "Please login to verify ownership"... I was, audio/audio DRM restrictions. But at the end of the day, some guy or gal that knows ASM can patch the code or NOOP a few address (Yeah I know, not THAT simple) can get around just about anything.
This is one reason I wanted something simple and easy to implement. Yeah someone will eventually get the art, but if they feel they need to disassemble my code and analyze the color layout of my images just to get a texture of concrete... I can't stop them, but It might be a little faster to hit up CGTextures.com or some other texture site in a fraction of the time.
07/18/2013 (2:08 pm)
@DemolishunI was looking in to RLE earlier actually and came to a similar conclusion.
This is by no means meant as a full on solution; just a deterrent. It's like a lock, it keeps honest people honest. I know it's not possible to fully protect anything. It has to be decoded or decrypted to be used.
I've felt companies put too much effort into protection systems in attempt to win an already lost battle and often hurt the customers. Sony's little copy protection rootkit scandal, SecuRom issues, EA's EADM back in the day; worst piece of garbage several times "Please login to verify ownership"... I was, audio/audio DRM restrictions. But at the end of the day, some guy or gal that knows ASM can patch the code or NOOP a few address (Yeah I know, not THAT simple) can get around just about anything.
This is one reason I wanted something simple and easy to implement. Yeah someone will eventually get the art, but if they feel they need to disassemble my code and analyze the color layout of my images just to get a texture of concrete... I can't stop them, but It might be a little faster to hit up CGTextures.com or some other texture site in a fraction of the time.
#5
Yeah, I hear you. I think this is a good solution to meet licensing requirements for 3rd party art. That alone can save a dev a lot of time so they don't have to implement "something". It also has a layer of protection which is not intrusive. That is good too.
It is too bad you can't somehow watermark it as part of your protection scheme. So that in game you don't see it, but viewing it out of the game the watermark would show up. That would be kind of cool.
Anyway, sorry for the nitpicking. I do like this solution.
07/18/2013 (3:55 pm)
@Travis,Yeah, I hear you. I think this is a good solution to meet licensing requirements for 3rd party art. That alone can save a dev a lot of time so they don't have to implement "something". It also has a layer of protection which is not intrusive. That is good too.
It is too bad you can't somehow watermark it as part of your protection scheme. So that in game you don't see it, but viewing it out of the game the watermark would show up. That would be kind of cool.
Anyway, sorry for the nitpicking. I do like this solution.
#6
Nitpicking? Nah, it's cool.
The watermark I don't think would be too difficult actually. You could use the same principal. Your watermark could be generated by XORing the pixel RBG bytes with a different RGB mask and saved, though you'd need a lossless image format such as a PNG, otherwise the decoding could leave a faded watermark from say a JPG. Then once the image data is loaded into Torque you can XOR the byes back removing the watermark. I kinda want to give this a shot now actually!
Another thing I was kicking around, for say close Alphas or Betas, is encoding a user's ID into a pixel color. Like if a user's ID was 24875, in hex that's 0x00612B or a dark green which you could then use for a logo color, but that's besides the fact.
07/18/2013 (5:12 pm)
@DemolishunNitpicking? Nah, it's cool.
The watermark I don't think would be too difficult actually. You could use the same principal. Your watermark could be generated by XORing the pixel RBG bytes with a different RGB mask and saved, though you'd need a lossless image format such as a PNG, otherwise the decoding could leave a faded watermark from say a JPG. Then once the image data is loaded into Torque you can XOR the byes back removing the watermark. I kinda want to give this a shot now actually!
Another thing I was kicking around, for say close Alphas or Betas, is encoding a user's ID into a pixel color. Like if a user's ID was 24875, in hex that's 0x00612B or a dark green which you could then use for a logo color, but that's besides the fact.
#7
07/19/2013 (5:53 am)
I appreciate the explanation. I wasn't even aware stuff like stealing textures existed. Thank you! 
Dino Srdoc