def add_linked_to_names_to_routine_ops(self): for _, r in self.routine_info: try: if r.type == SsbRoutineType.ACTOR: r.linked_to_name = SsbConstant.create_for(self._scriptdata.level_entities__by_id[r.linked_to]).name elif r.type == SsbRoutineType.OBJECT: r.linked_to_name = SsbConstant.create_for(self._scriptdata.objects__by_id[r.linked_to]).name except KeyError: pass
def _assert_same_vertex(self, i, self_v: 'Vertex', other_v: 'Vertex'): if self_v is None or other_v is None: assert self_v == other_v, f"Both must be None {self._r_info(i)}" return self_op: SsbOperation = self_v['op'] other_op: SsbOperation = other_v['op'] # We can't really check foreign jumps if isinstance(self_op, SsbForeignLabel) or isinstance( other_op, SsbForeignLabel): assert isinstance(self_op, SsbForeignLabel) and isinstance(other_op, SsbForeignLabel), \ f"If one is foreign label, both must be ({self._r_info(i)})." return # If this is a label jump, take root. if hasattr(self_op, 'root'): self_op = self_op.root if hasattr(other_op, 'root'): other_op = other_op.root # OPCODES EXCEPTIONS # We replace flag_CalcValue with the ASSIGN operator with flag_Set if self_op.op_code.name == OPS_FLAG__CALC_VALUE and self_op.params[ 1] == SsbCalcOperator.ASSIGN.value: self_op.op_code = SsbOpCode(-1, OPS_FLAG__SET) self_op.params = [self_op.params[0], self_op.params[2]] if other_op.op_code.name == OPS_FLAG__CALC_VALUE and other_op.params[ 1] == SsbCalcOperator.ASSIGN.value: other_op.op_code = SsbOpCode(-1, OPS_FLAG__SET) other_op.params = [other_op.params[0], other_op.params[2]] # We replace BranchBit + PERFORMANCE_PROGRESS_LIST with BranchPerformance if self_op.op_code.name == OP_BRANCH_BIT and self_op.params[ 0].name == SsbConstant.create_for( self._variables_by_name['PERFORMANCE_PROGRESS_LIST']).name: self_op.op_code.name = SsbOpCode(-1, OP_BRANCH_PERFORMANCE) self_op.params = [self_op.params[1], 1] if other_op.op_code.name == OP_BRANCH_BIT and other_op.params[ 0].name == SsbConstant.create_for( self._variables_by_name['PERFORMANCE_PROGRESS_LIST']).name: other_op.op_code.name = SsbOpCode(-1, OP_BRANCH_PERFORMANCE) other_op.params = [other_op.params[1], 1] assert self_op.op_code.name == other_op.op_code.name, f"Opcodes were not the same: {self_op.op_code.name} " \ f"vs. {other_op.op_code.name} [{self_v.index}," \ f"{other_v.index}] ({self._r_info(i)})." self_op_params = self_op.params other_op_params = other_op.params # PARAMETER EXCEPTIONS: # We force BranchVariation to be a boolean, because the game seems to treat it as such too. if self_op.op_code.name == OP_BRANCH_VARIATION: if self_op_params[0] > 1: self_op_params[0] = 1 if other_op_params[0] > 1: other_op_params[0] = 1 assert self_op_params == other_op_params, f"Parameters of opcode ({self_op.op_code.name}) [{self_v.index}," \ f"{other_v.index}] are not the same ({self._r_info(i)})."
def compile_explorerscript(self, es_src: str, exps_absolue_path: str, callback_after_parsing: Callable = None, lookup_paths: List[str] = None) -> Tuple[Ssb, SourceMap]: """ Compile ExplorerScript into a SSB model. Returns the Ssb model, the source map, and a list of macros that were used in the ExplorerScript file. lookup_paths is the list of include lookup paths. :raises: ParseError: On parsing errors :raises: SsbCompilerError: On logical compiling errors (eg. unknown opcodes) :raises: ValueError: On misc. logical compiling errors (eg. unknown constants) """ logger.debug("Compiling ExplorerScript (size: %d, path: %s)...", len(es_src), exps_absolue_path) base_compiler = ExplorerScriptSsbCompiler( SsbConstant.create_for(self.rom_data.script_data.game_variables__by_name['PERFORMANCE_PROGRESS_LIST']).name, lookup_paths ) base_compiler.compile(es_src, exps_absolue_path) # Profiling callback if callback_after_parsing: callback_after_parsing() return self.compile_structured( base_compiler.routine_infos, base_compiler.routine_ops, base_compiler.named_coroutines, base_compiler.source_map )
def to_explorerscript(self) -> Tuple[str, SourceMap]: self.add_linked_to_names_to_routine_ops() return ExplorerScriptSsbDecompiler( [x[1] for x in self.routine_info], self.get_filled_routine_ops(), self._scriptdata.common_routine_info__by_id, SsbConstant.create_for(self._scriptdata.game_variables__by_name['PERFORMANCE_PROGRESS_LIST']).name, SsbConstant.get_dungeon_mode_constants() ).convert()
def get_filled_routine_ops(self): """Returns self.routine_ops, but with constant strings, strings and constants from scriptdata filled out""" logger.debug("Disassembling SSB model data...") rtns: List[List[SkyTempleSsbOperation]] = [] pos_marker_increment = 0 for rtn in self.routine_ops: rtn_ops = [] for op in rtn: new_params = [] skip_arguments = 0 for i, param in enumerate(op.params): if skip_arguments > 0: skip_arguments -= 1 continue argument_spec = self._get_argument_spec(op.op_code, i) if argument_spec is not None: if argument_spec.type == 'uint': # TODO: Do unsigned parameters actually exist? If so are they also 14bit? new_params.append(param) elif argument_spec.type == 'sint': # 14 bit signed int. if param & 0x4000: param = -0x8000 + param new_params.append(param) elif argument_spec.type == 'Entity': new_params.append(SsbConstant.create_for(self._scriptdata.level_entities__by_id[param])) elif argument_spec.type == 'Object': new_params.append(SsbConstant.create_for(self._scriptdata.objects__by_id[param])) elif argument_spec.type == 'Routine': new_params.append(SsbConstant.create_for(self._scriptdata.common_routine_info__by_id[param])) elif argument_spec.type == 'Face': if param in self._scriptdata.face_names__by_id: new_params.append(SsbConstant.create_for(self._scriptdata.face_names__by_id[param])) else: logger.warning(f"Unknown face id: {param}") new_params.append(param) elif argument_spec.type == 'FaceMode': new_params.append(SsbConstant.create_for(self._scriptdata.face_position_modes__by_id[param])) elif argument_spec.type == 'GameVar': new_params.append(SsbConstant.create_for(self._scriptdata.game_variables__by_id[param])) elif argument_spec.type == 'Level': if param in self._scriptdata.level_list__by_id: new_params.append(SsbConstant.create_for(self._scriptdata.level_list__by_id[param])) else: logger.warning(f"Unknown level id: {param}") new_params.append(param) elif argument_spec.type == 'Menu': if param in self._scriptdata.menus__by_id: new_params.append(SsbConstant.create_for(self._scriptdata.menus__by_id[param])) else: logger.warning(f"Unknown menu id: {param}") new_params.append(param) elif argument_spec.type == 'ProcessSpecial': if param in self._scriptdata.process_specials__by_id: new_params.append(SsbConstant.create_for(self._scriptdata.process_specials__by_id[param])) else: new_params.append(param) logger.warning(f"Unknown special process id: {param}") elif argument_spec.type == 'Direction': if param in self._scriptdata.directions__by_ssb_id: new_params.append(SsbConstant.create_for(self._scriptdata.directions__by_ssb_id[param])) else: new_params.append(param) logger.warning(f"Unknown direction id: {param}") elif argument_spec.type == 'Bgm': if param in self._scriptdata.bgms__by_id: new_params.append(SsbConstant.create_for(self._scriptdata.bgms__by_id[param])) else: logger.warning(f"Unknown BGM id: {param}") new_params.append(param) elif argument_spec.type == 'Effect': if param in self._scriptdata.sprite_effects__by_id: new_params.append(SsbConstant.create_for(self._scriptdata.sprite_effects__by_id[param])) else: logger.warning(f"Unknown effect id: {param}") new_params.append(param) elif argument_spec.type == 'String': try: new_params.append(SsbOpParamLanguageString(self.get_single_string(param - len(self.constants)))) except IndexError: # Fall back to const table new_params.append(SsbOpParamConstString(self.constants[param])) elif argument_spec.type == 'ConstString': try: new_params.append(SsbOpParamConstString(self.constants[param])) except IndexError: # Fall back to lang string new_params.append(SsbOpParamLanguageString(self.get_single_string(param - len(self.constants)))) elif argument_spec.type == 'PositionMark': x_offset = y_offset = x_relative = y_relative = 0 try: x_offset = param y_offset = op.params[i + 1] x_relative = op.params[i + 2] y_relative = op.params[i + 3] except IndexError: logger.warning("SSB had wrong number of arguments for building a position marker.") new_params.append(SsbOpParamPositionMarker( f'm{pos_marker_increment}', x_offset, y_offset, x_relative, y_relative )) pos_marker_increment += 1 skip_arguments = 3 else: raise RuntimeError(f"Unknown argument type '{argument_spec.type}'") else: raise RuntimeError(f"Missing argument spec for argument #{i} for OpCode {op.op_code.name}") new_op = SkyTempleSsbOperation(op.offset, op.op_code, new_params) rtn_ops.append(new_op) rtns.append(rtn_ops) return rtns