设为首页收藏本站同能贴吧 切换语言 繁体中文
开启辅助访问 切换到窄版
扫描二维码关注官方公众号
返回列表
+ 发新帖
查看: 82|回复: 0

[制作教程] Safeguarding your assets from getting stolen

[复制链接]
累计送礼:
0 个
累计收礼:
1 个
  • TA的每日心情
    开心
    前天 04:10
  • 签到天数: 209 天

    连续签到: 2 天

    [LV.7]常住居民III

    4446

    主题

    864

    回帖

    2万

    积分

    管理员

    Rank: 9Rank: 9Rank: 9

    VIP
    7
    卡币
    22899
    OK点
    16
    推广点
    0
    同能卷
    50
    积分
    28239

    灌水之王

    发表于 4 天前 | 显示全部楼层 |阅读模式
    I don't think it's unknown that MV files are badly protected. MV editor allows image and audio encryption, however it's so weak that it's very easy to break through it. And due to the nature of MV code it's open for edits and vulnerable to code injections (as we could see with the episode where a hacker injected miner malware into stolen games and uploaded them on itch.io).
    I don't think you like it and neither do I. After all, nobody likes leaving their door open for a burglar to just enter and rob them. So I have invested a lot of time into improving the security of MV resources. I've gone through so many options even going through other languages (C++ is a very viable option, but contains many problems including the fact that coding in it is very tedious and I'd have to recreate all MV engine just for C++). And finally I'm ready to share the results of my research with you.

    Before I start though, I have to say one thing:

    What we need:

    A text editor that can handle JS well (I use Sublime text 2), encryption/compression scripts of our choice (I use crypto.js), PIXI-sound (possibly) and nw.js SDK. And of course MV 1.6.0 or more recent (although even if you don't have it, you may find some interesting ideas here). Why that? I'll explain later.

    Important notes:

    This is an advanced tutorial. Javascript knowledge of a certain level is absolutely mandatory.
    This tutorial also doesn't provide any plug and play code, you'll have to do a lot of work yourself.
    There are a couple of trade-offs, some nastier, some not so much, for the security. You'll lose convenience and cross-platformingness. But that's always like this. Either you make something secure or simple. The choice is yours.
    I am not responsible for any damage you may cause to your projects or resources and neither am I for any compatibility issues you cause to your project. Backup your project before starting! What I'm doing in this tutorial is no rocket science, but you can damage your project if you do something wrong!
    Also, whatever we do here will not protect your game from hacker attacks. It's only meant to safeguard your resources. After all, the biggest security issue, the html file itself, is hardly possible to protect.

    So let's get to it.

    I can't be talking too much about how default MV encryption works, because i'd be breaking the EULA. However, there is one field of security I am allowed to talk about, because it's open source and everybody can see it freely.
    MV's savefiles.
    Let's take a look at how they are made:
                    Code:       
    StorageManager.saveToLocalFile = function(savefileId, json) {    var data = LZString.compressToBase64(json);    var fs = require('fs');    var dirPath = this.localFileDirectoryPath();    var filePath = this.localFilePath(savefileId);    if (!fs.existsSync(dirPath)) {        fs.mkdirSync(dirPath);    }    fs.writeFileSync(filePath, data);};StorageManager.loadFromLocalFile = function(savefileId) {    var data = null;    var fs = require('fs');    var filePath = this.localFilePath(savefileId);    if (fs.existsSync(filePath)) {        data = fs.readFileSync(filePath, { encoding: 'utf8' });    }    return LZString.decompressFromBase64(data);};

    The first function is responsible for saving and the second one for loading. We can see that the file gets saved via nw.js's module "fs".
    Something we will be using to save our files once we're done with them. Other than that there's also one interesting line, the LZString method.
    LZString is a Javascript that allows us to compress a string to strings of different byte bases. In fact "compression" probably isn't the right word for it, because a base64 string is 1.3x bigger than the uncompressed version. Nevertheless, it's very popular for savefiles, although it's only good as a first layer of security, because you just decompress it and it's decrypted. Which is harmless for our JSON files, but useless for images and music. So let's take a look at some real encryption.

    Choice of encryption software

    There are multiple scripts out there that deal with encryption. Each one has unique algorithms and unique syntax, so choose whichever one you like. Whether it's encryption algorithm or hashing algorithm, it's up to you, because the decryption is always similar.
    I'll be, for this illustration, using an AES algorithm from series of Crypto.js scripts. This illustration is NOT plug and play though, because 1. You should apply your own means of protection (feel free to use the same algorithm, just don't use the same key!
    )
    2. There are some things that need to be handled first.

    Encryption of our assets

    It would be very nice if we could just mash a file into the encryption function and it would handle everything. Unfortunately, that's not how it works. Encryption algorithms handle only text strings and images and audios are binary files.
    So we need to convert an image to a dataURL before we can encrypt it. I have found a function here(credits: Guest271314) and slightly adapted it to make sure I could then manipulate the result. Let's illustrate it on Shadow1.png inside System folder, because it's a very small image:

                    Code:       
    var xhr = new XMLHttpRequest();      xhr.open("GET", "img/system/Shadow1.png", true);    xhr.responseType = "blob";    xhr.onload = function (e) {            var reader = new FileReader();            reader.onload = function(event) {            img = event.target.result;            }            var file = this.response;            reader.readAsDataURL(file)    };    xhr.send()

    It's very important that the img is a global variable, because if it's only a local variable, we won't be able to manipulate it. It's also important that the responseType is a blob, because readAsDataURL supports only blobs.
    Now that we have img or music converted to a string, we can now safely encrypt it. An encryption is actually a pretty easy process.
    In my case it's going to look like this:

                    Code:       
    var cryptoJS = require('crypto-js');var ciphertext = CryptoJS.AES.encrypt(img, 'secret key 123');var secondlayer = LZString.compressToBase64(ciphertext);

    As you can see, I only added two layers of security, first I encrypted the string and then I compressed it using LZString.
    And all that's left is to save it. We'll be using writeFileSync for it.
                    Code:       
    var fs = require('fs');fs.writeFileSync("img/System/Shadow.coolFormat", secondlayer);

    It doesn't matter which file format you want to use. You can use a completely custom format, you can overwrite the png, you can also save it as a jpeg to try and confuse potential hackers... It's completely up to you.
    However, encryption is the easier part. Decryption will be worse.

    Decryption of our assets and their use

    Decryption of the image is not a problem. We'll just decrypt it with the layers of security swapped. So since I did the compression last, I need to first decompress it before I can decrypt it.
    What I am doing here is merely following orders from the encryption script documentation, so there's nothing to explain here.

                    Code:       
    var fs = require('fs');var CryptoJS = require("crypto-js");var toDecompress = fs.readFileSync("img/System/Shadow.coolFormat", { encoding: 'utf8' });var decompressed = LZString.decompressFromBase64(toDecompress);var decrypted = CryptoJS.AES.decrypt(decompressed.toString(), 'secret key 123');finalImg = decrypted.toString(CryptoJS.enc.Utf8);


    finalImg can be used to freely create textures and sprites.
                    Code:       
    img = PIXI.Sprite.fromImage (finalImg);SceneManager._scene.addChild (img);

    If you aren't too familiar with PIXI, you can use default MV sprite, although you first have to generate texture for it:
                    Code:       
    texture = PIXI.Texture.fromImage (finalImg);sprite = new Sprite(texture.baseTexture);SceneManager._scene.addChild(sprite)


    As you can see, the encryption brings forth a new way of loading resources, requiring some edit to resource handling. But that's not all... Because while this is completely enough to both decrypt and deal with images, it's enough to only decrypt music files! WebAudio cannot use base64 files, because it can't find node points and other things it needs to find.

    So you need to convert them once again... To a blob and transform it to URL. Credit to Metal03326 for following code:
                    Code:       
    function dataURItoBlob(dataURI) {        dataURI = dataURI.split( ',' );        var type = dataURI[ 0 ].split( ':' )[ 1 ].split( ';' )[ 0 ];        var byteString = atob( dataURI[ 1 ] );        var byteStringLen = byteString.length;        var ab = new ArrayBuffer( byteStringLen );        var intArray = new Uint8Array( ab );        for ( var i = 0; i < byteStringLen; i++ )        {            intArray[ i ] = byteString.charCodeAt( i );        }        return new Blob( [ intArray ], {type: type} );    }


    This is of course only a function to create the blob, which unfortunately is not enough to use the audio, because there has to be an url to the source. So we need to generate URL from this blob. Eventually when we don't need it, we'll also need to get rid of it (see the post linked above for more information).

                    Code:       
    var fs = require('fs');var audio = fs.readFileSync ("audio/bgm/Castle1.coolFormat"), {encoding: 'utf8'});audioURL = URL.createObjectURL(dataURItoBlob(audio))


    Unfortunately this advance has broken all default MV use for me. So I had to use an external plugin (in this case PIXI.sound) to be able to play the music.
                    Code:       
    snd = PIXI.sound.Sound.from(audioURL);snd.loop = true;snd.play();


    However, I think it's worth it to invest some work in more advanced security. Although all this would be completely worthless, because no matter how strong the encryption is, JS files need to be accessible and therefore it's very easy to decrypt the files!
    But that's why there's the last step.

    JavaScript compilation

    That's right. The last step is compilation of our JS using NW.JS sdk, in particular nwjc. And it is a step that should ONLY be done to DEPLOYED projects!
    RPG maker MV supports the use of SDK by default (after all the SDK allows you to open the developer tools via f8), so you don't have to download anything.
    In order to compile your JS files, I suggest you find them, copy them and paste them inside nwjs-win-test folder inside MV. Windows Steam users should find that folder at c:\program files\Steam\steamapps\common\RPG maker MV\nwjs-win-test by default. Once you've got them there, open your command window (or shell for linux) and get in the folder. Then simply type in nwjc file finishedFilename.
    So to convert rpg_core.js type simply
                    Code:       
    nwjc rpg_core.js rpg_core.bin

    The format should be irrelevant.

    And last but not least, copy it back in the js folder.
    Then open your index.html. You need to tell the game to listen to your new files. Luckily it's not too hard. Open your index.html and replace
                    Code:       
    <script type="text/javascript" src="js/rpg_core.js"></script>

    with                                 Code:       
    <script>nw.Window.get().evalNWBin(null, "www/js/rpg_core.bin");</script>

    evalNWBin takes two parameters, first is frame, then comes filepath. Since we don't have any iframes and stuff, we'll leave it at null for the main frame.
    The www in the filepath is very important. Script src looks from the perspective of the index.html, but evalNWBin looks from the perspective of the executable file! (that's why you should do this only to deployed projects).

    So essentially your index.html, if you convert all core scripts, should look like this:
                    Code:       
    <script>nw.Window.get().evalNWBin(null, "www/js/libs/pixi.bin");</script><script>nw.Window.get().evalNWBin(null, "www/js/libs/pixi-tilemap.bin");</script><script>nw.Window.get().evalNWBin(null, "www/js/libs/pixi-picture.bin");</script><script>nw.Window.get().evalNWBin(null, "www/js/libs/fpsmeter.bin");</script>...<script>nw.Window.get().evalNWBin(null, "www/js/rpg_core.bin");</script><script>nw.Window.get().evalNWBin(null, "www/js/rpg_managers.bin");</script>..and so on



    Why isn't this possible with earlier versions of MV

    Well... Truth is, it is possible. You just need to update nw.js to at least version 0.22 though, because versions prior to this one suffered huge performance drops as a result of compilation. It was supposedly fixed at 0.22.
    MV 1.6.0 uses NW.js 0.25.4, so it's safe from everything.

    A couple of notes

    The compiled code isn't compatible neither cross-platform nor cross-version. That means for each platform you want to deploy it to, you'll have to compile it with appropriate SDK version and system type. And I'm not entirely sure how it's handled in mobile development.
    If you want to convert your plugins as well, make sure to work around the plugins.js first, because this plugin searches for .js stuff. Also, if you want to convert plugins, you may want to include them either inside index.html or somewhere else.
    Do NOT encrypt javascripts before or after compilation. It's pointless to do it after compilation and stupid to do before compilation, because it's compilation into binary native code.
    This doesn't fully safeguard your game from hackers. Native code still has some information that can tell something and index.html is always going to be vulnerable for code injections. But at least the resources are now more secure and harder to grasp.

    A couple of other tips

    Explore, explore and explore. That's the only way to learn. Who knows, maybe you'll find better ways than I have listed here. For example the URLs created from blobs are a pretty decent protection of music resources in online games, because the blob will never point to the native file. Using that you can make a pretty safe browser game, which can prevent people from editing your index.html.
    Obfuscate your project if you want additional layer of security. Join some scripts together. Create misleading names and file formats. Create fake code that is completely worthless, but doesn't crash your game.
    Since they are text strings now, you can also join several image files together and use split method to separate them. I wouldn't try it with audio files, since they are huge.
    Don't try to open encrypted music files in a text editor. It's so long it may freeze your machine.
    Instead of readFileSync you may want to use only readFile. The difference is, readFile is an asynchronous function and will require a callback once the file is loaded, while readFileSync doesn't require callback, but freezes whole processing while the resource is loading (which is what we know as lags). And decryption and stuff is going to take more time than pure loading, so you may want to have something like asynchronous precaching system.


    Epilogue

    I know the trade-offs for additional security are huge. It involves a lot of work for who knows which results and MV loses cross platformingness, something it relies on. So I completely understand if nobody decides to build on this. However, I hope it helps somebody.
    If you have any questions, I'll try my best to answer them.


    本贴来自国际rpgmaker官方论坛作者:Poryg处,因国际论坛即将永久关站,为了存档多年珍贵资料,署名转载到本论坛存档,由于官方帖子为英文原帖,需要中文翻译请点击论坛顶部切换语言为中文就可以将帖子翻译成中文浏览,方便大家随时查看,原文地址:https://forums.rpgmakerweb.com/threads/safeguarding-your-assets-from-getting-stolen.93803/

    本帖子中包含更多资源

    您需要 登录 才可以下载或查看,没有账号?立即注册

    x
    天天去同能,天天有童年!
    回复 送礼论坛版权

    使用道具 举报

    文明发言,和谐互动
    文明发言,和谐互动
    高级模式
    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    简体中文
    繁體中文
    English(英语)
    日本語(日语)
    Deutsch(德语)
    Русский язык(俄语)
    بالعربية(阿拉伯语)
    Türkçe(土耳其语)
    Português(葡萄牙语)
    ภาษาไทย(泰国语)
    한어(朝鲜语/韩语)
    Français(法语)
    关闭

    幸运抽奖

    社区每日抽奖来袭,快来试试你是欧皇还是非酋~

    立即查看

    聊天机器人
    Loading...

    QQ|Archiver|手机版|小黑屋|同能RPG制作大师 ( 沪ICP备12027754号-3 )

    GMT+8, 2026-7-14 14:21 , Processed in 0.065266 second(s), 56 queries .

    Powered by Discuz! X3.4

    Copyright © 2001-2020, Tencent Cloud.

    快速回复 返回顶部 返回列表