吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 3321|回复: 1
上一主题 下一主题
收起左侧

[Python 原创] 逆向WWMI制作的MOD文件工具

[复制链接]
跳转到指定楼层
楼主
anqie 发表于 2025-4-6 11:21 回帖奖励

事先声明:此MOD逆向工具仅用于学习,由于涉及官方以及二创问题,因此不放出完整代码和发行版工具。

工具名称:WWMI___MOD逆向工具


注意:这个帖子是在软件中编辑好粘贴过来的,可能格式有点问题,将就着看吧,谢谢理解。

逆向思路

初期查看了mod的输出文件夹,包含Meshes,Textures,mod.ini(部分包含disabledmod.ini)

Meshes文件夹下包含了所有的.buf文件(简单理解为一种缓存文件),如:position.buf,index.buf等文件,这些文件包含了所有的模型信息,简单来说就是二进制读取这些然后按照WWMI封装的顺序反向解除封装就可以了。

接下来去找有关排列顺序的部分,然后查看了WWMITools的源代码,因为这个blender配套工具本身是开源的,就直接下载源码看了,在exoprt_mod这块里面总共有大概四个python文件,这里面就包含了封装的过程。

当然,这样逆向出来的东西会变成一个整块,不是真的作者编辑出来的样子,那要怎么处理?这里找到了mod.ini文件,里面存储了mod的配置,主要包括了设置Component这种的,把模型分组件了,因为我本身也做过mod所以了解一点,这个模型都是按照Component划分的,最终采用读取mod.ini的组件配置来分割模型。

当然,我本人并不懂关于3d模型格式转换问题,所以实际做转换功能的时候是配合了各种工具和资料完成的。


代码区

前置条件:

@staticmethod
    def read_half_float(data):
        u_half = (data[0] << 8) | data[1]
        v_half = (data[2] << 8) | data[3]

        def half_to_float(h):
            s = int((h >> 15) & 0x1)
            e = int((h >> 10) & 0x1f)
            f = int(h & 0x3ff)

            if e == 0:
                if f == 0:
                    return -0.0 if s else 0.0
                else:
                    return (-1 if s else 1) * pow(2, -14) * (f / 1024)
            elif e == 31:
                return float('-inf' if s else 'inf')

            return (-1 if s else 1) * pow(2, e - 15) * (1 + f / 1024)

        return (half_to_float(u_half), 1.0 - half_to_float(v_half))

buf文件读取:

def read_buffer_file(self, file_path: str, element_size: int,
                         unpack_format: str, process_func=None) -> list:
        file_name = os.path.basename(file_path)
        if not os.path.exists(file_path):
            print(f"找不到文件: {file_name}")
            self.converted_files[file_name] = False
            return []

        print(f"\n读取文件: {file_name}")
        self.debug_file_header(file_path)

        results = []
        try:
            with open(file_path, 'rb') as f:
                file_size = os.path.getsize(file_path)
                expected_elements = file_size // element_size
                print(f"预期元素数量: {expected_elements}")

                count = 0
                while True:
                    try:
                        data = f.read(element_size)
                        if not data or len(data) < element_size:
                            break

                        values = struct.unpack(unpack_format, data)
                        if process_func:
                            values = process_func(values)

                        results.append(values)
                        count += 1

                        if count <= 5 or count % 10000 == 0:
                            print(f"元素 {count}: {values}")

                    except struct.error as e:
                        print(f"解析第{count}个元素时出错: {str(e)}")
                        break
                    except Exception as e:
                        print(f"处理第{count}个元素时出错: {str(e)}")
                        break

                print(f"成功读取了 {len(results)} 个元素")
                self.converted_files[file_name] = True

        except Exception as e:
            print(f"读取文件时出错: {str(e)}")
            self.converted_files[file_name] = False

        return results

模型转换

