Skip to content

Instantly share code, notes, and snippets.

@Pldare
Last active March 1, 2026 14:35
Show Gist options
  • Select an option

  • Save Pldare/ebf704c752a8d77ff9603d4adfe54083 to your computer and use it in GitHub Desktop.

Select an option

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);
}
}
}
import requests
import os
import json
import argparse
USER_AGENT = "Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/114.0"
HOST = "https://hub.vroid.com"
API_VERSION = "11"
def download_model_from_vroid(model_id, subdir=None):
#model_path_base = os.path.join(
# subdir if subdir else args.directory, model_id)
model_api_url = f"{HOST}/api/character_models/{model_id}"
#print(model_api_url)
return model_api_url
def get_user_model_ids(user_id):
model_ids = []
api_url = f"{HOST}/api/users/{user_id}/character_models?antisocial_or_hate_usage=&characterization_allowed_user=&corporate_commercial_use=&credit=&modification=&personal_commercial_use=&political_or_religious_usage=&redistribution=&sexual_expression=&violent_expression="
page_num = 1
while api_url:
user_r = requests.get(
api_url, headers={"User-Agent": USER_AGENT, "X-Api-Version": API_VERSION})
if not user_r.ok:
print(
f"[user:{user_id}:page:{page_num}] got bad response from vroid hub, {user_r.status_code}")
break
user_j = user_r.json()
if "next" in user_j["_links"]:
api_url = HOST + user_j["_links"]["next"]["href"]
else:
api_url = None
for model in user_j["data"]:
model_ids.append(model["id"])
print(f"[user:{user_id}] found {len(model_ids)} models")
return model_ids
def download_user_from_vroid(user_id):
user_api_url = f"{HOST}/api/users/{user_id}"
user_api_r = requests.get(user_api_url, headers={
"User-Agent": USER_AGENT, "X-Api-Version": API_VERSION})
if not user_api_r.ok:
print(
f"[user:{user_id}:api] got bad response from vroid hub, user might not exist, {user_api_r.status_code}")
return
user_api_j = user_api_r.json()
username = user_api_j["data"]["user"]["name"]
#user_base_path = os.path.join(args.directory, f"{username} ({user_id})")
#if not os.path.isdir(user_base_path):
# os.makedirs(user_base_path)
#json_path = f"{user_base_path}.info.json"
#if args.write_info_json:
# with open(json_path, "w") as json_file:
# json_file.write(json.dumps(user_api_j["data"]))
# print(f"[user:{user_id}:api] wrote '{os.path.basename(json_path)}'")
model_ids = get_user_model_ids(user_id)
all_url=[]
for model_id in model_ids:
url=download_model_from_vroid(model_id)#, user_base_path)
all_url.append(url)
return all_url
if __name__ == "__main__":
#parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter)
#parser.add_argument("-d", "--directory", type=str,
# help="save directory (defaults to current)", default=os.getcwd())
#args = parser.parse_args()
#print(args.directory)
#https://hub.vroid.com/en/users/36667771
download_user_from_vroid(36667771)
#use project https://github.com/Pldare/vrh-deobfuscator/tree/MatchAndBatch
#use example
#python dlhelp.py users_id or vroid_model_view_url
import os,sys
import re
import checkusersmodel
import time
def dl_glb(url,sdir):
print(url)
os.system("node src/index2.js {}".format(url))
url=sys.argv[1]
if "http" in url:
dl_glb(url,"model")
else:
all_url=checkusersmodel.download_user_from_vroid(url)
cou=len(all_url)
idd=1
for gurl in all_url:
print("{}/{}".format(idd,cou))
dl_glb(gurl,str(url))
#time.sleep(0)
idd+=1
@jdanny2002
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.

Hi excuse me to reply to you too, but i wanted to know if there's a way to import the glb models downloaded with your script to Blender, or any program at all... With blender i keep getting that PIXIV texture error

@pcerdo123alv
Copy link

pcerdo123alv commented Feb 6, 2025

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

Hi, is there any update about the complete model extraction code?

@Pldare
Copy link
Author

Pldare commented Feb 13, 2025

I checked the differences between the downloaded VRM and the VRM in the browser, and found that the vertex coordinate data had undergone some transformations

@twnlink
Copy link

twnlink commented Mar 2, 2025

VRoid Hub obfuscates the mesh of the preview models. You can read more on this here: https://toon.link/blog/1740863435/borrowing-intellectual-property.

@kotx
Copy link

kotx commented Mar 2, 2025

Nice post!

@Zer0TheObserver
Copy link

I checked the differences between the downloaded VRM and the VRM in the browser, and found that the vertex coordinate data had undergone some transformations

true dude, I hope this will shows how it works:
https://toon.link/blog/1740863435/borrowing-intellectual-property

@Pldare
Copy link
Author

Pldare commented Mar 1, 2026

Hello everyone, you can finally use this project https://github.com/Pldare/vrh-deobfuscator/tree/MatchAndBatch to download all or a single model from your user's collection (this was mostly made for research purposes). If you like vroid model, please support model author.

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