AST Poradniki 2082 12

O temacie

Autor Siemekk

Zaczęty 20.06.2017 roku

Wyświetleń 2082

Odpowiedzi 12

Siemekk

Siemekk

Złote Wrota
Szlachcic
posty1982
Propsy798
ProfesjaSkrypter
  • Złote Wrota
  • Szlachcic
Wymagana jest znajomość obiektowego C++
Poradniki
#1 CInvoke - Hook
Zacznijmy od podstaw - czym jest hook? Otóż Hook jest to doczepienie się do funkcji silnika. Pytanie po co? Dajmy przykład:
Mam funkcję sprawdzającą czy hero ma wyciągniętą broń dystansową. Podczas tego chciałbym mieć printa. Teraz mam dwa wyjścia:
A)Napisać szereg warunków i wywołać funkcję co klatkę
B)Zrobić Hook'a dzięki czemu oszczędzę sobie zbędnych warunków.
Wersja A:
Spoiler

Wersja B:
Spoiler

Chyba widać, że Hook jest krótszy. Teraz trzeba opisać co każdy element oznacza:
Zacznijmy od pustej funkcji:
int __fastcall _BowMode(oCAIHuman*, void*, int);
Pierwszy int wziął się stąd, że oryginalna funkcja BowMode zawraca Int'a.
0x00695F00 protected: int __thiscall oCAIHuman::BowMode(int)
__fastcall - Funkcja musi zostać wywołana szybciej niż oryginalna, więc rejestry idą w ruch.
_BowMode - Nazwa funkcji. Chyba nie trzeba wyjaśniać.
oCAIHuman* - Jest to klasa z której pochodzi metoda. W Ikarusie uznawana jako zmienna ECX.
void* - Zawsze po zastosowaniu __fastcall, o ile hookowana funkcja miała typ __thiscall, po klasie jest "virtual table" oznaczany zmienną void* nie jest wykorzystany, ale MUSI BYĆ!
int - Argument z oryginalnej funkcji - w Ikarusie był odczytywany za pomocą MEM_ReadInt(ESP + ...);
Przeanalizuj, i spróbuj zrozumieć co się dzieje - do skutku!

Wykonałeś punkt o myśleniu? Nie? To analizuj, bo nic nie zrozumiesz...
Teraz opiszę Hook'a. Do dzieła.
CInvoke<int(__thiscall*)(oCAIHuman*, int)> pBowMode(0x00695F00, _BowMode, IVK_AUTO);

CInvoke - Szablon stworzony wewnątrz AST, służy do Hookowania.
int - zawsze na początku CInvokemusimy podać co oryginalna funkcja zawraca. Tutaj zawracała Int'a, lecz jeżeli zawracała by void dalibyśmy void, jeżeli zawracałaby zCView*, dalibyśmy zCView* etc.
__thiscall* - funkcja była typu __thiscall, czyli musimy tu umieścić __thiscall*.
oCAIHuman* - Funkcja była metodą klasy, więc musimy podać wskaźnik do ów klasy.
int - Jej argumentem był tylko int, jeżeli miałaby więcej argumentów podawalibyśmy je w kolejności po przecinku. Co ciekawe - w CInvoke nigdy, nie podajemy void*, co ma sens, ponieważ tylko w __fastcall, musi być void* po klasie i to tylko pod warunkiem, że hookowana funkcja była typu __thiscall - jeżeli to wiedziałeś to dobrze zapamiętałeś pierwszy punkt o "Pustej funkcji"
pBowMode - Oryginalna funkcja, która posiada przed argumentami dodatkowy jeden argument którą jest klasa - w tym wypadku tym argumentem będzie oCAIHuman*.
0x00695F00 - adres do oryginalnej funkcji możemy go dostać na kilka sposobów.
Sposób 1: Używając IDA szukamy adresu.
Sposób 2: Tabela z WoG'a - niestety nie mam linku.
Sposób 3: W AST 003 mamy plik G2_Names - są tam wszystkie adresy z gry.
_BowMode - wskaźnik do funkcji w której będą przechowane informacje o Hooku. Tak samo jak w Ikarusie HookEngine(adres, Len, funkcja);
IVK_AUTO - Polecam używać tego, gdyż niektóre funkcje były użyte w AST.dll - po prostu zapobiega błędom.

