关于ミナシゴノシゴトRのサムネイル画像的尝试(只有部分资源的下载)

【公式ゲーム】ミナシゴノシゴトR|キャラ・ストーリー・X(旧Twitter)

许久没摸鱼了,今天突然看到想试试。

简单抓包看到config.json,不过文件数量太小了很明显不完全。

再找了会就可以找到resource.json,看了看文件是base64+aes加密的。

因为是cocos2djs的,找到index.json分析

找decrypt可以直接找到

\\\\\\\\\\\\\\\_decrypt
_decrypt: function(t) {

var e = CryptoJS.SHA256(“密钥"), i = CryptoJS.enc.Base64.stringify(e).substr(0, 32), n = {

iv: “IV",

mode: CryptoJS.mode.CTR

}, o = CryptoJS.AES.decrypt(t, i, n);

return JSON.parse(CryptoJS.enc.Utf8.stringify(o));

}

});

然后base64+aes解密就行(不过文件用了个openssl的Salted需要适配一下,模式与py的ctr有点出入)

解密出来大概是这样的

resource
{
    "version": "2.6.4",
    "sizes": {
        "0": 8399355112,
        "1": 1850563607,
        "3": 5870036352
    },
    "assets": {
        "coscli": {
            "0": {
                "md5": "66bb737e68efcb8aef88f882fe48d1d0"
            }
        }
    },
    "searchPaths": [],
    "commitHash": "d915065d470eef363680d288fcd243a29704d852"
}

然后看读取下载

在downlad里面

\\\\\\\\\\\\\\\_download
_download: function(t, e, i, n, o) {
var s = this._manifest.getManifestData(t);
if (s) {
if (n) {
var r = this._convertToMD5Path(s);
n.push(a.makeLocalStoragePath(r));
}
this._downloadAsset(s, e, o);
} else {
n && n.push(t);
this._downloadUrl(t, a.makeStoragePath(a.makeAssetPath(t)), !1, e, o);
}
},

大概就是各种取md5然后下载什么的,这里给出拼接下载的脚本。

拼接下载
import json
import hashlib
import os
import requests
from urllib.parse import urljoin
import sys

def l(t):
    """首字符0/1/2/3对应的分片规则"""
    return [t[0:2], t[4:6]]

def d(t):
    """首字符4/5/6/7对应的分片规则"""
    return [t[2:4], t[6:8], t[0:2]]

def p(t):
    """首字符8/9/a/b对应的分片规则"""
    return [t[4:6], t[0:2], t[6:8], t[2:4]]

def h(t):
    """首字符c/d/e/f对应的分片规则"""
    return [t[6:8], t[2:4], t[4:6], t[0:2]]

split_map = {
    '0': l, '1': l, '2': l, '3': l,
    '4': d, '5': d, '6': d, '7': d,
    '8': p, '9': p, 'a': p, 'b': p,
    'c': h, 'd': h, 'e': h, 'f': h
}

def f(md5_str):
    if not md5_str or len(md5_str) < 8:
        return ""
    first_char = md5_str[0].lower()
    if first_char not in split_map:
        return ""
    parts = split_map[first_char](md5_str)
    parts = [p for p in parts if p]
    return "/".join(parts)

def convert_md5_path(asset_path, asset_md5):
    if not asset_path or not asset_md5:
        return ""
    
    full_path_md5 = hashlib.md5(asset_path.encode('utf-8')).hexdigest()
    
    if '.' in asset_path:
        no_ext_path = asset_path[:asset_path.rindex('.')]
        ext = asset_path[asset_path.rindex('.'):]
    else:
        no_ext_path = asset_path
        ext = ""
    
    no_ext_md5 = hashlib.md5(no_ext_path.encode('utf-8')).hexdigest()
    split_path = f(no_ext_md5)
    
    final_md5_part = f"{full_path_md5}/{split_path}/{asset_md5}{ext}"
    final_md5_part = final_md5_part.replace("//", "/").rstrip("/")
    return final_md5_part

def parse_manifest_and_download(manifest_path, base_url):
    try:
        with open(manifest_path, 'r', encoding='utf-8') as f:
            manifest_data = json.load(f)
    except FileNotFoundError:
        print(f"❌ 错误:未找到manifest文件 {manifest_path}")
        return
    except json.JSONDecodeError:
        print(f"❌ 错误:{manifest_path} 不是有效的JSON文件")
        return
    
    version = manifest_data.get("version", "")
    if not version:
        print("⚠️  警告:manifest中未找到version字段,可能导致下载失败!")
    else:
        print(f"✅ 解析到manifest版本:{version}")
    
    assets = manifest_data.get("assets", {})
    output_list = []
    
    for asset_path, versions in assets.items():
        # 优先选3版本,无则选1,最后选0
        if "3" in versions:
            selected_version = versions["3"]
        elif "1" in versions:
            selected_version = versions["1"]
        elif "0" in versions:
            selected_version = versions["0"]
        else:
            print(f"⚠️  警告:{asset_path} 无可用版本,跳过")
            continue
        
        asset_md5 = selected_version.get("md5", "")
        if not asset_md5:
            print(f"⚠️  警告:{asset_path} 无MD5值,跳过")
            continue
        
        md5_path_part = convert_md5_path(asset_path, asset_md5)
        if not md5_path_part:
            print(f"⚠️  警告:{asset_path} 生成MD5路径失败,跳过")
            continue
        
        if version:
            full_url = urljoin(base_url + "/", f"{version}/{md5_path_part}")
        else:
            full_url = urljoin(base_url + "/", md5_path_part)
        
        output_list.append({
            "url": full_url,
            "path": asset_path
        })
        print(f"📌 生成URL:{full_url} → 对应路径:{asset_path}")
    
    # 保存output.json
    with open("output.json", 'w', encoding='utf-8') as f:
        json.dump(output_list, f, ensure_ascii=False, indent=4)
    print(f"\n✅ 已生成output.json,共{len(output_list)}个资源")
    
    #下载资源
    download_dir = "downloaded_assets"
    os.makedirs(download_dir, exist_ok=True)
    print(f"\n📥 开始下载资源(保存到 {download_dir})...")
    
    success_count = 0
    fail_count = 0
    fail_list = []
    
    for item in output_list:
        url = item["url"]
        asset_path = item["path"]
        save_path = os.path.join(download_dir, asset_path.replace("/", os.sep))
        os.makedirs(os.path.dirname(save_path), exist_ok=True)
        
        # 下载资源
        try:
            print(f"\n正在下载:{url} → {save_path}")
            headers = {
                "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36"
            }
            response = requests.get(url, stream=True, timeout=60, headers=headers)
            response.raise_for_status()  # 抛出HTTP错误(4xx/5xx)
            
            # 写入文件
            with open(save_path, 'wb') as f:
                for chunk in response.iter_content(chunk_size=8192):
                    if chunk:
                        f.write(chunk)
            print(f"✅ 下载完成:{save_path}")
            success_count += 1
        except requests.exceptions.RequestException as e:
            error_msg = f"❌ 下载失败:{url} → {str(e)}"
            print(error_msg)
            fail_count += 1
            fail_list.append({
                "url": url,
                "path": asset_path,
                "error": str(e)
            })
    
    print(f"\n📊 下载完成统计:")
    print(f"总资源数:{len(output_list)}")
    print(f"成功:{success_count}")
    print(f"失败:{fail_count}")
    
    if fail_list:
        with open("download_fail.json", 'w', encoding='utf-8') as f:
            json.dump(fail_list, f, ensure_ascii=False, indent=4)
        print(f"❌ 失败资源列表已保存到 download_fail.json")

if __name__ == "__main__":
    manifest_path = input("请输入manifest文件的路径(如./manifest.json):").strip()
    base_url = "https://minasigo-no-shigoto-pd-c-res.orphans-order.com"
    parse_manifest_and_download(manifest_path, base_url)



有部分额外文件下面也有回答了。decrypt的参数原来是token。。。

萌新看不懂但知道有这个: