Tuesday, December 27, 2011

Protecting WebGL content (and why you probably shouldn't)

I received an email this morning from a developer that is in a predicament that I think many WebGL developers will find themselves in pretty soon:
Do you have any experience with protecting assets in WebGL? I wrote a quick solution to protect textures during transfer - base64 encoding + concat server side and splitting + creating imgs client side. Still, using WebGL Inspector I can get all textures transferred to the graphics card. I wonder if you have any ideas on how to prevent that. I don't mind open sourcing my apps, but my clients apparently do :)
Ah, that last line is really the crux of the matter, isn't it? Most developers I know don't mind sharing their creations with the world at large, as we tend to understand that there's rarely anything sacred and secret in the lines of code we crank out, but that's not always an easy thing to convince management of. So: What's an enterprising WebGL developer to do when the boss decrees from on high "Protect our content?"

There's a couple ways to approach such a request, in my opinion. First: Do whatever you can to convince said management that you don't need to protect said content. Second: Convince them harder. Third: If all else fails, obscure the content as much as possible. I'll be talking about both approaches in todays post.

Why your content doesn't need protecting, and why it benefits you to leave it open!

You probably don't need to protect your content in the first place. I know that many people's knee-jerk reaction to that statement will probably be: "But you don't understand our situation! Our content is special!" But please, hear me out!

Most of the time when we hear about content protection schemes (DRM), it's in relation to movies and music. There's a good reason for that, too! In those cases the movie or music is the product. But does that really apply to the assets of your WebGL app? What, exactly, is the product that you are trying to provide?

Since the original email was about textures, let's look at those as an example:

This is a wood wall/floor texture from Team Fortress 2. Opinions may differ depending on who you talk to, but in my opinion this is really a work of art. It has an inherent value even as a stand-alone image. Some talented artist put their blood, sweat, and tears into this image. Valve paid for this image to be created. In some way, big or small, the game would be a less polished, less interesting product if not for this image. It is a valuable asset.

So obviously it needs protecting, right?

Well, yes and no.

Problem is that nobody is going to print this image out and hang it on their wall. You're never going to be able to sell high-quality prints of this image. Even though it has a value, for a consumer that value approaches zero pretty quickly unless it is part of a larger product. We value the level that this resource helped create more than the resource itself, and since that level would not be the same without this resource it continues to have value to us as a consumer, but only as part of the larger scene.

So the scene as a whole should be protected, yes?

Maybe, but how much does the consumer really value a static level? People do enjoy being able to simply walk around a scene like that. And in certain industries that may be the product. Autodesk Cloud makes a good case for using resources at this level, and certainly you can imagine that static scenes like this would be valuable for architectural design or similar areas. But how about our TF2 level?

The fact is that, as beautiful as they are, almost nobody would pay to simply walk around a TF2 level. But there are lots and lots of people who will pay very good money to shoot other people while walking around said level. And this is where the actual "product" lies. It's not the assets that people care about, it's the overall experience. The assets are a big part of that experience, true, but only one part of a larger whole. That larger whole also happens to include the multiplayer servers, the achievement systems, the accessory store, the friends list, etc. All things that easily can and should be secured.

The players pay money and in return they download content to their machine, and as such it's easy to mistake the content for the product, but it's not. The ability to play a great game is the product. In other words, Valve is selling the service, not the content. And you should be too!

This is a common theme that you hear from the HTML 5 folks over and over again, and in many cases I fully agree with it. It doesn't always work, but given a bit of thought you'd be surprised at how many scenarios it can be applied to. It all comes down to what you can offer to your customers that can't be replicated on their machine.

The fact is, no matter how hard you try to prevent it the end user will alway be able to scrape out your lovingly built resources and serve them locally if they're really motivated to do so. We don't want to simply roll over and accept piracy as a fact of life, though, so we need to look at what we can provide to complete the overall experience that the users can't simply copy to their hard drive. Multiplayer servers are certainly the obvious choice, but even for single player games or non-gaming apps there's a lot that can be offered server side that lends to the "product". Community features, social network integration, achievements, realtime data feeds, the list goes on! And as web apps, we should be striving to incorporate these connected features from day one. Otherwise, why are you bothering with the web as your platform at all?

Maybe what you're worried about is the sanctity of your artistic vision, though? The author of the email above also mentioned this worry:
Any kid with firebug can download [the textures], draw a penis on my character's face and upload a modified version somewhere.
To which I say: what a wonderful problem to have! If you have a user base that is interested enough in your product to actually start modding it, you've hit a Happy Place™. Certainly nobody looks forward to seeing YouTube videos of their creations running around with crude genitals scribbled on their head, but how much damage is that really doing? One guy modifying his local texture cache won't propagate those changes to anybody else, and if anyone else goes and downloads his changes it's because they want to play as Sir CrotchFace. They get a laugh out of it, everyone else plays on oblivious, and (crucially) they're all using your product! Isn't that the goal in the first place?

It's no mistake that some of the most successful games of all time have been the ones that are easily moddable. Doom, Quake, The Elder Scrolls, Unreal, Half Life, all games that lived on well past their normal life spans because of a thriving mod community. Do you think Minecraft would be nearly as popular as it is now if Mojang had actively been discouraging texture packs and server tweaks? People love to create something that is uniquely theirs, and love to push the boundaries of what any given tech can do. It should be any developers dream to be the target of such affection.

So, long story short: Provide value through your services, not your content, and don't fear the user that wants to tinker. They're often your biggest fans!

Okay, that's great, but my boss doesn't agree. Now what?

So let's give you the benefit of the doubt and say that your content really is honestly and truly so special that it needs to be protected somehow. There are some things you can do to make it more difficult for the other party. Note that I say "more difficult" because the fact is plain and simple that if your content can eventually be displayed on a screen or played on a speaker it CAN be copied. Period. (Big Media is still struggling with this concept, apparently). They best you can do is make it inconvenient enough to discourage casual piracy.

The first way to protect your content is (and I hate to say this) not to use WebGL. Look, let's face it guys, open web standards are designed to distribute content, not hoard it. You're swimming against the current if you want to have open standards and DRM all rolled into a neat package. Content protection is almost inherently proprietary in nature, so your best bet is to go to a plugin framework like Flash, Silverlight, or Unity. These are systems that are designed from the start to distribute content securely, and thus will be much easier to work with if protection is your goal.

If "plugin" is a dirty word for you (or your boss), though, it's also worth looking at Google's Native Client. For those of you that don't know, Native Client (or NaCl, as Google likes to call it) allows you to distribute compiled binaries to users that are then executed in the browser in a sandboxed environment. Because your dealing with native, compiled code there are all sorts of nasty little tricks you can pull off to try and obscure your content from casual browsing. The downside here is that (for now, anyway) NaCl apps will only work in Google's browser. Of course, if you're that concerned about protecting your content requiring a specific browser probably isn't a huge issue.

But... what if you're the type that wants to have their cake and eat it too? You want to embrace the open standards of the web while simultaneously preventing anyone from extracting your precious assets. (You confuse me, I'll ignore that for now.)

