Exemple #1
0
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
Exemple #2
0
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)
Exemple #3
0
    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)
Exemple #4
0
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
Exemple #5
0
    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
Exemple #6
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
Exemple #7
0
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
Exemple #8
0
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)
Exemple #9
0
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)
Exemple #10
0
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
Exemple #11
0
 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))
Exemple #12
0
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)