Trzeci punkt to rozbudowa funkcji _BowMode. Jednak to już zostawiam wam. Możecie robić tam co chcecie. Dodam tylko od siebie - jeżeli nie damy w roboczej funkcji pBowMode(_this, arg);Oryginalna funkcja nie wywoła się nigdy i nie będzie działać tak jak należy. Jeżeli w tym przykładzie zabrałbym pBowMode(_this, arg); to bohater nie mógłby strzelać z łuku, chować broni długodystansowej etc.
Tak więc spróbujcie zrobić kilka przykładów dla utrwalenia. Więcej przykładów pojawi się wkrótce - piszę to szybko, ponieważ boję się o utratę prądu :F

Pozdrawiam Siemekk :D
 

Bogdan Zwei

Bogdan Zwei

Użytkownicy
Wulgarny skurwiel pierdolony.
posty1817
Propsy512
Profesjabrak
  • Użytkownicy
  • Wulgarny skurwiel pierdolony.
Jeszcze wytłumacz, dlaczego "pBowMode". fastcall ma _BowMode, ale dlaczego CInvoke ma p zamiast _? Jakby co, to przeczytałem to:
Cytuj
pBowMode - Oryginalna funkcja, która posiada przed argumentami dodatkowy jeden argument którą jest klasa
 
:ok: zachęca do dalszej pomocy. Nie zapominaj o tym!

Prywatne wiadomości typu "Ej, pomocy" kasuję od razu. Od tego jest forum, a nie PW.

To me, defeat in anything is merely temporary, and its punishment is but an urge for me to greater effort to achieve my goal. Defeat simply tells me that something is wrong in my doing; it is a path leading to success and truth.

In order to realize our true self we must be willing to live without being dependent upon the opinion of others.

Siemekk

Siemekk

Złote Wrota
Szlachcic
posty1982
Propsy798
ProfesjaSkrypter
  • Złote Wrota
  • Szlachcic
Już wyjaśniam. CInvoke tworzy strukturę która jest podłączona do pBowMode. pBowMode(adres, funkcja zwrotna, typ);
Dotyczy to już bardziej C++, ponieważ w wersji 002 Hookowało się w taki sposób.
int(__thiscall* pBowMode)(oCAIHuman*, int) = (int(__thiscall*)(oCAIHuman*, int))0x00695F00 ;
To są tak zwane pointery do funkcji.
 

Siemekk

Siemekk

Złote Wrota
Szlachcic
posty1982
Propsy798
ProfesjaSkrypter
  • Złote Wrota
  • Szlachcic

Siemekk
Złote Wrota

AST Poradniki
#3 2017-07-29, 13:48(Ostatnia zmiana: 2017-07-31, 23:29)
#2 Tworzenie funkcji zewnętrznych
Co to są funkcje zewnętrzne? Funkcje zewnętrzne są to funkcje, które pozwolą nam wywołać nasz kod napisany w C++ w grze. Na przykład oryginalnymi funkcjami zewnętrznymi są:
[...]
-Npc_HasItems
-Hlp_GetNpc
-Hlp_GetInstanceID
[...]

No dobrze, ale jak to zdefiniować? Zacznijmy od tego co ma robić nasza funkcja. Dajmy na przykład, że chcę aby moja funkcja "Ext_SwapInv(instance, instance)" czyściła ekwipunek jednego NPC, i przenosiła go do ekwipunku drugiego NPC.
Zacznijmy od zdefiniowania funkcji zawracającej inta.

Mamy już podstawę naszej funkcji. Teraz musimy się zastanowić co ma być parametrami naszej funkcji. Są to dwie Instancje obie klasy oCNpc więc musimy zdefiniować obiekty klas i parser za pomocą metody zCParser::GetParser();

Super! Ale obiekty klasy oCNpc są zerowe, musimy je zdefiniować w taki sposób, że będą one oznaczały NPC z argumentów funkcji. Aby zdefiniować argumenty funkcji musimy je definiować od dołu do góry. Czyli na początek definiujemy "pOther", a na końcu "pSelf".



UWAGA! Jeżeli argumentem są instancje klas oCNpc lub oCItem użyjemy dynamic_cast'a, w przeciwnym wypadku użyjemy par->GetParametr(argument);
Teraz mamy argumenty. Jednak, chcemy zrobić coś dzięki czemu będziemy zbierać cały ekwipunek. Tutaj już napiszemy funkcję w C++ która nie jest jakoś straszne trudna. Na początek z pOther musimy dostać się do ekwipunku. Gdy już tam będziemy musimy znaleźć wskaźnik do listy przedmiotów w ekwipunku. Używając pętli zabieramy mu wszystkie przedmioty i tworzymy je w ekwipunku drugiego NPC pod warunkiem że pierwszy NPC:
-Nie nosi ich na sobie.
-Nie są pancerzem lub jego elementem.
Tak więc funkcja będzie wyglądać tak:

Mamy funkcję, ale to nie oznacza, że możemy już ją wywołać... Musimy dopisać ją do parsera. Stwórzmy zwykłą funkcję "DefineExtenals" i za pomocą parser->DefineExternals dodajmy naszą malutką funkcję do gry.


"Ext_SwapInv" - Tak będzie się nazywać nasza funkcja w skryptach.
SwapInventory - Jest to nasza funkcja, którą napisaliśmy
zPAR_TYPE_VOID - Identyfikator parsera - funkcja jest typu VOID
zPAR_TYPE_INSTANCE - Identyfikator parsera - Pierwszym argumentem jest instancja
zPAR_TYPE_INSTANCE - Identyfikator parsera - Drugim argumentem jest instancja
0 - Kończy definicję argumentów oraz samego externala.

Funkcja jest zdefiniowana, ale musimy ją wywołać w momencie kiedy są tworzone oryginalne funkcje zewnętrzne. Udajemy się do pliku "dllRefApp.cpp" i wyszukujemy funkcję "_cb_Externals_Define". Nad nią musimy zdefiniować naszą funkcję - DefineExtenals, za pomocą extern void DefineExtenals(); a potem dopisać ją do funkcji "_cb_Externals_Define"


Kompilujemy nasz plugin i od tej pory posiadamy funkcję "Ext_SwapInv". Użyjmy jej. Udajemy się do skryptów gry i odnajdujemy plik "AST_HandleEvent.d" należy je posiadać - są one w paczce gdzie jest AST.

W funkcji "HandleEvent" dopiszmy malutką funkcję.


Jeżeli podejdziemy do NPC i klikniemy RMB splądrujemy jego ekwipunek.
Spoiler
Ekwipunek gracza przed kopiowaniem.


Ekwipunek NPC przed kopiowaniem.


Ekwipunek gracza po kopiowaniu.


Ekwipunek NPC po kopiowaniu.

Należy pamiętać, że PC_Rockefeler miał wszystkie przedmioty, przez co na jeden raz nie zostało skopiowane wszystko. Na NPC pokroju zwykłego obywatela zadziała to lepiej.  :ok: To by było na tyle w tym poradniku. W razie niejasności pytania kierować w tym temacie.
 

Bogdan Zwei

Bogdan Zwei

Użytkownicy
Wulgarny skurwiel pierdolony.
posty1817
Propsy512
Profesjabrak
  • Użytkownicy
  • Wulgarny skurwiel pierdolony.
Ciekawi mnie tylko, dlaczego nie potrzeba funkcji o nazwie Ext_SwapInv i dlaczego nie ma podanych argumentów w funkcji (SwapInventory()).

