Add web-based .packed explorer, updated parser and ghidra untility script
This commit is contained in:
parent
8e0df74541
commit
58407ecc9f
35 changed files with 3897 additions and 353 deletions
177
tools/remaster/scrap_parse/.gitignore
vendored
Normal file
177
tools/remaster/scrap_parse/.gitignore
vendored
Normal file
|
@ -0,0 +1,177 @@
|
|||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
debug/
|
||||
target/
|
||||
|
||||
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
|
||||
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
|
||||
Cargo.lock
|
||||
|
||||
# These are backup files generated by rustfmt
|
||||
**/*.rs.bk
|
||||
|
||||
# MSVC Windows builds of rustc generate these, which store debugging information
|
||||
*.pdb
|
||||
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py,cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
cover/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
.pybuilder/
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
# For a library or package, you might want to ignore these files since the code is
|
||||
# intended to run in multiple environments; otherwise, check them in:
|
||||
# .python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
#Pipfile.lock
|
||||
|
||||
# poetry
|
||||
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||
# commonly ignored for libraries.
|
||||
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||
#poetry.lock
|
||||
|
||||
# pdm
|
||||
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
||||
#pdm.lock
|
||||
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
||||
# in version control.
|
||||
# https://pdm.fming.dev/#use-with-ide
|
||||
.pdm.toml
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
# pytype static type analyzer
|
||||
.pytype/
|
||||
|
||||
# Cython debug symbols
|
||||
cython_debug/
|
||||
|
||||
# PyCharm
|
||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.idea/
|
||||
|
||||
*.pkl.gz
|
747
tools/remaster/scrap_parse/Cargo.lock
generated
747
tools/remaster/scrap_parse/Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -9,13 +9,18 @@ edition = "2021"
|
|||
anyhow = "1.0.69"
|
||||
binrw = "0.11.1"
|
||||
chrono = { version = "0.4.23", features = ["serde"] }
|
||||
chrono-humanize = "0.2.2"
|
||||
# chrono-humanize = "0.2.2"
|
||||
clap = { version = "4.1.6", features = ["derive"] }
|
||||
configparser = { version = "3.0.2", features = ["indexmap"] }
|
||||
flate2 = "1.0.25"
|
||||
fs-err = "2.9.0"
|
||||
indexmap = { version = "1.9.2", features = ["serde"] }
|
||||
# memmap2 = "0.5.10"
|
||||
modular-bitfield = "0.11.2"
|
||||
rhexdump = "0.1.1"
|
||||
serde = { version = "1.0.152", features = ["derive"] }
|
||||
serde_json = { version = "1.0.93", features = ["unbounded_depth"] }
|
||||
serde-pickle = "1.1.1"
|
||||
serde_json = { version = "1.0.95", features = ["preserve_order", "unbounded_depth"] }
|
||||
steamlocate = "1.1.0"
|
||||
walkdir = "2.3.3"
|
||||
obj = "0.10.2"
|
||||
|
|
23
tools/remaster/scrap_parse/blender_plugin/__init__.py
Normal file
23
tools/remaster/scrap_parse/blender_plugin/__init__.py
Normal file
|
@ -0,0 +1,23 @@
|
|||
import pickle
|
||||
import subprocess as SP
|
||||
|
||||
from . import packed_browser
|
||||
from . import level_import
|
||||
|
||||
def scrap_bridge(*cmd):
|
||||
cmd=["scrap_parse",*cmd]
|
||||
proc=SP.Popen(cmd,stderr=None,stdin=None,stdout=SP.PIPE,shell=True,text=False)
|
||||
stdout,stderr=proc.communicate()
|
||||
code=proc.wait()
|
||||
if code:
|
||||
raise RuntimeError(str(stderr,"utf8"))
|
||||
return pickle.loads(stdout)
|
||||
|
||||
def register():
|
||||
packed_browser.register()
|
||||
level_import.regiser()
|
||||
|
||||
def unregister():
|
||||
packed_browser.unregister()
|
||||
level_import.unregister()
|
||||
|
|
@ -2,16 +2,16 @@ import bpy
|
|||
import sys
|
||||
import os
|
||||
import re
|
||||
import json
|
||||
import gzip
|
||||
import pickle
|
||||
import argparse
|
||||
import shutil
|
||||
from glob import glob
|
||||
from mathutils import Vector
|
||||
from pathlib import Path
|
||||
import numpy as np
|
||||
import itertools as ITT
|
||||
from pprint import pprint
|
||||
# from .. import scrap_bridge
|
||||
import bmesh
|
||||
from bpy.props import StringProperty, BoolProperty
|
||||
from bpy_extras.io_utils import ImportHelper
|
||||
|
@ -25,12 +25,6 @@ if "--" in sys.argv:
|
|||
parser.add_argument("file_list", nargs="+")
|
||||
cmdline = parser.parse_args(args)
|
||||
|
||||
|
||||
def fix_pos(xyz):
|
||||
x, y, z = xyz
|
||||
return x, z, y
|
||||
|
||||
|
||||
class ScrapImporter(object):
|
||||
def __init__(self, options):
|
||||
self.unhandled = set()
|
||||
|
@ -39,16 +33,22 @@ class ScrapImporter(object):
|
|||
self.model_scale = 1000.0
|
||||
self.spawn_pos = {}
|
||||
self.objects = {}
|
||||
print("Loading", filepath)
|
||||
with gzip.open(filepath, "r") as fh:
|
||||
data = json.load(fh)
|
||||
# print("Loading", filepath)
|
||||
# scrapland_path=scrap_bridge("find-scrapland")
|
||||
# print(scrapland_path)
|
||||
# packed_data=scrap_bridge("parse-packed",scrapland_path)
|
||||
# print(packed_data)
|
||||
# get_output(["scrap_parse","parse-file","--stdout",scrapland_path,"levels/temple"])
|
||||
# raise NotImplementedError()
|
||||
with gzip.open(filepath, "rb") as fh:
|
||||
data = pickle.load(fh)
|
||||
self.path = data.pop("path")
|
||||
self.root = data.pop("root")
|
||||
self.config = data.pop("config")
|
||||
self.dummies = data.pop("dummies")["DUM"]["dummies"]
|
||||
self.dummies = data.pop("dummies")["dummies"]
|
||||
self.moredummies = data.pop("moredummies")
|
||||
self.emi = data.pop("emi")["EMI"]
|
||||
self.sm3 = data.pop("sm3")["SM3"]
|
||||
self.emi = data.pop("emi")
|
||||
self.sm3 = data.pop("sm3")
|
||||
|
||||
def make_empty(self, name, pos, rot=None):
|
||||
empty = bpy.data.objects.new(name, None)
|
||||
|
@ -119,7 +119,7 @@ class ScrapImporter(object):
|
|||
bpy.context.scene.collection.objects.link(light)
|
||||
|
||||
def create_nodes(self):
|
||||
for node in self.sm3["scene"]["nodes"]:
|
||||
for node in self.sm3["scene"].get("nodes",[]):
|
||||
node_name = node["name"]
|
||||
node = node.get("content", {})
|
||||
if not node:
|
||||
|
@ -212,6 +212,8 @@ class ScrapImporter(object):
|
|||
)
|
||||
else:
|
||||
folders = ITT.chain([start_folder], start_folder.parents)
|
||||
folders=list(folders)
|
||||
print(f"Looking for {path} in {folders}")
|
||||
for folder in folders:
|
||||
for suffix in file_extensions:
|
||||
for dds in [".", "dds"]:
|
||||
|
@ -227,7 +229,7 @@ class ScrapImporter(object):
|
|||
return list(filter(lambda i: (i.type, i.name) == (dtype, name), node.inputs))
|
||||
|
||||
|
||||
def build_material(self, mat_key, mat_def):
|
||||
def build_material(self, mat_key, mat_def, map_def):
|
||||
mat_props = dict(m.groups() for m in re.finditer(r"\(\+(\w+)(?::(\w*))?\)",mat_key))
|
||||
for k,v in mat_props.items():
|
||||
mat_props[k]=v or True
|
||||
|
@ -260,13 +262,13 @@ class ScrapImporter(object):
|
|||
"Roughness": 0.0,
|
||||
"Specular": 0.2,
|
||||
}
|
||||
tex_slots=[
|
||||
"Base Color",
|
||||
"Metallic",
|
||||
None, # "Clearcoat" ? env map?
|
||||
"Normal",
|
||||
"Emission"
|
||||
]
|
||||
tex_slot_map={
|
||||
"base": "Base Color",
|
||||
"metallic":"Metallic",
|
||||
"unk_1":None, # "Clearcoat" ? env map?
|
||||
"bump":"Normal",
|
||||
"glow":"Emission"
|
||||
}
|
||||
|
||||
mat = bpy.data.materials.new(mat_key)
|
||||
mat.use_nodes = True
|
||||
|
@ -275,7 +277,13 @@ class ScrapImporter(object):
|
|||
imgs = {}
|
||||
animated_textures={}
|
||||
is_transparent = True
|
||||
for slot,tex in zip(tex_slots,mat_def["maps"]):
|
||||
print(map_def)
|
||||
if map_def[0]:
|
||||
print("=== MAP[0]:",self.resolve_path(map_def[0]))
|
||||
if map_def[2]:
|
||||
print("=== MAP[2]:",self.resolve_path(map_def[2]))
|
||||
for slot,tex in mat_def["maps"].items():
|
||||
slot=tex_slot_map.get(slot)
|
||||
if (slot is None) and tex:
|
||||
self.unhandled.add(tex["texture"])
|
||||
print(f"Don't know what to do with {tex}")
|
||||
|
@ -286,9 +294,7 @@ class ScrapImporter(object):
|
|||
continue
|
||||
tex_name = os.path.basename(tex_file)
|
||||
if ".000." in tex_name:
|
||||
tex_files=glob(tex_file.replace(".000.",".*."))
|
||||
num_frames=len(tex_files)
|
||||
animated_textures[slot]=num_frames
|
||||
animated_textures[slot]=len(glob(tex_file.replace(".000.",".*.")))
|
||||
mat_props.update(overrides.get(tex_name,{}))
|
||||
if any(
|
||||
tex_name.find(fragment) != -1
|
||||
|
@ -297,7 +303,7 @@ class ScrapImporter(object):
|
|||
continue
|
||||
else:
|
||||
is_transparent = False
|
||||
imgs[slot]=bpy.data.images.load(tex_file)
|
||||
imgs[slot]=bpy.data.images.load(tex_file,check_existing=True)
|
||||
for n in nodes:
|
||||
nodes.remove(n)
|
||||
out = nodes.new("ShaderNodeOutputMaterial")
|
||||
|
@ -311,7 +317,6 @@ class ScrapImporter(object):
|
|||
settings.update(glass_settings)
|
||||
for name, value in settings.items():
|
||||
shader.inputs[name].default_value = value
|
||||
sockets_used = set()
|
||||
for socket,img in imgs.items():
|
||||
img_node = nodes.new("ShaderNodeTexImage")
|
||||
img_node.name = img.name
|
||||
|
@ -369,17 +374,20 @@ class ScrapImporter(object):
|
|||
node_tree.links.new(imgs["Base Color"].outputs["Color"],transp_shader.inputs["Color"])
|
||||
shader_out=mix_shader.outputs["Shader"]
|
||||
node_tree.links.new(shader_out, out.inputs["Surface"])
|
||||
# try:
|
||||
# bpy.ops.node.button()
|
||||
# except:
|
||||
# pass
|
||||
return mat
|
||||
|
||||
def apply_maps(self, ob, m_mat, m_map):
|
||||
mat_key, m_mat = m_mat
|
||||
map_key, m_map = m_map # TODO?: MAP
|
||||
map_key, m_map = m_map
|
||||
if mat_key == 0:
|
||||
return
|
||||
mat_name = m_mat.get("name", f"MAT:{mat_key:08X}")
|
||||
map_name = f"MAP:{map_key:08X}"
|
||||
if mat_name not in bpy.data.materials:
|
||||
ob.active_material = self.build_material(mat_name, m_mat)
|
||||
ob.active_material = self.build_material(mat_name, m_mat, m_map)
|
||||
else:
|
||||
ob.active_material = bpy.data.materials[mat_name]
|
||||
|
||||
|
@ -424,17 +432,17 @@ class ScrapImporter(object):
|
|||
ob = bpy.data.objects.new(name, me)
|
||||
self.apply_maps(ob, m_mat, m_map)
|
||||
bpy.context.scene.collection.objects.link(ob)
|
||||
self.objects.setdefault(name, []).append(ob)
|
||||
self.objects.setdefault(name.split("(")[0], []).append(ob)
|
||||
return ob
|
||||
|
||||
|
||||
class Scrap_Load(Operator, ImportHelper):
|
||||
|
||||
bl_idname = "scrap_utils.import_json"
|
||||
bl_label = "Import JSON"
|
||||
bl_idname = "scrap_utils.import_pickle"
|
||||
bl_label = "Import Pickle"
|
||||
|
||||
filename_ext = ".json.gz"
|
||||
filter_glob: StringProperty(default="*.json.gz", options={"HIDDEN"})
|
||||
filename_ext = ".pkl.gz"
|
||||
filter_glob: StringProperty(default="*.pkl.gz", options={"HIDDEN"})
|
||||
|
||||
create_dummies: BoolProperty(
|
||||
name="Import dummies",
|
||||
|
@ -447,25 +455,20 @@ class Scrap_Load(Operator, ImportHelper):
|
|||
)
|
||||
|
||||
create_tracks: BoolProperty(
|
||||
name="Create track curves",
|
||||
default=True
|
||||
name="Create track curves",
|
||||
default=True
|
||||
)
|
||||
|
||||
merge_objects: BoolProperty(
|
||||
name="Merge objects by name",
|
||||
default=False
|
||||
name="Merge objects by name",
|
||||
default=False
|
||||
)
|
||||
|
||||
remove_dup_verts: BoolProperty(
|
||||
name="Remove overlapping vertices\nfor smoother meshes",
|
||||
default=True
|
||||
name="Remove overlapping vertices\nfor smoother meshes",
|
||||
default=True
|
||||
)
|
||||
|
||||
# remove_dup_verts: BoolProperty(
|
||||
# name="Remove overlapping vertices for smoother meshes",
|
||||
# default=False
|
||||
# )
|
||||
|
||||
|
||||
def execute(self, context):
|
||||
bpy.ops.preferences.addon_enable(module = "node_arrange")
|
||||
|
@ -488,7 +491,7 @@ def unregister():
|
|||
if __name__ == "__main__":
|
||||
if cmdline is None or not cmdline.file_list:
|
||||
register()
|
||||
bpy.ops.scrap_utils.import_json("INVOKE_DEFAULT")
|
||||
bpy.ops.scrap_utils.import_pickle("INVOKE_DEFAULT")
|
||||
else:
|
||||
for file in cmdline.file_list:
|
||||
bpy.context.preferences.view.show_splash = False
|
118
tools/remaster/scrap_parse/blender_plugin/packed_browser.py
Normal file
118
tools/remaster/scrap_parse/blender_plugin/packed_browser.py
Normal file
|
@ -0,0 +1,118 @@
|
|||
import sys
|
||||
from .. import scrap_bridge
|
||||
from bpy.props import (StringProperty, BoolProperty, CollectionProperty,
|
||||
IntProperty)
|
||||
|
||||
bl_info = {
|
||||
"name": "Packed Archive File",
|
||||
"blender": (2, 71, 0),
|
||||
"location": "File > Import",
|
||||
"description": "Import data from Scrapland .packed Archive",
|
||||
"category": "Import-Export"}
|
||||
|
||||
|
||||
|
||||
|
||||
class ImportFilearchives(bpy.types.Operator):
|
||||
"""Import whole filearchives directory."""
|
||||
bl_idname = "import_scene.packed"
|
||||
bl_label = 'Import Scrapland .packed'
|
||||
|
||||
directory = StringProperty(name="'Scrapland' folder",
|
||||
subtype="DIR_PATH", options={'HIDDEN'})
|
||||
filter_folder = BoolProperty(default=True, options={'HIDDEN'})
|
||||
filter_glob = StringProperty(default="", options={'HIDDEN'})
|
||||
|
||||
def invoke(self, context, event):
|
||||
context.window_manager.fileselect_add(self)
|
||||
return {'RUNNING_MODAL'}
|
||||
|
||||
def execute(self, context):
|
||||
# TODO: Validate filepath
|
||||
bpy.ops.ui.packed_browser('INVOKE_DEFAULT',filepath=self.directory)
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class PackedFile(bpy.types.PropertyGroup):
|
||||
path = bpy.props.StringProperty()
|
||||
packed_file = bpy.props.StringProperty()
|
||||
selected = bpy.props.BoolProperty(name="")
|
||||
offset = bpy.props.IntProperty()
|
||||
size = bpy.props.IntProperty()
|
||||
|
||||
|
||||
archive = None
|
||||
class PackedBrowser(bpy.types.Operator):
|
||||
bl_idname = "ui.packed_browser"
|
||||
bl_label = "Packed Browser"
|
||||
bl_options = {'INTERNAL'}
|
||||
|
||||
files = CollectionProperty(type=PackedFile)
|
||||
selected_index = IntProperty(default=0)
|
||||
|
||||
def invoke(self, context, event):
|
||||
scrapland_path=scrap_bridge("find-scrapland")
|
||||
print(scrapland_path)
|
||||
packed_data=scrap_bridge("parse-packed",scrapland_path)
|
||||
print(packed_data)
|
||||
self.packed_data=packed_data
|
||||
return context.window_manager.invoke_props_dialog(self)
|
||||
|
||||
def draw(self, context):
|
||||
if self.selected_index != -1:
|
||||
print("new selected_index: " + str(self.selected_index))
|
||||
self.files.clear()
|
||||
for packed_name,files in self.archive:
|
||||
for file in files:
|
||||
entry = self.files.add()
|
||||
entry.packed_file = packed_name
|
||||
[entry.path,entry.offset,entry.size]=file
|
||||
self.selected_index = -1
|
||||
self.layout.template_list("PackedDirList", "", self, "current_dir", self, "selected_index")
|
||||
|
||||
def execute(self, context):
|
||||
print("execute")
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class PackedDirList(bpy.types.UIList):
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
||||
operator = data
|
||||
packed_entry = item
|
||||
|
||||
if self.layout_type in {'DEFAULT', 'COMPACT'}:
|
||||
layout.prop(packed_entry, "name", text="", emboss=False, icon_value=icon)
|
||||
layout.prop(packed_entry, "selected")
|
||||
elif self.layout_type in {'GRID'}:
|
||||
layout.alignment = 'CENTER'
|
||||
layout.label(text="", icon_value=icon)
|
||||
|
||||
|
||||
def menu_func_import(self, context):
|
||||
self.layout.operator(ImportFilearchives.bl_idname, text="Scrapland .packed")
|
||||
|
||||
classes=[
|
||||
PackedFile,
|
||||
PackedDirList,
|
||||
PackedBrowser,
|
||||
ImportFilearchives,
|
||||
]
|
||||
|
||||
def register():
|
||||
for cls in classes:
|
||||
bpy.utils.regiser_class(cls)
|
||||
bpy.types.INFO_MT_file_import.append(menu_func_import)
|
||||
|
||||
|
||||
def unregister():
|
||||
for cls in reversed(classes):
|
||||
bpy.utils.unregister_class(cls)
|
||||
bpy.types.INFO_MT_file_import.remove(menu_func_import)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import imp
|
||||
imp.reload(sys.modules[__name__])
|
||||
for cls in classes:
|
||||
bpy.utils.regiser_class(cls)
|
||||
|
66
tools/remaster/scrap_parse/get_vertex_size.cpp
Normal file
66
tools/remaster/scrap_parse/get_vertex_size.cpp
Normal file
|
@ -0,0 +1,66 @@
|
|||
|
||||
int _D3DXGetFVFVertexSize(uint fvf)
|
||||
|
||||
{
|
||||
uint uVar1;
|
||||
uint uVar2;
|
||||
uint uVar3;
|
||||
int vert_size;
|
||||
|
||||
uVar1 = fvf & 0xe;
|
||||
vert_size = 0;
|
||||
if (uVar1 == 2) {
|
||||
vert_size = 0xc;
|
||||
}
|
||||
else if ((uVar1 == 4) || (uVar1 == 6)) {
|
||||
vert_size = 0x10;
|
||||
}
|
||||
else if (uVar1 == 8) {
|
||||
vert_size = 0x14;
|
||||
}
|
||||
else if (uVar1 == 0xa) {
|
||||
vert_size = 0x18;
|
||||
}
|
||||
else if (uVar1 == 0xc) {
|
||||
vert_size = 0x1c;
|
||||
}
|
||||
else if (uVar1 == 0xe) {
|
||||
vert_size = 0x20;
|
||||
}
|
||||
if ((fvf & 0x10) != 0) {
|
||||
vert_size += 0xc;
|
||||
}
|
||||
if ((fvf & 0x20) != 0) {
|
||||
vert_size += 4;
|
||||
}
|
||||
if ((fvf & 0x40) != 0) {
|
||||
vert_size += 4;
|
||||
}
|
||||
if (fvf < '\0') {
|
||||
vert_size += 4;
|
||||
}
|
||||
uVar1 = fvf >> 8 & 0xf;
|
||||
uVar3 = fvf >> 16;
|
||||
if (uVar3 == 0) {
|
||||
vert_size += uVar1 * 8;
|
||||
}
|
||||
else {
|
||||
for (; uVar1 != 0; uVar1 -= 1) {
|
||||
uVar2 = uVar3 & 3;
|
||||
if (uVar2 == 0) {
|
||||
vert_size += 8;
|
||||
}
|
||||
else if (uVar2 == 1) {
|
||||
vert_size += 0xc;
|
||||
}
|
||||
else if (uVar2 == 2) {
|
||||
vert_size += 0x10;
|
||||
}
|
||||
else if (uVar2 == 3) {
|
||||
vert_size += 4;
|
||||
}
|
||||
uVar3 >>= 2;
|
||||
}
|
||||
}
|
||||
return vert_size;
|
||||
}
|
|
@ -1,103 +0,0 @@
|
|||
bl_info = {
|
||||
"name": "Riot Archive File (RAF)",
|
||||
"blender": (2, 71, 0),
|
||||
"location": "File > Import",
|
||||
"description": "Import LoL data of an Riot Archive File",
|
||||
"category": "Import-Export"}
|
||||
|
||||
|
||||
import bpy
|
||||
from io_scene_lolraf import raf_utils
|
||||
from bpy.props import (StringProperty, BoolProperty, CollectionProperty,
|
||||
IntProperty)
|
||||
|
||||
|
||||
class ImportFilearchives(bpy.types.Operator):
|
||||
"""Import whole filearchives directory."""
|
||||
bl_idname = "import_scene.rafs"
|
||||
bl_label = 'Import LoL filearchives'
|
||||
|
||||
directory = StringProperty(name="'filearchives' folder",
|
||||
subtype="DIR_PATH", options={'HIDDEN'})
|
||||
filter_folder = BoolProperty(default=True, options={'HIDDEN'})
|
||||
filter_glob = StringProperty(default="", options={'HIDDEN'})
|
||||
|
||||
def invoke(self, context, event):
|
||||
context.window_manager.fileselect_add(self)
|
||||
return {'RUNNING_MODAL'}
|
||||
|
||||
def execute(self, context):
|
||||
# TODO: Validate filepath
|
||||
bpy.ops.ui.raf_browser('INVOKE_DEFAULT',filepath=self.directory)
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class RAFEntry(bpy.types.PropertyGroup):
|
||||
name = bpy.props.StringProperty()
|
||||
selected = bpy.props.BoolProperty(name="")
|
||||
|
||||
|
||||
archive = None
|
||||
class RAFBrowser(bpy.types.Operator):
|
||||
bl_idname = "ui.raf_browser"
|
||||
bl_label = "RAF-browser"
|
||||
bl_options = {'INTERNAL'}
|
||||
|
||||
filepath = StringProperty()
|
||||
current_dir = CollectionProperty(type=RAFEntry)
|
||||
selected_index = IntProperty(default=0)
|
||||
|
||||
def invoke(self, context, event):
|
||||
global archive
|
||||
archive = raf_utils.RAFArchive(self.filepath)
|
||||
return context.window_manager.invoke_props_dialog(self)
|
||||
|
||||
def draw(self, context):
|
||||
if self.selected_index != -1:
|
||||
print("new selected_index: " + str(self.selected_index))
|
||||
global archive
|
||||
# TODO: change current directory of archive
|
||||
self.current_dir.clear()
|
||||
for dir in archive.current_dir():
|
||||
entry = self.current_dir.add()
|
||||
entry.name = dir
|
||||
self.selected_index = -1
|
||||
self.layout.template_list("RAFDirList", "", self, "current_dir", self, "selected_index")
|
||||
|
||||
def execute(self, context):
|
||||
print("execute")
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class RAFDirList(bpy.types.UIList):
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
||||
operator = data
|
||||
raf_entry = item
|
||||
|
||||
if self.layout_type in {'DEFAULT', 'COMPACT'}:
|
||||
layout.prop(raf_entry, "name", text="", emboss=False, icon_value=icon)
|
||||
layout.prop(raf_entry, "selected")
|
||||
elif self.layout_type in {'GRID'}:
|
||||
layout.alignment = 'CENTER'
|
||||
layout.label(text="", icon_value=icon)
|
||||
|
||||
|
||||
def menu_func_import(self, context):
|
||||
self.layout.operator(ImportFilearchives.bl_idname, text="LoL Filearchives")
|
||||
|
||||
|
||||
def register():
|
||||
bpy.utils.register_module(__name__)
|
||||
bpy.types.INFO_MT_file_import.append(menu_func_import)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_module(__name__)
|
||||
bpy.types.INFO_MT_file_import.remove(menu_func_import)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import imp
|
||||
imp.reload(raf_utils)
|
||||
bpy.utils.register_module(__name__)
|
||||
|
15
tools/remaster/scrap_parse/src/find_scrap.rs
Normal file
15
tools/remaster/scrap_parse/src/find_scrap.rs
Normal file
|
@ -0,0 +1,15 @@
|
|||
use std::path::PathBuf;
|
||||
|
||||
use steamlocate::SteamDir;
|
||||
use anyhow::{bail,Result};
|
||||
const APP_ID: u32 = 897610;
|
||||
|
||||
pub(crate) fn get_executable() -> Result<PathBuf> {
|
||||
let Some(mut steam) = SteamDir::locate() else {
|
||||
bail!("Failed to find steam folder");
|
||||
};
|
||||
let Some(app) = steam.app(&APP_ID) else {
|
||||
bail!("App {APP_ID} is not installed!");
|
||||
};
|
||||
Ok(app.path.clone())
|
||||
}
|
|
@ -5,6 +5,7 @@ use binrw::prelude::*;
|
|||
use binrw::until_exclusive;
|
||||
use chrono::{DateTime, NaiveDateTime, Utc};
|
||||
use clap::Parser;
|
||||
use clap::Subcommand;
|
||||
use configparser::ini::Ini;
|
||||
use flate2::write::GzEncoder;
|
||||
use flate2::Compression;
|
||||
|
@ -15,14 +16,37 @@ use modular_bitfield::specifiers::B2;
|
|||
use modular_bitfield::specifiers::B4;
|
||||
use modular_bitfield::BitfieldSpecifier;
|
||||
use serde::Serialize;
|
||||
use serde_json::Map;
|
||||
use serde_json::Value;
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::Debug;
|
||||
use std::fs::File;
|
||||
use std::io::{BufReader, Read, Seek};
|
||||
use std::io::{BufReader, Cursor, Read, Seek};
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
use walkdir::WalkDir;
|
||||
|
||||
mod find_scrap;
|
||||
|
||||
type IniData = IndexMap<String, IndexMap<String, Option<String>>>;
|
||||
|
||||
#[binread]
|
||||
#[derive(Serialize, Debug)]
|
||||
struct PackedFile{
|
||||
path: PascalString,
|
||||
size: u32,
|
||||
offset: u32
|
||||
}
|
||||
|
||||
#[binread]
|
||||
#[br(magic = b"BFPK")]
|
||||
#[derive(Serialize, Debug)]
|
||||
struct PackedHeader {
|
||||
#[br(temp,assert(version==0))]
|
||||
version: u32,
|
||||
#[br(temp)]
|
||||
num_files: u32,
|
||||
#[br(count=num_files)]
|
||||
files: Vec<PackedFile>,
|
||||
}
|
||||
|
||||
#[binread]
|
||||
#[derive(Serialize, Debug)]
|
||||
|
@ -141,6 +165,7 @@ struct IniSection {
|
|||
#[br(magic = b"INI\0")]
|
||||
#[derive(Debug)]
|
||||
struct INI {
|
||||
#[br(temp)]
|
||||
size: u32,
|
||||
#[br(temp)]
|
||||
num_sections: u32,
|
||||
|
@ -153,13 +178,17 @@ impl Serialize for INI {
|
|||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
use serde::ser::Error;
|
||||
let blocks: Vec<String> = self
|
||||
.sections
|
||||
.iter()
|
||||
.flat_map(|s| s.sections.iter())
|
||||
.map(|s| s.string.clone())
|
||||
.collect();
|
||||
Ini::new().read(blocks.join("\n")).serialize(serializer)
|
||||
Ini::new()
|
||||
.read(blocks.join("\n"))
|
||||
.map_err(Error::custom)?
|
||||
.serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -227,7 +256,7 @@ enum Pos {
|
|||
#[repr(u32)]
|
||||
#[derive(Debug, Serialize, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct FVF {
|
||||
reserved_1: bool,
|
||||
reserved: bool,
|
||||
pos: Pos,
|
||||
normal: bool,
|
||||
point_size: bool,
|
||||
|
@ -267,17 +296,17 @@ impl FVF {
|
|||
}
|
||||
}
|
||||
|
||||
fn num_w(&self) -> usize {
|
||||
use Pos::*;
|
||||
match self.pos() {
|
||||
XYZ | XYZRHW => 0,
|
||||
XYZB1 => 1,
|
||||
XYZB2 => 2,
|
||||
XYZB3 => 3,
|
||||
XYZB4 => 4,
|
||||
XYZB5 => 5,
|
||||
}
|
||||
}
|
||||
// fn num_w(&self) -> usize {
|
||||
// use Pos::*;
|
||||
// match self.pos() {
|
||||
// XYZ | XYZRHW => 0,
|
||||
// XYZB1 => 1,
|
||||
// XYZB2 => 2,
|
||||
// XYZB3 => 3,
|
||||
// XYZB4 => 4,
|
||||
// XYZB5 => 5,
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
fn vertex_size_from_id(fmt_id: u32) -> Result<u32> {
|
||||
|
@ -361,6 +390,7 @@ struct MD3D {
|
|||
tris: Vec<[u16; 3]>,
|
||||
mesh_data: LFVF,
|
||||
unk_table_1: RawTable<2>,
|
||||
rest: Unparsed<0x100>
|
||||
// TODO:
|
||||
// ==
|
||||
// unk_t1_count: u32,
|
||||
|
@ -383,7 +413,7 @@ enum NodeData {
|
|||
#[br(magic = 0x0u32)]
|
||||
Null,
|
||||
#[br(magic = 0xa1_00_00_01_u32)]
|
||||
TriangleMesh, // Empty?
|
||||
TriangleMesh(Unparsed<0x10>), // TODO: Empty or unused?
|
||||
#[br(magic = 0xa1_00_00_02_u32)]
|
||||
Mesh(MD3D),
|
||||
#[br(magic = 0xa2_00_00_04_u32)]
|
||||
|
@ -393,7 +423,7 @@ enum NodeData {
|
|||
#[br(magic = 0xa4_00_00_10_u32)]
|
||||
Ground(SUEL),
|
||||
#[br(magic = 0xa5_00_00_20_u32)]
|
||||
SisPart(Unparsed<0x10>), // TODO: Particles
|
||||
SistPart(Unparsed<0x10>), // TODO: Particles
|
||||
#[br(magic = 0xa6_00_00_40_u32)]
|
||||
Graphic3D(SPR3),
|
||||
#[br(magic = 0xa6_00_00_80_u32)]
|
||||
|
@ -521,6 +551,16 @@ struct MAP {
|
|||
unk_3: Option<[u8; 0xc]>,
|
||||
}
|
||||
|
||||
#[binread]
|
||||
#[derive(Debug, Serialize)]
|
||||
struct Textures {
|
||||
base: Optional<MAP>,
|
||||
metallic: Optional<MAP>,
|
||||
unk_1: Optional<MAP>,
|
||||
bump: Optional<MAP>,
|
||||
glow: Optional<MAP>
|
||||
}
|
||||
|
||||
#[binread]
|
||||
#[br(magic = b"MAT\0")]
|
||||
#[derive(Debug, Serialize)]
|
||||
|
@ -532,7 +572,7 @@ struct MAT {
|
|||
name: Option<PascalString>,
|
||||
unk_f: [RGBA; 7],
|
||||
unk_data: [RGBA; 0x18 / 4],
|
||||
maps: [Optional<MAP>; 5], // Base Color, Metallic?, ???, Normal, Emission
|
||||
maps: Textures
|
||||
}
|
||||
|
||||
#[binread]
|
||||
|
@ -556,9 +596,9 @@ struct SCN {
|
|||
#[br(temp,assert(unk_3==1))]
|
||||
unk_3: u32,
|
||||
num_nodes: u32,
|
||||
#[br(count = num_nodes)] // 32
|
||||
#[br(count = 1)] // 32
|
||||
nodes: Vec<Node>,
|
||||
ani: Optional<ANI>, // TODO:?
|
||||
// ani: Optional<ANI>, // TODO: ?
|
||||
}
|
||||
|
||||
fn convert_timestamp(dt: u32) -> Result<DateTime<Utc>> {
|
||||
|
@ -682,11 +722,11 @@ struct CM3 {
|
|||
#[binread]
|
||||
#[derive(Debug, Serialize)]
|
||||
struct Dummy {
|
||||
has_next: u32,
|
||||
name: PascalString,
|
||||
pos: [f32; 3],
|
||||
rot: [f32; 3],
|
||||
info: Optional<INI>,
|
||||
has_next: u32,
|
||||
}
|
||||
|
||||
#[binread]
|
||||
|
@ -697,7 +737,6 @@ struct DUM {
|
|||
#[br(assert(version==1, "Invalid DUM version"))]
|
||||
version: u32,
|
||||
num_dummies: u32,
|
||||
unk_1: u32,
|
||||
#[br(count=num_dummies)]
|
||||
dummies: Vec<Dummy>,
|
||||
}
|
||||
|
@ -826,13 +865,6 @@ enum Data {
|
|||
EMI(EMI),
|
||||
}
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
struct Args {
|
||||
root: PathBuf,
|
||||
path: PathBuf,
|
||||
}
|
||||
|
||||
fn parse_file(path: &PathBuf) -> Result<Data> {
|
||||
let mut rest_size = 0;
|
||||
let mut fh = BufReader::new(fs::File::open(path)?);
|
||||
|
@ -842,11 +874,11 @@ fn parse_file(path: &PathBuf) -> Result<Data> {
|
|||
.unwrap_or(0)
|
||||
.try_into()
|
||||
.unwrap_or(u32::MAX);
|
||||
println!("Read {} bytes from {}", pos, path.display());
|
||||
eprintln!("Read {} bytes from {}", pos, path.display());
|
||||
let mut buffer = [0u8; 0x1000];
|
||||
if let Ok(n) = fh.read(&mut buffer) {
|
||||
if n != 0 {
|
||||
println!("Rest:\n{}", rhexdump::hexdump_offset(&buffer[..n], pos));
|
||||
eprintln!("Rest:\n{}", rhexdump::hexdump_offset(&buffer[..n], pos));
|
||||
}
|
||||
};
|
||||
while let Ok(n) = fh.read(&mut buffer) {
|
||||
|
@ -855,52 +887,182 @@ fn parse_file(path: &PathBuf) -> Result<Data> {
|
|||
}
|
||||
rest_size += n;
|
||||
}
|
||||
println!("+{rest_size} unparsed bytes");
|
||||
eprintln!("+{rest_size} unparsed bytes");
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
fn load_ini(path: &PathBuf) -> IndexMap<String, IndexMap<String, Option<String>>> {
|
||||
fn load_ini(path: &PathBuf) -> IniData {
|
||||
Ini::new().load(path).unwrap_or_default()
|
||||
}
|
||||
|
||||
fn load_data(root: &Path, path: &Path) -> Result<Value> {
|
||||
let full_path = &root.join(path);
|
||||
let emi_path = full_path.join("map").join("map3d.emi");
|
||||
let sm3_path = emi_path.with_extension("sm3");
|
||||
let dum_path = emi_path.with_extension("dum");
|
||||
let config_file = emi_path.with_extension("ini");
|
||||
let moredummies = emi_path.with_file_name("moredummies").with_extension("ini");
|
||||
let mut data = serde_json::to_value(HashMap::<(), ()>::default())?;
|
||||
data["config"] = serde_json::to_value(load_ini(&config_file))?;
|
||||
data["moredummies"] = serde_json::to_value(load_ini(&moredummies))?;
|
||||
data["emi"] = serde_json::to_value(parse_file(&emi_path)?)?;
|
||||
data["sm3"] = serde_json::to_value(parse_file(&sm3_path)?)?;
|
||||
data["dummies"] = serde_json::to_value(parse_file(&dum_path)?)?;
|
||||
data["path"] = serde_json::to_value(path)?;
|
||||
data["root"] = serde_json::to_value(root)?;
|
||||
Ok(data)
|
||||
#[derive(Serialize, Debug)]
|
||||
|
||||
struct Level {
|
||||
config: IniData,
|
||||
moredummies: IniData,
|
||||
emi: EMI,
|
||||
sm3: SM3,
|
||||
dummies: DUM,
|
||||
path: PathBuf,
|
||||
root: PathBuf,
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let args = Args::try_parse()?;
|
||||
impl Level {
|
||||
fn load(root: &Path, path: &Path) -> Result<Self> {
|
||||
let full_path = &root.join(path);
|
||||
let emi_path = full_path.join("map").join("map3d.emi");
|
||||
let sm3_path = emi_path.with_extension("sm3");
|
||||
let dum_path = emi_path.with_extension("dum");
|
||||
let config_file = emi_path.with_extension("ini");
|
||||
let moredummies = emi_path.with_file_name("moredummies").with_extension("ini");
|
||||
let config = load_ini(&config_file);
|
||||
let moredummies = load_ini(&moredummies);
|
||||
let Data::EMI(emi) = parse_file(&emi_path)? else {
|
||||
bail!("Failed to parse EMI at {emi_path}", emi_path=emi_path.display());
|
||||
};
|
||||
let Data::SM3(sm3) = parse_file(&sm3_path)? else {
|
||||
bail!("Failed to parse SM3 at {sm3_path}", sm3_path=sm3_path.display());
|
||||
};
|
||||
let Data::DUM(dummies) = parse_file(&dum_path)? else {
|
||||
bail!("Failed to parse DUM at {dum_path}", dum_path=dum_path.display());
|
||||
};
|
||||
Ok(Level {
|
||||
config,
|
||||
moredummies,
|
||||
emi,
|
||||
sm3,
|
||||
dummies,
|
||||
path: path.into(),
|
||||
root: root.into(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Subcommand, Debug)]
|
||||
enum Commands {
|
||||
FindScrapland,
|
||||
ParsePacked {
|
||||
scrap_path: PathBuf,
|
||||
},
|
||||
ParseFile {
|
||||
#[clap(long)]
|
||||
/// Write to stdout
|
||||
stdout: bool,
|
||||
/// Scrapland root path
|
||||
root: PathBuf,
|
||||
/// Level to parse and convert
|
||||
level: PathBuf,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
#[command(propagate_version = true)]
|
||||
struct Args {
|
||||
#[arg(long,short)]
|
||||
/// Write data as JSON
|
||||
json: bool,
|
||||
#[command(subcommand)]
|
||||
command: Commands,
|
||||
}
|
||||
|
||||
fn cmd_parse_packed(root: &Path) -> Result<HashMap<PathBuf, Vec<PackedFile>>> {
|
||||
let mut packed_map = HashMap::new();
|
||||
for entry in WalkDir::new(root).into_iter().filter_map(|e| e.ok()) {
|
||||
let path = entry.path();
|
||||
if path
|
||||
.extension()
|
||||
.map(|e| e.to_str() == Some("packed"))
|
||||
.unwrap_or(false)
|
||||
{
|
||||
let path = entry.path().to_owned();
|
||||
let header: PackedHeader = BufReader::new(File::open(&path)?).read_le()?;
|
||||
packed_map.insert(path, header.files);
|
||||
}
|
||||
}
|
||||
Ok(packed_map)
|
||||
}
|
||||
|
||||
fn to_bytes<T>(data: &T, json: bool) -> Result<Vec<u8>> where T: Serialize {
|
||||
if json {
|
||||
Ok(serde_json::to_vec_pretty(data)?)
|
||||
} else {
|
||||
Ok(serde_pickle::to_vec(data,Default::default())?)
|
||||
}
|
||||
}
|
||||
|
||||
fn cmd_parse_file(stdout: bool, root: &Path, path: &Path, json: bool) -> Result<()> {
|
||||
let out_path = PathBuf::from(
|
||||
args.path
|
||||
path
|
||||
.components()
|
||||
.last()
|
||||
.unwrap()
|
||||
.as_os_str()
|
||||
.to_string_lossy()
|
||||
.into_owned(),
|
||||
)
|
||||
.with_extension("json.gz");
|
||||
let full_path = &args.root.join(&args.path);
|
||||
let data = if full_path.is_dir() {
|
||||
load_data(&args.root, &args.path)?
|
||||
);
|
||||
let out_path = if json {
|
||||
out_path.with_extension("json.gz")
|
||||
} else {
|
||||
serde_json::to_value(parse_file(full_path)?)?
|
||||
out_path.with_extension("pkl.gz")
|
||||
};
|
||||
let full_path = &root.join(path);
|
||||
let data = if full_path.is_dir() {
|
||||
let level = Level::load(root, path)?;
|
||||
to_bytes(&level,json)?
|
||||
} else {
|
||||
let data = parse_file(full_path)?;
|
||||
to_bytes(&data,json)?
|
||||
};
|
||||
let mut data = Cursor::new(data);
|
||||
if stdout {
|
||||
let mut stdout = std::io::stdout().lock();
|
||||
std::io::copy(&mut data, &mut stdout)?;
|
||||
} else {
|
||||
let mut fh = GzEncoder::new(File::create(&out_path)?, Compression::best());
|
||||
std::io::copy(&mut data, &mut fh)?;
|
||||
eprintln!("Wrote {path}", path = out_path.display());
|
||||
};
|
||||
let mut dumpfile = GzEncoder::new(File::create(&out_path)?, Compression::best());
|
||||
serde_json::to_writer_pretty(&mut dumpfile, &data)?;
|
||||
println!("Wrote {path}", path = out_path.display());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn emi_to_obj(emi: EMI) -> ! {
|
||||
// let mut obj_data = obj::ObjData::default();
|
||||
|
||||
// for mesh in emi.tri {
|
||||
// for vert in mesh.data.verts_1.inner.map(|d| d.data).unwrap_or_default() {
|
||||
// obj_data.position.push(vert.xyz);
|
||||
// obj_data.normal.push(vert.normal.unwrap_or_default());
|
||||
// obj_data.texture.push(vert.tex_1.unwrap_or_default().0.try_into().unwrap());
|
||||
// }
|
||||
// for vert in mesh.data.verts_2.inner.map(|d| d.data).unwrap_or_default() {
|
||||
// obj_data.position.push(vert.xyz);
|
||||
// obj_data.normal.push(vert.normal.unwrap_or_default());
|
||||
// }
|
||||
// }
|
||||
todo!("EMI to OBJ converter");
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let args = Args::try_parse()?;
|
||||
match args.command {
|
||||
Commands::FindScrapland => {
|
||||
let data = to_bytes(&find_scrap::get_executable()?,args.json)?;
|
||||
let mut stdout = std::io::stdout().lock();
|
||||
std::io::copy(&mut &data[..], &mut stdout)?;
|
||||
}
|
||||
Commands::ParsePacked { scrap_path } => {
|
||||
let data = to_bytes(&cmd_parse_packed(&scrap_path)?,args.json)?;
|
||||
let mut stdout = std::io::stdout().lock();
|
||||
std::io::copy(&mut &data[..], &mut stdout)?;
|
||||
}
|
||||
Commands::ParseFile {
|
||||
stdout,
|
||||
root,
|
||||
level,
|
||||
} => {
|
||||
cmd_parse_file(stdout, &root, &level, args.json)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
// https://learn.microsoft.com/en-us/windows/win32/direct3dhlsl/dx9-graphics-reference-asm-ps-1-x
|
||||
//
|
||||
// ################################################
|
||||
//
|
||||
// #[derive(Debug)]
|
||||
// enum VecArg {
|
||||
// Tex(f32,f32,f32,f32),
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue