-
Notifications
You must be signed in to change notification settings - Fork 10
AddAddress Flag
AddAddress
modifies the memory address(es) of the following condition by the calculated value. It works similar to AddSource
, but modifies the address, not the resulting value. Additionally, it affects addresses on both sides of the condition.
AddAddress
only affects the next condition. If multiple AddAddress
lines are chained together, each lookup in the chain will be altered; the values won't all be added to the final lookup.
The 32-bit value at 0x0112F8 is a pointer to the data for the first character in the party. When the party order changes, the pointer will point at a different block of memory, but data within the block will have the same structure.
- The value is an address in the actual system representation and differs from the addresses provided by the memory inspector. However, you can rely on the fact that the memory inspector represents the same memory, so while the actual address may differ, the sequence of bytes does not.
To translate the real pointer to a memory inspector address, we compare the two values. For example:
- The 32-bit value at 0x0112F8 is 0x800110B8.
- The memory we're interested in is at 0x011114 in the memory inspector.
- Using a 24-bit read instead of a 32-bit read gives us 0x0110B8
- 0x011114 - 0x0110B8 = 0x00005C, so that becomes the "base address" in the second condition.
AddAddress
is how RAIntegration handles pointers. There are four major types of pointer support:
Direct Pointer: The pointer contains another address that is referenced without modification. These are most commonly used for string pointers. To use a direct pointer, the base address to which the pointer is added would be 0.
Indirect Pointer: The pointer contains another address that indicates the start of some block of data. The data of interest is a fixed number of bytes into the block of data. These are commonly used for attributes of a character/object.
For both Direct and Indirect Pointers, the AddAddress
line should reference the pointer, and the address in the following line should be the offset into the block of data for the information relevant to the condition. While you would typically use 0 for a Direct Pointers, the following line would also contain any value necessary to convert between a real address and a memory inspector address.
The example above is an Indirect Pointer, where the data is 0x5C bytes into a block of data. The conversion from a real address to a memory inspector address is handled by using a 24-bit read instead of a 32-bit read.
Array Index: The pointer contains an offset to apply to a fixed pointer.
Scaled Array Index: The pointer contains an offset to apply to a fixed pointer after it has been scaled.
For Array Indices, the AddAddress
line should reference the offset to apply, and the following line should contain the address of the first element of the array (index 0). For example:
AddAddress 16-bit Mem 0x1234
8-bit Mem 0x4567
Would read a 16-bit number from 0x1234 and read the single byte that is that many bytes after 0x4567.
To scale an array index, click on the space in the Cmp column for the array index address and select the *
symbol. Then enter the size of each array item in the second mem/val column. For example:
AddAddress 16-bit Mem 0x1234 * Value 4
8-bit Mem 0x4567
Would read a 16-bit number from 0x1234, multiply it by 4, and read the single byte that is that many bytes after 0x4567.
AddAddress
can be chained to perform multi-step lookups, but only one step at a time. As such, you can reference data pointed at by a pointer that's pointed at by another pointer, but you can't reference data in an array that's pointed at by another pointer unless either the array or the array index is at a fixed address.
To do a delta check on an AddAddress
chain, you should only put the delta on the final condition. You want to use the current value for each step of the chain to derive the final result, and you only care about the changes in the final value. Using previous values for the pointers will have you reading invalid memory and will likely cause problems.
A) AddAddress Mem 0000
B) AddAddress Mem 0000
C) Delta 0000
Assuming Mem: $0000=0, $0001=2, and $0002=4, this will evaluate to 0 for the first few frames, and when Mem 0 changes to 1, the following happens:
Frame N : A = Mem[0] = 1 (delta[0] = 0), B = Mem[A] = Mem[1] = 2 (delta B = 0), C = Mem[B] = Mem[2] = 4 (delta C = 0)
Frame N+1: A = Mem[0] = 1 (delta[0] = 1), B = Mem[A] = Mem[1] = 2 (delta B = 2), C = Mem[B] = Mem[2] = 4 (delta C = 4)
You can see the entire delta chain updates in the same frame (one frame after the change occurs).
If you use Delta for A and B, something different happens:
A) AddAddress Delta 0000
B) AddAddress Delta 0000
C) Delta 0000
Frame N : Mem[0] = 1 (delta[0] = 0), B = Mem[deltaA] = Mem[0] = 1 (delta B = 0), C = Mem[deltaB] = Mem[0] = 1 (delta C = 0)
Frame N+1: Mem[0] = 1 (delta[0] = 1), B = Mem[deltaA] = Mem[1] = 2 (delta B = 1), C = Mem[deltaB] = Mem[1] = 2 (delta C = 1)
Frame N+2: Mem[0] = 1 (delta[0] = 1), B = Mem[deltaA] = Mem[1] = 2 (delta B = 2), C = Mem[deltaB] = Mem[2] = 4 (delta C = 2)
Frame N+3: Mem[0] = 1 (delta[0] = 1), B = Mem[deltaA] = Mem[1] = 2 (delta B = 2), C = Mem[deltaB] = Mem[2] = 4 (delta C = 4)
Not only does it take two extra frames for delta C to be correct, it has two intermediate values that are not correct. The value went from 0 to 4 in a single frame and never was 1 or 2.
Once you think that you've found your data and something that points at (or near) your data, you need to calculate the offset to use to read that data.
- 32-bit data at
0x821448
(RetroAchievements address) - Pointer at
0xA1CA14
(RetroAchievements address) (value is0x8C821440
)
First, determine how much of the pointer you need. For a system with 16MB of RAM, you need 24-bits to address it all (2^24 = 16777216). For 32MB, you'd need 25-bits. For 8MB, you'd only need 23-bits.
For the data provided above, we have 16MB of memory, so we only need 24-bits of the pointer (0x8C821440
& 0x00FFFFFF
= 0x821440
)
Then, subtract that value from the data address to calculate the offset: 0x821448
- 0x821440
= 0x000008
Now, you can construct the logic. Place the pointer address and size/mask in the AddAddress
condition and the offset/logic in the second line:
AddAddress Mem 24-bit 0x00A1CA14
Mem 32-bit 0x00000008 < 1000
Executing this example with the provided data:
- Read 24-bit data at
0x00A1CA14
:0x00821440
- Add the offset:
0x00821440
+0x00000008
=0x00821448
- Read 32-bit data at new address (
0x00821448
)
- 16-bit data at
0x1B385E0
(RetroAchievements address) - Pointer at
0xE7B054
(RetroAchievements address) (value is0x09B42F0C
) - System has 32MB of RAM (25 bits needed)
0x09B42F0C
&0x01FFFFFF
=0x1B42F0C
- Calculate offset:
0x1B385E0
-0x1B42F0C
=0xFFFFFFFFFFFF56D4
(truncate to 32-bits:0xFFFF56D4
) - Construct the logic:
AddAddress Mem 32-bit 0x00E7B054 & 0x1FFFFFF Mem 16-bit 0xFFFF56D4 = 0x0E
In this case, the pointer is actually pointing backwards, so the offset is a really big number. That's how computers represent negative values. Due to the rules of twos-compliment and overflow, you can add the really big number to perform subtraction. Executing this example with the provided data:
- Read 32-bit data at
0x00E7B054
:0x09B42F0C
- Mask off 25-bits:
0x09B42F0C
&0x01FFFFFF
=0x01B42F0C
- Add the offset:
0x1B42F0C
+0xFFFF56D4
=0x101B385E0
(truncated to 32-bits:0x01B385E0
) - Read 16-bit data at new address (
0x01B385E0
)
The experienced achievement creator wilhitewarrior made a nice video showing how to work with pointers and AddAddress flag.
You can check it here: https://www.youtube.com/watch?v=_gk0vYYlm-E.
- User Guidelines
- Developer Guidelines
- Content Guidelines
- FAQ
- Setup Guide
- Emulator Support and Issues
- Ways to Contribute
- RABot, the RA Discord Robot
- Events
- Overlay Themes
- Useful Links
- Contributing with the docs
- About Us
- Tutorials
- Developer Docs
- How to Become an Achievement Developer
- Getting Started as an Achievement Developer
- Game Identification
- Achievement Design
- Achievement Scoring
- Difficulty Scale and Balance
- Progression and Win Condition Typing
- Badge and Icon Creation
- Achievement Development Overview
- Flags
- BitCount Size
- Alt Groups
- Hit Counts
- Delta Values
- Prior Values
- Value Definition
- Condition Syntax
- Minimum Required Versions for Logic Features
- Memory Inspector
- Real Examples
- Set Development Roadmap
- Achievement Templates
- Tips and Tricks
- Leaderboards
- Rich Presence
- RATools
- Console Specific Tips
- Emulator Hotkeys for Developers
- libretro core support
- Docs To Do List
- WIP User Code of Conduct
- WIP CoC FAQ
- WIP Content Guidelines
- WIP-Jr
- WIP---Dev-Tips---Code-Notes-En-Masse
- WIP-‐-Reauthorship-Policy
- Manifesto RetroAchievements
- Código de Conduta do Usuário
- FAQ - Perguntas Frequentes
- Como contribuir se você não é um desenvolvedor
- Tutorial para Jogos Multi-Discos
- Introdução
- Primeiros Passos como um Desenvolvedor de Conquistas
- Recursos de Lógica para Achievements
- Exemplos Reais
- Dicas e Truques
- Dicas Específicas de Console
- Modelos de Achievement
- Escala de Dificuldade e Equilíbrio
- Roteiro de Desenvolvimento de um Set de Conquistas
- Criação de Ícones e Emblemas
- Leaderboards
- Rich Presence
- Design de Conquistas
- Manifesto RetroAchievements
- Código de Conducta del Usuario
- FAQ - Preguntas Frecuentes
- Tablas Globales y Reglas para la Casería de Logros
- Mi juego no esta cargando los logros
- Como contribuir si no eres un desarrollador
- Por que no deberías utilizar la función de cargar estado
- Contribuyendo con los documentos
- Como funciona la Documentación de RA
- Descargas
- Intro
- Código de Conducta del Desarrollador
- Como convertirme en un Desarrollador de Logros
- Primeros pasos como un Desarrollador de Logros
- Un vistazo al Inspector de Memoria
- Características en la Logica de un Logro
- Ejemplos Reales
- Intro
- Utilizando Hit Counts como un Temporizador
- Utilizando Valores Delta y Hit Counts para Detectar un Incremento
- Un Ejemplo Simple en como evitar el Abuso de Estados de Guardado
- Evitar el Problema de que un Contador se Incremente Dos Veces en el Mismo Frame
- Creando un Temporizador con un ResetIf Hits basándote en la Velocidad de un Juego
- Plantillas para Logros
- Tips y Trucos
- Escala de Dificultad y Balance
- Diseño de Logros
- Mapa de Desarrollo de Set
- Revisiones en Set de Logros
- Creación de Iconos y Badges
- Tablas de Clasificación
- Rich Presence
- Trabajando con el ROM apropiado
- Identificación del Juego
- Guía para Sets Bonus
- Logros para ROM hacks
- Tips Específicos por Consola