游戏资源保护之简单的虚拟文件系统

在游戏发布的时候,为了不让用户直接查看游戏资源,通常会对游戏资源进行保护。保护的方式可以分为:一、对文件进行加密,然后在读取文件的时候进行解密。二、将所有文件进行打包,做成类似文件系统那样,在读取的时候,提供根据名称读取文件数据的功能。今天我要实现的是第二种方式,对文件进行打包。所以我们必须实现自动对所有文件进行打包和提供根据输入的路径返回文件数据的功能。
这边我想到nosql是以key-value对数据进行存储数据库,读取的时候根据key就能获取数据,而且可以直接使用nosql数据库提供的api对进行数据的存储和读取,这样就符合实现文件系统的所需的条件。现在我们需要的是一个轻量级的nosql数据库,进行一番寻找后我找到了levelDB这个轻量级数据库来实现。
首先,我们来实现自动对文件进行打包也就是把所有文件以路径为key存入数据库。


    #!/usr/bin/python
    import leveldb
    import os
    import sys
    import zipfile
    
    out_path = "./data"
    zip_fileName = 'data.sys'
    # read argv
    print "------read config------"
    if len(sys.argv) == 1 or sys.argv == '-h':
        print "help"
    for i, argv in enumerate(sys.argv):
        if(argv == "-in"):
            in_path = sys.argv*
            print "in path:" + in_path
        if(argv == "-out"):
            out_path = sys.argv*
            print "out path:" + out_path
            if not os.path.exists(out_path):
                os.makedirs(out_path)
    
    db = leveldb.LevelDB(out_path)
    
    
    def walk_dir(dir, topdown=True):
        lst = ]
        for root, dirs, files in os.walk(dir, topdown):
            for name in files:
                lst.append(os.path.join(root, name))
        flst = 
        return lst
    
    
    def write_file2db(files):
        batch = leveldb.WriteBatch()
        for i in in_files:
            f = file(i)
            fstr = f.read()
            batch.Put(i.replace(in_path + "/", ""), fstr)
            # remove root
            print "put:" + i.replace(in_path + "/", "")
        db.Write(batch, sync=True)
    
    
    def write_db2file():
        for key, value in list(db.RangeIter()):
            dirName = os.path.split(key)
            if os.path.exists(dirName) == False:
                os.makedirs(dirName)
            f = open(key, "w")
            f.write(value)
            f.close()
            print "out:" + key
    
    
    def db2zip(zip_fileName):
        f = zipfile.ZipFile(zip_fileName, 'w', zipfile.ZIP_DEFLATED)
        for dirpath, dirnames, filenames in os.walk(out_path):
            f.write(dirpath)
            for filename in filenames:
                f.write(os.path.join(dirpath, filename))
        f.close()
    
    
    in_files = walk_dir(in_path)
    write_file2db(in_files)
    db2zip(zip_fileName)
    
    # write_db2file()

**

这样,所有文件已经被我们存入数据库000016.ldb

接下来,我们需要在引擎里面读取文件的代码进行修改,实现根据路径名从文件系统里面读取文件。修改CCFileUtils.cppgetData方法。


Data ret;
    unsigned char* buffer = nullptr;
    ssize_t size = 0;
    
    //read from lvdb
    DB *db;
    Options options;
    Status s;
    string dbPath = FileUtils::getInstance()->getWritablePath()+"data";
    s = DB::Open(options, dbPath, &db);
    string value;
    if(s.ok()) {
        s = db->Get(ReadOptions(), filename, &value);
        if (value == "") {
            const char* mode = nullptr;
            if (forString)
                mode = "rt";
            else
                mode = "rb";
            
            do
            {
                // Read the file from hardware
                std::string fullPath = FileUtils::getInstance()->fullPathForFilename(filename);
                FILE *fp = fopen(fullPath.c_str(), mode);
                CC_BREAK_IF(!fp);
                fseek(fp,0,SEEK_END);
                size = ftell(fp);
                fseek(fp,0,SEEK_SET);
                
                if (forString)
                {
                    buffer = (unsigned char*)malloc(sizeof(unsigned char) * (size + 1));
                    buffer = '\0';
                }
                else
                {
                    buffer = (unsigned char*)malloc(sizeof(unsigned char) * size);
                }
                
                size = fread(buffer, sizeof(unsigned char), size, fp);
                fclose(fp);
            } while (0);
        }else {
            buffer = (unsigned char*)value.c_str();
            size= value.length();
        }
        
        if (nullptr == buffer || 0 == size)
        {
            std::string msg = "Get data from file(";
            msg.append(filename).append(") failed!");
            CCLOG("%s", msg.c_str());
        }
        else
        {
            ret.fastSet(buffer, size);
        }
        delete db;
    }
    
    return ret;

这样,一个简单的文件系统就实现了。但是这里有个问题,levelDB在对数据库进行读取的时候,会产生日志文件,而我们是没有权限对Resources文件进行修改,所以,我们必须把levelDB数据库拷贝到Documents下才能对读写权限。
如果用脚本开发,在对资源进行热更新的时候,我们把下载的文件按路径名update到数据库里面,这样就解决了对文件系统就像更新的问题了。
用nosql进行文件系统的制作,比较简单,但是也比较庞大,如果有条件的话,自己实现一个文件系统,我想会更方便,更简洁。等有时间,再自己去实现一个文件系统。好了,就分享这些了,这篇文章主要是介绍文件系统的实现。

原文:http://geass.im/index.php/cocos2d-js/5.html