From 0b677529e1aa6a6039d6e8dff59903fe53a38e7e Mon Sep 17 00:00:00 2001 From: min Date: Mon, 8 Jun 2026 16:23:18 +0300 Subject: [PATCH] =?UTF-8?q?feat(rbxl-importer):=20=D0=BF=D0=BE=D0=B4=D0=B4?= =?UTF-8?q?=D0=B5=D1=80=D0=B6=D0=BA=D0=B0=20XML-=D1=84=D0=BE=D1=80=D0=BC?= =?UTF-8?q?=D0=B0=D1=82=D0=B0=20.rbxl=20(=D1=81=D1=82=D0=B0=D1=80=D1=8B?= =?UTF-8?q?=D0=B5=20=D0=BA=D0=B0=D1=80=D1=82=D1=8B=20=D0=B4=D0=BE=202010)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Старые Roblox-карты (Crossroads, ROBLOX Battle, и др. из эры 2007-2010) сохранены в XML-формате (... вместо binary заворачивается в BrickColor объект — converter ожидает .code атрибут. - Алиасы PascalCase: name→Name, size→Size, shape→Shape (старый XML использовал camelCase с маленькой первой буквой). app.py: - /analyze: авто-детект XML vs Binary по magic bytes. Если XML — используем parse_xml(), иначе старый parse(). Тест на arch1_Original_Crossroads.rbxl: 877 instances, 777 Part, 83 Model — конвертится в 777 примитивов без warnings. --- .../src/__pycache__/converter.cpython-314.pyc | Bin 59456 -> 56367 bytes .../rbxl_binreader.cpython-314.pyc | Bin 0 -> 15005 bytes .../__pycache__/rbxl_parser.cpython-314.pyc | Bin 0 -> 20598 bytes .../__pycache__/rbxl_types.cpython-314.pyc | Bin 0 -> 29959 bytes .../rbxl_xml_parser.cpython-314.pyc | Bin 0 -> 17055 bytes rbxl-importer/src/app.py | 17 +- rbxl-importer/src/rbxl_xml_parser.py | 342 ++++++++++++++++++ 7 files changed, 356 insertions(+), 3 deletions(-) create mode 100644 rbxl-importer/src/__pycache__/rbxl_binreader.cpython-314.pyc create mode 100644 rbxl-importer/src/__pycache__/rbxl_parser.cpython-314.pyc create mode 100644 rbxl-importer/src/__pycache__/rbxl_types.cpython-314.pyc create mode 100644 rbxl-importer/src/__pycache__/rbxl_xml_parser.cpython-314.pyc create mode 100644 rbxl-importer/src/rbxl_xml_parser.py diff --git a/rbxl-importer/src/__pycache__/converter.cpython-314.pyc b/rbxl-importer/src/__pycache__/converter.cpython-314.pyc index af16ebfa9b81825da1e43612d31bb842b8b90668..6f7af917aede50d7dc23403ed78f374e4b9983d0 100644 GIT binary patch delta 5605 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 delta 7704 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%0DJ3A}qbFeZFEZ7ZR}dilbF%5Xs8N+yOh=T_;0e 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 CGAp0} literal 0 HcmV?d00001 diff --git a/rbxl-importer/src/__pycache__/rbxl_types.cpython-314.pyc b/rbxl-importer/src/__pycache__/rbxl_types.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..44b6b88d8b7fb8df532f61748dacd956a8795f33 GIT binary patch literal 29959 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 literal 0 HcmV?d00001 diff --git a/rbxl-importer/src/app.py b/rbxl-importer/src/app.py index 00a113c..b9788db 100644 --- a/rbxl-importer/src/app.py +++ b/rbxl-importer/src/app.py @@ -122,12 +122,23 @@ def analyze(): blob = upload.read() if len(blob) > MAX_RBXL_SIZE: return jsonify({'error': f'file too large (> {MAX_RBXL_SIZE} bytes)'}), 413 - if not blob.startswith(b'... + stripped = blob.lstrip() + is_binary = stripped.startswith(b' и содержит дерево .... + +Возвращает тот же `RobloxModel` что и rbxl_parser.parse — чтобы converter.py +работал без изменений. + +Пример входного файла: + + + + Workspace + + + + + 0100 + 1...1 + + 412 + 4286611584 + 21 + + + + + +Поддерживает все типичные property-теги: string, bool, int, float, double, +token, Vector3, Vector2, CoordinateFrame, Color3, Color3uint8, BrickColor, +Content, ProtectedString, Ref, BinaryString, UDim, UDim2, Rect2D. +""" +from __future__ import annotations +from typing import Dict, List, Any, Optional, Tuple +import xml.etree.ElementTree as ET +import re +import base64 +import struct + +from rbxl_parser import Instance, RobloxModel +from rbxl_types import ( + Vector3, Vector2, Color3, CFrame, BrickColor, + EnumValue, PhysicalProperties, OptionalCFrame, +) + + +# Magic для XML-формата +XML_MAGIC = b' bool: + """Проверяет XML это или нет. Бинарный начинается с str: + """Текст элемента (None → default).""" + return (el.text if el.text is not None else default).strip() + + +def _f(el: ET.Element, default: float = 0.0) -> float: + """Float из text.""" + try: + return float(_text(el, '0')) + except (ValueError, TypeError): + return default + + +def _i(el: ET.Element, default: int = 0) -> int: + """Int из text.""" + try: + s = _text(el, '0') + # Roblox иногда пишет '1.0' где ожидается int + return int(float(s)) if '.' in s else int(s) + except (ValueError, TypeError): + return default + + +def _parse_vector3(el: ET.Element) -> Vector3: + x = _f(el.find('X')) + y = _f(el.find('Y')) + z = _f(el.find('Z')) + return Vector3(x, y, z) + + +def _parse_vector2(el: ET.Element) -> Vector2: + x = _f(el.find('X')) + y = _f(el.find('Y')) + return Vector2(x, y) + + +def _parse_cframe(el: ET.Element) -> CFrame: + """CoordinateFrame: 3 позиции + 9 элементов матрицы ротации.""" + pos = Vector3(_f(el.find('X')), _f(el.find('Y')), _f(el.find('Z'))) + matrix = tuple(_f(el.find(f'R{i}{j}'), 1.0 if i == j else 0.0) + for i in range(3) for j in range(3)) + return CFrame(position=pos, matrix=matrix) + + +def _parse_color3(el: ET.Element) -> Color3: + """.........""" + r = _f(el.find('R')) + g = _f(el.find('G')) + b = _f(el.find('B')) + return Color3(r, g, b) + + +def _parse_color3uint8(el: ET.Element) -> Color3: + """4286611584 — packed RGB как uint32.""" + val = _i(el, 0) + # uint32 = 0xFFRRGGBB (alpha=FF). r=byte2, g=byte1, b=byte0 + b = (val & 0xff) / 255.0 + g = ((val >> 8) & 0xff) / 255.0 + r = ((val >> 16) & 0xff) / 255.0 + return Color3(r, g, b) + + +def _parse_property(prop_el: ET.Element) -> Tuple[str, Any]: + """Парсит один <тип name="имя">значение. Возвращает (name, value).""" + tag = prop_el.tag + name = prop_el.attrib.get('name', '') + + if tag == 'string' or tag == 'ProtectedString' or tag == 'Content': + return name, _text(prop_el) + + if tag == 'bool': + return name, _text(prop_el).lower() == 'true' + + if tag in ('int', 'int64'): + val = _i(prop_el) + # В старом XML цвет хранится как 21, + # а converter ожидает BrickColor-объект с .code. + if name == 'BrickColor': + return name, BrickColor(code=val) + return name, val + + if tag in ('float', 'double'): + return name, _f(prop_el) + + if tag == 'token': + # token — int-значение enum + return name, EnumValue(value=_i(prop_el)) + + if tag == 'Vector3': + return name, _parse_vector3(prop_el) + + if tag == 'Vector2': + return name, _parse_vector2(prop_el) + + if tag == 'CoordinateFrame': + return name, _parse_cframe(prop_el) + + if tag == 'Color3': + return name, _parse_color3(prop_el) + + if tag == 'Color3uint8': + return name, _parse_color3uint8(prop_el) + + if tag == 'BrickColor': + return name, BrickColor(code=_i(prop_el)) + + if tag == 'Ref': + # Ссылка на другой Item по referent (например "RBX42" или "null") + txt = _text(prop_el, 'null') + if txt in ('null', 'nil', ''): + return name, None + return name, txt # храним как строку-referent + + if tag == 'BinaryString': + # base64 → bytes + try: + return name, base64.b64decode(_text(prop_el)) + except Exception: + return name, b'' + + if tag == 'UDim': + scale = _f(prop_el.find('S')) + offset = _i(prop_el.find('O')) + return name, {'scale': scale, 'offset': offset} + + if tag == 'UDim2': + xs = _f(prop_el.find('XS')) + xo = _i(prop_el.find('XO')) + ys = _f(prop_el.find('YS')) + yo = _i(prop_el.find('YO')) + return name, {'x_scale': xs, 'x_offset': xo, 'y_scale': ys, 'y_offset': yo} + + if tag == 'Rect2D': + # min/max + min_el = prop_el.find('min') + max_el = prop_el.find('max') + return name, { + 'min': _parse_vector2(min_el) if min_el is not None else Vector2(0, 0), + 'max': _parse_vector2(max_el) if max_el is not None else Vector2(0, 0), + } + + if tag == 'OptionalCoordinateFrame': + cf_el = prop_el.find('CFrame') + return name, OptionalCFrame(cframe=_parse_cframe(cf_el)) if cf_el is not None else OptionalCFrame(cframe=None) + + if tag == 'PhysicalProperties': + cust = prop_el.find('CustomPhysics') + custom = cust is not None and _text(cust).lower() == 'true' + return name, PhysicalProperties( + custom_physics=custom, + density=_f(prop_el.find('Density'), 0.7), + friction=_f(prop_el.find('Friction'), 0.3), + elasticity=_f(prop_el.find('Elasticity'), 0.5), + friction_weight=_f(prop_el.find('FrictionWeight'), 1.0), + elasticity_weight=_f(prop_el.find('ElasticityWeight'), 1.0), + ) + + if tag == 'NumberRange': + return name, {'min': _f(prop_el.find('Min')), 'max': _f(prop_el.find('Max'))} + + # SharedString / Uri / другие незнакомые — оставляем как текст + return name, _text(prop_el) + + +# Регекс для извлечения referent из строк типа "RBX42" +_REF_RE = re.compile(r'^RBX(\d+)$') + + +def _ref_to_int(ref: Optional[str]) -> Optional[int]: + """RBX42 → 42, null → None. Если уникальной номер не найден — None.""" + if ref is None or ref == 'null': + return None + m = _REF_RE.match(str(ref)) + if m: + return int(m.group(1)) + return None + + +def parse_xml(blob: bytes) -> RobloxModel: + """Главный entry: bytes → RobloxModel.""" + try: + text = blob.decode('utf-8', errors='replace') + except Exception: + text = blob.decode('latin-1', errors='replace') + + # XML может иметь BOM или leading whitespace + text = text.lstrip('').lstrip() + + root = ET.fromstring(text) + + instances: List[Instance] = [] + by_referent: Dict[int, Instance] = {} + roots: List[Instance] = [] + + # Auto-increment id для Item'ов без referent (старые форматы) + next_id_counter = [100000] + + def _walk(item_el: ET.Element, parent_ref: Optional[int]) -> None: + """Рекурсивный обход элементов.""" + cls = item_el.attrib.get('class', 'Unknown') + + # Referent из атрибута (например referent="RBX42") + ref_attr = item_el.attrib.get('referent') or item_el.attrib.get('Referent') + ref_int = _ref_to_int(ref_attr) if ref_attr else None + if ref_int is None: + # Назначаем auto-id чтобы converter мог отслеживать parent_referent + ref_int = next_id_counter[0] + next_id_counter[0] += 1 + + # Парсим properties + props: Dict[str, Any] = {} + props_el = item_el.find('Properties') + if props_el is not None: + for prop_el in props_el: + try: + pname, pval = _parse_property(prop_el) + if pname: + props[pname] = pval + except Exception: + continue + + # Roblox в старых картах использовал имена с маленькой первой буквы: + # name → Name, size → Size, shape → Shape, и т.д. Converter ожидает + # PascalCase. Делаем алиасы (старое имя остаётся, новое добавляется). + _ALIAS_TO_PASCAL = { + 'name': 'Name', + 'size': 'Size', + 'shape': 'Shape', + 'archivable': 'Archivable', + 'shape3d': 'Shape', + } + for old, new in _ALIAS_TO_PASCAL.items(): + if old in props and new not in props: + props[new] = props[old] + + # Convert Ref-properties (string "RBX42") в parent_referent если нужно + # — пока оставляем как строки. + + inst = Instance( + referent=ref_int, + class_name=cls, + properties=props, + parent_referent=parent_ref, + children=[], + ) + instances.append(inst) + by_referent[ref_int] = inst + if parent_ref is None: + roots.append(inst) + + # Рекурсивно дочерние Item'ы + for child in item_el.findall('Item'): + _walk(child, ref_int) + + # Roblox-XML: top-level идут под + for item in root.findall('Item'): + _walk(item, None) + + # Заполняем children после полного прохода (для удобства converter'а) + for inst in instances: + if inst.parent_referent is not None: + parent = by_referent.get(inst.parent_referent) + if parent is not None: + parent.children.append(inst) + + # Версия из атрибута + version_attr = root.attrib.get('version', '4') + try: + version = int(version_attr) + except ValueError: + version = 4 + + return RobloxModel( + version=version, + class_count=len(set(i.class_name for i in instances)), + instance_count=len(instances), + instances=instances, + by_referent=by_referent, + roots=roots, + shared_strings=[], + meta={}, + warnings=[], + )