Concluindo esta parte você vai:
Entrar na "deep web" do jogo. Nas profundezas onde você pode conseguir ler e escrever vários valores antes inacessíveis.
Você irá aprender a manipular valores nas classes do jogo, "caminhando" pelas classes etc para encontrar as informações que você deseja em seu mod.
Esta é mais uma parte onde você vai entrar (novamente) em um novo mundo; aprender a, realmente, criar mods como bem entender. Ultrapassar mais limites.
Eu já falei isso anteriormente na parte sobre
Condições, e realmente, eu não estava brincando, né?
Se você já tem conhecimento sobre alguma programação orientada à objetos, parabéns! Você já sabe tudo e pode ir pra próxima parte- mentira, o que vamos fazer aqui é muito diferente do que você deve saber. E provavelmente você odiará :)
Se você é novo no assunto, vai achar muito interessante!
Quando eu aprendi (iniciando diretamente em CLEO, sem conhecer OOP) eu tive um pouco de dificuldade no início, e isso é bom, pois agora eu acredito que sei como evitar que você também tenha dificuldade..
Introdução
O GTA III, VC, SA e vários outros jogos foram programados em C++ ou alguma outra programação orientada à objetos (C#, Java etc etc...). É um tipo de programação extremamente útil para jogos, pois nela você pode criar várias "
class", onde elas podem organizar uma coleção de informações sobre alguma coisa, como algum veículo, um pedestre, ou até mesmo a câmera do jogo e coisas que você nunca vai saber o que significa.
Imagine: Você tem uma class com nome "Carro", onde, é claro, ela está interligada com algum carro específico dentro do jogo.
Dentro desta class você tem informações como: Qual é a massa; Velocidade máxima; Quantidade de marchas (for english: "Marchas" = "Gears") e Ocupantes:
Que tipo de data type cada "membro" usaria?
Você consegue imaginar?
Bem, vamos colocar:
Código: Selecionar tudo
Carro {
FLOAT Massa
FLOAT VelocidadeMáxima
BYTE Marchas
BYTE Ocupantes
}
Faz sentido, certo?
1 byte vai até
255
(
-128
até
127
caso
signed, mas não vem ao caso), só precisamos de 1 byte para a quantidade de marchas e ocupantes, mas a massa e velocidade precisam de números mais definidos: floats.
Atenção: Eu só estou mostrando pseudocodes. Estes códigos não existem, são somente didáticos!
Agora, tente imaginar estes valores preenchidos:
Código: Selecionar tudo
Carro {
FLOAT Massa = 1500.0
FLOAT VelocidadeMáxima = 200.0
BYTE Marchas = 5
BYTE Ocupantes = 3
}
Certo, nós temos um carro com estes valores.
Estas informações são usadas durante o processamento do carro no jogo!
Ou seja, quando o jogo precisar saber quantos ocupantes este carro tem, ele pega o valor em
Carro -> Ocupantes
.
Quando um novo ocupante entrar no carro, o jogo aumentará este valor (por exemplo,
Ocupantes
ficará
4
)
Mas como
exatamente é isso?
Dentro da memória, toda esta "estrutura" (struct) está, é claro, em algum lugar!
É aqui que iremos revisar o que aprendemos sobre
Thread Memory.
Vamos supor que o endereço onde está localizado esta estrutura (mais especificamente o início dela), é
1000
. (vamos esquecer dos
0xD34DC0DE
e simplificar a conta com um
1000
)
No início dela, temos:
Que guarda um valor
float
, onde nosso float tem 4 bytes, lembra?
Portanto, se nós lermos 4 bytes de
1000 + 0
(ou seja,
1000
, dã!) nós teremos o valor da massa deste carro (que é
1500.0
).
Certo, se nos primeiros 4 bytes nós temos um valor, onde que está o segundo valor? +4!
Se você tem um valor de 4 bytes, basta você subir 4 bytes para "pular" este valor e assim encontrar o próximo. Você já sabe disso.
Enfim, se você ler
1000 + 4
(ou seja, o endereço
1004
) você terá outro valor de 4 bytes, agora, a velocidade máxima.
Continuando, em
1000 + 8
você tem a quantidade de marchas! Leia 1 byte para ter o valor de marchas do carro, neste caso em específico, você terá o valor
5
.
1000 + 9
tem você a quantidade de ocupantes. Lembre-se, a quantidade de marchas é só 1 byte, portanto, o próximo número foi só
+1
.
Aumentando mais um byte, portanto,
1000 + 10
, nós chegamos no fim. Se você ler
1000 + 10
você encontrará coisas estranhas, pois a nossa class já acabou.
Perceba que esta class tem o total de 10 bytes.
O mundo real
Agora vamos ver um exemplo mais "verdadeiro" do que acontece no modding de GTA (e uns outros jogos):
Código: Selecionar tudo
00000000 RwRGBA struc ; (sizeof=0x4)
00000000 red db ?
00000001 green db ?
00000002 blue db ?
00000003 alpha db ?
00000004 RwRGBA ends
Certo, temos a class
RwRGBA
(
Rw
= RenderWare = a engine gráfica do jogo, o nome é basicamente
RGBA
)
Com certeza você já sabe: RGBA = Red, Green, Blue, Alpha. Simplesmente, uma cor, e com transparência.
O primeiro byte, é o vermelho, depois verde... E assim sucessivamente.
A struct tem 4 bytes no total.
Simplesmente, se o primeiro byte ser o valor hexadecimal
FF
, o segundo ser
00
, terceiro
00
e quarto
FF
, teríamos a cor vermelha total e opaca. Mesmo de
FF0000FF
.
Tenha a nota de que essa struct foi retirada do IDA Pro por engenharia reversa, as classes não são originalmente definidas desta maneira (mesmo assim, é semelhante). Mas este formato de leitura nos ajuda muito no nosso trabalho do "modding-hacking".
Imagine que você tenha o ponteiro dela numa variável com nome
pRGBA
e você quer ter somente o valor azul dela.
Você faria assim:
Ou, mais convenientemente, assim:
Simples. Você subiu
2
bytes e leu
1
byte deste local, agora, você tem o valor do azul dentro da variável
iBlue
Perceba que eu uso p
para "pointer" (ponteiro) e i
para "integer" (número inteiro) nas variáveis. Está aí uma dica, caso você gostar!
Perceba que você facilmente consegue ler a struct desta class:
Inclusive a própria struct diz qual é o offset de cada field.
Em
+2
você tem um byte (o
b
do
db
é "byte") que aponta para a field
blue
(o valor do azul).
É muito importante notar que esse
2
não é decimal, e sim hexadecimal!!! Ou seja, é um
0x2
.
Portanto, o mais correto seria assim:
Código: Selecionar tudo
pBlue = pRGBA + 0x2
READ_MEMORY pBlue 1 FALSE (iBlue)
Neste caso o resultado é o mesmo, mas escrito de maneira mais correta, você esquecer disso será um desastre dependente do caso.
Vamos lembrar da contagem hexadecimal:
0x10
não é
10
mas sim
16
! Não se esqueça da contagem hexadecimal!!!
0 1 2 3 4 5 6 7 8 9 A B C D E F 10 11 12 13 14 15 16 17 18 19 1A 1B
(...)
Outro detalhe importante é que do mesmo modo que
01
(decimal) é igual a
1
, você digitar
0x01
(hexadecimal) é igual a
0x1
, assim como
0x001
,
0x000000001
etc. Só um detalhe para não se confundir. Guarde isto na sua cachola.
Outra coisa que quero te lembrar, é que hexadecimal é simplesmente um modo diferente de digitar números, se você preferir digitar
10
ao invés de
0xA
(e vice versa) dá na mesma, não há obrigação.
Portanto, se você está contando de 4 em 4, você pode simplesmente fazer "4, 8, 12, 16", você não é obrigado a digitar "0x4, 0x8, 0xC, 0x10" (que é um tanto estranho para
pessoas comuns).
Depois deste tira-dúvidas rápido sobre hexadecimal, vamos voltar ao foco:
Colocando a mão na massa
Vamos aplicar o que aprendemos até agora!
Abaixo temos a CPed (Class Ped) que a comunidade montou (eu o peguei do database do IDA, e limpei ele).
Código: Selecionar tudo
00000000 CPed struc ; (sizeof=0x79C, mappedto_36)
00000000 physical CPhysical ?
00000138 m_pedAudio CAEPedAudioEntity ?
00000294 m_pedSpeech CAEPedSpeechAudioEntity
00000394 m_weaponAudio CAEPedWeaponAudioEntity
0000043C field_43C db 36 dup(?)
00000460 field_460 db 8 dup(?)
00000468 field_468 dd ?
0000046C m_nPedFlags dd 4 dup(?)
0000047C m_pIntelligence dd ?
00000480 m_pPlayerData dd ?
00000484 m_nCreatedBy db ?
00000485 _pad0 db 3 dup(?)
00000488 m_apPedBones dd 19 dup(?)
000004D4 m_dwAnimGroup dd ?
000004D8 m_vAnimMovingShiftLocal CVector2D ?
000004E0 m_acquaintance CPedAcquaintance ?
000004F4 m_pWeaponObject dd ?
000004F8 m_pGunflashObject dd ?
000004FC m_pGogglesObject dd ?
00000500 m_pGogglesState dd ?
00000504 m_nWeaponGunflashStateLeftHand dw ?
00000506 field_506 dw ?
00000508 m_nWeaponGunflashStateRightHand dw ?
0000050A field_50A dw ?
0000050C m_pedIK CPedIK ?
0000052C field_52C dd ?
00000530 m_nPedState dd ?
00000534 m_dwMoveState dd ?
00000538 field_538 dd ?
0000053C field_53C dd ?
00000540 m_fHealth dd ?
00000544 m_fMaxHealth dd ?
00000548 m_fArmour dd ?
0000054C m_dwTimeTillWeNeedThisPed dd ?
00000550 m_vAnimMovingShift CVector2D ?
00000558 m_fCurrentRotation dd ?
0000055C m_fAimingRotation dd ?
00000560 m_fHeadingChangeRate dd ?
00000564 m_dwMoveAnim dd ?
00000568 field_568 dd ?
0000056C field_56C RwV3d ?
00000578 field_578 RwV3d ?
00000584 m_pContactEntity dd ?
00000588 m_pLastVehicle dd ?
0000058C m_pVehicle dd ?
00000590 field_590 dd ?
00000594 field_594 dd ?
00000598 m_nPedType dd ?
0000059C m_pStats dd ?
000005A0 m_aWeapons CWeapon 13 dup(?)
0000070C m_nSavedWeapon dd ?
00000710 m_nDelayedWeapon dd ?
00000714 m_dwDelayedWeaponAmmo dd ?
00000718 m_nActiveWeaponSlot db ?
00000719 m_nWeaponShootingRate db ?
0000071A m_nWeaponAccuracy db ?
0000071B _pad db ?
0000071C m_pTargetedObject dd ?
00000720 field_720 dd ?
00000724 field_724 dd ?
00000728 field_728 dd ?
0000072C m_nWeaponSkill db ?
0000072D m_nFightingStyle db ?
0000072E m_nAllowedAttackMoves db ?
0000072F field_72F db ?
00000730 m_pFire dd ?
00000734 field_734 dd ?
00000738 m_pLookAtEntity dd ?
0000073C m_fLookHeading dd ?
00000740 m_dwWeaponModelId dd ?
00000744 field_744 dd ?
00000748 m_dwLookTime dd ?
0000074C field_74C dd ?
00000750 m_dwTimeWhenDead dd ?
00000754 m_nBodypartToRemove db ?
00000755 field_755 db ?
00000756 m_nMoneyCount dw ?
00000758 field_758 dd ?
0000075C field_75C dd ?
00000760 m_nLastWeaponDamage db ?
00000761 field_761 db 3 dup(?)
00000764 m_pLastEntityDamaged dd ?
00000768 field_768 dd ?
0000076C m_vTurretOffset CVector ?
00000778 m_fTurretAngleA dd ?
0000077C m_fTurretAngleB dd ?
00000780 m_dwTurretPosnMode dd ?
00000784 m_dwTurretAmmo dd ?
00000788 m_pCoverPoint dd ?
0000078C m_pEnex dd ?
00000790 m_fRemovalDistMultiplier dd ?
00000794 m_wSpecialModelIndex dw ?
00000796 field_796 db 2 dup(?)
00000798 field_798 dd ?
0000079C CPed ends
Nota: Você pode ver a classe corretamente definida aqui, mas não inclui os offsets.
Por exemplo, em
CPed+0x548
nós temos o valor do colete:
Tá, mas como nós podemos ter acesso à uma
CPed
?
Código: Selecionar tudo
GET_PED_POINTER scplayer (pPed)
pPed += 0x548 // m_fArmour
READ_MEMORY pPed 4 FALSE (iArmour)
Ahhhhh... Agora comecei a falar a sua língua, né?
Originalmente na CLEO você tem os seguintes comandos:
Código: Selecionar tudo
GET_PED_POINTER // CPed
GET_VEHICLE_POINTER // CVehicle
GET_OBJECT_POINTER // CObject
Assim você pode pegar a class de algum
CHAR
(CPed),
CAR
(CVehicle) ou
OBJECT
(CObject) para ter acesso à várias (muitas!) informações extras para você usar nos seus mods.
Veja só: Em
CPed+0x480
nós temos uma nova class:
Não está muito claro, mas se você ler este endereço (4 bytes) você vai receber um ponteiro para a
CPlayerData
.
Código: Selecionar tudo
GET_PED_POINTER scplayer (pPed)
pPed += 0x480 // m_pPlayerData
READ_MEMORY pPed 4 FALSE (pPlayerData)
Pronto, agora nós temos o início da
CPlayerData
na nossa variável
pPlayerData
!
E assim nós encontramos mais informações úteis:
Código: Selecionar tudo
00000000 CPlayerData struc ; (sizeof=0xAC, mappedto_130)
00000000 m_pWanted dd ?
00000004 m_pClothes dd ?
00000008 m_ArrestingOfficer dd ?
0000000C m_vecFightMovement CVector2D ?
00000014 m_fMoveBlendRatio dd ?
00000018 m_fTimeCanRun dd ?
0000001C m_fSprintEnergy dd ?
00000020 m_nChosenWeapon db ?
00000021 m_nCarDangerCounter db ?
00000022 __pad0 db 2 dup(?)
00000024 m_nStandStillTimer dd ?
00000028 m_nHitAnimDelayTimer dd ?
0000002C m_fAttackButtonCounter dd ?
00000030 m_pDangerCar dd ?
00000034 m_dwPlayerFlags dd ?
00000038 m_PlayerGroup dd ?
0000003C m_AdrenalineEndTime dd ?
00000040 m_nDrunkenness db ?
00000041 m_bFadeDrunkenness db ?
00000042 m_nDrugLevel db ?
00000043 m_nScriptLimitToGangSize db ?
00000044 m_fBreath dd ?
00000048 m_MeleeWeaponAnimReferenced dd ?
0000004C m_MeleeWeaponAnimReferencedExtra dd ?
00000050 m_fFPSMoveHeading dd ?
00000054 m_fLookPitch dd ?
00000058 m_fSkateBoardSpeed dd ?
0000005C m_fSkateBoardLean dd ?
00000060 m_pSpecialAtomic dd ?
00000064 m_fGunSpinSpeed dd ?
00000068 m_fGunSpinAngle dd ?
0000006C m_LastTimeFiring dd ?
00000070 m_nTargetBone dd ?
00000074 m_vecTargetBoneOffset CVector ?
00000080 m_busFaresCollected dd ?
00000084 m_bPlayerSprintDisabled db ?
00000085 m_bDontAllowWeaponChange db ?
00000086 m_bForceInteriorLighting db ?
00000087 _pad1 db ?
00000088 m_DPadDownPressedInMilliseconds dw ?
0000008A m_DPadUpPressedInMilliseconds dw ?
0000008C m_wetness db ?
0000008D m_playersGangActive db ?
0000008E m_waterCoverPerc db ?
0000008F _pad2 db ?
00000090 m_waterHeight dd ?
00000094 m_FireHSMissilePressedTime dd ?
00000098 m_LastHSMissileTarget dd ?
0000009C m_nModelIndexOfLastBuildingShot dd ?
000000A0 m_dwPlayerMissile dd ?
000000A4 m_pCurrentProstitutePed dd ?
000000A8 m_pLastProstituteShagged dd ?
000000AC CPlayerData ends
CPlayerData definido aqui
Se você ler
pPlayerData+0x0
(ou seja, simplesmente o mesmo endereço, no mesmo ponteiro) você vai ler isto:
E assim nós encontramos o
CWanted
!
Código: Selecionar tudo
00000000 CWanted struc ; (sizeof=0x29C, mappedto_90)
00000000 m_nChaosLevel dd ?
00000004 m_nChaosLevelBeforeParole dd ?
00000008 m_nLastTimeWantedDecreased dd ?
0000000C m_nLastTimeWantedLevelChanged dd ?
00000010 m_nTimeOfParole dd ?
00000014 m_fMultiplier dd ? ; float
00000018 m_nCopsInPursuit db ?
00000019 m_nMaxCopsInPursuit db ?
0000001A m_nMaxCopCarsInPursuit db ?
0000001B m_nCopsBeatingSuspect db ?
0000001C m_nChanceOnRoadBlock dw ?
0000001E m_nFlags db ?
0000001F _pad1F db ?
00000020 m_nCurrentChaseTime dd ?
00000024 m_nCurrentChaseTimeCounter dd ?
00000028 m_nTimeCounting dd ?
0000002C m_nWantedLevel dd ?
00000030 m_nWantedLevelBeforeParole dd ?
00000034 m_CrimesBeingQd CCrimeBeingQd 16 dup(?)
000001F4 m_pCopsInPursuit dd 10 dup(?) ; offset
0000021C m_PoliceScannerAudio CAEPoliceScannerAudioEntity ?
00000298 m_bLeavePlayerAlone db ?
00000299 _pad299 db 3 dup(?)
0000029C CWanted ends
CWanted definido aqui
Você pode usar estes números para fazer checagens, e vários deles você pode editar para fazer alterações no gameplay.
Veja este script que lê o valor do campo
m_nCopsInPursuit
(que é a quantidade de policiais te perseguindo) e mostra na tela:
Código: Selecionar tudo
SCRIPT_START
{
LVAR_INT scplayer
LVAR_INT pPed pPlayerData pWanted pnCops iCops
GET_PLAYER_CHAR 0 scplayer
main_loop:
WAIT 0
GET_PED_POINTER scplayer (pPed)
pPlayerData = pPed + 0x480 // CPed.CPlayerData
READ_MEMORY pPlayerData 4 FALSE pPlayerData
pWanted = pPlayerData + 0x0 // CPlayerData.pWanted
READ_MEMORY pWanted 4 FALSE pWanted
pnCops = pWanted + 0x18 // CWanted.m_nCopsInPursuit
READ_MEMORY pnCops 1 FALSE (iCops)
PRINT_FORMATTED_NOW "Cops: %i" 1000 (iCops)
GOTO main_loop
}
SCRIPT_END
É claro, eu escrevi o código desta maneira para facilitar o entendimento e ficar mais conveniente.
Você pode otimizar para economizar variáveis e 1 linha:
Código: Selecionar tudo
SCRIPT_START
{
LVAR_INT scplayer
LVAR_INT temp iCops
GET_PLAYER_CHAR 0 scplayer
main_loop:
WAIT 0
GET_PED_POINTER scplayer (temp)
temp += 0x480 // CPed.CPlayerData
READ_MEMORY temp 4 FALSE temp
//temp += 0x0 // CPlayerData.pWanted
READ_MEMORY temp 4 FALSE temp
temp += 0x18 // CWanted.m_nCopsInPursuit
READ_MEMORY temp 1 FALSE (iCops)
PRINT_FORMATTED_NOW "Cops: %i" 1000 (iCops)
GOTO main_loop
}
SCRIPT_END
Assim é um pouco menos organizado (por exemplo, você não guardou uma variável com o CPed, portanto você vai ter que pegar de novo caso precisar) mas mais aconselhável na maioria das vezes.
Os comentários que coloquei no script foram como se fosse uma "conversão" para o formato de linguagens de programação orientadas à objetos. Se você entende delas, conseguiu ter a noção do que nós fizemos aqui.
Chega a ser até engraçado, pois os mods cleos não são feitos em programações orientadas à objetos, mas a gente improvisa!
Se aprofundando
Vamos começar a complicar mais:
Código: Selecionar tudo
00000000 CPed struc ; (sizeof=0x79C, mappedto_36)
00000000 physical CPhysical ?
00000138 m_pedAudio CAEPedAudioEntity ?
(...)
Veja que em
CPed+0x0
nós temos um
CPhysical
.
Isto não é um ponteiro para o
CPhysical
, se você pensou isso, preste mais atenção!
O
CPhysical
está
dentro do
CPed
, perceba o offset do próximo campo, é
0x138
, ou seja, temos
0x138
(312) bytes ali!
Assim como não tem nenhum indicativo de que isso é um ponteiro nem nada, você tem que prestar atenção (eu já passei muitas horas de dor de cabeça por erros bobos assim)
Vamos ir no
CPhysical
ver o que temos lá:
Código: Selecionar tudo
00000000 CPhysical struc ; (sizeof=0x138, mappedto_756)
00000000 entity CEntity ?
00000038 field_34 dd ?
0000003C m_dwLastCollisionTime dd ?
00000040 m_dwFlags dd ?
00000044 m_vecMoveSpeed CVector ?
00000050 m_vecTurnSpeed CVector ?
0000005C m_vecFrictionMoveSpeed CVector ?
00000068 m_vecFrictionTurnSpeed CVector ?
00000074 m_vecForce CVector ?
00000080 m_vecTorque CVector ?
0000008C m_fMass dd ?
00000090 m_fTurnMass dd ?
00000094 m_fVelocityFrequency dd ?
00000098 m_fAirResistance dd ?
0000009C m_fElasticity dd ?
000000A0 m_fBuoyancyConstant dd ?
000000A4 m_vecCentreOfMass CVector ?
000000B0 m_pCollisionList dd ?
000000B4 m_pMovingList dd ?
000000B8 m_bFakePhysics db ?
000000B9 m_nNumEntitiesCollided db ?
000000BA m_nContactSurface db ?
000000BB field_BB db ?
000000BC m_apCollidedEntities dd 6 dup(?)
000000D4 m_fMovingSpeed dd ?
000000D8 m_fDamageIntensity dd ?
000000DC m_pDamageEntity dd ?
000000E0 m_vLastCollisionDirection RwV3d ?
000000EC m_vecLastCollisionPosn CVector ?
000000F8 m_nDamagedPart dw ?
000000FA field_FA dw ?
000000FC m_pAttachedTo dd ?
00000100 m_vAttachOffset RwV3d ?
0000010C m_vAttachRotation RwV3d ?
00000118 m_qAttachRotation CQuaternion ?
00000128 EntityIgnoredCollision dd ?
0000012C m_fContactSurfaceBrightness dd ?
00000130 m_fDynamicLighting dd ?
00000134 m_pShadowData dd ?
00000138 CPhysical ends
Oh, percebeu o
sizeof=0x138
ali?
Vou parar de dar os links das classes atualizadas e definidas em C++ aqui;
todas as classes estão no repositório do DK22Pac! Nosso salvador! Dê uma olhada nos .h — se bem que os .cpp também podem ser interessantes para você. Ver tais arquivos pode te trazer melhor entendimento, principalmente se você é acostumado com OOP.
Perceba que o início do
CPhysical
, que é o início do
CPed
, é também o início de um
CEntity
.
Código: Selecionar tudo
00000000 CEntity struc ; (sizeof=0x38, mappedto_757)
00000000 placeable CPlaceable ?
00000018 object ClumpAtomicUnion ?
0000001C m_nFlags dd ?
00000020 m_nRandomSeed dw ?
00000022 m_nModelIndex dw ?
00000024 m_pReferences dd ?
00000028 m_pStreamingLink dd ?
0000002C m_nLastScanCode dw ?
0000002E m_iplIndex db ?
0000002F m_nbInterior db ?
00000030 m_pLod dd ?
00000034 m_bNumLodChildren db ?
00000035 m_bNumLodChildrenRendered db ?
00000036 m_nTypeStatus db ?
00000037 _pad0 db ?
00000038 CEntity ends
E mais outro!
CPlaceable
.
Código: Selecionar tudo
00000000 CPlaceable struc ; (sizeof=0x18)
00000000 __vmt dd ? ; offset
00000004 stransform CSimpleTransform ?
00000014 m_pMatrix dd ? ; offset
00000018 CPlaceable ends
Uffa, terminamos.
Você está entendendo onde estamos?
Tudo isso acima está dentro do CPed, logo no início dele (não tecnicamente correto, isto na verdade são os "pais" uns dos outros).
É um pouco
mindblow, mas se você ler 4 bytes de
CPed+0x4
, você estará lendo um campo do
CPlaceable
, e como visto acima, você estará entrando na
CSimpleTransform
:
Código: Selecionar tudo
00000000 CSimpleTransform struc ; (sizeof=0x10, mappedto_46)
00000000 m_vPosn CVector ?
0000000C m_fAngle dd ?
00000010 CSimpleTransform ends
Certo, e logo no início temos o início de um
CVector
.
Código: Selecionar tudo
00000000 CVector struc ; (sizeof=0xC, mappedto_495)
00000000 x dd ?
00000004 y dd ?
00000008 z dd ?
0000000C CVector ends
Simplesmente um X Y Z...
E como chegar lá?
CPed+0x4
Sua mente já está com bug?
Se você está confuso, reveja lentamente todo o caminho que percorremos.
Entenda, nós só precisamos subir o offset aqui:
Nada mais, automaticamente chegamos no campo
x
do
CVector
.
Curiosamente, aparentemente o
CSimpleTransform
não é utilizado aqui, o que não é comum de se ver — o mod
Active Dashboard aproveita o não uso do
CSimpleTransform
para guardar informações extras nos veículos, o que não é uma prática muito boa, mas enfim.
Ou seja, não adianta tentar ler, vai retornar
0.0
, não sei o motivo, mas pelo menos aprendemos muito aqui.
Nós podemos testar isso lendo outra coisa lá atrás no
CPhysical
:
Para chegarmos aí é só
CPed+0x8C
, simplesmente.
E assim nós podemos saber o peso do CJ:
Código: Selecionar tudo
GET_PED_POINTER scplayer (pPed)
pMass = pPed + 0x8C // CPed.CPhysical.m_fMass
READ_MEMORY pMass 4 FALSE (fMass)
Ele tem 70 kgs
Experimente escrever um valor alto, como
1000.0
e encostar num carro!
Ou abaixar um pouco. Fará o CJ pular mais alto e corrigirá a coisa irreal de
empurrar
carros com o corpo.
Num último exemplo, vamos no CVehicle:
Código: Selecionar tudo
00000000 CVehicle struc ; (sizeof=0x5A0, mappedto_755)
00000000 physical CPhysical ?
00000138 m_vehicleAudio CAEVehicleAudioEntity ?
00000384 m_pHandlingData dd ?
00000388 m_pFlyingHandlingData dd ?
0000038C m_nHandlingFlags dd ?
00000390 m_autoPilot CAutoPilot ?
00000428 m_nFlags db 8 dup(?)
00000430 m_nCreationTime dd ?
00000434 m_nPrimaryColor db ?
00000435 m_nSecondaryColor db ?
00000436 m_nTertiaryColor db ?
00000437 m_nQuaternaryColor db ?
00000438 m_anExtras db 2 dup(?)
0000043A m_anUpgrades dw 15 dup(?)
00000458 m_fWheelScale dd ?
0000045C m_nAlarmState dw ?
0000045E m_nForcedRandomRouteSeed dw ?
00000460 m_pDriver dd ?
00000464 m_apPassengers dd 8 dup(?)
00000484 m_nNumPassengers db ?
00000485 m_nNumGettingIn db ?
00000486 m_nGettingInFlags db ?
00000487 m_nGettingOutFlags db ?
00000488 m_nMaxPassengers db ?
00000489 m_nWindowsOpenFlags db ?
0000048A m_nNitroBoosts db ?
0000048B m_nSpecialColModel db ?
0000048C m_pEntityWeAreOn dd ?
00000490 m_pFire dd ?
00000494 m_fSteerAngle dd ?
00000498 m_f2ndSteerAngle dd ?
0000049C m_fGasPedal dd ?
000004A0 m_fBreakPedal dd ?
000004A4 m_nCreatedBy db ?
000004A5 _pad0 db ?
000004A6 m_wExtendedRemovalRange dw ?
000004A8 m_nBombLightsWinchFlags db ?
000004A9 m_nGunsFlags db ?
000004AA m_nUsedForCover db ?
000004AB m_nAmmoInClip db ?
000004AC m_nPacMansCollected db ?
000004AD m_nPedsPositionForRoadBlock db ?
000004AE m_nNumCopsForRoadBlock db ?
000004AF _pad1 db ?
000004B0 m_fDirtLevel dd ?
000004B4 m_nCurrentGear db ?
000004B5 _pad2 db 3 dup(?)
000004B8 m_fGearChangeCount dd ?
000004BC m_fWheelSpinForAudio dd ?
000004C0 m_fHealth dd ?
000004C4 m_pTractor dd ?
000004C8 m_pTrailer dd ?
000004CC m_pWhoInstalledBombOnMe dd ?
000004D0 m_dwTimeTillWeNeedThisCar dd ?
000004D4 m_dwGunFiringTime dd ?
000004D8 m_dwTimeWhenBlowedUp dd ?
000004DC m_wCopsInCarTimer dw ?
000004DE m_wBombTimer dw ?
000004E0 m_pWhoDetonatedMe dd ?
000004E4 m_fVehicleFrontGroundZ dd ?
000004E8 m_fVehicleRearGroundZ dd ?
000004EC _i_nu_field_4EC db ?
000004ED _nu_field_4ED db 11 dup(?)
000004F8 m_dwDoorLock dd ?
000004FC m_dwProjectileWeaponFiringTime dd ?
00000500 m_dwAdditionalProjectileWeaponFiringTime
00000504 m_dwTimeForMinigunFiring dd ?
00000508 m_nLastWeaponDamageType db ?
00000509 _pad3 db 3 dup(?)
0000050C m_pLastDamageEntity dd ?
00000510 _nu_field_510 db ?
00000511 _i_nu_field_511 db ?
00000512 _i_nu_field_512 db ?
00000513 m_nVehicleWeaponInUse db ?
00000514 m_nHornCounter dd ?
00000518 randomIdRelatedToSiren db ?
00000519 m_nCarHornTimer db ?
0000051A field_51A db ?
0000051B m_nHasslePosId db ?
0000051C m_FrontCollPoly CStoredCollPoly ?
00000548 m_RearCollPoly CStoredCollPoly ?
00000574 m_anCollisionLighting db 4 dup(?)
00000578 m_pOverheatParticle dd ?
0000057C m_pFireParticle dd ?
00000580 m_pDustParticle dd ?
00000584 m_dwRenderLights dd ?
00000588 m_pCustomCarPlate dd ?
0000058C m_fSteeringLeftRight dd ?
00000590 m_dwVehicleClass dd ?
00000594 m_dwVehicleSubClass dd ?
00000598 m_wPreviousPaintjobTxd dw ?
0000059A m_wPaintJobTxd dw ?
0000059C m_pPaintJobTexture dd ?
000005A0 CVehicle ends
E ainda,
CVehicle
vem da
CAutomobile
(de fato, ao usar o comando
GET_VEHICLE_POINTER
você está pegando um
CAutomobile
que contém o
CVehicle
no início):
Código: Selecionar tudo
00000000 CAutomobile struc ; (sizeof=0x988, mappedto_763)
00000000 vehicle CVehicle ?
000005A0 m_damageManager CDamageManager ?
000005B8 m_doors CDoor 6 dup(?)
00000648 m_aCarNodes dd 25 dup(?)
000006AC m_panels CBouncingPanel 3 dup(?)
0000070C m_swingingChassis CDoor ?
00000724 m_wheelColPoint CColPoint 4 dup(?)
000007D4 wheelsDistancesToGround1 dd 4 dup(?)
000007E4 wheelsDistancesToGround2 dd 4 dup(?)
000007F4 field_7F4 dd 4 dup(?)
00000804 field_800 dd ?
00000808 field_804 dd ?
0000080C field_80C dd ?
00000810 field_810 dd 4 dup(?)
00000820 field_81C db 4 dup(?)
00000824 field_820 dd ?
00000828 m_fWheelRotation dd 4 dup(?)
00000838 field_838 dd 4 dup(?)
00000848 m_fWheelSpeed dd 4 dup(?)
00000858 field_858 dd 4 dup(?)
00000868 taxiAvaliable db ?
00000869 field_869 db ?
0000086A field_86A db ?
0000086B field_867 db ?
0000086C m_wMiscComponentAngle dw ?
0000086E field_86E dw ?
00000870 m_dwBusDoorTimerEnd dd ?
00000874 m_dwBusDoorTimerStart dd ?
00000878 field_878 dd ?
0000087C wheelOffsetZ dd 4 dup(?)
0000088C field_88C dd 3 dup(?)
00000898 m_fFrontHeightAboveRoad dd ?
0000089C m_fRearHeightAboveRoad dd ?
000008A0 m_fCarTraction dd ?
000008A4 m_fNitroValue dd ?
000008A8 field_8A4 dd ?
000008AC m_fRotationBalance dd ?
000008B0 m_fMoveDirection dd ?
000008B4 field_8B4 dd 6 dup(?)
000008CC field_8C8 dd 6 dup(?)
000008E4 m_dwBurnTimer dd ?
000008E8 m_pWheelCollisionEntity dd 4 dup(?)
000008F8 m_vWheelCollisionPos CVector 4 dup(?)
00000928 field_924 db ?
00000929 field_925 db ?
0000092A field_926 db ?
0000092B field_927 db ?
0000092C field_928 db ?
0000092D field_929 db ?
0000092E field_92A db ?
0000092F field_92B db ?
00000930 field_92C db ?
00000931 field_92D db ?
00000932 field_92E db ?
00000933 field_92F db ?
00000934 field_930 db ?
00000935 field_931 db ?
00000936 field_932 db ?
00000937 field_933 db ?
00000938 field_934 db ?
00000939 field_935 db ?
0000093A field_936 db ?
0000093B field_937 db ?
0000093C field_938 db ?
0000093D field_939 db ?
0000093E field_93A db ?
0000093F field_93B db ?
00000940 field_93C db ?
00000941 field_93D db ?
00000942 field_93E db ?
00000943 field_93F db ?
00000944 field_940 dd ?
00000948 field_944 dd ?
0000094C m_fDoomVerticalRotation dd ?
00000950 m_fDoomHorizontalRotation dd ?
00000954 m_fForcedOrientation dd ?
00000958 m_fUpDownLightAngle dd 2 dup(?)
00000960 m_nNumContactWheels db ?
00000961 m_nWheelsOnGround db ?
00000962 field_962 db ?
00000963 field_963 db ?
00000964 field_964 dd ?
00000968 field_968 dd 4 dup(?)
00000978 pNitroParticle dd 2 dup(?)
00000980 field_980 db ?
00000981 field_981 db ?
00000982 field_982 dw ?
00000984 field_984 dd ?
00000988 CAutomobile ends
A
CAutomobile
tem um total de 2,44 KB.
GIGANTESCA.
Quando nós fazemos isso:
Nós estamos adquirindo a
CAutomobile
, que contém a
CVehicle
, que contém a
CEntity
, que contém a
CPhysical
... E todas as outras classes espalhadas entre elas, como
CDoor
,
CFire
etc etc etc... Basta você encontrar o caminho que você conseguirá
milhares de dados úteis.
No
CVehicle
nós podemos por exemplo ter o ponteiro do handling do veículo:
Veja que quando o valor é um ponteiro para outra class, o nome da field tem um "p" (ponteiro) no início, e, sempre o campo tem 4 bytes (os ponteiros
sempre têm 4 bytes num aplicativo 32 bits!)
Basta nós lermos lá para ter as informações do handling, ou, mais especificamente, um
tHandlingData
:
Código: Selecionar tudo
00000000 tHandlingData struc ; (sizeof=0xE0, mappedto_123)
00000000 index dd ?
00000004 fMass dd ?
00000008 field_8 dd ?
0000000C fTurnMass dd ?
00000010 fDragMult dd ?
00000014 centreOfMass RwV3d ?
00000020 nPercentSubmerged db ?
00000021 field_21 db ?
00000022 field_22 db ?
00000023 field_23 db ?
00000024 fBuoyancyConstant dd ?
00000028 fTractionMultiplier dd ?
0000002C transmissionData cTransmission ?
00000094 fBrakeDeceleration dd ?
00000098 fBrakeBias dd ?
0000009C bABS db ?
0000009D field_9D db ?
0000009E field_9E db ?
0000009F field_9F db ?
000000A0 fSteeringLock dd ?
000000A4 fTractionLoss dd ?
000000A8 fTractionBias dd ?
000000AC fSuspensionForceLevel dd ?
000000B0 fSuspensionDampingLevel dd ?
000000B4 fSuspensionHighSpdComDamp dd ?
000000B8 fSuspensionUpperLimit dd ?
000000BC fSuspensionLowerLimit dd ?
000000C0 fSuspensionBiasBetweenFrontAndRear dd ?
000000C4 fSuspensionAntiDiveMultiplier dd ?
000000C8 fCollisionDamageMultiplier dd ?
000000CC modelFlags dd ?
000000D0 handlingFlags dd ?
000000D4 fSeatOffsetDistance dd ?
000000D8 nMonetaryValue dd ?
000000DC frontLights db ?
000000DD rearLights db ?
000000DE animGroup db ?
000000DF field_DF db ?
000000E0 tHandlingData ends
Ou seja, por exemplo, os freios:
Para nós alterarmos os freios do nosso carro, basta:
Código: Selecionar tudo
SCRIPT_START
{
LVAR_INT scplayer
LVAR_INT hCar pVehicle pHandling pBrakeDeceleration
GET_PLAYER_CHAR 0 scplayer
main_loop:
WAIT 0
IF TEST_CHEAT BRAKE
IF IS_CHAR_SITTING_IN_ANY_CAR scplayer
STORE_CAR_CHAR_IS_IN_NO_SAVE scplayer hCar
GET_VEHICLE_POINTER hCar (pVehicle)
pHandling = pVehicle + 0x384 // CAutomobile.CVehicle.m_pHandlingData
READ_MEMORY pHandling 4 FALSE (pHandling) // tHandlingData
pBrakeDeceleration = pHandling + 0x94 // tHandlingData.fBrakeDeceleration
WRITE_MEMORY pBrakeDeceleration 4 0.0 FALSE
ENDIF
ENDIF
GOTO main_loop
}
SCRIPT_END
No exemplo acima eu desativei totalmente os freios do veículo atual do player.
Detalhe importante sobre este exemplo: O handling dos veículos são compartilhados por modelo, portanto, se você ativou em um Elegy, todos os carros de modelo Elegy irão sofrer as alterações. Para corrigir isso é necessário usar IndieVehHandlings.cs (um cleo que vem em vários mods meus, como Tuning Mod e Real Traffic Fix). Neste caso, você pode simplesmente colocar um WAIT (para dar tempo do IndieVehHandlings fazer o trabalho), já deve servir, mas o mais aconselhável é usar uma função para checar se o ponteiro do handling recebeu o patch. Basta abrir o .cs com alguma versão mais atual do Sanny Builder que você terá muitas informações e códigos prontos, no entanto, até agora só no formato Sanny Builder, de fato eu penso em recriá-lo em .asi.
Eu ainda lhe enviarei a lista dessas classes com offsets para você usar.
Revisão e mais detalhes:
50 — offset em hexadecimal.
m_wTimesUsed — nome da field, geralmente com letras dando dicas do seu conteúdo. Por exemplo se tem um "f" no início nós sabemos que é um float etc.
dw — a segunda letra indica a quantidade de bytes (BYTE, WORD, DWORD), como já foi explicado nas partes anteriores.
O
p
no início do nome da field com tamanho de 4 bytes indicam que isso é um ponteiro para outra class (ou seja, você tem que ler este valor, e o resultado será o endereço da outra class).
O nome da field estando
field_{offset}
é porque simplesmente está sem nome (a comunidade não colocou ou simplesmente já colocaram mas a struct que você está olhando está desatualizada).
Lembre-se, 99,99% das fields são usadas no jogo por algum motivo. Esse monte de
field
pode ter coisas importantes, ou não, e, como eu disse, você pode ter as classes com nomes atualizados
aqui, na qual pode incluir campos ainda não nomeados aqui.
Código: Selecionar tudo
00000074 m_vecForce CVector ?
00000080 m_vecTorque CVector ?
Se notavelmente não é um ponteiro, e normalmente há o nome de uma classe do lado (geralmente, mas não sempre, iniciadas com
C
), e tendo o próximo campo numa distância do tamanho do CVector (que é
0xC
, ou seja, 12 bytes), é porque não é um ponteiro, e sim a classe inteira está ali ali.
dup — indica um array, ou seja, não é só 1 elemento de 1 byte, mas sim, neste caso,
8
elementos de 1 byte. Neste exemplo específico (do
CVehicle
) é o offset para todas as 8 possíveis variações de cores primárias do veículo (perceba, os veículos suportam só 8 variações de cores!)
Dois asteriscos juntos ("**") indicam um ponteiro para um ponteiro. Isso quer dizer que...
Código: Selecionar tudo
READ_MEMORY pointer 4 FALSE (pointer)
READ_MEMORY pointer 4 FALSE (value)
Entendeu?
"Como eu obtenho acesso à class X?"
Isso varia demais, muito mesmo!
Geralmente, por base de uma class você encontra várias outras. Você tem que encontrar o caminho, como já explicado.
Muitas vezes elas estão em um endereço estático do .exe, como por exemplo a
CCamera
que no Crack 1.0 US sempre fica (literalmente) em
0xB6F028
.
Certo, acredito que já está bem explicado.
Download das structs:
Para você que não quer
usar o IDA Pro, você pode baixar o
IDA.txt que é um arquivo com a lista de todas as structs documentadas pela comunidade. Eu uso muito isto!
Vou tentar deixá-lo atualizado como possível.
Há também o
IDA - VC.txt que são as structs do GTA Vice City
Eu quero deixar claro que, se você acha isso poderoso, você tem razão. Na verdade, você ainda nem deve ter noção do real poder de saber fazer este tipo de coisa.
Com um simples
GET_VEHICLE_POINTER
é possível você até trocar texturas de um material específico do modelo do carro.
Praticamente (se não todos) os meus mods mais complexos/avançados usam isso... É ir além do normalmente acessível.
Você ter aprendido isso multiplicou (e muito) suas possibilidades de mods. Nós já estamos chegando ao fim das partes mais avançadas deste tutorial, você estando aqui já deve saber muita coisa, ou pelo menos já tem as portas abertas para começar a saber.
Próxima parte:
M.4. Chamando funções (CALL)