Ero-Lab 预告游戏 转职魔王X 相关问题

一大早收到日本朋友的邮件又是一个dmm已经存在一段时间的游戏
Ero-Lab预告
https://www.ero-labs.com/cn/prereg_iframe.html?id=45&hgame_id=136


dmm链接
https://play.games.dmm.co.jp/game/tenshoku_maoux

没看出什么引擎浅浅分析了一下资源列表在

https://dik52o19344s2.cloudfront.net/king/prod/66b9705e-297a-4c5f-8dbf-fdd19e0f17db/config.json

例如
部分文件下载链接如

https://dik52o19344s2.cloudfront.net/king/prod/66b9705e-297a-4c5f-8dbf-fdd19e0f17db/files/assets/190980851/1/005.mp3
对应json
		"190980851": {
			"name": "005.mp3",
			"type": "audio",
			"file": {
				"filename": "005.mp3",
				"size": 8789,
				"hash": "5886de84fc6097c0f95bb1edf0afd879",
				"url": "files/assets/190980851/1/005.mp3"
			},
			"data": null,
			"preload": true,
			"tags": [
				"se"
			],
			"i18n": {},
			"id": "190980851"
		},

而且资源文件似乎挺乱的


由于目前还在维护所以没有继续分析

看到json中存在spine字样但是没发现类似上述json结构
先这样吧。。。看看有没有大佬见过

1 个赞

666去年就想搞 今年就来ero了 :star_struck:

已解决问题
image

还是需要请教大佬

如何使用


每次拆解basis文件都会产生一堆垃圾

试试
./basisuD -unpack -no_ktx -format_only 6 texture.basis,这个会只生成BC7的png,然后用命令行保留其中的rbga的png就行。
话说有对应的png可以下吗?我有些png403,basis倒是可以。

import json
import os
import subprocess
import shutil

CDN_PREFIX = "https://dik52o19344s2.cloudfront.net/king/prod/66b9705e-297a-4c5f-8dbf-fdd19e0f17db/"
VALID_SUFFIXES = ('.basis', '.json', '.atlas.txt')
JSON_PATH = r"C:\Users\username\Downloads\config.json"
DOWNLOAD_DIR = "resdownload/zzmw"
TEMP_DIR = os.path.join(DOWNLOAD_DIR, "temp")
BASISU_EXE = "basisu.exe"

os.makedirs(DOWNLOAD_DIR, exist_ok=True)


file_map = {}

def basis_to_png(basis_path):
    basename = os.path.splitext(os.path.basename(basis_path))[0]

    
    if os.path.exists(TEMP_DIR):
        shutil.rmtree(TEMP_DIR)
    os.makedirs(TEMP_DIR, exist_ok=True)

    
    temp_basis_path = os.path.join(TEMP_DIR, os.path.basename(basis_path))
    shutil.copy(basis_path, temp_basis_path)

    
    result = subprocess.run([os.path.abspath(BASISU_EXE), "-file", os.path.basename(temp_basis_path)], cwd=TEMP_DIR)
    if result.returncode != 0:
        print(f"[!] basisu解码失败: {basis_path}")
        return False

    
    candidates = [
        f"{basename}_unpacked_rgba_RGBA32_0_0000.png",
        f"{basename}_unpacked_rgb_RGBA32_0_0000.png",
        f"{basename}_unpacked_rgba_BC7_RGBA_0000.png",
        f"{basename}_unpacked_rgb_BC7_RGBA_0000.png",
    ]

    for png_file in candidates:
        temp_png_path = os.path.join(TEMP_DIR, png_file)
        if os.path.exists(temp_png_path):
            final_png_path = os.path.join(DOWNLOAD_DIR, f"{basename}.png")
            shutil.move(temp_png_path, final_png_path)
            print(f"[+] 转换成功: {basis_path} → {final_png_path}")
            
            shutil.rmtree(TEMP_DIR)
            return True

    print(f"[!] 没找到有效PNG: {basis_path}")
    shutil.rmtree(TEMP_DIR)
    return False


