Consider the following scene; you are the entity marked "P," there is another entity marked "E," and there is the origin named "O:"

Strip away the buildings and all the fancy graphical effects, and this boils down to:

Now knowing this, how are we to translate the entity E's position into a 2D coordinate? Well the first measure is to understand what we actually mean. There is no way to directly translate a 3D coordinate into a 2D coordinate, just as there is no way to directly translate a cube into a square; however, notice the word "directly." Let us continue with the example of a cube - there obviously has to be a way to translate a cube into a square, or at least into a 2D space, otherwise something like:

would simply not be possible. So how do we go about this? Whenever we choose to draw a 3D object on a 2D plane (such as paper), we must first pick a view-port, which is exactly what it sounds like - a static view of the scene with a fixed source. To turn a cube into a square, we simply pick the view-port to be directly facing one of the sides of the cube, as so:

To model the earlier model (the common drawing that is used to represent a cube), we simply place the view-port so that it looks directly at one of the angles:

This same idea applies to any F.P.S.'s - in fact, it is central to the notion of them. Most modern F.P.S.'s conceive the illusion that as you move, you move your character around the map; this is simply not true. When "moving," all one is doing is adjusting their view-port in relation to the world. To track entity location then, most games make use of a series of 3D vectors that give the location of an entity relative to the origin of the map.
Knowing this, let us now go back to original representation of our scene and make it more accurate:

If we eliminate the Y-axis in our problem (we will assume the map is constantly flat), we can take a top down view of the above image, and greatly simplify our task:

Given that these two entities could be at any location on the map (even though we assume they are always in Quadrant I), it makes sense to stop trying to focus the scene around the map's origin, and instead makes sense to allow our entity P to be the origin - we can do this by subtracting our origin vector and the enemy's origin vector. The result will be the absolute distance of the entity E from the entity P.
Assume vAbsDistance is a three dimensional vector. Let vAbsDistance be equal to:
vAbsDistance = vEnemy - vPlayer
In relation to operations on vectors, let:
vAbsDistance.x = vEnemy.x - vPlayer.x
vAbsDistance.y = vEnemy.y - vPlayer.y [not needed right now, but we will keep it so we can add in the Y-Axis later]
vAbsDistance.z = vEnemy.z - vPlayer.z
The vector vAsbDistance will now be a vector from the origin to an object we will call E' (E prime):

