def collect(self) -> SsbLabelJumpBlueprint: self.ctx: ExplorerScriptParser.If_headerContext is_positive = self._is_positive self.ctx: ExplorerScriptParser.If_headerContext # Complex branches if self._header_cmplx_handler: if isinstance(self._header_cmplx_handler, OperationCompileHandler): # An operation as condition op = self._header_cmplx_handler.collect() if len(op) != 1: raise SsbCompilerError( _("Invalid content for an if-header")) op = op[0] if op.op_code.name not in OPS_BRANCH.keys(): raise SsbCompilerError( f( _("Invalid operation for if condition: {op.op_code.name} (line {self.ctx.start.line})" ))) jmp = SsbLabelJumpBlueprint(self.compiler_ctx, self.ctx, op.op_code.name, op.params) jmp.set_jump_is_positive(is_positive) return jmp else: # A regular complex if condition tmpl: SsbLabelJumpBlueprint = self._header_cmplx_handler.collect( ) tmpl.set_jump_is_positive(is_positive) return tmpl raise SsbCompilerError( f(_("Unknown if operation in line {self.ctx.start.line}).")))
def collect(self) -> SsbLabelJumpBlueprint: if self.scn_var_target is None: raise SsbCompilerError(_("No variable for assignment.")) if self.operator not in [ SsbOperator.EQ, SsbOperator.LE, SsbOperator.LT, SsbOperator.GE, SsbOperator.GT ]: raise SsbCompilerError( f( _("The only supported operators for scn if " "conditions are ==,<,<=,>,>= (line {self.ctx.start.line})" ))) scn_value = exps_int(str(self.ctx.INTEGER(0))) level_value = exps_int(str(self.ctx.INTEGER(1))) if self.operator == SsbOperator.LE: return SsbLabelJumpBlueprint( self.compiler_ctx, self.ctx, OP_BRANCH_SCENARIO_NOW_BEFORE, [self.scn_var_target, scn_value, level_value]) if self.operator == SsbOperator.LT: return SsbLabelJumpBlueprint( self.compiler_ctx, self.ctx, OP_BRANCH_SCENARIO_BEFORE, [self.scn_var_target, scn_value, level_value]) if self.operator == SsbOperator.GE: return SsbLabelJumpBlueprint( self.compiler_ctx, self.ctx, OP_BRANCH_SCENARIO_NOW_AFTER, [self.scn_var_target, scn_value, level_value]) if self.operator == SsbOperator.GT: return SsbLabelJumpBlueprint( self.compiler_ctx, self.ctx, OP_BRANCH_SCENARIO_AFTER, [self.scn_var_target, scn_value, level_value]) return SsbLabelJumpBlueprint( self.compiler_ctx, self.ctx, OP_BRANCH_SCENARIO_NOW, [self.scn_var_target, scn_value, level_value])
def _parse_param(self, param: SsbOpParam, built_strings: Dict[str, List[str]], built_constants: List[str]) -> int: if isinstance(param, int): return param if isinstance(param, SsbOpParamConstant): try: return SsbConstant(param.name, self.rom_data.script_data).value.id except ValueError as err: raise SsbCompilerError(str(err)) from err if isinstance(param, SsbOpParamConstString): i = len(built_constants) built_constants.append(param.name) return i if isinstance(param, SsbOpParamLanguageString): i = len(built_strings[next(iter(built_strings.keys()))]) if len(param.strings.keys()) == 1: # Single language convenience mode, apply this to all languages. only_value = param.strings[next(iter(param.strings.keys()))] for lang in built_strings.keys(): built_strings[lang].append(only_value) else: # Multi language regular case. All languages must be known. for lang, string in param.strings.items(): if lang not in built_strings: raise SsbCompilerError(f(_("Unknown language for string: {lang}"))) built_strings[lang].append(string) return StringIndexPlaceholder(i) raise SsbCompilerError(f(_("Invalid parameter supplied for an operation: {param}")))
def collect(self) -> List[SsbOperation]: ops = [] self.ctx: ExplorerScriptParser.Ctx_blockContext if self._for_id is None: raise SsbCompilerError(_("No target ID set for with(){} block.")) if not self._sub_stmt: raise SsbCompilerError( _("A with(){} block needs exactly one statement.")) for_type = str(self.ctx.ctx_header().CTX_TYPE()) if for_type == 'actor': ops.append(self._generate_operation(OPS_CTX_LIVES, [self._for_id])) elif for_type == 'object': ops.append(self._generate_operation(OPS_CTX_OBJECT, [self._for_id])) elif for_type == 'performer': ops.append( self._generate_operation(OPS_CTX_PERFORMER, [self._for_id])) else: raise SsbCompilerError( f(_("Invalid with(){{}} target type '{for_type}'."))) sub_ops = self._sub_stmt.collect() if len(sub_ops) != 1: raise SsbCompilerError( _("A with(){} block needs exactly one binary operation. " "The handler for it generated multiple operations.")) ops += sub_ops return ops
def _check_cycles(self): for v in self._dependency_graph.vs: if any(self._has_path(out_e.target, v) for out_e in v.out_edges()): raise SsbCompilerError(f(_("Dependency cycle detected while trying to resolve macros" " (for macro '{v['name']}')."))) # Check direct cycles if any(v.index == e.target for e in v.out_edges()): raise SsbCompilerError(f(_("Dependency cycle detected while trying to resolve macros" " (for macro '{v['name']}').")))
def collect(self) -> SsbOperation: if self.scn_var_target is None: raise SsbCompilerError(_("No variable set for scn switch condition.")) index = exps_int(str(self.ctx.INTEGER())) if index == 0: return self._generate_operation(OP_SWITCH_SCENARIO, [self.scn_var_target]) elif index == 1: return self._generate_operation(OP_SWITCH_SCENARIO_LEVEL, [self.scn_var_target]) raise SsbCompilerError(f(_("Index for scn() if condition must be 0 or 1 (line {self.ctx.start.line}).")))
def collect(self) -> List[SsbOperation]: if self.var_target is None: raise SsbCompilerError(_("No variable for dungeon_mode set.")) if self.value is None: raise SsbCompilerError(_("No value for dungeon_mode set.")) return [ self._generate_operation(OPS_FLAG__SET_DUNGEON_MODE, [self.var_target, self.value]) ]
def collect(self) -> List[SsbOperation]: if self.is_message_case: raise SsbCompilerError(_("Invalid message switch case call.")) self.compiler_ctx.add_switch_case(self) retval = self._process_block(False) self.compiler_ctx.remove_switch_case() return retval
def collect(self) -> SsbOperator: self.ctx: ExplorerScriptParser.Conditional_operatorContext if self.ctx.OP_FALSE(): return SsbOperator.FALSE if self.ctx.OP_TRUE(): return SsbOperator.TRUE if self.ctx.OP_EQ(): return SsbOperator.EQ if self.ctx.OP_GE(): return SsbOperator.GE if self.ctx.OP_LE(): return SsbOperator.LE if self.ctx.CLOSE_SHARP(): return SsbOperator.GT if self.ctx.OPEN_SHARP(): return SsbOperator.LT if self.ctx.OP_NEQ(): return SsbOperator.NOT if self.ctx.OP_AND(): return SsbOperator.AND if self.ctx.OP_XOR(): return SsbOperator.XOR if self.ctx.OP_BICH(): return SsbOperator.BIT_SET raise SsbCompilerError("Unknown conditional operator.")
def collect(self) -> List[SsbOperation]: if self.scn_var_target is None and not self.ctx.DUNGEON_RESULT(): raise SsbCompilerError(_("No target for reset.")) if self.ctx.DUNGEON_RESULT(): return [self._generate_operation(OPS_FLAG__RESET_DUNGEON_RESULT, [])] return [self._generate_operation(OPS_FLAG__RESET_SCENARIO, [self.scn_var_target])]
def collect(self) -> Union[int, SsbOpParamConstant]: self.ctx: ExplorerScriptParser.Integer_likeContext if self.ctx.INTEGER(): return exps_int(str(self.ctx.INTEGER())) if self.ctx.IDENTIFIER(): return SsbOpParamConstant(str(self.ctx.IDENTIFIER())) if self.ctx.VARIABLE(): return SsbOpParamConstant(str(self.ctx.VARIABLE())) raise SsbCompilerError("Unknown 'integer like'.")
def collect(self) -> SsbLabelJumpBlueprint: if self.value is None: raise SsbCompilerError(_("No value set for if condition.")) if self.is_menu_2: return SsbLabelJumpBlueprint(self.compiler_ctx, self.ctx, OP_CASE_MENU2, [self.value]) return SsbLabelJumpBlueprint(self.compiler_ctx, self.ctx, OP_CASE_MENU, [self.value])
def _resolve_imported_file(self, dir_name): """ Returns the full paths to all imports specified in self.imports. If any file can not be found, raises an SsbCompilerError. """ fs = [] for import_file in self.imports: if import_file.startswith('.') or import_file.startswith('/'): # Relative or absolute import abs_path = os.path.realpath( str( PurePath( PurePosixPath(dir_name).joinpath( PurePosixPath(import_file))))) if not os.path.exists(abs_path): raise SsbCompilerError( f( _("The file to import ('{import_file}') was not found." ))) else: # Relative to one of the lookup paths abs_path = None path_parts = import_file.split('/') if '.' in path_parts or '..' in path_parts: raise SsbCompilerError( f( _("Invalid import: '{import_file}'. Non absolute/relative " "imports must not contain relative paths."))) for lp in self.lookup_paths: abs_path_c = os.path.realpath( str( PurePath(dir_name).joinpath( PurePosixPath(lp).joinpath(import_file)))) if os.path.exists(abs_path_c): abs_path = abs_path_c break if abs_path is None: raise SsbCompilerError( f( _("The file to import ('{import_file}') was not found." ))) fs.append(abs_path) return fs
def collect(self) -> List[SsbOperation]: if self.var_target is None: raise SsbCompilerError(_("No variable for assignment.")) if self.operator is None: raise SsbCompilerError(_("No operator set for assignment.")) if self.value is None: raise SsbCompilerError(_("No value set for assignment.")) if self.ctx.INTEGER(): index = exps_int(str(self.ctx.INTEGER())) # CalcBit / SetPerformance if self.value_is_a_variable: raise SsbCompilerError( f( _("value(X) can not be used with index based assignments " "(line {self.ctx.start.line})."))) if str(self.var_target ) == self.compiler_ctx.performance_progress_list_var_name: return [ self._generate_operation(OPS_FLAG__SET_PERFORMANCE, [index, self.value]) ] return [ self._generate_operation(OPS_FLAG__CALC_BIT, [self.var_target, index, self.value]) ] # CalcValue / CalcVariable / Set if self.value_is_a_variable: return [ self._generate_operation( OPS_FLAG__CALC_VARIABLE, [self.var_target, self.operator.value, self.value]) ] if self.operator == SsbCalcOperator.ASSIGN: return [ self._generate_operation(OPS_FLAG__SET, [self.var_target, self.value]) ] return [ self._generate_operation( OPS_FLAG__CALC_VALUE, [self.var_target, self.operator.value, self.value]) ]
def collect(self) -> List[SsbOperation]: self.ctx: ExplorerScriptParser.Message_switch_blockContext if self.ctx.MESSAGE_SWITCH_MONOLOGUE(): switch_op = self._generate_operation( OP_MESSAGE_SWITCH_MONOLOGUE, [self._switch_header_handler.collect()]) elif self.ctx.MESSAGE_SWITCH_TALK(): switch_op = self._generate_operation( OP_MESSAGE_SWITCH_TALK, [self._switch_header_handler.collect()]) else: raise SsbCompilerError(_("Invalid message switch.")) case_ops = [] for h in self._case_handlers: if not h.is_message_case: raise SsbCompilerError( f( _("A message_ switch can only contain cases with strings " "(line {self.ctx.start.line})."))) header_handler = h.collect_header_handler() if header_handler.get_header_handler_type( ) != IntegerLikeCompileHandler: raise SsbCompilerError( f( _("Invalid case type for message_ switch (line {self.ctx.start.line})." ))) string = h.get_text() value_blueprint = header_handler.collect() # We obviously don't want the bluprint value = value_blueprint.params[0] case_ops.append( self._generate_operation(OP_CASE_TEXT, [value, string])) if self._default_handler: if not self._default_handler.is_message_case: raise SsbCompilerError( f( _("A message_ switch can only contain cases with strings (line {self.ctx.start.line})." ))) case_ops.append( self._generate_operation(OP_DEFAULT_TEXT, [self._default_handler.get_text()])) return [switch_op] + case_ops
def collect( self) -> Union[SsbOpParamLanguageString, SsbOpParamConstString]: self.ctx: ExplorerScriptParser.StringContext if self.ctx.STRING_LITERAL(): return SsbOpParamConstString( string_literal(self.ctx.STRING_LITERAL())) if self.lang_string_handler: return self.lang_string_handler.collect() raise SsbCompilerError( "Invalid string, neither literal nor language string")
def collect(self) -> List[SsbOperation]: if self.var_target is None: raise SsbCompilerError(_("No variable for assignment set.")) return [ self._generate_operation(OPS_FLAG__SET_SCENARIO, [ self.var_target, exps_int(str(self.ctx.INTEGER(0))), exps_int(str(self.ctx.INTEGER(1))) ]) ]
def collect(self) -> SsbCalcOperator: self.ctx: ExplorerScriptParser.Assign_operatorContext if self.ctx.OP_MINUS(): return SsbCalcOperator.MINUS if self.ctx.OP_PLUS(): return SsbCalcOperator.PLUS if self.ctx.OP_MULTIPLY(): return SsbCalcOperator.MULTIPLY if self.ctx.OP_DIVIDE(): return SsbCalcOperator.DIVIDE if self.ctx.ASSIGN(): return SsbCalcOperator.ASSIGN raise SsbCompilerError("Unknown conditional operator.")
def collect(self) -> SsbLabelJumpBlueprint: if self.var_target is None: raise SsbCompilerError(_("No variable for if condition.")) if self.operator is None: raise SsbCompilerError(_("No operator set for if condition.")) if self.value is None: raise SsbCompilerError(_("No value set for if condition.")) if self.value_is_a_variable: # BranchVariable return SsbLabelJumpBlueprint( self.compiler_ctx, self.ctx, OP_BRANCH_VARIABLE, [self.var_target, self.operator.value, self.value]) if self.operator == SsbOperator.EQ: # Branch return SsbLabelJumpBlueprint(self.compiler_ctx, self.ctx, OP_BRANCH, [self.var_target, self.value]) # BranchValue return SsbLabelJumpBlueprint( self.compiler_ctx, self.ctx, OP_BRANCH_VALUE, [self.var_target, self.operator.value, self.value])
def collect(self) -> SsbLabelJumpBlueprint: if self.operator is None: raise SsbCompilerError(_("No operator set for if condition.")) if self.value is None: raise SsbCompilerError(_("No value set for if condition.")) if self.value_is_a_variable: # CaseVariable return SsbLabelJumpBlueprint(self.compiler_ctx, self.ctx, OP_CASE_VARIABLE, [self.operator.value, self.value]) #if self.operator == SsbOperator.EQ: # # Case # return SsbLabelJumpBlueprint( # self.compiler_ctx, self.ctx, # OP_CASE, [self.value] # ) # CaseValue return SsbLabelJumpBlueprint(self.compiler_ctx, self.ctx, OP_CASE_VALUE, [self.operator.value, self.value])
def collect(self) -> SsbLabelJumpBlueprint: self.ctx: ExplorerScriptParser.If_h_bitContext if self.var_target is None: raise SsbCompilerError(_("No variable in if condition.")) var_target_name = None if hasattr(self.var_target, 'name'): var_target_name = self.var_target.name index = exps_int(str(self.ctx.INTEGER())) is_simple_positive = self.ctx.NOT() is None if var_target_name == self.compiler_ctx.performance_progress_list_var_name: return SsbLabelJumpBlueprint( self.compiler_ctx, self.ctx, OP_BRANCH_PERFORMANCE, [index, 1 if is_simple_positive else 0]) elif not is_simple_positive: raise SsbCompilerError( f( _("The variable {var_target_name} can not be used with 'not' " "(line {self.ctx.start.line})."))) return SsbLabelJumpBlueprint(self.compiler_ctx, self.ctx, OP_BRANCH_BIT, [self.var_target, index])
def collect(self) -> SsbOperation: self.ctx: ExplorerScriptParser.Case_headerContext # Complex branches if self._header_cmplx_handler: if isinstance(self._header_cmplx_handler, IntegerLikeCompileHandler): # Switch return self._generate_operation( OP_SWITCH, [self._header_cmplx_handler.collect()]) elif isinstance(self._header_cmplx_handler, OperationCompileHandler): # An operation as condition op = self._header_cmplx_handler.collect() if len(op) != 1: raise SsbCompilerError( _("Invalid content for a switch-header")) op = op[0] return self._generate_operation(op.op_code.name, op.params) else: # A regular complex if condition return self._header_cmplx_handler.collect() raise SsbCompilerError(_("Unknown switch operation."))
def collect(self) -> SsbLabelJumpBlueprint: self.ctx: ExplorerScriptParser.Case_headerContext # Complex branches if self._header_cmplx_handler: if isinstance(self._header_cmplx_handler, IntegerLikeCompileHandler): # Case return SsbLabelJumpBlueprint( self.compiler_ctx, self.ctx, OP_CASE, [self._header_cmplx_handler.collect()]) else: # A regular complex if condition return self._header_cmplx_handler.collect() raise SsbCompilerError(_("Unknown case operation."))
def add(self, obj: any): if isinstance(obj, CaseBlockCompileHandler): self._case_handlers.append(obj) return if isinstance(obj, DefaultCaseBlockCompileHandler): if self._default_handler is not None: raise SsbCompilerError( f( _("A switch block can only have a single default case (line {self.ctx.start.line}" ))) self._default_handler = obj return if isinstance(obj, IntegerLikeCompileHandler): self._switch_header_handler = obj return self._raise_add_error(obj)
def collect(self) -> List[SsbOperation]: self.ctx: ExplorerScriptParser.Macro_callContext name = str(self.ctx.MACRO_CALL())[1:] args: List[SsbOpParam] = [] if self.arg_list_handler: args = self.arg_list_handler.collect() if name not in self.compiler_ctx.macros.keys(): raise SsbCompilerError(f(_("Macro {name} not found."))) macro = self.compiler_ctx.macros[name] self.compiler_ctx.source_map_builder.next_macro_opcode_called_in( None, self.ctx.start.line - 1, self.ctx.start.column) return macro.build(self.compiler_ctx.counter_ops, self.compiler_ctx.counter_labels, dict(zip(macro.variables, args)), self.compiler_ctx.source_map_builder)
def add(self, obj: any): # supports: # simple_stmt := operation | cntrl_stmt | jump | call | assignment - For labels a logical error is raised. if isinstance(obj, OperationCompileHandler) or isinstance(obj, ControlStatementCompileHandler) \ or isinstance(obj, JumpCompileHandler) or isinstance(obj, CallCompileHandler) \ or isinstance(obj, AbstractAssignmentCompileHandler): self._check_sub_stmt() self._sub_stmt = obj return if isinstance(obj, LabelCompileHandler): raise SsbCompilerError( _("A with(){} block can not contain labels.")) # integer_like (for for_id) if isinstance(obj, IntegerLikeCompileHandler): self._for_id = obj.collect() return self._raise_add_error(obj)
def add(self, obj: any): if isinstance(obj, IntegerLikeCompileHandler): if self.var_target is None: # integer_like[0] -> variable self.var_target = obj.collect() return if self.value is None: # (integer_like[1] | value_of) -> var to set to self.value = obj.collect() self.value_is_a_variable = False return raise SsbCompilerError("Assignment: unexpected INTEGER_LIKE.") if isinstance(obj, ConditionalOperatorCompileHandler): self.operator = obj.collect() return if isinstance(obj, ValueOfCompileHandler): self.value = obj.collect() self.value_is_a_variable = True return self._raise_add_error(obj)
def get_text(self) -> SsbOpParam: if not self.is_message_case: raise SsbCompilerError(_("Invalid message switch case call.")) return self._added_string_handler.collect()
def collect(self) -> SsbOperation: if self.value is None: raise SsbCompilerError(_("No value set for random switch condition.")) return self._generate_operation(OP_SWITCH_RANDOM, [self.value])
def compile_structured( self, routine_infos: List[SsbRoutineInfo], routine_ops: List[List[SsbOperation]], named_coroutines: List[str], original_source_map: SourceMap ) -> Tuple[Ssb, SourceMap]: """Compile the structured data from a base compiler for SsbScript or ExplorerScript into an SSB model.""" logger.debug("Assembling SSB model...") model = Ssb.create_empty(self.rom_data.script_data) if len(routine_ops) != len(routine_ops) != len(named_coroutines): raise SsbCompilerError(_("The routine data lists for the decompiler must have the same lengths.")) # Build routines and opcodes. if len(routine_ops) > 0: header_class = SsbHeaderUs if self.rom_data.game_region == GAME_REGION_EU: header_class = SsbHeaderEu built_strings: Dict[str, List[str]] = {lang: [] for lang in header_class.supported_langs()} built_constants: List[str] = [] for i, r in enumerate(routine_infos): if r is None: raise SsbCompilerError(f(_("Routine {i} not found."))) input_routine_structure: List[ Tuple[SsbRoutineInfo, str, List[SsbOperation]] ] = list(zip(routine_infos, named_coroutines, routine_ops)) # The cursor position of the written routine opcodes. # The opcodes start after the routine info, which has a fixed length, based on the number of routines. opcode_cursor = SSB_LEN_ROUTINE_INFO_ENTRY * len(input_routine_structure) + SSB_PADDING_BEFORE_ROUTINE_INFO # If it has any coroutines, they all have to be. has_coroutines = routine_infos[0].type == SsbRoutineType.COROUTINE # Run coroutine checks and sortings. if has_coroutines: # Assert, that the data contains all coroutines from the ROM schema and sort all three lists by this if len(input_routine_structure) != len(self.rom_data.script_data.common_routine_info): raise SsbCompilerError( f(_("The script must contain exactly {len(self.rom_data.script_data.common_routine_info)} coroutines.")) ) if len(routine_infos) != len(set(named_coroutines)): raise SsbCompilerError(f(_("The script must not contain any duplicate coroutines."))) try: input_routine_structure = sorted( input_routine_structure, key=lambda k: self.rom_data.script_data.common_routine_info__by_name[k[1]].id ) except KeyError as err: raise SsbCompilerError(f(_("Unknown coroutine {err}"))) from err # Build Routine Infos built_routine_info_with_offset: List[Tuple[int, SsbRoutineInfo]] = [] built_routine_ops: List[List[SsbOperation]] = [] # A list of lists for ALL opcodes that maps all opcode indices to their memory address. opcode_index_mem_offset_mapping: Dict[int, int] = {} bytes_written_last_rtn = 0 for i, (input_info, __, input_ops) in enumerate(input_routine_structure): if ( has_coroutines and input_info.type != SsbRoutineType.COROUTINE ) or ( not has_coroutines and input_info.type == SsbRoutineType.COROUTINE ): raise SsbCompilerError(f(_("Coroutines and regular routines can not be mixed in a script file."))) routine_start_cursor = opcode_cursor # Build OPs built_ops: List[SkyTempleSsbOperation] = [] if len(input_ops) == 0: # ALIAS ROUTINE. This alias the PREVIOUS routine routine_start_cursor = opcode_cursor - bytes_written_last_rtn else: bytes_written_last_rtn = 0 for in_op in input_ops: if in_op.op_code.name not in self.rom_data.script_data.op_codes__by_name: raise SsbCompilerError(f(_("Unknown operation {in_op.op_code.name}."))) op_codes: List[Pmd2ScriptOpCode] = self.rom_data.script_data.op_codes__by_name[in_op.op_code.name] if len(op_codes) > 1: # Can be either a variable length opcode or the "normal" variant. var_len_op_code = next(o for o in op_codes if o.params == -1) normal_op_code = next(o for o in op_codes if o.params != -1) if self._correct_param_list_len(in_op.params) == normal_op_code.params: op_code = normal_op_code elif self._correct_param_list_len(in_op.params) > normal_op_code.params: op_code = var_len_op_code else: raise SsbCompilerError(f(_("The number of parameters for {normal_op_code.name} " "must be at least {normal_op_code.params}, is {self._correct_param_list_len(in_op.params)}."))) else: op_code = op_codes[0] new_params: List[int] = [] op_len = 2 if op_code.params == -1: # Handle variable length opcode by inserting the number of opcodes as the first argument. # ... nothing to do here! Writing the first "meta-argument" for the number of arguments # is the job of the writer later! op_len += 2 pass elif self._correct_param_list_len(in_op.params) != op_code.params: # TODO: This might be a confusing count for end users in the case of position markers. raise SsbCompilerError(f(_("The number of parameters for {op_code.name} " "must be {op_code.params}, is {self._correct_param_list_len(in_op.params)}."))) for param in in_op.params: if isinstance(param, SsbOpParamPositionMarker): # Handle multi-argument case position markers new_params.append(param.x_offset) new_params.append(param.y_offset) new_params.append(param.x_relative) new_params.append(param.y_relative) op_len += 8 else: # Handle the rest new_params.append(self._parse_param(param, built_strings, built_constants)) op_len += 2 built_ops.append(SkyTempleSsbOperation(opcode_cursor, op_code, new_params)) # Create actual offset mapping for this opcode and update source map opcode_index_mem_offset_mapping[in_op.offset] = int(opcode_cursor / 2) bytes_written_last_rtn += op_len opcode_cursor += op_len # Find out the target for this routine if it's specified by name if input_info.linked_to == -1: input_info.linked_to = SsbConstant(input_info.linked_to_name, self.rom_data.script_data).value.id built_routine_info_with_offset.append((routine_start_cursor, input_info)) built_routine_ops.append(built_ops) # Second pass: Update all jumps to their correct place and update string index positions for built_routine in built_routine_ops: for op in built_routine: if op.op_code.name in OPS_WITH_JUMP_TO_MEM_OFFSET: param_id = OPS_WITH_JUMP_TO_MEM_OFFSET[op.op_code.name] index_to_jump_to = op.params[param_id] op.params[param_id] = opcode_index_mem_offset_mapping[index_to_jump_to] for i, param in enumerate(op.params): if isinstance(param, StringIndexPlaceholder): # If the parameter is a reference to a language string, the length of the constants # has to be added, because the language strings are after the const strings. op.params[i] = len(built_constants) + int(param) # Fill the model model.routine_info = built_routine_info_with_offset model.routine_ops = built_routine_ops model.constants = built_constants model.strings = built_strings # Update the source map original_source_map.rewrite_offsets(opcode_index_mem_offset_mapping) return model, original_source_map