def getpngmain():
    for f in os.listdir(DOWNLOAD_DIR):
        if f.endswith(".basis"):
            full_path = os.path.join(DOWNLOAD_DIR, f)
            if basis_to_png(full_path):
                
                try:
                    os.remove(full_path)
                    print(f"[+] 删除原文件: {full_path}")
                except Exception as e:
                    print(f"删除原文件失败: {full_path} - {e}")

def organize_files_by_prefix(spine_prefixes):
    for prefix in spine_prefixes:
        folder_path = os.path.join(DOWNLOAD_DIR, prefix)
        os.makedirs(folder_path, exist_ok=True)

        
        for f in os.listdir(DOWNLOAD_DIR):
            if f == prefix:
                
                continue
            if f.startswith(prefix) and os.path.isfile(os.path.join(DOWNLOAD_DIR, f)):
                src = os.path.join(DOWNLOAD_DIR, f)
                dst = os.path.join(folder_path, f)
                shutil.move(src, dst)
                print(f"[+] 移动: {f} → {folder_path}")

def rename_atlas_txt():
    for f in os.listdir(DOWNLOAD_DIR):
        if f.endswith(".atlas.txt"):
            old_path = os.path.join(DOWNLOAD_DIR, f)
            new_name = f[:-4]  
            new_path = os.path.join(DOWNLOAD_DIR, new_name)
            os.rename(old_path, new_path)
            print(f"[+] 重命名: {f} → {new_name}")


with open(JSON_PATH, "r", encoding="utf-8") as f:
    data = json.load(f)

assets = data.get("assets", {})
for asset_id, asset_info in assets.items():
    file_info = asset_info.get("file")
    if not file_info:
        continue

    url = None
    filename = None

    
    basis_variant = file_info.get("variants", {}).get("basis")
    if basis_variant:
        filename = basis_variant.get("filename")
        url = basis_variant.get("url")

    if not url or not filename:
        filename = file_info.get("filename")
        url = file_info.get("url")

    if not filename or not url:
        continue

    if not filename.endswith(VALID_SUFFIXES):
        continue

    full_url = CDN_PREFIX + url
    file_map[filename] = full_url


with open("zzmw.txt", "w", encoding="utf-8") as out_file:
    for fname, url in file_map.items():
        if fname.endswith(".json"):
            out_file.write(f"{url}\n   out={fname}\n\n")


os.system(f"aria2c -i zzmw.txt -j 16 -s 16 -x 16 --check-certificate=false --dir={DOWNLOAD_DIR}")


spine_prefixes = set()
for fname in os.listdir(DOWNLOAD_DIR):
    if fname.endswith(".json"):
        fpath = os.path.join(DOWNLOAD_DIR, fname)
        try:
            with open(fpath, "r", encoding="utf-8") as f:
                j = json.load(f)
                if "skeleton" in j and isinstance(j["skeleton"], dict) and "spine" in j["skeleton"]:
                    name_prefix = os.path.splitext(fname)[0]
                    spine_prefixes.add(name_prefix)
                else:
                    os.remove(fpath)
        except Exception:
            os.remove(fpath)


with open("zzmw.txt", "w", encoding="utf-8") as out_file:
    for fname, url in file_map.items():
        if fname.endswith(".json"):
            continue  
        for prefix in spine_prefixes:
            if fname.startswith(prefix):
                out_file.write(f"{url}\n   out={fname}\n\n")
                break


os.system(f"aria2c -i zzmw.txt -j 32 -s 16 -x 16 --check-certificate=false --dir={DOWNLOAD_DIR}")
getpngmain()
rename_atlas_txt()
organize_files_by_prefix(spine_prefixes)

目前的几个问题
1.png解码速度太慢
2.png背景非透明导致spine还原画面影响严重

这个有透明通道背景是透明的

我写的解压脚本,需要basisu.exe(就是编译出来的改个名)

解压basis.py
import os
import subprocess
import sys
from concurrent.futures import ThreadPoolExecutor
import multiprocessing
import re