def convert_mod_to_model(self, mod_folder: str, output_path: str, format='FBX'):

        self._ensure_blender_available()

        print(f"\n开始转换mod到{format}...")
        print(f"Mod文件夹: {mod_folder}")
        print(f"输出路径: {output_path}")

        try:

            bpy.ops.object.select_all(action='SELECT')
            bpy.ops.object.delete()

            meshes_dir = os.path.join(mod_folder, 'Meshes')
            if not os.path.exists(meshes_dir):
                raise Exception(f"找不到Meshes文件夹: {meshes_dir}")

            print(f"\n读取buffer文件...")

            positions = self.read_buffer_file(
                os.path.join(meshes_dir, 'Position.buf'),
                element_size=12,
                unpack_format='fff'
            )

            vectors = self.read_buffer_file(
                os.path.join(meshes_dir, 'Vector.buf'),
                element_size=8,
                unpack_format='8b',
                process_func=lambda x: (
                    (x[0] / 127.0, x[1] / 127.0, x[2] / 127.0),
                    (x[4] / 127.0, x[5] / 127.0, x[6] / 127.0, x[7] / 127.0)
                )
            )

            uvs = self.read_buffer_file(
                os.path.join(meshes_dir, 'TexCoord.buf'),
                element_size=16,
                unpack_format='16B',
                process_func=lambda x: self.read_half_float(x[:4])
            )

            colors = self.read_buffer_file(
                os.path.join(meshes_dir, 'Color.buf'),
                element_size=4,
                unpack_format='4B',
                process_func=lambda x: tuple(v / 255.0 for v in x)
            )

            blend_data = self.read_buffer_file(
                os.path.join(meshes_dir, 'Blend.buf'),
                element_size=8,
                unpack_format='8B',
                process_func=lambda x: (
                    (x[0], x[1], x[2], x[3]),
                    tuple(w / 255.0 for w in x[4:8])
                )
            )

            indices = self.read_buffer_file(
                os.path.join(meshes_dir, 'Index.buf'),
                element_size=4,
                unpack_format='I',
                process_func=lambda x: x[0]
            )

            print("\n读取形态键数据...")

            shape_key_offsets = self.read_buffer_file(
                os.path.join(meshes_dir, 'ShapeKeyVertexOffset.buf'),
                element_size=12,
                unpack_format='fff'
            )

            shape_key_vertex_ids = self.read_buffer_file(
                os.path.join(meshes_dir, 'ShapeKeyVertexId.buf'),
                element_size=4,
                unpack_format='I',
                process_func=lambda x: x[0]
            )

            shape_key_ranges = self.read_buffer_file(
                os.path.join(meshes_dir, 'ShapeKeyOffset.buf'),
                element_size=4,
                unpack_format='I',
                process_func=lambda x: x[0]
            )

            components = self.parse_mod_ini(mod_folder)
            if not components:
                print("警告:无法从mod.ini获取组件信息,将创建单个模型")
                components = [{
                    "name": "SingleMesh",
                    "start_idx": 0,
                    "count": len(indices),
                    "bone_start": 0,
                    "bone_count": max(max(indices) for indices, _ in blend_data) + 1
                }]

            print("\n开始根据组件信息分割模型...")

            # 为每个组件创建顶点组
            vertex_groups = {}
            vertex_to_group = {}

            for comp in components:
                start_idx = comp["start_idx"]
                count = comp["count"]

                used_vertices = set()
                faces = set()

                if "sub_components" in comp:
                    for sub in comp["sub_components"]:
                        sub_start = sub["start_idx"]
                        sub_count = sub["count"]
                        for i in range(sub_start, sub_start + sub_count, 3):
                            if i + 2 >= len(indices):
                                break
                            face = indices[i:i+3]
                            faces.add(tuple(face))
                            used_vertices.update(face)
                else:

                    for i in range(start_idx, start_idx + count, 3):
                        if i + 2 >= len(indices):
                            break
                        face = indices[i:i+3]
                        faces.add(tuple(face))
                        used_vertices.update(face)

                vertex_groups[comp["name"]] = {
                    "vertices": sorted(list(used_vertices)),
                    "vertex_map": {v: i for i, v in enumerate(sorted(list(used_vertices)))},
                    "faces": faces,
                    "bone_range": (comp["bone_start"], comp["bone_start"] + comp["bone_count"])
                }

                for v in used_vertices:
                    vertex_to_group[v] = comp["name"]
# 省略---------------------------------------------------------------------

部分说明

转换核心在于.buf文件的读取和实际转换时顶点位置记录

读取后需要需要建立网格(3d建模里面有这个概念),在网格基础上设置顶点映射等信息

ku和mi的mod基础都是3dmigoto,它主要用ib和vb文件记录模型信息,万变不离其宗,只要获取到模型实际信息,最后转换成什么模型格式都是可以的,当然,作者仍然处于小白阶段,大佬们看看就好,别当真。

最后给大家的提醒:知识无罪,但也不要随意侵害他人权益。

免费评分

参与人数 2吾爱币 +8 热心值 +2 收起 理由
笙若 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
苏紫方璇 + 7 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!

查看全部评分

发帖前要善用【论坛搜索】功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

沙发
雪色的夏天 发表于 2025-4-28 17:21
大佬,想请教一下你的Shading: Textures(纹理)怎么处理的?我自己也写过类似的脚本,但是纹理和组件完全找不到关系
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - 52pojie.cn ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2026-10-23 21:21

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表