aboutsummaryrefslogtreecommitdiff
path: root/codegen/lib/code
diff options
context:
space:
mode:
Diffstat (limited to 'codegen/lib/code')
-rw-r--r--codegen/lib/code/packet.py148
-rw-r--r--codegen/lib/code/utils.py73
-rw-r--r--codegen/lib/code/version.py31
3 files changed, 252 insertions, 0 deletions
diff --git a/codegen/lib/code/packet.py b/codegen/lib/code/packet.py
new file mode 100644
index 00000000..0d3ad138
--- /dev/null
+++ b/codegen/lib/code/packet.py
@@ -0,0 +1,148 @@
+from .utils import burger_type_to_rust_type, write_packet_file
+from ..utils import padded_hex, to_snake_case, to_camel_case
+from ..mappings import Mappings
+
+
+def make_packet_mod_rs_line(packet_id: int, packet_class_name: str):
+ return f' {padded_hex(packet_id)}: {to_snake_case(packet_class_name)}::{to_camel_case(packet_class_name)},'
+
+
+def generate(burger_packets, mappings: Mappings, target_packet_id, target_packet_direction, target_packet_state):
+ for packet in burger_packets.values():
+ if packet['id'] != target_packet_id:
+ continue
+
+ direction = packet['direction'].lower() # serverbound or clientbound
+ state = {'PLAY': 'game'}.get(packet['state'], packet['state'].lower())
+
+ if state != target_packet_state or direction != target_packet_direction:
+ continue
+
+ generated_packet_code = []
+ uses = set()
+ generated_packet_code.append(
+ f'#[derive(Clone, Debug, McBuf, {to_camel_case(state)}Packet)]')
+ uses.add(f'packet_macros::{{{to_camel_case(state)}Packet, McBuf}}')
+
+ obfuscated_class_name = packet['class'].split('.')[0].split('$')[0]
+ class_name = mappings.get_class(
+ obfuscated_class_name).split('.')[-1].split('$')[0]
+
+ generated_packet_code.append(
+ f'pub struct {to_camel_case(class_name)} {{')
+
+ for instruction in packet.get('instructions', []):
+ if instruction['operation'] == 'write':
+ obfuscated_field_name = instruction['field']
+ if '.' in obfuscated_field_name or ' ' in obfuscated_field_name or '(' in obfuscated_field_name:
+ generated_packet_code.append(f'// TODO: {instruction}')
+ continue
+ field_name = mappings.get_field(
+ obfuscated_class_name, obfuscated_field_name)
+ if not field_name:
+ generated_packet_code.append(
+ f'// TODO: unknown field {instruction}')
+ continue
+
+ field_type = instruction['type']
+ field_type_rs, is_var, instruction_uses = burger_type_to_rust_type(
+ field_type)
+ if is_var:
+ generated_packet_code.append('#[var]')
+ generated_packet_code.append(
+ f'pub {to_snake_case(field_name)}: {field_type_rs},')
+ uses.update(instruction_uses)
+ else:
+ generated_packet_code.append(f'// TODO: {instruction}')
+ continue
+
+ generated_packet_code.append('}')
+
+ if uses:
+ # empty line before the `use` statements
+ generated_packet_code.insert(0, '')
+ for use in uses:
+ generated_packet_code.insert(0, f'use {use};')
+
+ print(generated_packet_code)
+ write_packet_file(state, to_snake_case(class_name),
+ '\n'.join(generated_packet_code))
+ print()
+
+ mod_rs_dir = f'../azalea-protocol/src/packets/{state}/mod.rs'
+ with open(mod_rs_dir, 'r') as f:
+ mod_rs = f.read().splitlines()
+
+ pub_mod_line = f'pub mod {to_snake_case(class_name)};'
+ if pub_mod_line not in mod_rs:
+ mod_rs.insert(0, pub_mod_line)
+ packet_mod_rs_line = make_packet_mod_rs_line(
+ packet['id'], class_name)
+
+ in_serverbound = False
+ in_clientbound = False
+ for i, line in enumerate(mod_rs):
+ if line.strip() == 'Serverbound => {':
+ in_serverbound = True
+ continue
+ elif line.strip() == 'Clientbound => {':
+ in_clientbound = True
+ continue
+ elif line.strip() in ('}', '},'):
+ if (in_serverbound and direction == 'serverbound') or (in_clientbound and direction == 'clientbound'):
+ mod_rs.insert(i, packet_mod_rs_line)
+ break
+ in_serverbound = in_clientbound = False
+ continue
+
+ if line.strip() == '' or line.strip().startswith('//') or (not in_serverbound and direction == 'serverbound') or (not in_clientbound and direction == 'clientbound'):
+ continue
+
+ line_packet_id_hex = line.strip().split(':')[0]
+ assert line_packet_id_hex.startswith('0x')
+ line_packet_id = int(line_packet_id_hex[2:], 16)
+ if line_packet_id > packet['id']:
+ mod_rs.insert(i, packet_mod_rs_line)
+ break
+
+ with open(mod_rs_dir, 'w') as f:
+ f.write('\n'.join(mod_rs))
+
+
+def set_packet_ids(packet_ids: list, packet_class_names: list, direction: str, state: str):
+ assert len(packet_ids) == len(packet_class_names)
+
+ mod_rs_dir = f'../azalea-protocol/src/packets/{state}/mod.rs'
+ with open(mod_rs_dir, 'r') as f:
+ mod_rs = f.read().splitlines()
+ new_mod_rs = []
+
+ ignore_lines = False
+
+ for line in mod_rs:
+ if line.strip() == 'Serverbound => {':
+ if direction == 'serverbound':
+ ignore_lines = True
+ for packet_id, packet_class_name in zip(packet_ids, packet_class_names):
+ new_mod_rs.append(
+ make_packet_mod_rs_line(packet_id, packet_class_name)
+ )
+ else:
+ ignore_lines = False
+ elif line.strip() == 'Clientbound => {':
+ if direction == 'serverbound':
+ ignore_lines = True
+ for packet_id, packet_class_name in zip(packet_ids, packet_class_names):
+ new_mod_rs.append(
+ make_packet_mod_rs_line(packet_id, packet_class_name)
+ )
+ else:
+ ignore_lines = False
+ elif line.strip() in ('}', '},'):
+ ignore_lines = False
+
+ if not ignore_lines:
+ new_mod_rs.append(line)
+
+ with open(mod_rs_dir, 'w') as f:
+ f.write('\n'.join(new_mod_rs))
diff --git a/codegen/lib/code/utils.py b/codegen/lib/code/utils.py
new file mode 100644
index 00000000..92d1a9e9
--- /dev/null
+++ b/codegen/lib/code/utils.py
@@ -0,0 +1,73 @@
+
+import os
+
+
+def burger_type_to_rust_type(burger_type):
+ is_var = False
+ uses = set()
+
+ if burger_type == 'byte':
+ field_type_rs = 'i8'
+ elif burger_type == 'short':
+ field_type_rs = 'i16'
+ elif burger_type == 'int':
+ field_type_rs = 'i32'
+ elif burger_type == 'long':
+ field_type_rs = 'i64'
+ elif burger_type == 'float':
+ field_type_rs = 'f32'
+ elif burger_type == 'double':
+ field_type_rs = 'f64'
+
+ elif burger_type == 'varint':
+ is_var = True
+ field_type_rs = 'i32'
+ elif burger_type == 'varlong':
+ is_var = True
+ field_type_rs = 'i64'
+
+ elif burger_type == 'boolean':
+ field_type_rs = 'bool'
+ elif burger_type == 'string':
+ field_type_rs = 'String'
+
+ elif burger_type == 'chatcomponent':
+ field_type_rs = 'Component'
+ uses.add('azalea_chat::component::Component')
+ elif burger_type == 'identifier':
+ field_type_rs = 'ResourceLocation'
+ uses.add('azalea_core::resource_location::ResourceLocation')
+ elif burger_type == 'uuid':
+ field_type_rs = 'Uuid'
+ uses.add('uuid::Uuid')
+ elif burger_type == 'position':
+ field_type_rs = 'BlockPos'
+ uses.add('azalea_core::BlockPos')
+ elif burger_type == 'nbtcompound':
+ field_type_rs = 'azalea_nbt::Tag'
+ elif burger_type == 'itemstack':
+ field_type_rs = 'Slot'
+ uses.add('azalea_core::Slot')
+ elif burger_type == 'metadata':
+ field_type_rs = 'EntityMetadata'
+ uses.add('crate::mc_buf::EntityMetadata')
+ elif burger_type == 'enum':
+ # enums are too complicated, leave those to the user
+ field_type_rs = 'todo!()'
+ elif burger_type.endswith('[]'):
+ field_type_rs, is_var, uses = burger_type_to_rust_type(
+ burger_type[:-2])
+ field_type_rs = f'Vec<{field_type_rs}>'
+ else:
+ print('Unknown field type:', burger_type)
+ exit()
+ return field_type_rs, is_var, uses
+
+
+def write_packet_file(state, packet_name_snake_case, code):
+ with open(f'../azalea-protocol/src/packets/{state}/{packet_name_snake_case}.rs', 'w') as f:
+ f.write(code)
+
+
+def fmt():
+ os.system('cd .. && cargo fmt')
diff --git a/codegen/lib/code/version.py b/codegen/lib/code/version.py
new file mode 100644
index 00000000..b7e5cbcc
--- /dev/null
+++ b/codegen/lib/code/version.py
@@ -0,0 +1,31 @@
+import re
+
+README_DIR = '../README.md'
+VERSION_REGEX = r'\*Currently supported Minecraft version: `(.*)`.\*'
+
+
+def get_version_id() -> str:
+ with open(README_DIR, 'r') as f:
+ readme_text = f.read()
+
+ version_line_match = re.search(VERSION_REGEX, readme_text)
+ if version_line_match:
+ version_id = version_line_match.group(1)
+ return version_id
+ else:
+ raise Exception('Could not find version id in README.md')
+
+
+def set_version_id(version_id: str) -> None:
+ with open(README_DIR, 'r') as f:
+ readme_text = f.read()
+
+ version_line_match = re.search(VERSION_REGEX, readme_text)
+ if version_line_match:
+ readme_text = readme_text.replace(
+ version_line_match.group(1), version_id)
+ else:
+ raise Exception('Could not find version id in README.md')
+
+ with open(README_DIR, 'w') as f:
+ f.write(readme_text)