def process_basis_file(basis_file_path, format_code=6):
    """处理单个basis文件:解压、清理并重命名"""
    file_base_name = os.path.splitext(os.path.basename(basis_file_path))[0]
    file_dir = os.path.dirname(basis_file_path)
    print(f"正在处理: {basis_file_path}")
    
    # 获取脚本所在目录
    script_dir = os.path.dirname(os.path.abspath(__file__))
    
    # 构建解压命令
    cmd = f'basisu -unpack -no_ktx -format_only {format_code} "{basis_file_path}"'
    try:
        # 指定UTF-8编码以避免解码错误
        result = subprocess.run(cmd, shell=True, check=True, capture_output=True, text=True, encoding='utf-8')
        if result.stderr:
            print(f"解压输出: {result.stderr}")
    except subprocess.CalledProcessError as e:
        print(f"错误: 解压 {basis_file_path} 失败: {e.stderr}")
        return False
    except UnicodeDecodeError:
        # 作为备选,尝试使用系统默认编码
        try:
            result = subprocess.run(cmd, shell=True, check=True, capture_output=True, text=True)
            if result.stderr:
                print(f"解压输出: {result.stderr}")
        except Exception as e:
            print(f"错误: 解压 {basis_file_path} 时发生未知错误: {str(e)}")
            return False
    
    # 定义优先级文件匹配模式
    pattern1 = re.compile(f'^{re.escape(file_base_name)}_unpacked_rgba_BC7_RGBA_0000\\.png$', re.IGNORECASE)
    pattern2 = re.compile(f'^{re.escape(file_base_name)}_unpacked_rgba_BC7_RGBA_0_0000\\.png$', re.IGNORECASE)
    cleanup_pattern = re.compile(f'^{re.escape(file_base_name)}_unpacked_.*\\.png$', re.IGNORECASE)
    
    # 按优先级查找匹配的RGBA文件
    rgba_file = None
    for pattern in [pattern1, pattern2]:
        matches = [f for f in os.listdir(script_dir) if pattern.match(f)]
        if matches:
            rgba_file = os.path.join(script_dir, matches[0])
            print(f"找到匹配文件 (优先级{(pattern == pattern1) + 1}): {rgba_file}")
            break
    
    if rgba_file is None:
        print(f"错误: 未找到符合优先级的RGBA文件")
        return False
    
    # 重命名RGBA文件到原始目录
    target_name = os.path.join(file_dir, f'{file_base_name}.png')
    
    # 直接覆盖目标文件
    if os.path.exists(target_name):
        os.remove(target_name)
        print(f"已删除重复文件: {target_name}")
    
    # 移动并重命名文件
    os.replace(rgba_file, target_name)
    print(f"已移动并重命名: {rgba_file} -> {target_name}")
    
    # 清理其他解压文件
    for f in os.listdir(script_dir):
        file_path = os.path.join(script_dir, f)
        if cleanup_pattern.match(f) and file_path != rgba_file:
            os.remove(file_path)
            print(f"已清理: {file_path}")
    
    return True

def find_basis_files(directory):
    """递归查找目录及其子目录中的所有basis文件"""
    basis_files = []
    for root, _, files in os.walk(directory):
        for file in files:
            if file.lower().endswith('.basis'):
                basis_files.append(os.path.join(root, file))
    return basis_files

def main():
    print("=" * 40)
    print("Basis文件批量处理工具 (格式代码固定为6 - BC7_RGBA)")
    print("=" * 40)
    
    while True:
        input_path = input("\n请输入Basis文件/目录路径 (输入q退出): ").strip()
        if input_path.lower() == 'q':
            print("程序已退出。")
            break
        
        if not os.path.exists(input_path):
            print("错误: 路径不存在,请重新输入。")
            continue
        
        # 处理文件或目录
        if os.path.isfile(input_path):
            if input_path.lower().endswith('.basis'):
                process_basis_file(input_path)
            else:
                print("错误: 非Basis文件。")
        else:
            basis_files = find_basis_files(input_path)
            if not basis_files:
                print("目录及其子目录中未找到Basis文件。")
            else:
                print(f"找到 {len(basis_files)} 个Basis文件,开始处理...")
                cpu_count = multiprocessing.cpu_count()
                with ThreadPoolExecutor(max_workers=cpu_count) as executor:
                    results = list(executor.map(lambda file: process_basis_file(file), basis_files))
                success = sum(results)
                print(f"处理完成: 成功 {success} 个,失败 {len(basis_files)-success} 个。")

if __name__ == "__main__":
    main()

改了几次,解决了会在py目录生成一些不知道什么的png的问题。能的话吧线程开小一点,线程太多感觉io流会过载。

import json
import os
import subprocess
import shutil
from concurrent.futures import ThreadPoolExecutor, as_completed


CDN_PREFIX = "https://dik52o19344s2.cloudfront.net/king/prod/66b9705e-297a-4c5f-8dbf-fdd19e0f17db/"
VALID_SUFFIXES = ('.basis', '.json', '.atlas.txt')
JSON_PATH = r"C:\Users\username\Downloads\config.json"
DOWNLOAD_DIR = "resdownload/zzmw"
TEMP_DIR = os.path.join(DOWNLOAD_DIR, "temp")
BASISU_EXE = "basisu.exe"

os.makedirs(DOWNLOAD_DIR, exist_ok=True)
os.makedirs(TEMP_DIR, exist_ok=True)

file_map = {}


def basis_to_png_threadsafe(basis_path):
    basename = os.path.splitext(os.path.basename(basis_path))[0]
    work_dir = os.path.join(TEMP_DIR, basename)

    try:
        os.makedirs(work_dir, exist_ok=True)

        temp_basis_path = os.path.join(work_dir, os.path.basename(basis_path))
        shutil.copy(basis_path, temp_basis_path)

        result = subprocess.run(
            [os.path.abspath(BASISU_EXE), "-file", os.path.basename(temp_basis_path)],
            cwd=work_dir
        )

        if result.returncode != 0:
            print(f"[!] basisu解码失败: {basis_path}")
            return False

        candidates = [
            f"{basename}_unpacked_rgba_PVRTC1_4_RGBA_0000.png",
            f"{basename}_still1_unpacked_rgb_PVRTC1_4_RGBA_0000.png",
            f"{basename}_unpacked_rgb_PVRTC1_4_RGB_0000.png"
        ]

        for png_file in candidates:
            temp_png_path = os.path.join(work_dir, png_file)
            if os.path.exists(temp_png_path):
                final_png_path = os.path.join(DOWNLOAD_DIR, f"{basename}.png")
                shutil.move(temp_png_path, final_png_path)
                print(f"[+] 转换成功: {basis_path} → {final_png_path}")
                return True

        print(f"[!] 没找到有效PNG: {basis_path}")
        return False

    finally:
        shutil.rmtree(work_dir, ignore_errors=True)


def clean_temp():
    if os.path.exists(TEMP_DIR):
        shutil.rmtree(TEMP_DIR)
        print("[*] 已清理临时目录")


def getpngmain():
    basis_files = [
        os.path.join(DOWNLOAD_DIR, f)
        for f in os.listdir(DOWNLOAD_DIR)
        if f.endswith(".basis")
    ]

    with ThreadPoolExecutor(max_workers=10) as executor:
        future_to_path = {executor.submit(basis_to_png_threadsafe, path): path for path in basis_files}

        for future in as_completed(future_to_path):
            path = future_to_path[future]
            try:
                if future.result():
                    os.remove(path)
                    print(f"[+] 删除原文件: {path}")
            except Exception as e:
                print(f"[!] 处理失败 {path}: {e}")

    clean_temp()

def organize_files_by_prefix(spine_prefixes):
    for prefix in spine_prefixes:
        folder_path = os.path.join(DOWNLOAD_DIR, prefix)
        os.makedirs(folder_path, exist_ok=True)

        
        for f in os.listdir(DOWNLOAD_DIR):
            if f == prefix:
                
                continue
            if f.startswith(prefix) and os.path.isfile(os.path.join(DOWNLOAD_DIR, f)):
                src = os.path.join(DOWNLOAD_DIR, f)
                dst = os.path.join(folder_path, f)
                shutil.move(src, dst)
                print(f"[+] 移动: {f} → {folder_path}")

def rename_atlas_txt():
    for f in os.listdir(DOWNLOAD_DIR):
        if f.endswith(".atlas.txt"):
            old_path = os.path.join(DOWNLOAD_DIR, f)
            new_name = f[:-4]  
            new_path = os.path.join(DOWNLOAD_DIR, new_name)
            os.rename(old_path, new_path)
            print(f"[+] 重命名: {f} → {new_name}")


