def get_snowlinetable_action(snowline_table): assert len(snowline_table) == 12 * 32 action6.free_parameters.save() action_list = [] tmp_param_map = {} # Cache for tmp parameters act6 = action6.Action6() act0, offset = create_action0(0x08, expression.ConstantNumeric(0), act6, action_list) act0.num_ids = 1 offset += 1 # Skip property number data_table = [] idx = 0 while idx < len(snowline_table): val = snowline_table[idx] if isinstance(val, expression.ConstantNumeric): data_table.append(val.value) idx += 1 continue if idx + 3 >= len(snowline_table): tmp_param, tmp_param_actions = actionD.get_tmp_parameter(val) tmp_param_map[val] = tmp_param act6.modify_bytes(tmp_param, 1, offset + idx) action_list.extend(tmp_param_actions) data_table.append(0) idx += 1 continue # Merge the next 4 values together in a single parameter. val2 = nmlop.SHIFT_LEFT(snowline_table[idx + 1], 8) val3 = nmlop.SHIFT_LEFT(snowline_table[idx + 2], 16) val4 = nmlop.SHIFT_LEFT(snowline_table[idx + 3], 24) expr = nmlop.OR(val, val2) expr = nmlop.OR(expr, val3) expr = nmlop.OR(expr, val4) expr = expr.reduce() # Cache lookup, saves some ActionDs if expr in tmp_param_map: tmp_param, tmp_param_actions = tmp_param_map[expr], [] else: tmp_param, tmp_param_actions = actionD.get_tmp_parameter(expr) tmp_param_map[expr] = tmp_param act6.modify_bytes(tmp_param, 4, offset + idx) action_list.extend(tmp_param_actions) data_table.extend([0, 0, 0, 0]) idx += 4 act0.prop_list.append( ByteListProp(0x10, "".join([chr(x) for x in data_table]))) if len(act6.modifications) > 0: action_list.append(act6) action_list.append(act0) action6.free_parameters.restore() return action_list
def parse_conditional(expr): """ Parse an expression and return enough information to use that expression as a conditional statement. Return value is a tuple with the following elements: - Parameter number (as integer) to use in comparison or None for unconditional skip - List of actions needed to set the given parameter to the correct value - The type of comparison to be done - The value to compare against (as integer) - The size of the value (as integer) """ if expr is None: return (None, [], (2, r"\7="), 0, 4) if isinstance(expr, expression.BinOp): if expr.op == nmlop.HASBIT or expr.op == nmlop.NOTHASBIT: if isinstance(expr.expr1, expression.Parameter) and isinstance( expr.expr1.num, expression.ConstantNumeric): param = expr.expr1.num.value actions = [] else: param, actions = actionD.get_tmp_parameter(expr.expr1) if isinstance(expr.expr2, expression.ConstantNumeric): bit_num = expr.expr2.value else: if isinstance(expr.expr2, expression.Parameter) and isinstance( expr.expr2.num, expression.ConstantNumeric): param = expr.expr2.num.value else: param, tmp_action_list = actionD.get_tmp_parameter( expr.expr2) actions.extend(tmp_action_list) act6 = action6.Action6() act6.modify_bytes(param, 1, 4) actions.append(act6) bit_num = 0 comp_type = (1, r"\70") if expr.op == nmlop.HASBIT else (0, r"\71") return (param, actions, comp_type, bit_num, 1) elif expr.op in (nmlop.CMP_EQ, nmlop.CMP_NEQ, nmlop.CMP_LE, nmlop.CMP_GE) and isinstance( expr.expr2, expression.ConstantNumeric): if isinstance(expr.expr1, expression.Parameter) and isinstance( expr.expr1.num, expression.ConstantNumeric): param = expr.expr1.num.value actions = [] else: param, actions = actionD.get_tmp_parameter(expr.expr1) op = op_to_cond_op(expr.op) return (param, actions, op, expr.expr2.value, 4) if isinstance(expr, expression.Boolean): expr = expr.expr if isinstance(expr, expression.Not): param, actions = actionD.get_tmp_parameter(expr.expr) return (param, actions, (3, r"\7!"), 0, 4) param, actions = actionD.get_tmp_parameter(expr) return (param, actions, (2, r"\7="), 0, 4)
def parse_binop(self, expr): if expr.op.act2_num is None: expr.supported_by_action2(True) if (isinstance(expr.expr2, (expression.ConstantNumeric, expression.Variable)) or isinstance(expr.expr2, (VarAction2LoadTempVar, VarAction2LoadCallParam)) or (isinstance(expr.expr2, expression.Parameter) and isinstance(expr.expr2.num, expression.ConstantNumeric)) or expr.op == nmlop.VAL2): expr2 = expr.expr2 elif expr.expr2.supported_by_actionD(False): tmp_param, tmp_param_actions = actionD.get_tmp_parameter( expr.expr2) self.extra_actions.extend(tmp_param_actions) expr2 = expression.Parameter(expression.ConstantNumeric(tmp_param)) else: # The expression is so complex we need to compute it first, store the # result and load it back later. self.parse(expr.expr2) tmp_var = VarAction2StoreTempVar() self.var_list.append(nmlop.STO_TMP) self.var_list.append(tmp_var) self.var_list.append(nmlop.VAL2) # the +2 is for both operators self.var_list_size += tmp_var.get_size() + 2 expr2 = VarAction2LoadTempVar(tmp_var) # parse expr1 self.parse(expr.expr1) self.var_list.append(expr.op) self.var_list_size += 1 self.parse(expr2)
def parse_error_block(error): action6.free_parameters.save() action_list = [] act6 = action6.Action6() severity = actionD.write_action_value(error.severity, action_list, act6, 1, 1)[0] langs = [0x7F] if isinstance(error.msg, expression.String): custom_msg = True msg_string = error.msg grfstrings.validate_string(msg_string) langs.extend(grfstrings.get_translations(msg_string)) for lang in langs: assert lang is not None else: custom_msg = False msg = error.msg.reduce_constant().value if error.data is not None: error.data = error.data.reduce() if isinstance(error.data, expression.String): grfstrings.validate_string(error.data) langs.extend(grfstrings.get_translations(error.data)) for lang in langs: assert lang is not None elif not isinstance(error.data, expression.StringLiteral): raise generic.ScriptError( "Error parameter 3 'data' should be the identifier of a custom sting", error.data.pos) params = [] for expr in error.params: if isinstance(expr, expression.Parameter) and isinstance( expr.num, expression.ConstantNumeric): params.append(expr.num) else: tmp_param, tmp_param_actions = actionD.get_tmp_parameter(expr) action_list.extend(tmp_param_actions) params.append(expression.ConstantNumeric(tmp_param)) langs = list(set(langs)) langs.sort() for lang in langs: if custom_msg: msg = grfstrings.get_translation(msg_string, lang) if error.data is None: data = None elif isinstance(error.data, expression.StringLiteral): data = error.data.value else: data = grfstrings.get_translation(error.data, lang) if len(act6.modifications) > 0: action_list.append(act6) action_list.append(ActionB(severity, lang, msg, data, params)) action6.free_parameters.restore() return action_list
def parse_expr_to_constant(self, expr, offset): if isinstance(expr, expression.ConstantNumeric): return expr.value tmp_param, tmp_param_actions = actionD.get_tmp_parameter(expr) self.extra_actions.extend(tmp_param_actions) self.mods.append( Modification(tmp_param, 4, self.var_list_size + offset)) return 0
def get_basecost_action(basecost): action6.free_parameters.save() action_list = [] tmp_param_map = {} # Cache for tmp parameters # We want to avoid writing lots of action0s if possible i = 0 while i < len(basecost.costs): cost = basecost.costs[i] act6 = action6.Action6() act0, offset = create_action0(0x08, cost.name, act6, action_list) first_id = cost.name.value if isinstance( cost.name, expression.ConstantNumeric) else None num_ids = 1 # Number of values that will be written in one go values = [] # try to capture as much values as possible while True: cost = basecost.costs[i] if isinstance(cost.value, expression.ConstantNumeric): values.append(cost.value) else: # Cache lookup, saves some ActionDs if cost.value in tmp_param_map: tmp_param, tmp_param_actions = tmp_param_map[ cost.value], [] else: tmp_param, tmp_param_actions = actionD.get_tmp_parameter( cost.value) tmp_param_map[cost.value] = tmp_param act6.modify_bytes(tmp_param, 1, offset + num_ids) action_list.extend(tmp_param_actions) values.append(expression.ConstantNumeric(0)) # check if we can append the next to this one (it has to be consecutively numbered) if first_id is not None and (i + 1) < len(basecost.costs): nextcost = basecost.costs[i + 1] if isinstance(nextcost.name, expression.ConstantNumeric ) and nextcost.name.value == first_id + num_ids: num_ids += 1 i += 1 # Yes We Can, continue the loop to append this value to the list and try further continue # No match, so stop it and write an action0 break act0.prop_list.append(Action0Property(0x08, values, 1)) act0.num_ids = num_ids if len(act6.modifications) > 0: action_list.append(act6) action_list.append(act0) i += 1 action6.free_parameters.restore() return action_list
def get_tracktypelist_action(table_prop_id, cond_tracktype_not_defined, tracktype_list): action6.free_parameters.save() act6 = action6.Action6() action_list = [] action0, offset = create_action0(0x08, expression.ConstantNumeric(0), act6, action_list) id_table = [] offset += 1 # Skip property number for tracktype in tracktype_list: if isinstance(tracktype, expression.StringLiteral): id_table.append(tracktype) offset += 4 continue param, extra_actions = actionD.get_tmp_parameter( expression.ConstantNumeric( expression.parse_string_to_dword(tracktype[-1]))) action_list.extend(extra_actions) for idx in range(len(tracktype) - 2, -1, -1): val = expression.ConstantNumeric( expression.parse_string_to_dword(tracktype[idx])) action_list.append( action7.SkipAction(0x09, 0x00, 4, (cond_tracktype_not_defined, None), val.value, 1)) action_list.append( actionD.ActionD( expression.ConstantNumeric(param), expression.ConstantNumeric(0xFF), nmlop.ASSIGN, expression.ConstantNumeric(0xFF), val, )) act6.modify_bytes(param, 4, offset) id_table.append(expression.StringLiteral(r"\00\00\00\00", None)) offset += 4 action0.prop_list.append(IDListProp(table_prop_id, id_table)) action0.num_ids = len(tracktype_list) if len(act6.modifications) > 0: action_list.append(act6) action_list.append(action0) action6.free_parameters.restore() return action_list
def get_string_action4s(feature, string_range, string, id = None): """ Let a string from the lang files be used in the rest of NML. This may involve adding actions directly, but otherwise an ID is allocated and the string will be written later @param feature: Feature that uses the string @type feature: C{int} @param string_range: String range to use, either a value from L{string_ranges} or C{None} if N/A (item names) @type string_range: C{int} or C{None} @param string: String to parse @type string: L{expression.String} @param id: ID to use for this string, or C{None} if it will be allocated dynamically (random_id is true for the string range) @type id: L{Expression} or C{None} @return: A tuple of two values: - ID of the string (useful if allocated dynamically) - Resulting action list to be appended @rtype: C{tuple} of (C{int}, C{list} of L{BaseAction}) """ grfstrings.validate_string(string) write_action4s = True action6.free_parameters.save() actions = [] mod = None if string_range is not None: size = 2 if string_ranges[string_range]['random_id']: # ID is allocated randomly, we will output the actions later write_action4s = False if (feature, string) in used_strings[string_range]: id_val = used_strings[string_range][(feature, string)] else: id_val = string_ranges[string_range]['ids'].pop() used_strings[string_range][(feature, string)] = id_val else: # ID must be supplied assert id is not None assert isinstance(id, expression.ConstantNumeric) id_val = id.value | (string_range << 8) else: # Not a string range, so we must have an id assert id is not None size = 3 if feature <= 3 else 1 if isinstance(id, expression.ConstantNumeric): id_val = id.value else: id_val = 0 tmp_param, tmp_param_actions = actionD.get_tmp_parameter(id) actions.extend(tmp_param_actions) # Apply ID via action4 later mod = (tmp_param, 2 if feature <= 3 else 1, 5 if feature <= 3 else 4) if write_action4s: strings = [(lang_id, grfstrings.get_translation(string, lang_id)) for lang_id in grfstrings.get_translations(string)] # Sort the strings for deterministic ordering and prepend the default language strings = [(0x7F, grfstrings.get_translation(string))] + sorted(strings, key=lambda lang_text: lang_text[0]) for lang_id, text in strings: if mod is not None: act6 = action6.Action6() act6.modify_bytes(*mod) actions.append(act6) actions.append(Action4(feature, lang_id, size, id_val, [text])) action6.free_parameters.restore() return (id_val, actions)
def parse_property(prop_info, value_list, feature, id): """ Parse a single property @param prop_info: A dictionary with property information @type prop_info: C{dict} @param value_list: List of values for the property, with unit conversion applied @type value_list: C{list} of L{Expression} @param feature: Feature of the associated item @type feature: C{int} @param id: ID of the associated item @type id: L{Expression} @return: A tuple containing the following: - List of properties to add to the action 0 - List of actions to prepend - List of modifications to apply via action 6 - List of actions to append @rtype: C{tuple} of (C{list} of L{Action0Property}, C{list} of L{BaseAction}, C{list} of 3-C{tuple}, C{list} of L{BaseAction}) """ action_list = [] action_list_append = [] mods = [] if 'custom_function' in prop_info: props = prop_info['custom_function'](*value_list) else: # First process each element in the value_list final_values = [] for i, value in enumerate(value_list): if 'string_literal' in prop_info and ( isinstance(value, expression.StringLiteral) or prop_info['string_literal'] != 4): # Parse non-string exprssions just like integers. User will have to take care of proper value. # This can be used to set a label (=string of length 4) to the value of a parameter. if not isinstance(value, expression.StringLiteral): raise generic.ScriptError( "Value for property {:d} must be a string literal". format(prop_info['num']), value.pos) if len(value.value) != prop_info['string_literal']: raise generic.ScriptError( "Value for property {:d} must be of length {:d}". format(prop_info['num'], prop_info['string_literal']), value.pos) elif isinstance(value, expression.ConstantNumeric): pass elif isinstance(value, expression.Parameter) and isinstance( value.num, expression.ConstantNumeric): mods.append((value.num.value, prop_info['size'], i * prop_info['size'] + 1)) value = expression.ConstantNumeric(0) elif isinstance(value, expression.String): if not 'string' in prop_info: raise generic.ScriptError( "String used as value for non-string property: " + str(prop_info['num']), value.pos) string_range = prop_info['string'] stringid, string_actions = action4.get_string_action4s( feature, string_range, value, id) value = expression.ConstantNumeric(stringid) action_list_append.extend(string_actions) else: tmp_param, tmp_param_actions = actionD.get_tmp_parameter(value) mods.append( (tmp_param, prop_info['size'], i * prop_info['size'] + 1)) action_list.extend(tmp_param_actions) value = expression.ConstantNumeric(0) final_values.append(value) # Now, write a single Action0 Property with all of these values if prop_info['num'] != -1: props = [ Action0Property(prop_info['num'], final_values, prop_info['size']) ] else: props = [] return (props, action_list, mods, action_list_append)
def parse_conditional_block(cond_list): global recursive_cond_blocks recursive_cond_blocks += 1 if recursive_cond_blocks == 1: # We only save a single state (at toplevel nml-blocks) because # we don't know at the start of the block how many labels we need. # Getting the same label for a block that was already used in a # sub-block would be very bad, since the action7/9 would skip # to the action10 of the sub-block. free_labels.save() blocks = [] for cond in cond_list.statements: if isinstance(cond.expr, expression.ConstantNumeric): if cond.expr.value == 0: continue else: blocks.append({"expr": None, "statements": cond.statements}) break blocks.append({"expr": cond.expr, "statements": cond.statements}) if blocks: blocks[-1]["last_block"] = True if len(blocks) == 1 and blocks[0]["expr"] is None: action_list = [] for stmt in blocks[0]["statements"]: action_list.extend(stmt.get_action_list()) return action_list action6.free_parameters.save() if len(blocks) > 1: # the skip all parameter is used to skip all blocks after one # of the conditionals was true. We can't always skip directly # to the end of the blocks since action7/action9 can't always # be mixed param_skip_all, action_list = actionD.get_tmp_parameter( expression.ConstantNumeric(0xFFFFFFFF)) else: action_list = [] # use parse_conditional here, we also need to know if all generated # actions (like action6) can be skipped safely for block in blocks: ( block["param_dst"], block["cond_actions"], block["cond_type"], block["cond_value"], block["cond_value_size"], ) = parse_conditional(block["expr"]) if "last_block" not in block: block["action_list"] = [ actionD.ActionD( # If this isn't the last block, len(blocks) > 1 so param_skip_all is initialized. expression.ConstantNumeric( param_skip_all ), # lgtm [py/uninitialized-local-variable] expression.ConstantNumeric(0xFF), nmlop.ASSIGN, expression.ConstantNumeric(0), expression.ConstantNumeric(0), ) ] else: block["action_list"] = [] for stmt in block["statements"]: block["action_list"].extend(stmt.get_action_list()) # Main problem: action10 can't be skipped by action9, so we're # nearly forced to use action7, but action7 can't safely skip action6 # Solution: use temporary parameter, set to 0 for not skip, !=0 for skip. # then skip every block of actions (as large as possible) with either # action7 or action9, depending on which of the two works. for i, block in enumerate(blocks): param = block["param_dst"] if i == 0: action_list.extend(block["cond_actions"]) else: action_list.extend( cond_skip_actions(block["cond_actions"], param_skip_all, (2, r"\7="), 0, 4, cond_list.pos)) if param is None: param = param_skip_all else: action_list.append( actionD.ActionD( expression.ConstantNumeric(block["param_dst"]), expression.ConstantNumeric(block["param_dst"]), nmlop.AND, expression.ConstantNumeric(param_skip_all), )) action_list.extend( cond_skip_actions( block["action_list"], param, block["cond_type"], block["cond_value"], block["cond_value_size"], cond_list.pos, )) if recursive_cond_blocks == 1: free_labels.restore() recursive_cond_blocks -= 1 action6.free_parameters.restore() return action_list
def parse_via_actionD(self, expr): tmp_param, tmp_param_actions = actionD.get_tmp_parameter(expr) self.extra_actions.extend(tmp_param_actions) num = expression.ConstantNumeric(tmp_param) self.parse(expression.Parameter(num))
def parse_result(value, action_list, act6, offset, parent_action, none_result, var_range, repeat_result=1): """ Parse a result (another switch or CB result) in a switch block. @param value: Value to parse @type value: L{Expression} @param action_list: List to append any extra actions to @type action_list: C{list} of L{BaseAction} @param act6: Action6 to add any modifications to @type act6: L{Action6} @param offset: Current offset to use for action6 @type offset: C{int} @param parent_action: Reference to the action of which this is a result @type parent_action: L{BaseAction} @param none_result: Result to use to return the computed value @type none_result: L{Expression} @param var_range: Variable range to use for variables in the expression @type var_range: C{int} @param repeat_result: Repeat any action6 modifying of the next sprite this many times. @type repeat_result: C{int} @return: A tuple of two values: - The value to use as return value - Comment to add to this value @rtype: C{tuple} of (L{ConstantNumeric} or L{SpriteGroupRef}), C{str} """ if value is None: comment = "return;" assert none_result is not None if isinstance(none_result, expression.SpriteGroupRef): result = parse_sg_ref_result(none_result, action_list, parent_action, var_range) else: result = none_result elif isinstance(value, expression.SpriteGroupRef): result = parse_sg_ref_result(value, action_list, parent_action, var_range) comment = result.name.value + ";" elif isinstance(value, expression.ConstantNumeric): comment = "return {:d};".format(value.value) result = value if not (-16384 <= value.value <= 32767): msg = ( "Callback results are limited to -16384..16383 (when the result is a signed number)" " or 0..32767 (unsigned), encountered {:d}.").format( value.value) raise generic.ScriptError(msg, value.pos) elif isinstance(value, expression.String): comment = "return {};".format(str(value)) str_id, actions = action4.get_string_action4s(0, 0xD0, value) action_list.extend(actions) result = expression.ConstantNumeric(str_id - 0xD000 + 0x8000) elif value.supported_by_actionD(False): tmp_param, tmp_param_actions = actionD.get_tmp_parameter( nmlop.OR(value, 0x8000).reduce()) comment = "return param[{:d}];".format(tmp_param) action_list.extend(tmp_param_actions) for i in range(repeat_result): act6.modify_bytes(tmp_param, 2, offset + 2 * i) result = expression.ConstantNumeric(0) else: global return_action_id extra_actions, result = create_return_action( value, parent_action.feature, "@return_action_{:d}".format(return_action_id), var_range) return_action_id += 1 action2.add_ref(result, parent_action) action_list.extend(extra_actions) comment = "return {}".format(value) return (result, comment)