Browse Source

Pack the doggone Python in the exporter

No, I don't want to talk about it...
pull/128/head
Adam Johnson 6 years ago
parent
commit
ec2070a000
Signed by: Hoikas
GPG Key ID: 0B6515D6FF6F271E
  1. 19
      korman/exporter/convert.py
  2. 35
      korman/exporter/manager.py
  3. 133
      korman/exporter/outfile.py
  4. 4
      korman/korlib/python.py
  5. 15
      korman/nodes/node_python.py

19
korman/exporter/convert.py

@ -62,6 +62,7 @@ class Exporter:
self.report.progress_add_step("Exporting Scene Objects") self.report.progress_add_step("Exporting Scene Objects")
self.report.progress_add_step("Exporting Logic Nodes") self.report.progress_add_step("Exporting Logic Nodes")
self.report.progress_add_step("Finalizing Plasma Logic") self.report.progress_add_step("Finalizing Plasma Logic")
self.report.progress_add_step("Handling Snakes")
self.report.progress_add_step("Exporting Textures") self.report.progress_add_step("Exporting Textures")
self.report.progress_add_step("Composing Geometry") self.report.progress_add_step("Composing Geometry")
self.report.progress_add_step("Saving Age Files") self.report.progress_add_step("Saving Age Files")
@ -94,6 +95,9 @@ class Exporter:
# processing that needs to inspect those objects # processing that needs to inspect those objects
self._post_process_scene_objects() self._post_process_scene_objects()
# Step 3.3: Ensure any helper Python files are packed
self._pack_ancillary_python()
# Step 4: Finalize... # Step 4: Finalize...
self.mesh.material.finalize() self.mesh.material.finalize()
self.mesh.finalize() self.mesh.finalize()
@ -346,6 +350,17 @@ class Exporter:
proc(self, bl_obj, sceneobject) proc(self, bl_obj, sceneobject)
inc_progress() inc_progress()
def _pack_ancillary_python(self):
texts = bpy.data.texts
self.report.progress_advance()
self.report.progress_range = len(texts)
inc_progress = self.report.progress_increment
for i in texts:
if i.name.endswith(".py") and self.output.want_py_text(i):
self.output.add_python_code(i.name, text_id=i)
inc_progress()
def _save_age(self): def _save_age(self):
self.report.progress_advance() self.report.progress_advance()
@ -369,6 +384,10 @@ class Exporter:
def envmap_method(self): def envmap_method(self):
return bpy.context.scene.world.plasma_age.envmap_method return bpy.context.scene.world.plasma_age.envmap_method
@property
def python_method(self):
return bpy.context.scene.world.plasma_age.python_method
@property @property
def texcache_path(self): def texcache_path(self):
age = bpy.context.scene.world.plasma_age age = bpy.context.scene.world.plasma_age

35
korman/exporter/manager.py

@ -19,6 +19,7 @@ from PyHSPlasma import *
import weakref import weakref
from . import explosions from . import explosions
from ..plasma_magic import *
# These objects have to be in the plSceneNode pool in order to be loaded... # These objects have to be in the plSceneNode pool in order to be loaded...
# NOTE: We are using Factory indices because I doubt all of these classes are implemented. # NOTE: We are using Factory indices because I doubt all of these classes are implemented.
@ -136,15 +137,19 @@ class ExportManager:
def create_builtins(self, age, textures): def create_builtins(self, age, textures):
# BuiltIn.prp # BuiltIn.prp
if bpy.context.scene.world.plasma_age.age_sdl: if bpy.context.scene.world.plasma_age.age_sdl:
builtin = self.create_page(age, "BuiltIn", -2, True) self._create_builtin_pages(age)
sdl = self.add_object(plSceneObject, name="AgeSDLHook", loc=builtin) self._pack_agesdl_hook(age)
pfm = self.add_object(plPythonFileMod, name="VeryVerySpecialPythonFileMod", so=sdl)
pfm.filename = age
# Textures.prp # Textures.prp
if textures: if textures:
self.create_page(age, "Textures", -1, True) self.create_page(age, "Textures", -1, True)
def _create_builtin_pages(self, age):
builtin = self.create_page(age, "BuiltIn", -2, True)
sdl = self.add_object(plSceneObject, name="AgeSDLHook", loc=builtin)
pfm = self.add_object(plPythonFileMod, name="VeryVerySpecialPythonFileMod", so=sdl)
pfm.filename = age
def create_page(self, age, name, id, builtin=False): def create_page(self, age, name, id, builtin=False):
location = plLocation(self.mgr.getVer()) location = plLocation(self.mgr.getVer())
location.prefix = bpy.context.scene.world.plasma_age.seq_prefix location.prefix = bpy.context.scene.world.plasma_age.seq_prefix
@ -233,6 +238,28 @@ class ExportManager:
else: else:
return key.location return key.location
def _pack_agesdl_hook(self, age):
get_text = bpy.data.texts.get
output = self._exporter().output
# AgeSDL Hook Python
py_filename = "{}.py".format(age)
age_py = get_text(py_filename, None)
if output.want_py_text(age_py):
py_code = age_py.as_string()
else:
py_code = very_very_special_python.format(age_name=age).lstrip()
output.add_python_mod(py_filename, text_id=age_py, str_data=py_code)
# AgeSDL
sdl_filename = "{}.sdl".format(age)
age_sdl = get_text(sdl_filename)
if age_sdl is not None:
sdl_code = None
else:
sdl_code = very_very_special_sdl.format(age_name=age).lstrip()
output.add_sdl(sdl_filename, text_id=age_sdl, str_data=sdl_code)
def save_age(self): def save_age(self):
self._write_age() self._write_age()
self._write_fni() self._write_fni()

133
korman/exporter/outfile.py

@ -16,9 +16,11 @@
from contextlib import contextmanager from contextlib import contextmanager
import enum import enum
from hashlib import md5 from hashlib import md5
from .. import korlib
import locale import locale
import os import os
from pathlib import Path from pathlib import Path
from ..plasma_magic import plasma_python_glue
from PyHSPlasma import * from PyHSPlasma import *
import shutil import shutil
import time import time
@ -40,6 +42,9 @@ def _hashfile(filename, hasher, block=0xFFFF):
class _FileType(enum.Enum): class _FileType(enum.Enum):
generated_dat = 0 generated_dat = 0
sfx = 1 sfx = 1
sdl = 2
python_code = 3
generated_ancillary = 4
class _OutputFile: class _OutputFile:
@ -48,8 +53,9 @@ class _OutputFile:
self.dirname = kwargs.get("dirname") self.dirname = kwargs.get("dirname")
self.filename = kwargs.get("filename") self.filename = kwargs.get("filename")
self.skip_hash = kwargs.get("skip_hash", False) self.skip_hash = kwargs.get("skip_hash", False)
self.internal = kwargs.get("internal", False)
if self.file_type == _FileType.generated_dat: if self.file_type in (_FileType.generated_dat, _FileType.generated_ancillary):
self.file_data = kwargs.get("file_data", None) self.file_data = kwargs.get("file_data", None)
self.file_path = kwargs.get("file_path", None) self.file_path = kwargs.get("file_path", None)
self.mod_time = Path(self.file_path).stat().st_mtime if self.file_path else None self.mod_time = Path(self.file_path).stat().st_mtime if self.file_path else None
@ -69,6 +75,23 @@ class _OutputFile:
if self.id_data.packed_file is not None: if self.id_data.packed_file is not None:
self.file_data = self.id_data.packed_file.data self.file_data = self.id_data.packed_file.data
if self.file_type in (_FileType.sdl, _FileType.python_code):
self.id_data = kwargs.get("id_data")
self.file_data = kwargs.get("file_data")
self.needs_glue = kwargs.get("needs_glue", True)
assert bool(self.id_data) or bool(self.file_data)
self.mod_time = None
self.file_path = None
if self.id_data is not None:
path = Path(self.id_data.filepath)
if path.exists():
self.mod_time = path.stat().st_mtime
self.file_path = self.id_data.filepath
if self.file_data is None:
self.file_data = self.id_data.as_string()
def __eq__(self, rhs): def __eq__(self, rhs):
return str(self) == str(rhs) return str(self) == str(rhs)
@ -76,7 +99,7 @@ class _OutputFile:
return hash(str(self)) return hash(str(self))
def hash_md5(self): def hash_md5(self):
if self.file_path is not None: if self.file_path:
with open(self.file_path, "rb") as handle: with open(self.file_path, "rb") as handle:
h = md5() h = md5()
data = handle.read(0xFFFF) data = handle.read(0xFFFF)
@ -106,28 +129,36 @@ class OutputFiles:
self._export_path = self._export_file.parent.parent self._export_path = self._export_file.parent.parent
self._files = set() self._files = set()
self._is_zip = self._export_file.suffix.lower() == ".zip" self._is_zip = self._export_file.suffix.lower() == ".zip"
self._py_files = set()
self._time = time.time() self._time = time.time()
def add_python(self, filename, text_id=None, str_data=None): def add_python_code(self, filename, text_id=None, str_data=None):
assert filename not in self._py_files
of = _OutputFile(file_type=_FileType.python_code, of = _OutputFile(file_type=_FileType.python_code,
dirname="Python", filename=filename, dirname="Python", filename=filename,
id_data=text_id, file_data=str_data, id_data=text_id, file_data=str_data,
skip_hash=True) skip_hash=True,
internal=(self._version != pvMoul),
needs_glue=False)
self._files.add(of) self._files.add(of)
self._py_files.add(filename)
def add_sdl(self, filename, text_id=None, str_data=None): def add_python_mod(self, filename, text_id=None, str_data=None):
version = self._version assert filename not in self._py_files
if version == pvEoa: of = _OutputFile(file_type=_FileType.python_code,
enc = plEncryptedStream.kEncAes dirname="Python", filename=filename,
elif version == pvMoul: id_data=text_id, file_data=str_data,
enc = None skip_hash=True,
else: internal=(self._version != pvMoul),
enc = plEncryptedStream.kEncXtea needs_glue=True)
self._files.add(of)
self._py_files.add(filename)
def add_sdl(self, filename, text_id=None, str_data=None):
of = _OutputFile(file_type=_FileType.sdl, of = _OutputFile(file_type=_FileType.sdl,
dirname="SDL", filename=filename, dirname="SDL", filename=filename,
id_data=text_id, file_data=str_data, id_data=text_id, file_data=str_data,
enc=enc) enc=self.super_secure_encryption)
self._files.add(of) self._files.add(of)
@ -165,11 +196,14 @@ class OutputFiles:
if not stream is backing_stream: if not stream is backing_stream:
backing_stream.close() backing_stream.close()
dirname = kwargs.get("dirname", "dat")
kwargs = { kwargs = {
"file_type": _FileType.generated_dat, "file_type": _FileType.generated_dat if dirname == "dat" else
"dirname": "dat", _FileType.generated_ancillary,
"dirname": dirname,
"filename": filename, "filename": filename,
"skip_hash": skip_hash, "skip_hash": kwargs.get("skip_hash", False),
"internal": kwargs.get("internal", False),
} }
if isinstance(backing_stream, hsRAMStream): if isinstance(backing_stream, hsRAMStream):
kwargs["file_data"] = backing_stream.buffer kwargs["file_data"] = backing_stream.buffer
@ -188,13 +222,46 @@ class OutputFiles:
else: else:
yield i yield i
def _package_compyled_python(self):
func = lambda x: x.file_type == _FileType.python_code
report = self._exporter().report
version = self._version
# There can be some debate about what the correct Python version for pvMoul is.
# I, quite frankly, don't give a rat's ass at the moment because CWE will only
# load Python.pak and no ancillary packages. Maybe someone should fix that, mm?
if version <= pvPots:
py_version = (2, 2)
else:
py_version = (2, 3)
try:
pyc_objects = []
for i in self._generate_files(func):
if i.needs_glue:
py_code = "{}\n\n{}\n".format(i.file_data, plasma_python_glue)
else:
py_code = i.file_data
result, pyc = korlib.compyle(i.filename, py_code, py_version, report, indent=1)
if result:
pyc_objects.append((i.filename, pyc))
except korlib.PythonNotAvailableError as error:
report.warn("Python {} is not available. Your Age scripts were not packaged.", error, indent=1)
else:
if pyc_objects:
with self.generate_dat_file("{}.pak".format(self._exporter().age_name),
dirname="Python", enc=self.super_secure_encryption) as stream:
korlib.package_python(stream, pyc_objects)
def save(self): def save(self):
# At this stage, all Plasma data has been generated from whatever crap is in # At this stage, all Plasma data has been generated from whatever crap is in
# Blender. The only remaining part is to make sure any external dependencies are # Blender. The only remaining part is to make sure any external dependencies are
# copied or packed into the appropriate format OR the asset hashes are generated. # copied or packed into the appropriate format OR the asset hashes are generated.
version = self._version
# Step 1: Handle Python # Step 1: Handle Python
# ... todo ... if self._exporter().python_method != "none" and version != pvMoul:
self._package_compyled_python()
# Step 2: Generate sumfile # Step 2: Generate sumfile
if self._version != pvMoul: if self._version != pvMoul:
@ -206,9 +273,31 @@ class OutputFiles:
else: else:
self._write_deps() self._write_deps()
@property
def super_secure_encryption(self):
version = self._version
if version == pvEoa:
return plEncryptedStream.kEncAes
elif version == pvMoul:
# trollface.jpg
return None
else:
return plEncryptedStream.kEncXtea
def want_py_text(self, text_id):
if text_id is None:
return False
method = self._exporter().python_method
if method == "none":
return False
elif method == "all":
return text_id.name not in self._py_files
else:
return text_id.plasma_text.package and text_id.name not in self._py_files
def _write_deps(self): def _write_deps(self):
func = lambda x: x.file_type == _FileType.sfx
times = (self._time, self._time) times = (self._time, self._time)
func = lambda x: not x.internal and x.file_type not in (_FileType.generated_ancillary, _FileType.generated_dat)
for i in self._generate_files(func): for i in self._generate_files(func):
# Will only ever run for non-"dat" directories. # Will only ever run for non-"dat" directories.
@ -229,9 +318,9 @@ class OutputFiles:
enc = plEncryptedStream.kEncAes if version >= pvEoa else plEncryptedStream.kEncXtea enc = plEncryptedStream.kEncAes if version >= pvEoa else plEncryptedStream.kEncXtea
filename = "{}.sum".format(self._exporter().age_name) filename = "{}.sum".format(self._exporter().age_name)
if dat_only: if dat_only:
func = lambda x: not x.skip_hash and x.dirname == "dat" func = lambda x: (not x.skip_hash and not x.internal) and x.dirname == "dat"
else: else:
func = lambda x: not x.skip_hash func = lambda x: not x.skip_hash and not x.internal
with self.generate_dat_file(filename, enc=enc, skip_hash=True) as stream: with self.generate_dat_file(filename, enc=enc, skip_hash=True) as stream:
files = list(self._generate_files(func)) files = list(self._generate_files(func))
@ -256,9 +345,9 @@ class OutputFiles:
dat_only = self._exporter().dat_only dat_only = self._exporter().dat_only
export_time = time.localtime(self._time)[:6] export_time = time.localtime(self._time)[:6]
if dat_only: if dat_only:
func = lambda x: x.dirname == "dat" func = lambda x: x.dirname == "dat" and not x.internal
else: else:
func = None func = lambda x: not x.internal
with zipfile.ZipFile(str(self._export_file), 'w', zipfile.ZIP_DEFLATED) as zf: with zipfile.ZipFile(str(self._export_file), 'w', zipfile.ZIP_DEFLATED) as zf:
for i in self._generate_files(func): for i in self._generate_files(func):

4
korman/korlib/python.py

@ -98,10 +98,10 @@ def _find_python(py_version):
except ImportError: except ImportError:
pass pass
else: else:
py_executable = _find_python_reg(winreg.HKEY_LOCAL_MACHINE, py_version) py_executable = _find_python_reg(winreg.HKEY_CURRENT_USER, py_version)
if _verify_python(py_version, py_executable): if _verify_python(py_version, py_executable):
return py_executable return py_executable
py_executable = _find_python_reg(winreg.HKEY_CURRENT_USER, py_version) py_executable = _find_python_reg(winreg.HKEY_LOCAL_MACHINE, py_version)
if _verify_python(py_version, py_executable): if _verify_python(py_version, py_executable):
return py_executable return py_executable

15
korman/nodes/node_python.py

@ -246,7 +246,20 @@ class PlasmaPythonFileNode(PlasmaVersionedNode, bpy.types.Node):
def export(self, exporter, bo, so): def export(self, exporter, bo, so):
pfm = self.get_key(exporter, so).object pfm = self.get_key(exporter, so).object
pfm.filename = Path(self.filename).stem py_name = Path(self.filename).stem
pfm.filename = py_name
# Check to see if we should pack this file
if exporter.output.want_py_text(self.text_id):
exporter.report.msg("Including Python '{}' for package", self.filename, indent=3)
exporter.output.add_python_mod(self.filename, text_id=self.text_id)
# PFMs can have their own SDL...
sdl_text = bpy.data.texts.get("{}.sdl".format(py_name), None)
if sdl_text is not None:
exporter.report.msg("Including corresponding SDL '{}'", sdl_text.name, indent=3)
exporter.output.add_sdl(sdl_text.name, text_id=sdl_text)
# Handle exporting the Python Parameters
attrib_sockets = (i for i in self.inputs if i.is_linked) attrib_sockets = (i for i in self.inputs if i.is_linked)
for socket in attrib_sockets: for socket in attrib_sockets:
attrib = socket.attribute_type attrib = socket.attribute_type

Loading…
Cancel
Save