Source code for livemaker.lpm
# -*- coding: utf-8
#
# Copyright (C) 2020 Peter Rowlands <peter@pmrowla.com>
# Copyright (C) 2014 tinfoil <https://bitbucket.org/tinfoil/>
#
# This file is a part of pylivemaker.
#
# This program is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation, either version 3 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along with
# this program. If not, see <http://www.gnu.org/licenses/>.
"""LiveMaker preview menu file (LPM) module."""
from io import IOBase
import construct
from .exceptions import LiveMakerException
DEFAULT_LPM_VERSION = 106
class _LPMVersionAdapter(construct.Adapter):
def _decode(self, obj, ctx, path):
return int(obj)
def _encode(self, obj, ctx, path):
return f"{obj:03}".encode("ascii")
[docs]class LPMVersionValidator(construct.Validator):
def _validate(self, obj, ctx, path):
return obj >= 100
def _decode(self, obj, ctx, path):
if not self._validate(obj, ctx, path):
raise construct.ValidationError(f"Unsupported LPM version: {obj}")
return obj
[docs]class LMLivePrevMenu:
"""Class for handling preview menu files.
RE'd from TLivePrevMenu, TLivePrevImages classes in LiveMaker code.
"""
def __init__(self, version=DEFAULT_LPM_VERSION, unk1=0, buttons=[], **kwargs):
self.version = version
self.unk1 = unk1
self.buttons = buttons
def __iter__(self):
return iter(self.items())
def __getitem__(self, key):
if key in self.keys():
return getattr(self, key)
raise KeyError
@classmethod
def _struct(cls):
return construct.Struct(
"signature" / construct.Const(b"LivePrevMenu"),
"version" / LPMVersionValidator(_LPMVersionAdapter(construct.Bytes(3))),
"unk1" / construct.Bytes(8),
"buttons"
/ construct.PrefixedArray(
construct.Int32ul,
construct.Struct(
"width" / construct.Int32ul,
"height" / construct.Int32ul,
"src" / construct.PascalString(construct.Int32ul, "cp932"),
"unk2" / construct.Byte,
"name" / construct.PascalString(construct.Int32ul, "cp932"),
"src_selected" / construct.PascalString(construct.Int32ul, "cp932"),
"unk3" / construct.PascalString(construct.Int32ul, "cp932"),
"unk4" / construct.PascalString(construct.Int32ul, "cp932"),
"unk5"
/ construct.If(
construct.this._._.version > 100,
construct.PascalString(construct.Int32ul, "cp932"),
),
"unk6"
/ construct.If(
construct.this._._.version > 102,
construct.Struct(
construct.PascalString(construct.Int32ul, "cp932"),
construct.PascalString(construct.Int32ul, "cp932"),
),
),
"unk7" / construct.PascalString(construct.Int32ul, "cp932"),
"unk8" / construct.PascalString(construct.Int32ul, "cp932"),
"unk9" / construct.PascalString(construct.Int32ul, "cp932"),
"unk10"
/ construct.If(
construct.this._._.version > 101,
construct.Struct(
construct.PascalString(construct.Int32ul, "cp932"),
construct.PascalString(construct.Int32ul, "cp932"),
),
),
"unk15" / construct.Int32ul,
"unk16" / construct.Int32ul,
"unk17" / construct.PascalString(construct.Int32ul, "cp932"),
"unk18"
/ construct.If(
construct.this._._.version > 103,
construct.Struct(
construct.PascalString(construct.Int32ul, "cp932"),
construct.PascalString(construct.Int32ul, "cp932"),
construct.PascalString(construct.Int32ul, "cp932"),
construct.PascalString(construct.Int32ul, "cp932"),
construct.PascalString(construct.Int32ul, "cp932"),
construct.Int32ul,
),
),
"unk19"
/ construct.If(
construct.this._._.version > 104,
construct.PascalString(construct.Int32ul, "cp932"),
),
"unk20"
/ construct.If(
construct.this._._.version > 105,
construct.PascalString(construct.Int32ul, "cp932"),
),
),
),
)
[docs] @classmethod
def from_struct(cls, struct, **kwargs):
"""Create an LMProject from the specified struct."""
if isinstance(struct, construct.Container):
d = {k: v for k, v in struct.items()}
d.update(kwargs)
return cls(**d)
raise NotImplementedError
[docs] @classmethod
def from_file(cls, infile):
"""Parse the specified file into an LMLivePrevMenu.
Args:
infile: Input .lpm file. Can be string, path-like or file-like object.
"""
if not isinstance(infile, IOBase):
infile = open(infile, "rb")
try:
return cls.from_struct(cls._struct().parse_stream(infile))
except construct.ConstructError as e:
raise BadLPMError(e)
[docs] def to_lpm(self):
"""Compile settings into binary .lpm format."""
try:
return self._struct().build(self)
except construct.ConstructError as e:
raise BadLPMError(e)