First off, you'll want to make all of your code as hard to read as possible. Not the original source files, obviously, but the ones that you put on your public servers. The Closure compiler is great for this, and can actually make your code run faster in some cases! Bonus!

It probably also goes without saying that you'll need to invent your own formats for just about everything. Proprietary mesh and texture formats are always going to be harder to read than a standard COLLADA file and PNG.

Again I'll focus on textures for the moment: You can encrypt your texture all that you want, but at some point you have to unpack it into something that can be displayed on screen, right? The idea is to delay that step as long as possible to avoid having any intermediate tools like Firefox or WebGL Inspector be able to display it normally. To this end, you can get really creative with how you unpack things in your shaders. Ben Adams has a great post about his experiments with manually unpacking compressed textures in a shader that provides some insight into the type of steps that could be taken here. In any case, the idea is to have the fragment shader (which should be distributed obfuscated) do the appropriate math to decrypt/decompress your texture as it's getting written out to screen. Obviously you'll take a performance hit in doing so, but once again I'm guessing that isn't a problem if you are really concerned about protecting your assets.

(You can similarly decrypt vertex buffers in the vertex shader if you're worried about those being lifted, but they tend to be more obscure by nature anyway.)

Another thing to help prevent textures from being easily visible is that you don't have to use HTML images as your texture source. You can send TypedArrays to the GPU directly as your texture source as long as you provide the dimensions and format of your data. I use this to create textures of a flat color all the time:

function createSolidTexture(gl, r, g, b) {
    var data = new Uint8Array([r, g, b]);
    var texture = gl.createTexture();
    gl.bindTexture(gl.TEXTURE_2D, texture);
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, 1, 1, 0, gl.RGB, gl.UNSIGNED_BYTE, data);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
    return texture;

This means you can request the texture as an opaque binary through an AJAX call rather than with the Image object, which prevents it from showing up in the Chrome developer tools. (It will still show up in WebGL Inspector, though, so encrypting the texture values is still needed for maximum obscurity.)

Beyond that... well... I think you're out of luck. Certainly there's nothing that I've suggested that prevents someone from tracing through your code, figuring out the algorithms, capturing the data streams, running them through the same decryption process, and reconstructing the original files. But as I said earlier, the idea is to make it inconvenient, not impossible.

But if you've got a user that's really so bound and determined to reverse engineer your entire app just to access the underlying content, I would ask you to think really hard about why you're trying to stop them in the first place.