Now let us incorporate our view-port. Most games make use of either trio of angles that represent the yaw, pitch, and roll of a player's view-port relative to some origin (usually facing directly North or South, East or West, directly above or directly below) or a trio of 3D vectors that represent the same thing, but in vector format. For us, working with vectors is the easiest way for us to complete our task - luckily, these view-angles can be translated to vectors with some math.
* Call of Duty 4 uses both of these methods; they are held in refdef->ViewAngles (a vec3_t) and refdef->ViewAxis (an array of vec3_t's) respectively. *
* A vec3_t is nothing more than float[ 3 ]. *
Imposing the vectors that represent the view-port, and assuming we are looking at the origins, we receive:

By eliminating the fluff, and separating the vectors, we are left with two vector compliments:

Enter the Dot Product. If we let A and B be two vectors, than the Dot Product is nothing more than ( A.x * B.x ) + ( A.y * B.y ) + ( A.z * B.z ). However, this value has some interesting attributes:
Let A and B be two vectors, and let |A| and |B| be the length of A and B respectively, and let |A| and |B| not equal 0. Then:
A . B = |A||B|cos( theta )
Where theta is the angle represented by the image below:

In our current example, this will give us the value that will represent the distance E' is away from our view-vector. As such, we will take the Dot Product of both sets of vectors, and store them appropriately.
Assume vRightTransform and vForwardTransform are two floats. Let these equal:
vRightTransform = vAbsDistance . vRightView
vForwardTransform = vAbsDistance . vForwardView
Now we quickly need to take a diversion to account for something - the Dot Product is signed operation, meaning it will work regardless of whether or not the vector is negative. Consider the case:

In our following equation to transform our values to a 2D coordinate, we assume vForwardTransform is positive; as such, if the enemy is behind us, then our equation will give us the same value as if he was in front of us. To account for this fact, we will need to ensure that vForwardTransform is positive before continuing! Keep this in mind for later, when we code this!
We are on to the final steps - let us now consider our problem focusing on our view-port:

In a picture I have now lost the link to, it was explained that X is equal to the result of:

This of course can be reduced to:
X = C + [ (C * X') / (Z'* FOV) ]
We, of course, have X' and Z' as result of our operations with the Dot Product - in addition, C and FOV are held in refdef->Width / 2 and refdef->FOVx respectively. Letting centerX equal refdef->Width / 2, this becomes:
X = centerX + ( ( centerX * vRightTransform ) / ( vForwardTransform * refdef->FOVx ) )
As such, we have concluded our series of steps to get our 2D coordinate (in the X-Axis, at least):
vAbsDistance = vEnemy - vPlayer vRightTransform = vAbsDistance . vRightView vForwardTransform = vAbsDistance . vForwardView if( vForwardTransform < 0 ) return Let centerX = refdef->Width / 2 X = centerX + ( ( centerX * vRightTransform ) / ( vForwardTransform * refdef->FOVx ) )
There is a quick issue to address before we start coding this - Call of Duty 4 holds our right view vector opposite of how we want it (which causes problems similar to the vForwardTransform being negative). To fix this, we simply need to multiply the three elements of the right view vector before using them. As such, our code becomes:
vAbsDistance = vEnemy - vPlayer vRightView *= -1 vRightTransform = vAbsDistance . vRightView vForwardTransform = vAbsDistance . vForwardView if( vForwardTransform < 0 ) return Let centerX = refdef->Width / 2 X = centerX + ( ( centerX * vRightTransform ) / ( vForwardTransform * refdef->FOVx ) )
* Brief break to jam out. *
Explaining how to hook, how to find a place to hook, and how to find the Draw_Text would be reduntant and make this already long article longer. You can find out how to do these by reading my tutorial "Drawing Text Using Engine Functions" located here: http://www.doxcoding....php?f=27&t=256
Likewise, explaining how to find the structures and their members is also pointless, as I explained such concepts in my tutorial "Understanding What Structures Mean in Relation to FPS Games" located here: http://www.doxcoding....php?f=27&t=386
* Please note a few things - in regards to the Draw_Text tutorial, in this code, I have hooked R_EndFrame located at 42c010h instead of the location I was hooking before. Also, I am using the game's Draw_Text_To_Frame_Scene located at 5f6b00h as opposed to the Draw_Ingame_Menu_Text I used in the tutorial. In regards to the structures, please note that even though I said the size of cg_entities is 77000h, this is simply the game clearing a giant section of memory to hold the cg_entities array. The true size of cg_entities is 1dch. *
With the above considerations, the following base code exists:
.386 .model flat, stdcall option casemap: none ;include needed functions VirtualAlloc proto stdcall :DWORD, :DWORD, :DWORD, :DWORD VirtualProtect proto stdcall :DWORD, :DWORD, :DWORD, :DWORD VirtualFree proto stdcall :DWORD, :DWORD, :DWORD includelib \masm32\lib\kernel32.lib .data ori_print_text dd 5f6b00h x real4 0.0f y real4 300.0f negate real4 -1.0f .code _main: push ebp mov ebp, esp mov eax, dword ptr ss:[ ebp + 0ch] cmp eax, 1 jnz @DLL_NOT_ATTACH push eax push 40h push 1000h push 4 push 0 call VirtualAlloc mov ebx, eax push ebx push 40h push 5 push 42c011h call VirtualProtect mov byte ptr ds:[ 42c011h ], 0e8h lea ecx, @R_EndFrame_Hook sub ecx, 42c016h mov dword ptr ds:[ 42c012h ], ecx push ebx push dword ptr ds:[ ebx ] push 5 push 42c011h call VirtualProtect push 4000h push 4 push ebx call VirtualFree pop eax @DLL_NOT_ATTACH: leave retn 0ch ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;call CoD4's native Draw_Text function ;eax = x ;push y on stack ;edx = text ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @draw_text: push 0 push 0 sub esp, 10h fld1 fst dword ptr ss:[ esp + 0ch ] fstp dword ptr ss:[ esp + 8 ] fld dword ptr ds:[ ebx ] fstp dword ptr ss:[ esp + 4 ] fld dword ptr ds:[ eax ] fstp dword ptr ss:[ esp ] mov ecx, dword ptr ds:[ 84cd8ch ] push ecx push 7fffffffh push edx mov ecx, 6b4498h call dword ptr cs:[ ori_print_text ] add esp, 24h retn ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;convert 3d origin to 2d coordinates ;edi = origin ;will return in variable x ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @WorldToScreen: retn ;the main display hook @R_EndFrame_Hook: pushad popad mov eax, dword ptr ds:[ 8c63b8h ] retn end _main
We will begin our job at the @WorldToScreen function; we will assume we are getting the enemy's origins via edi. Now, doing anything related to floats in assembly is well... a complete fucking mess, if you will excuse my French. For an example, take the following lines of C code:
float x = 1.0f; x = x + 1;
The corresponding assembly code:
fld dword ptr ds:[ DEADBEEFh ] ;location holding x fld1 ;push 1 on the stack fadd ;pop st(1) and st(1), push their sum fstp dword ptr ds:[ DEADBEEFh ] ;pop the top value of the stack into x again
Writing this code, I decided enough people already had fears about assembly, so instead of trying to optimise, I decided to write the code such that it took every piece one section at a time, and did not utilise operations such as FXCH to avoid the memory hit every FSTP brings.
Now, consider our outline:
vAbsDistance = vEnemy - vPlayer vRightView *= -1 vRightTransform = vAbsDistance . vRightView vForwardTransform = vAbsDistance . vForwardView if( vForwardTransform < 0 ) return Let centerX = refdef->Width / 2 X = centerX + ( ( centerX * vRightTransform ) / ( vForwardTransform * refdef->FOVx ) )
vPlayer is held in refdef->ViewOrg, which starts at 797618h. Taking the first section:
;vAbsDistance = vEnemy - vPlayer fld dword ptr ds:[ edi ] ;push origin[ 0 ] fsub dword ptr ds:[ 797618h ] ;subtract refdef->ViewOrg[ 0 ] from it fstp dword ptr ds:[ esp ] ;store the result in esp fld dword ptr ds:[ edi + 4 ] ;push origin[ 1 ] fsub dword ptr ds:[ 79761ch ] ;subtract refdef->ViewOrg[ 1 ] from it fstp dword ptr ds:[ esp + 4 ] ;store the result in esp + 4 fld dword ptr ds:[ edi + 8 ] ;push origin[ 2 ] fsub dword ptr ds:[ 797620h ] ;subtract refdef->ViewOrg[ 2 ] from it fstp dword ptr ds:[ esp + 8 ] ;store the result in esp + 8
Next, since we need to reverse vRightView, we need to store it in a temporary location. We can accomplish both these in one step:
;vRightView = refdef->Viewaxis[ 1 ] ;vRightView[ 0 ] *= -1 ;vRightView[ 1 ] *= -1 ;vRightView[ 2 ] *= -1 fld dword ptr ds:[ 797630h ] ;push refdef->ViewAxis[ 1 ][ 0 ] fmul dword ptr ds:[ negate ] ;multiply by -1 fstp dword ptr ss:[ esp + 0ch ] ;store in esp + ch fld dword ptr ds:[ 797634h ] ;push refdef->ViewAxis[ 1 ][ 1 ] fmul dword ptr ds:[ negate ] ;multiply by -1 fstp dword ptr ss:[ esp + 10h ] ;store in esp + 10h fld dword ptr ds:[ 797638h ] ;push refdef->ViewAxis[ 1 ][ 2 ] fmul dword ptr ds:[ negate ] ;multiply by -1 fstp dword ptr ss:[ esp + 14h ] ;store in esp + 14h
Hopefully you are getting the hang of this. For the next couple steps, we will be using esp + 18h, esp + 1ch, and esp + 20h for temporary storage. Recalling what we know about the Dot Product, and recalling from our earlier example about how FADD works, this next step should not be too hard:
;vRightTransform = vAbsDistance . vRightView fld dword ptr ds:[ esp ] ;push vAbsDistance[ 0 ] fmul dword ptr ss:[ esp + 0ch ] ;multiply by vRightView[ 0 ] fstp dword ptr ss:[ esp + 18h ] ;store in esp + 18h fld dword ptr ds:[ esp + 4 ] ;push vAbsDistance[ 1 ] fmul dword ptr ss:[ esp + 10h ] ;mutliply by vRightView fstp dword ptr ss:[ esp + 1ch ] ;store in esp + 1ch fld dword ptr ds:[ esp + 8 ] ;push vAbsDistance[ 2 ] fmul dword ptr ss:[ esp + 14h ] ;multiply by vRightView[ 2 ] fstp dword ptr ss:[ esp + 20h ] ;store in esp + 20h fld dword ptr ss:[ esp + 18h ] ;push vAbsDistance[ 0 ] * vRightView[ 0 ] fld dword ptr ss:[ esp + 1ch ] ;push vAbsDistance[ 1 ] * vRightView[ 1 ] fadd ;pop st(1), st(0), put their sum in st(0) fld dword ptr ss:[ esp + 20h ] ;push vAbsDistance[ 2 ] * vRightView[ 2 ] fadd ;pop st(1), st(0), put their sum in st(0) fstp dword ptr ss: [ esp + 24h ] ;store in esp + 24h
A similar approach for the next Dot Product:
;vForwardTransform = vAbsDistance . vForwardView ;vForwardView = refdef->ViewAxis[ 0 ] fld dword ptr ds:[ esp ] fmul dword ptr ds:[ 797624h ] fstp dword ptr ss:[ esp + 18h ] fld dword ptr ds:[ esp + 4 ] fmul dword ptr ds:[ 797628h ] fstp dword ptr ss:[ esp + 1ch ] fld dword ptr ds:[ esp + 8 ] fmul dword ptr ds:[ 79762ch ] fstp dword ptr ss:[ esp + 20h ] fld dword ptr ss:[ esp + 18h ] fld dword ptr ss:[ esp + 1ch ] fadd fld dword ptr ss:[ esp + 20h ] fadd fstp dword ptr ss:[ esp + 28h ]
Next, we need check if vForwardTransform is less than zero; for this, we will need to use FCOMP:
;if( vForwardTransform < 0 ) ;return fldz ;push 0 fcomp dword ptr ss:[ esp + 28h ] ;compare this with vForwardTransform fstsw ax ;throw the results in ax so we can use conditionals test ah, ah ;compare ah to 0 jnz @continue ;if it is not, continue add esp, 2ch ;will explain later retn ;else return @continue:
Time to grab the center; to reduce the amount of work on myself, I used a small trick to allow me to easily divide refdef->Width by two. Just accept this as it is, or if you desire, go look up why this works
;Let centerX = refdef->Width / 2 mov eax, dword ptr ds:[ 797608h ] ;grab refdef->Width, put in eax cdq ;extend eax to 64bits, place in edx:eax sub eax, edx ;subtract edx from eax sar eax, 1 ;shift eax arithmetically right by one mov dword ptr ss:[ esp + 20h ], eax ;place the result in esp + 20h
On to our last equation:
;X = centerX + ( ( centerX * vRightTransform ) / ( vForwardTransform * refdef->FOVx ) ) fild dword ptr ss:[ esp + 20h ] ;convert refdef->Width / 2 to a float, push fmul dword ptr ss:[ esp + 24h ] ;multiplay this by vRightTransform fstp dword ptr ss:[ esp + 18h ] ;store in esp + 18h fld dword ptr ss:[ esp + 28h] ;push vForwardTransform fmul dword ptr ds:[ 797610h ] ;multiply by refdef->FOVx fstp dword ptr ss:[ esp + 1ch ] ;store in esp + 1ch fld dword ptr ss:[ esp + 18h ] ;push centerX * vRightTransform fld dword ptr ss:[ esp + 1ch ] ;push vForwardTransform * refdef->FOVx fdiv ;divide the two fild dword ptr ss:[ esp + 20h ] ;convert refdef->Width / 2 to a float, push fadd ;add refdef->Width / 2 to the result of the division fstp dword ptr ds:[ x ] ;store this in x
We are close to done - however, we cannot freely just start throwing data in esp without the risk of corrupting the stack. To use these sections, we need to first allocate them. At the top of our @WorldToScreen function:
sub esp, 2ch ;grab what we need + 4 to account for our call
Likewise, at the bottom:
add esp, 2ch retn
A little bit of work there, but nothing mind-boggling.
On to the R_EndFrame hook; what we are doing can be summed up by this C code:
for( int i = 0; i < cgs->maxPlayers; i++ )
{
if( cg_entities[ i ].isAlive && cg_entities[ i ].clientNum != cg->clientNum )
{
WorldToScreen( cg_entities[ i ].lerpOrigin, &x );
Draw_Text( x, y, clientinfo[ i ].name );
}
}
If we could do the @WorldToScreen function, this should be easy:
@R_EndFrame_Hook: pushad ;save the registers mov edi, 84f2f4h ;move cg_entities[ 0 ].lerpOrigins in edi mov edx, 83927ch ;mov clientinfo[ 0 ].name into edx xor esi, esi ;set esi to 0 entities_loop: ;loop cmp dword ptr ds:[ edi + 1a4h ], 0 ;is cg_entities[ i ] alive? jz @not_valid_entity ;jump if not mov eax, dword ptr ds:[ edi + 0b0h ] ;place cg_entities[ i ].clientNum in eax cmp eax, dword ptr ds:[ 74e338h ] ;is cg_entities[ i ] our player? jz @not_valid_entity ;jump if it is push edx ;save edx call @WorldToScreen ;call our WorldToScreen function pop edx ;restore edx lea eax, x ;place x in eax lea ebx, y ;place a static 300 in ebx push edx ;save edx call @draw_text ;call our printing routine pop edx ;restore edx @not_valid_entity: add edx, 4cch ;next element of clientinfo add edi, 1dch ;next element of cg_entities inc esi ;increase esi cmp esi, dword ptr ds:[ 74aa48h ] ;check if esi < cgs->maxPlayers jl entities_loop ;loop if it is popad ;restore registers mov eax, dword ptr ds:[ 8c63b8h ] ;restore original function we hooked retn ;return
Our final code for comparison:
.386 .model flat, stdcall option casemap: none ;include needed functions VirtualAlloc proto stdcall :DWORD, :DWORD, :DWORD, :DWORD VirtualProtect proto stdcall :DWORD, :DWORD, :DWORD, :DWORD VirtualFree proto stdcall :DWORD, :DWORD, :DWORD includelib \masm32\lib\kernel32.lib .data ori_print_text dd 5f6b00h x real4 0.0f y real4 300.0f negate real4 -1.0f .code _main: push ebp mov ebp, esp mov eax, dword ptr ss:[ ebp + 0ch] cmp eax, 1 jnz @DLL_NOT_ATTACH push eax push 40h push 1000h push 4 push 0 call VirtualAlloc mov ebx, eax push ebx push 40h push 5 push 42c011h call VirtualProtect mov byte ptr ds:[ 42c011h ], 0e8h lea ecx, @R_EndFrame_Hook sub ecx, 42c016h mov dword ptr ds:[ 42c012h ], ecx push ebx push dword ptr ds:[ ebx ] push 5 push 42c011h call VirtualProtect push 4000h push 4 push ebx call VirtualFree pop eax @DLL_NOT_ATTACH: leave retn 0ch ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;call CoD4's native Draw_Text function ;eax = x ;push y on stack ;edx = text ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @draw_text: push 0 push 0 sub esp, 10h fld1 fst dword ptr ss:[ esp + 0ch ] fstp dword ptr ss:[ esp + 8 ] fld dword ptr ds:[ ebx ] fstp dword ptr ss:[ esp + 4 ] fld dword ptr ds:[ eax ] fstp dword ptr ss:[ esp ] mov ecx, dword ptr ds:[ 84cd8ch ] push ecx push 7fffffffh push edx mov ecx, 6b4498h call dword ptr cs:[ ori_print_text ] add esp, 24h retn ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;convert 3d origin to 2d coordinates ;edi = origin ;will return in variable x ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @WorldToScreen: sub esp, 2ch ;vAbsDistance = vEnemy - vPlayer fld dword ptr ds:[ edi ] fsub dword ptr ds:[ 797618h ] fstp dword ptr ds:[ esp ] fld dword ptr ds:[ edi + 4 ] fsub dword ptr ds:[ 79761ch ] fstp dword ptr ds:[ esp + 4 ] fld dword ptr ds:[ edi + 8 ] fsub dword ptr ds:[ 797620h ] fstp dword ptr ds:[ esp + 8 ] ;vRightView = refdef->Viewaxis[ 1 ] ;vRightView[ 0 ] *= -1 ;vRightView[ 1 ] *= -1 ;vRightView[ 2 ] *= -1 fld dword ptr ds:[ 797630h ] fmul dword ptr ds:[ negate ] fstp dword ptr ss:[ esp + 0ch ] fld dword ptr ds:[ 797634h ] fmul dword ptr ds:[ negate ] fstp dword ptr ss:[ esp + 10h ] fld dword ptr ds:[ 797638h ] fmul dword ptr ds:[ negate ] fstp dword ptr ss:[ esp + 14h ] ;vRightTransform = vAbsDistance . vRightView fld dword ptr ds:[ esp ] fmul dword ptr ss:[ esp + 0ch ] fstp dword ptr ss:[ esp + 18h ] fld dword ptr ds:[ esp + 4 ] fmul dword ptr ss:[ esp + 10h ] fstp dword ptr ss:[ esp + 1ch ] fld dword ptr ds:[ esp + 8 ] fmul dword ptr ss:[ esp + 14h ] fstp dword ptr ss:[ esp + 20h ] fld dword ptr ss:[ esp + 18h ] fld dword ptr ss:[ esp + 1ch ] fadd fld dword ptr ss:[ esp + 20h ] fadd fstp dword ptr ss: [ esp + 24h ] ;vForwardTransform = vAbsDistance . vForwardView ;vForwardView = refdef->ViewAxis[ 0 ] fld dword ptr ds:[ esp ] fmul dword ptr ds:[ 797624h ] fstp dword ptr ss:[ esp + 18h ] fld dword ptr ds:[ esp + 4 ] fmul dword ptr ds:[ 797628h ] fstp dword ptr ss:[ esp + 1ch ] fld dword ptr ds:[ esp + 8 ] fmul dword ptr ds:[ 79762ch ] fstp dword ptr ss:[ esp + 20h ] fld dword ptr ss:[ esp + 18h ] fld dword ptr ss:[ esp + 1ch ] fadd fld dword ptr ss:[ esp + 20h ] fadd fstp dword ptr ss:[ esp + 28h ] ;if( vForwardTransform < 0 ) ;return fldz fcomp dword ptr ss:[ esp + 28h ] fstsw ax test ah, ah jnz @continue add esp, 2ch retn @continue: ;Let centerX = refdef->Width / 2 mov eax, dword ptr ds:[ 797608h ] cdq sub eax, edx sar eax, 1 mov dword ptr ss:[ esp + 20h ], eax ;X = centerX + ( ( centerX * vRightTransform ) / ( vForwardTransform * refdef->FOVx ) ) fild dword ptr ss:[ esp + 20h ] fmul dword ptr ss:[ esp + 24h ] fstp dword ptr ss:[ esp + 18h ] fld dword ptr ss:[ esp + 28h] fmul dword ptr ds:[ 797610h ] fstp dword ptr ss:[ esp + 1ch ] fld dword ptr ss:[ esp + 18h ] fld dword ptr ss:[ esp + 1ch ] fdiv fild dword ptr ss:[ esp + 20h ] fadd fstp dword ptr ds:[ x ] add esp, 2ch retn ;the main display hook @R_EndFrame_Hook: pushad mov edi, 84f2f4h mov edx, 83927ch xor esi, esi entities_loop: cmp dword ptr ds:[ edi + 1a4h ], 0 jz @not_valid_entity mov eax, dword ptr ds:[ edi + 0b0h ] cmp eax, dword ptr ds:[ 74e338h ] jz @not_valid_entity push edx call @WorldToScreen pop edx lea eax, x lea ebx, y push edx call @draw_text pop edx @not_valid_entity: add edx, 4cch add edi, 1dch inc esi cmp esi, dword ptr ds:[ 74aa48h ] jl entities_loop popad mov eax, dword ptr ds:[ 8c63b8h ] retn end _main
The result:

Hopefully if you have gotten this far, you learned the method well enough to apply it to the Y-Axis, and to write it in your language of choice! I do apologise if anything I explained was unclear.
I would like to suffix this article with this:

My Dad found this while clearing out my Grandfather's house - it is a English to German translation book given to all troops occupying Germany after the end of the Second World War. My grandfather got this book after being deployed to Germany after the end of the Korean War (1953). I just thought it was a little cool thing, and I can now say "stay" in German, as in "I should Aufenthalt with English."
Until next time,
attilathedud
Shout-outs to atom0s because he loves when I spam up search results of him with links to my tutorials, which cause him to rage when looking for old work of his. <3
Pictures are either made by me in Paint, or taken from Wikipedia.