EDIT:
Cytuj
"Ext_SwapInv" - Tak będzie się nazywać nasza funkcja w skryptach.
Czyli to pozwala nam na używanie SwapInventory() w skryptach Daedalusa pod nazwą Ext_SwapInv? Ale dlaczego nie posiada to argumentów self,other (SwapInventory(self, other)/Ext_SwapInv(self, other))?
 
:ok: zachęca do dalszej pomocy. Nie zapominaj o tym!

Prywatne wiadomości typu "Ej, pomocy" kasuję od razu. Od tego jest forum, a nie PW.

To me, defeat in anything is merely temporary, and its punishment is but an urge for me to greater effort to achieve my goal. Defeat simply tells me that something is wrong in my doing; it is a path leading to success and truth.

In order to realize our true self we must be willing to live without being dependent upon the opinion of others.

Siemekk

Siemekk

Złote Wrota
Szlachcic
posty1982
Propsy798
ProfesjaSkrypter
  • Złote Wrota
  • Szlachcic
Argumenty są definiowane przez parser w funkcji DefineExternals. W funkcji SwapInventory mam pSelf i pOther - dzięki parserowi przyjmą one wartości z gry np. Self lub Other. To co jest zapisane w Stringu to nazwa funkcji, jeżeli nazwałbym ją "dupa" to też by działało tylko, że w skryptach użyłbym "dupa" zamiast "Ext_SwapInv"
 

Adanos

Adanos

Administrator
Szara eminencja
posty5162
Propsy3790
ProfesjaProgramista
  • Administrator
  • Szara eminencja
UWAGA! Jeżeli argumentem są instancje klas oCNpc lub oCItem użyjemy dynamic_cast'a, w przeciwnym wypadku użyjemy par->GetParametr(argument);
A nie wystarczy static_cast zamiast dynamic_cast? Raczej jesteś pewien jakiego typu jest obiekt.

Co do funkcji SwapInventory, po co zwraca jakąkolwiek wartość? Zwraca w dwóch przypadkach 0, a wartość zwracana nie jest do niczego potrzebna. Niech funkcja będzie typu void.

Siemekk

Siemekk

Złote Wrota
Szlachcic
posty1982
Propsy798
ProfesjaSkrypter
  • Złote Wrota
  • Szlachcic

Siemekk
Złote Wrota

AST Poradniki
#7 2017-07-30, 11:58(Ostatnia zmiana: 2017-07-31, 23:29)
Zazwyczaj używam dynamic_cast'a, nigdy nie próbowałem static_cast. W AST isnieje jeszcze zDYNAMIC_CAST którego używam zazwyczaj do dostania się do klas które są pochodne np. zCVob. zDYNAMIC_CAST używa się w następujący sposób:

oCNpc* pNpc = zDYNAMIC_CAST <oCNpc>(player->GetFocusVob());
if(pNpc)
//Jest Npc więc np. zmień mu nazwę
pNpc->SetName(zSTRING("Nowe imię "));


Funkcja musi być typu int, ponieważ budowa funkcji zCParser::DefineExternal wygląda tak:
void DefineExternal(zSTRING const&, int (__cdecl*)(void), int, int, ...)
{
XCALL(0x007A0190);
};
Nie mogę nic zmienić, w funkcji DefineExternal, bo gra wywali na pulpit.
 

Siemekk

Siemekk

Złote Wrota
Szlachcic
posty1982
Propsy798
ProfesjaSkrypter
  • Złote Wrota
  • Szlachcic
Z czego chcielibyście kolejny poradnik? ASTApi, G2Api?
 

Siemekk

Siemekk

Złote Wrota
Szlachcic
posty1982
Propsy798
ProfesjaSkrypter
  • Złote Wrota
  • Szlachcic

Siemekk
Złote Wrota

AST Poradniki
#9 2017-09-30, 14:32(Ostatnia zmiana: 2017-09-30, 22:10)
#3 CCursor i CViewAction

#3.1 Pojęcie podstawowe
LeGo oferowało nam pakiet kursora i buttonów. Ale to było najgorsze rozwiązanie, ponieważ prędkość kursora zależała od ilości FPS. AST postanowiło rozwiązać ten problem w sposób taki, że przechwyciło input myszki, i przepisało go z DX7 na ten z DX8. Sam kursor nie oferowałby nic ciekawego, więc wypadałoby dodatkowo napisać coś takiego jak przyciski. Dzięki tym rozwiązaniom powstały dwie nowe klasy: CViewAction i CCursor. Zacznijmy od podstaw, czyli utwórzmy obiekt klasy CViewAction. Najpierw zobaczmy, jak wygląda ów klasa:

ASTTOOLS = dllimport, mam tak ponieważ używam wersji 004!!!
Jak widzimy mamy dwa konstruktory. Którego użyjemy zależy od nas. Mamy dwie opcje:
Opcja #1:
CViewAction* pAction1 = new CViewAction;
pAction1->mouse_enter = MyMouseEnterFunc;

Opcja #2:
CViewAction* pAction1 = new CViewAction(MyMouseEnterFunc, NULL, NULL, NULL, NULL, NULL, NULL);
Wybór tego którego zapisu użyjemy, zależy już tylko od nas.

#3.2 Funkcja dla CViewAction
Wyżej podałem przykład, że funkcja przy wejściu na obiekt będzie się nazywała MyMouseEnterFunc. Należy pamiętać, że zACTION podany w klasie to tak naprawdę typedef funkcji, gdzie argumentem jest wskaźnik do obiektu CViewAction.
Więc nasza funkcja musi wyglądać tak jak na przykładzie podanym niżej.

Przy takim rozwiązaniu mamy dostęp do naszego przycisku, i np. po najechaniu na niego możemy zmienić mu kolor!

#3.3 Wyświetlenie kursora
Kursor wyświetlany i chowany jest za pomocą dwóch prostych metod:
CCursor::Show i CCursor::Hide. Jednak metody te nie są statyczne, co oznacza, że musimy dostać się najpierw do systemowego kursora. Jest to bardzo proste, gdyż nasza klasa ma funkcję CCursor* CCursor::GetActiveCursor() która jest statyczna i zawraca wskaźnik do CCursor!
Tak więc jeżeli chcemy używać metod które są w kursorze, musimy je najpierw znać (Plik CCursor.h) oraz dostać się do aktywnego kursora (Metoda CCursor::GetActiveCursor())

Wyświetlanie kursora:


#3.4 Przykładowy przycisk
Dobrze więc, znamy już zasadę działania kursora i CViewAction. Stwórzmy teraz prosty przycisk, który będzie:
-Po "najechaniu" zmieniał kolor na czerwony
-Po "wyjechaniu" zmieniał kolor ponownie na biały.
Let's go!


Jak widać skrypt działa - przycisk zmienia kolor na czerwony, gdy na niego wjedziemy.
W tej części byłoby wszystko, w razie pytań i problemów proszę zadawać pytania w dziale z AST (Wystarczy kliknąć w zębatkę pod moim postem, a zostaniecie przeniesieni do działu z AST)
Pozdrawiam.
 

Siemekk

Siemekk

Złote Wrota
Szlachcic
posty1982
Propsy798
ProfesjaSkrypter
  • Złote Wrota
  • Szlachcic
#4 CInvoke - rozszerzenie
Pierwszy post opisywał podstawę HookEngine w AST. W tym punkcie zostają opisane wszystkie typy Hookowania:
  • Metody wirtualne
  • Zwykłe funkcje
  • Przekierowanie do nowej metody
  • Błędy związane z HookEngine


Cały opis możemy znaleźć tutaj:
 

Bogdan Zwei

Bogdan Zwei

Użytkownicy
Wulgarny skurwiel pierdolony.
posty1817
Propsy512
Profesjabrak
  • Użytkownicy
  • Wulgarny skurwiel pierdolony.
Nie chcę być chujem, ale ostatnia kartka twojego poradnika na google drive jest pusta.
 
:ok: zachęca do dalszej pomocy. Nie zapominaj o tym!

Prywatne wiadomości typu "Ej, pomocy" kasuję od razu. Od tego jest forum, a nie PW.

To me, defeat in anything is merely temporary, and its punishment is but an urge for me to greater effort to achieve my goal. Defeat simply tells me that something is wrong in my doing; it is a path leading to success and truth.

In order to realize our true self we must be willing to live without being dependent upon the opinion of others.

Siemekk

Siemekk

Złote Wrota
Szlachcic
posty1982
Propsy798
ProfesjaSkrypter
  • Złote Wrota
  • Szlachcic
#5 Wywołanie funkcji skryptowych z poziomu C++
Wywołanie funkcji z Gothic'a w AST nie jest dość trudne, lecz nie polega to na zasadzie przedstawionej przykładem niżej.
Funkcja w skryptach Gothic'a:

Niewłaściwe wywołanie funkcji w C++:

Jest to podstawowy błąd, ponieważ C++ nie zna skryptów Gothica...
Ale jak wywołać takie funkcje  :hmmm:

Aby wywołać taką funkcję musimy użyć parsera. Parser gry przechowuje wszystkie informacje o języku Daedalus. On sprawdza wszystkie klamry, średniki, funkcje itp. Funkcja jest w nim zapisana, jednak musimy ją znaleźć wywołać.
Kroki jakie musimy dokonać to:
  • Zapisać do zmiennej indeks funkcji
  • Sprawdzić wartość funkcji
  • Wywołać funkcję gdy indeks jest większy od zera
  • Opcjonalnie zapisać to co funkcja zawróci do odpowiedniej zmiennej
Tak naprawdę mamy trzy kroki które zostały przedstawione w tym kodzie:

W taki sposób po naciśnięciu na klawisz J pojawi się print z napisem "Wywołałem".
Brawo - umiesz wywołać  funkcję Gothica z poziomu C++ (AST).
Jednak funkcje mogą również posiadać argumenty lub coś zawracać... Zajmijmy się teraz tym drugim - pobranie wartości jaką zawróciła funkcja. Zacznijmy od lekkiej modyfikacji naszej funkcji w skryptach gry.

Teraz nasza funkcja zawróci 100, lecz aby zachować ten wynik do zmiennej typu int w C++ musimy użyć wskaźników, przy okazji realizując pozostałe 3 punkty więc, nasz kod będzie wyglądał tak jak na obrazku zamieszczonym niżej.

Jak widać, krok 3 oraz 4 są wykonane w tym samym momencie, co jest logiczne, bo funkcja nie musiała zostać zapisana do zmiennej. Teraz, gdy użyjemy naszej zmiennej to będzie ona miała wartość 100. Przykład kodu:
Say::Box(zSTRING(ret)); // Wyświetli MessageBox'a z tekstem "100"

To na koniec zostały argumenty, są one po prostu banalne, ponieważ podczas wywołania parser->CallFunc, po indeksie wypisujemy po przecinku argumenty (Ile tylko chcemy). Zatem z tą wiedzą zróbmy prostą funkcję dodawania. Krok pierwszy to przebudowa funkcji w skryptach.

Funkcja otrzymała argumenty, i za ich pomocą zawraca wartość. Teraz została przebudowa kodu w C++.

Kod wyświetli MessageBox'a z wynikiem dodawania liczb podanych w argumentach.
Mam nadzieję, że dzięki temu będziesz już wiedział jak wywołać taką funkcję w C++.

WAŻNE!
W AST 003, w metodzie CallFunc, jako argumenty możemy podawać tylko int, tak samo funkcja zawróci tylko int. W wersji Union metoda została rozszerzona a podawanie jako argumentów obiektów klas czy ciągów znaków.

Pozdrawiam i miłej zabawy  :D
 


0 użytkowników i 1 Gość przegląda ten wątek.
0 użytkowników
Do góry