with open(JSON_PATH, "r", encoding="utf-8") as f:
    data = json.load(f)

assets = data.get("assets", {})
for asset_id, asset_info in assets.items():
    file_info = asset_info.get("file")
    if not file_info:
        continue

    url = None
    filename = None

    
    basis_variant = file_info.get("variants", {}).get("basis")
    if basis_variant:
        filename = basis_variant.get("filename")
        url = basis_variant.get("url")

    if not url or not filename:
        filename = file_info.get("filename")
        url = file_info.get("url")

    if not filename or not url:
        continue

    if not filename.endswith(VALID_SUFFIXES):
        continue

    full_url = CDN_PREFIX + url
    file_map[filename] = full_url


with open("zzmw.txt", "w", encoding="utf-8") as out_file:
    for fname, url in file_map.items():
        if fname.endswith(".json"):
            out_file.write(f"{url}\n   out={fname}\n\n")


os.system(f"aria2c -i zzmw.txt -j 16 -s 16 -x 16 --check-certificate=false --dir={DOWNLOAD_DIR}")


spine_prefixes = set()
for fname in os.listdir(DOWNLOAD_DIR):
    if fname.endswith(".json"):
        fpath = os.path.join(DOWNLOAD_DIR, fname)
        try:
            with open(fpath, "r", encoding="utf-8") as f:
                j = json.load(f)
                if "skeleton" in j and isinstance(j["skeleton"], dict) and "spine" in j["skeleton"]:
                    name_prefix = os.path.splitext(fname)[0]
                    spine_prefixes.add(name_prefix)
                else:
                    os.remove(fpath)
        except Exception:
            os.remove(fpath)


with open("zzmw.txt", "w", encoding="utf-8") as out_file:
    for fname, url in file_map.items():
        if fname.endswith(".json"):
            continue  
        for prefix in spine_prefixes:
            if fname.startswith(prefix):
                out_file.write(f"{url}\n   out={fname}\n\n")
                break


os.system(f"aria2c -i zzmw.txt -j 32 -s 16 -x 16 --check-certificate=false --dir={DOWNLOAD_DIR}")
getpngmain()
rename_atlas_txt()
organize_files_by_prefix(spine_prefixes)

就这样先吧…由于是dmm他的资源清单获取逻辑一路追踪上去要jwt会话令牌就不管了先等erolab版吧(好歹e服没码)
注意这个脚本只会下载spine文件(似乎只有hcg是spine?)当然全下也行自己改函数都写好了就去掉个过滤就行完整资源大约2200个文件
config.json
config.zip (4.8 MB)

1 个赞

只有CG是spine
人物立绘是静态图,名字为

king_img_chara… .basis

静态CG是

king_adv… .basis

CG文本是

adv… .json

def basis_to_png_threadsafe(basis_path):
    basename = os.path.splitext(os.path.basename(basis_path))[0]
    work_dir = os.path.join(TEMP_DIR, basename)

    try:
        os.makedirs(work_dir, exist_ok=True)

        temp_basis_path = os.path.join(work_dir, os.path.basename(basis_path))
        shutil.copy(basis_path, temp_basis_path)

        result = subprocess.run(
            [os.path.abspath(BASISU_EXE), "-file", os.path.basename(temp_basis_path)],
            cwd=work_dir
        )

        if result.returncode != 0:
            print(f"[!] basisu解码失败: {basis_path}")
            return False

        candidates = [
            f"{basename}_unpacked_rgba_BC7_RGBA_0000.png"]

        for png_file in candidates:
            temp_png_path = os.path.join(work_dir, png_file)
            if os.path.exists(temp_png_path):
                final_png_path = os.path.join(DOWNLOAD_DIR, f"{basename}.png")
                shutil.move(temp_png_path, final_png_path)
                print(f"[+] 转换成功: {basis_path} → {final_png_path}")
                return True

        print(f"[!] 没找到有效PNG: {basis_path}")
        return False

    finally:
        shutil.rmtree(work_dir, ignore_errors=True)

修改一下这个函数png的文件名有点问题
下载器已经准备好就等E服上线了