RATools icon indicating copy to clipboard operation
RATools copied to clipboard

[feature request] offset function for pointer arithmatic on memory accessors

Open gdeOo opened this issue 6 years ago • 2 comments

In much of the code I've been writing, being able to modify the address held by a memory accessor would be a massive code quality improvement, mostly for adding offsets to a given base address.

Some use cases:

Arrays

items_array__addr = 0x1234  // can't put the accessor here
items_array__len = 10
function has_item(item_id) {
    cond = always_true()
    for i in range(0, items_array__len) {
        cond = cond && byte(items_array__addr + i) == 1  // must be here
    }
    return cond
}

If there was a function capable of adding an offset to a given memory accessor, that accessor could be placed in a much more natural place:

items_array__addr = byte(0x1234)
items_array__len = 10
function has_item(item_id) {
    cond = always_true()
    for i in range(0, items_array__len) {
        cond = cond && offset(items_array__addr, i) == 1  // offset would perfom c-like pointer arithmatic
    }
    return cond
}

Structs

inventory_item__addr = 0x1234
inventory_item__fields = {
  "id":       0x00, // byte
  "quantity": 0x01, // word
  "level":    0x03  // byte
}

// it's impossible to write this function:
function get_item_field(field_name) {
    return which_accessor?(inventory_item__addr + inventory_item__fields[field_name])
}

// ...one has to resort to per-field accessor functions. for example:
function get_item_id() { return byte(inventory_item__addr + inventory_item__fields["id"]) }

// ...or at least one accessor function per field size, forcing the caller to know that size:
function get_item_byte_field(field_name) { ... }

Given the same offset function, this could be written in a much better way:

inventory_item__addr = 0x1234
inventory_item__fields = {
  "id":       byte(0x00),
  "quantity": word(0x01),
  "level":    byte(0x03)
}

function get_item_field(field_name) {
    return offset(inventory_item__fields[field_name], inventory_item__addr)
}

Regional alts

In many cases supporting other regions is a matter of adding offsets to memory addresses, in which case the offset function would also be very useful.

gdeOo avatar Mar 10 '20 18:03 gdeOo

I see this as two separate ideas. I've already put some thought into structures and this is my latest draft:

class player_info(base_address)
{
   function hp() => word(base_address + 0x0000)
   function max_hp() => word(base_address + 0x0002)
   function class() => byte(base_address + 0x0008)
   function level() => byte(base_address + 0x0009)
   function inventory(index) => byte(base_address + 0x000A + index)
}

The class keyword defines an object that looks like a function. The parameter list serves as the constructor for the object, populating local variables within the object and would support default parameters just like function definitions.

However, the body of the class is a nested scope. Additional variables and functions can be defined within the class and become scoped through the class. Functions within the class could access the class parameter as a class-scoped variable.

To use the class, you would write something like this:

    trigger = player_info(0x1234).level() == 6

while would evaluate to:

    trigger = byte(0x1234 + 0x0009) == 6

Of course, the class instance could be abstracted behind a variable or function:

leader = player_info(0x1234)
function get_player_info(index) => player_info(0x1234 + 40 * index)

Jamiras avatar Mar 11 '20 01:03 Jamiras

The second thought is the offset method. After getting some clarification, the intent of offset is to take one accessor expression and generate a second accessor expression based on the first.

For example offset(byte(0x1234), 6) would generate byte(0x1234 + 6), whereas offset(word(0x1234), 6) would generate word(0x1234 + 6 * 2).

While I would normally just write these sort of expressions longhand, things get a little more complicated when pointers are added to the mix:

data_start = word(0x1234)
some_array = byte(data_start + 16)
element_8 = offset(some_array, 8)

without the offset function, the last line requires duplicating the some_array definition:

element_8 = byte(data_start + 16 + 8)

However, I would recommend giving it a different name. offset can be both a verb and a noun, and has a decent chance of already being used as a variable name in existing scripts. My recommendation is element_at:

element_8 = element_at(some_array, 8)

It is functionally descriptive, and unlikely to conflict.

Jamiras avatar Mar 11 '20 01:03 Jamiras