Skip to content

Instantly share code, notes, and snippets.

@Pldare
Created August 10, 2021 01:46
Show Gist options
  • Star 11 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Pldare/ebf704c752a8d77ff9603d4adfe54083 to your computer and use it in GitHub Desktop.
Save Pldare/ebf704c752a8d77ff9603d4adfe54083 to your computer and use it in GitHub Desktop.
vroid web view vrm decrypt
using System;
using System.Security.Cryptography;
using System.IO;
using System.IO.Compression;
namespace Vroid
{
class Vroiddec
{
static void Main(string[] args)
{
using (FileStream fs = new FileStream(args[0],FileMode.Open)) {
using (BinaryReader bs = new BinaryReader(fs)) {
int buff_size=(int)(fs.Length)-48;
RijndaelManaged rDel=new RijndaelManaged();
rDel.IV=bs.ReadBytes(16);
rDel.Key=bs.ReadBytes(32);
rDel.Mode=CipherMode.CBC;
byte[] resultarray=rDel.CreateDecryptor().TransformFinalBlock(bs.ReadBytes(buff_size),0,buff_size);
using(MemoryStream ms =new MemoryStream(resultarray)){
using(GZipStream gzs=new GZipStream(ms,CompressionMode.Decompress)){
using(FileStream df=new FileStream(args[0]+".dec",FileMode.OpenOrCreate,FileAccess.Write))
{
int data;
while((data=gzs.ReadByte())!=-1)
{
df.WriteByte((byte)data);
}
}
}
}
}
}
log_msg("Done!");
}
static void log_msg(string msg)
{
Console.WriteLine(msg);
}
}
}
@sr229
Copy link

sr229 commented Nov 17, 2021

Tried opening this on this model but the resulting VRM seems to have blanked out textures, is there something I'm missing or I'm using the incorrect endpoint?

I modernized the script a bit and documented a lot of things so everyone can understand how it works now (I used .NET 6.0 here).

using System.Security.Cryptography;
using System.IO.Compression;

namespace Vroid
{
    class VroidDecode
     {
         public static void Main(string[] args) 
         {

            // read the file from arguments, then get the binary representation
            var fs = new FileStream(args[0], FileMode.Open);
            var bs = new BinaryReader(fs);

            // VRoid Hub Encrypted VRMs usually have a the following data structure:
            // * The first 16 bytes is the Initialization Vector
            // * 32 bytes is the actual AES key
            // * The cipher is AES-CBC
            // * Then the rest of the VRM is Gzipped compressed for web transfer.
            // 
            // with this in mind, we now try to access those specific bits first.
            int bufferSize = ((int)fs.Length)-48;
            using Aes aes = Aes.Create();
            aes.IV = bs.ReadBytes(16);
            aes.Key = bs.ReadBytes(32);
            aes.Mode = CipherMode.CBC;

            // now everything is set, we now try to decrypt it!
            var decrypted = aes.CreateDecryptor().TransformFinalBlock(bs.ReadBytes(bufferSize), 0, bufferSize);

            // of course now we got the decrypted part, we still have to deal with the Gzip compression.
            var decryptedStream = new MemoryStream(decrypted);
            var decompressedStream = new GZipStream(decryptedStream, CompressionMode.Decompress);
            var finalStream = new FileStream(args[0] + ".dec", FileMode.OpenOrCreate, FileAccess.Write);

            int data;
            while ((data = decompressedStream.ReadByte()) != -1)
            {
                finalStream.WriteByte((byte)data);
            }

            Console.WriteLine($"Done! Saved as {args[0]}.dec");

        }
     }
}

@DerpyMario
Copy link

DerpyMario commented Mar 10, 2022

it's not working for me, is there a slight problem?
157239899-ca91de91-9935-4af8-80d4-cd1550b82fc4

@DerpyMario
Copy link

image

@DerpyMario
Copy link

Can somebody fix it?

