Ikarus Machine Code Implementation (ASM)
Machine code refers to a program or program segment written in machine language, which can be directly executed by a processor without further translation steps. The relevant machine language for us is that belonging to the x86 processor architecture. All machine instructions, what they do, and how they are encoded in machine language can be found in the Intel Manuals.
In practice, dealing with (abstract) machine instructions and manually translating them into (concrete) machine code is rarely necessary due to its complexity.
However, machine code can be useful for performing technical tasks that cannot be expressed in Daedalus directly. For example, the CALL package use the ASM function set as a basis.
Note
The functions in this chapter have the ASM_
prefix for Assembly (language). Assembly language is a human-readable language with one-to-one correspondences to machine language. Strictly speaking, the ASM_
prefix is misleading here, as it pertains to machine code rather than assembly language. However, conceptually, the two are closely related.
Initialization
The best way to initialize all Ikarus functions is to call MEM_InitAll()
in the Init_Global()
initialization function.
Warning
If you want to use Ikarus in Gothic 1, it is best to define your own Init_Global()
function and call it from every world initialization function.
Implementation
Opcodes
The code defines several constants that represent different machine code instructions. Each constant is assigned a hexadecimal value and corresponds to a specific machine code instruction. Here is a link to all instructions.
Internal Stack
The code includes an internal stack implementation, allowing the storage of data. The stack is already used at two points:
- When calling an engine function, the address of the current run is stored in the internal stack.
- When nesting the use of the CALL package, a push and pop operation is performed to manage the context.
The internal stack is implemented using an array, and the following functions are provided:
ASMINT_Push
ASMINT_Push
Pushes the specified data
onto the internal stack.
var int data
Data pushed onto internal stack
ASMINT_Pop
ASMINT_Pop
Pops and returns the topmost data from the internal stack.
The function returns a data popped form the internal stack.
Functions (Core)
The ASM core functionality provides a framework for assembling machine code instructions and executing them. The following functions are included:
ASMINT_Init
ASMINT_Init
Initializes the ASM system by creating an internal stack and finding function addresses.
Tip
It's worth noting that ASMINT_Ini
is also invoked by the MEM_InitAll
function.
ASM_Open
ASM_Open
Changes the size of the memory allocated at the start o the dictation
The memory in which the machine code is stored is allocated at the beginning of the dictation. If this function isn't called a default size (see Constant below) is allocated by ASM
or ASM_Here
function. The 256 bytes is often sufficient for simple applications, but if more memory is required, this function must be called at the beginning of the dictation.
var int space
Space allocated for machine code (in bytes)
Constant
ASM_StandardStreamLength
constant defines the default space available for an Assembler sequence (in bytes).
ASM
ASM
Writes machine code instructions to the stream.
Using this function it is possible to dictate machine code little by little. The data
bytes of the length
(maximum 4!) are appended to the previously dictated part. This creates a program piece by piece that can be executed by the processor.
var int data
The machine code instruction or its partvar int length
Length of thedata
(max 4 bytes)
ASM_Here
ASM_Here
Provides, the address of the cursor, i.e., the address of the location that will be described next by a call to ASM
. It is guaranteed that the location where the code is written is also the location where it will be executed.
Return value
The function returns an address that is the current position in the machine code stream.
ASM_Close
ASM_Close
Finalizes the stream by adding a return instruction and returns the starting address of the stream. This pointer can now be passed to at any time and any number of times to execute the machine code.
Warning
The memory area obtained by ASM_Close
must be released manually using MEM_Free
to avoid memory leaks. It is probably sufficient for almost all practical purposes.
The function returns a starting address of the stream (pointer to the stream).
ASM_Run
ASM_Run
Executes a machine code (stream) from a pointer.
Note
ASM_Run
can also be used to call engine functions with no parameters and no relevant return value. In this case ptr
would simply have to point to the function to be executed in the code segment.
var int ptr
Pointer to the executed code (returned formASM_Close
)
ASM_RunOnce
ASM_RunOnce
Executes the code dictated up to that point, similar to how an external function is executed. After that the code is released, and new code can be dictated.
Example
The following function sets the NPC passed as slf as the player, as if you had pressed O in Marvin mode with this NPC in focus. This is so short because there is already a function for this exact purpose, it's just not normally accessible from the scripts. It is therefore sufficient to write assembly code that pushes the parameter of the function (the this
pointer) into the appropriate register and then calls the function.
Note
Call targets are specified relative to the instruction that would have been executed after the actual call instruction. Therefore, both ASM_Here() and the subtraction of 4 in the call parameter are necessary.
The above example describes, among other things, CALL__thiscall
function form the CALL Package that can be also used to implement SetAsPlayer
.