@DerpyMario
Copy link

DerpyMario commented Mar 11, 2022

wait, i forgot that real vdroid vrms have this at the end of the file, maybe the decompression or encryption is imperfect:
image

@sr229
Copy link

sr229 commented Mar 11, 2022

wait, i forgot that real vdroid vrms have this at the end of the file, maybe the decompression or encryption is imperfect: image

I encourage you to fix it, because I don't think we're using GZip incorrectly. VRoid Hub uses Pako but I'm not so sure how they did the compression.

@DatTaiwanGuy
Copy link

@641i130
Copy link

641i130 commented Mar 16, 2022

The GZip looks correct from my testings, It's something we're missing in the file that we're decrypting I think. The file has a texture basis that the program must be looking for. In blender I tried to import it and get this error:
image

@641i130
Copy link

641i130 commented Mar 16, 2022

Heres the error I get with a GLB/GLTF validator:
https://clbin.com/7wnf9
(https://github.khronos.org/glTF-Validator/)

@DatTaiwanGuy
Copy link

DatTaiwanGuy commented Mar 17, 2022 via email

@DatTaiwanGuy
Copy link

DatTaiwanGuy commented Mar 17, 2022 via email

@sr229
Copy link

sr229 commented Dec 29, 2022

The GZip looks correct from my testings, It's something we're missing in the file that we're decrypting I think. The file has a texture basis that the program must be looking for. In blender I tried to import it and get this error: image

I think extensions can be ignored or removed from the headers so it can render properly.

@641i130
Copy link

641i130 commented Dec 31, 2022

The GZip looks correct from my testings, It's something we're missing in the file that we're decrypting I think. The file has a texture basis that the program must be looking for. In blender I tried to import it and get this error: image

I think extensions can be ignored or removed from the headers so it can render properly.

Any references on doing this? Or tools that do this already?

@sr229
Copy link

sr229 commented Jan 3, 2023

The GZip looks correct from my testings, It's something we're missing in the file that we're decrypting I think. The file has a texture basis that the program must be looking for. In blender I tried to import it and get this error: image

I think extensions can be ignored or removed from the headers so it can render properly.

Any references on doing this? Or tools that do this already?

Best way to do is probably read the file as-is then writing out the extension-specific bits to their VRM-standard counterparts and exporting them. This will lead to a semi-broken VRM but can be fixable if it reads properly in Blender. Unfortunately, this is the first time this has been done so we will have to write this ourselves.

@darbdarb
Copy link

The GZip looks correct from my testings, It's something we're missing in the file that we're decrypting I think. The file has a texture basis that the program must be looking for. In blender I tried to import it and get this error: image

I think extensions can be ignored or removed from the headers so it can render properly.

Any references on doing this? Or tools that do this already?

Best way to do is probably read the file as-is then writing out the extension-specific bits to their VRM-standard counterparts and exporting them. This will lead to a semi-broken VRM but can be fixable if it reads properly in Blender. Unfortunately, this is the first time this has been done so we will have to write this ourselves.

any update on this? i know its been awhile but i manged to get to this stage, when i try to remove the pixiv texture basis i get a decode error UnicodeDecodeError: 'utf-8' codec can't decode byte 0xf7 in position 128055: invalid start byte

@sALTaccount
Copy link

sALTaccount commented Sep 10, 2023

i was able to get the model to at least import by modifying the import_gltf2_with_indices method of the blender vrm importer with

        json_dict['extensionsRequired'].pop(0)
        for i in range(len(json_dict['textures'])):
            if 'extensions' in json_dict['textures'][i]:
                json_dict['textures'][i].pop('extensions')

(it's just a dumb hack to remove the extension requirement)

However, the meshes seem to not be quite right
image

I will keep looking into this and update if I find more

@darbdarb
Copy link

i was able to get the model to at least import by modifying the import_gltf2_with_indices method of the blender vrm importer with

        json_dict['extensionsRequired'].pop(0)
        for i in range(len(json_dict['textures'])):
            if 'extensions' in json_dict['textures'][i]:
                json_dict['textures'][i].pop('extensions')

(it's just a dumb hack to remove the extension requirement)

However, the meshes seem to not be quite right image

I will keep looking into this and update if I find more

nice nice keep us updated :)

@Pldare
Copy link
Author

Pldare commented Oct 28, 2023

hihihi Everyone
I’m glad that everyone pays attention to this code of mine
I share some json parsing methods of gltf models for everyone to facilitate extraction and error correction.
get block info

import json
import sys
with open(sys.argv[1],encoding='utf-8') as f:
    data = json.load(f)


for i in data.keys():
    a=data[i]
    #print(type(a))

accessors = data["accessors"]
buffer_views = data["bufferViews"]
type_num_dict = {"SCALAR": 1, "VEC2": 2, "VEC3": 3, "VEC4": 4, "MAT4": 16}

type_dict={
    5120: "BYTE",
    5121: "UNSIGNED_BYTE",
    5122: "SHORT",
    5123: "UNSIGNED_SHORT",
    5124: "INT",
    5125: "UNSIGNED_INT",
    5126: "FLOAT"
}

for accessor_index, accessor in enumerate(accessors):
    type_count = type_num_dict[accessor["type"]]
    #ct=accessor["componentType"]
    pos=buffer_views[accessor["bufferView"]]["byteOffset"]
    count=accessor["count"]
    ac=accessor["componentType"]
    print(accessor_index,count,type_dict[ac],type_count==1,pos)

get mesh info

print("mesh")
for n, mesh in enumerate(data["meshes"]):
    #print(n,mesh.keys())
    for j,primive in enumerate(mesh["primitives"]):
        #print(primive)
        if primive["mode"] != 4:
            raise "1"
        dbb=primive["indices"]
        print(dbb)
        
        va=primive["attributes"]
        print(va)

The id in meshinfo corresponds to the id in blockinfo
I will provide the complete model extraction code later

@darbdarb
Copy link

hihihi Everyone I’m glad that everyone pays attention to this code of mine I share some json parsing methods of gltf models for everyone to facilitate extraction and error correction. get block info

import json
import sys
with open(sys.argv[1],encoding='utf-8') as f:
    data = json.load(f)


for i in data.keys():
    a=data[i]
    #print(type(a))

accessors = data["accessors"]
buffer_views = data["bufferViews"]
type_num_dict = {"SCALAR": 1, "VEC2": 2, "VEC3": 3, "VEC4": 4, "MAT4": 16}

type_dict={
    5120: "BYTE",
    5121: "UNSIGNED_BYTE",
    5122: "SHORT",
    5123: "UNSIGNED_SHORT",
    5124: "INT",
    5125: "UNSIGNED_INT",
    5126: "FLOAT"
}

for accessor_index, accessor in enumerate(accessors):
    type_count = type_num_dict[accessor["type"]]
    #ct=accessor["componentType"]
    pos=buffer_views[accessor["bufferView"]]["byteOffset"]
    count=accessor["count"]
    ac=accessor["componentType"]
    print(accessor_index,count,type_dict[ac],type_count==1,pos)

get mesh info

print("mesh")
for n, mesh in enumerate(data["meshes"]):
    #print(n,mesh.keys())
    for j,primive in enumerate(mesh["primitives"]):
        #print(primive)
        if primive["mode"] != 4:
            raise "1"
        dbb=primive["indices"]
        print(dbb)
        
        va=primive["attributes"]
        print(va)

The id in meshinfo corresponds to the id in blockinfo I will provide the complete model extraction code later

ur amazing <3, after 2 years i didnt expect u to comment but here u are and im here for it, keep up the good work ive got notifcations on for this post

@DerpyMario
Copy link

any updates?

@CetaceanNation
Copy link

VRoid is now using ZSTD in place of GZIP. I wrote a Python script which handles the new decompression along with the decryption https://github.com/CetaceanNation/misc-scripts/blob/main/vroid-hub-downloader/vroid-hub-downloader.py#L52. If @Pldare completes their extraction code it would be nice to incorporate or at least add to my workflow.

@OwariSync
Copy link

i was able to get the model to at least import by modifying the import_gltf2_with_indices method of the blender vrm importer with

        json_dict['extensionsRequired'].pop(0)
        for i in range(len(json_dict['textures'])):
            if 'extensions' in json_dict['textures'][i]:
                json_dict['textures'][i].pop('extensions')

(it's just a dumb hack to remove the extension requirement)

However, the meshes seem to not be quite right image

I will keep looking into this and update if I find more

Sorry for what might be an obvious question, but how can I implement this as well? Using Cetacean's new script, I was able to get a model, but I can't import it due to this extension requirement.

@seed93
Copy link

seed93 commented Jan 19, 2024

How to crack this extension?
Even skip this extension, the geometry is wrong as before. Any ideas?

@TenjoinWai
Copy link

Tried opening this on this model but the resulting VRM seems to have blanked out textures, is there something I'm missing or I'm using the incorrect endpoint?

I modernized the script a bit and documented a lot of things so everyone can understand how it works now (I used .NET 6.0 here).

using System.Security.Cryptography;
using System.IO.Compression;

namespace Vroid
{
    class VroidDecode
     {
         public static void Main(string[] args) 
         {

            // read the file from arguments, then get the binary representation
            var fs = new FileStream(args[0], FileMode.Open);
            var bs = new BinaryReader(fs);

            // VRoid Hub Encrypted VRMs usually have a the following data structure:
            // * The first 16 bytes is the Initialization Vector
            // * 32 bytes is the actual AES key
            // * The cipher is AES-CBC
            // * Then the rest of the VRM is Gzipped compressed for web transfer.
            // 
            // with this in mind, we now try to access those specific bits first.
            int bufferSize = ((int)fs.Length)-48;
            using Aes aes = Aes.Create();
            aes.IV = bs.ReadBytes(16);
            aes.Key = bs.ReadBytes(32);
            aes.Mode = CipherMode.CBC;

            // now everything is set, we now try to decrypt it!
            var decrypted = aes.CreateDecryptor().TransformFinalBlock(bs.ReadBytes(bufferSize), 0, bufferSize);

            // of course now we got the decrypted part, we still have to deal with the Gzip compression.
            var decryptedStream = new MemoryStream(decrypted);
            var decompressedStream = new GZipStream(decryptedStream, CompressionMode.Decompress);
            var finalStream = new FileStream(args[0] + ".dec", FileMode.OpenOrCreate, FileAccess.Write);

            int data;
            while ((data = decompressedStream.ReadByte()) != -1)
            {
                finalStream.WriteByte((byte)data);
            }

            Console.WriteLine($"Done! Saved as {args[0]}.dec");

        }
     }
}

I have downloaded a model from vroidhub in Feb 2021. all i got is a xs3 file. the model itself may be vrm or pmx format since it could display vmd motion online. and when i process the xs2 file with the cs code, things always go wrong. I wonder how to decypt this xs3 file correctly, could you give some suggestions? the files are here:https://github.com/TenjoinWai/symphony-miku/tree/main

@Mika-Ra
Copy link

Mika-Ra commented Mar 31, 2024

Just think this will be helpful?
https://inside.pixiv.blog/2024/02/05/120000

@TenjoinWai
Copy link

Just think this will be helpful? https://inside.pixiv.blog/2024/02/05/120000

now i get a decrypted .bin file. it seems that this .bin file contains a pmx model and its textures and meshes. but i cannnot read this file. hope somebody could give me some suggestions. https://github.com/TenjoinWai/symphony-miku

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment