Jestem zmuszony napisać nowy art, po usunięciu poprzedniego (ci admini...). Z artykułu dowiecie się m. in.
- Jak zmieniać tytuł okna Tibii (i nie tylko)
- Jak obliczać exp do levelu
- Jak wyświetlać tekst w pasku statusu Tibii
- Oraz wiele innych rzeczy...
Aby móc napisać własnego bota, będzie niezbędne:
- Kompilator Pascala (Delphi lub Lazarus)
- Dobre chęci
- Znajomość angielskiego i podstaw Pascala
Włączamy Delphi (lub Lazarusa - choć dalszy ciąg artykułu będzie oparty na Delphi 7 Personal). Klikamy File -> New -> Application. Po chwili pojawi się formularz designera, a za nim okno kodu. Rozmiary okien można zmieniać, klikając na ich krawędziach i przeciągając. Umieszczamy na formie przycisk, klikając jego symbol na palecie, a potem klikając i przeciągając w formie.
Jego właściwości możemy zmienić, zaznaczając go i przechodząc do Object Inspectora. Następnie klikamy przycisk dwukrotnie, wywołując okno kodu z procedurą:
procedure TForm1.Button1Click(Sender: TObject);
begin
end;
Uzupełnij ją tak, aby wyglądała jak poniżej:begin
end;
procedure TForm1.Button1Click(Sender: TObject);
var
Tibia: Cardinal;
begin
Tibia := FindWindow('TibiaClient',nil);
SetWindowText(Tibia,PChar('Tibia <<Mój Bot>>'));
end;
Tekst "Tibia <<Mój Bot>>" możemy oczywiście zamienić na coś innego.var
Tibia: Cardinal;
begin
Tibia := FindWindow('TibiaClient',nil);
SetWindowText(Tibia,PChar('Tibia <<Mój Bot>>'));
end;
Zapiszmy teraz program. Wciśnij Ctrl+S. Pojawi się najpierw okno zapisu pliku *.pas, jest to plik kodu źródłowego formy. Nadaj mu nazwę BotMForm.pas lub podobną. Potem pojawi się okno zapisu pliku *.dpr. Będzie to jednocześnie nazwa naszego programu. Zapisz go jako TibiaBot lub coś podobnego. Potem kliknij Run -> Run, aby uruchomić bota.
3. Dostęp do pamięci procesu
Większość operacji, które wykonuje bot, wymaga ingerencji w pamięć procesu Tibii. Jednak niewłaściwe postepowanie w tym zakresie może prowadzić do błędów, wskutek czego najczęściej zawieszał się będzie klient gry. Każda operacja wymaga podania identyfikatora procesu, adresu pamięci, oraz bufora i jego długości. Korzystając z funkcji udostępnionych przez MasterT (www.tibiatech.tk), możemy w łatwy sposób operować na obszarze pamięci Tibii.
Aby skorzystać z operacji na pamięci, zadeklarujemy kilka stałych będących adresami w pamięci Tibii. Stałe definiujemy pomiędzy słowami var a implementation. Pokażę, jak będzie wyglądać nasza deklaracja:
(...)
var
Form1: TForm1;
const
BATTLELIST_START = $005F7994;
BATTLELIST_END = $005FD460;
PLAYER_X = $00602B08;
PLAYER_Y = $00602B04;
PLAYER_Z = $00602B00;
PLAYER_LEVEL = $005F7920;
PLAYER_EXP = $00605A04; // zmieniony - Tibia 7.9
STATUS_TEXT = $0074F1D0;
STATUS_TIMER = $0074F1CC;
SPEED_USE = $0074DBD0;
implementation
(...)
var
Form1: TForm1;
const
BATTLELIST_START = $005F7994;
BATTLELIST_END = $005FD460;
PLAYER_X = $00602B08;
PLAYER_Y = $00602B04;
PLAYER_Z = $00602B00;
PLAYER_LEVEL = $005F7920;
PLAYER_EXP = $00605A04; // zmieniony - Tibia 7.9
STATUS_TEXT = $0074F1D0;
STATUS_TIMER = $0074F1CC;
SPEED_USE = $0074DBD0;
implementation
(...)
Zamiast unitu Mem.pas, wykorzystamy własne procedury, podane na końcu.
Math zawiera procedury matematyczne: potęgi, pierwiastki, itp.
4. Poziom i doświadczenie
Procedury poznane w poprzednim rozdziale przydadzą się teraz do obliczania doświadczenia potrzebnego do zdobycia poziomu. Przy okazji wykorzystamy też potęgi z unitu Math.
Wzór na obliczenie doświadczenia to (zaczerpnięty z Tibia.pl):
W(x) = 50/3*x^3 - 100*x^2 + 850/3*x - 200
Przekształcony do Object Pascal i osadzony w funkcji, wygląda następująco:
function ExpToLevel(Level: Integer): Integer;
begin
Result := Round(50/3*Power(Level,3) - 100*Power(Level,2) + 850/3*Level - 200);
end;
function CalculateLeftExp: string;
var
Tibia: Cardinal;
AExp, ALevel, ANextLevelExp, AExpLeft: Cardinal;begin
{Obliczamy zawartość zmiennych}
Tibia := FindWindow('TibiaClient',nil);
AExp := MemoryReadInt(Tibia,PLAYER_EXP,Len,Rdd);
ALevel := MemoryReadInt(Tibia,PLAYER_LEVEL,Len,Rdd);
ANextLevelExp := ExpToLevel(ALevel+1);
AExpLeft := ANextLevelExp-AExp;
{Formatujemy wyjściowy ciąg tekstowy}
Result := Format('Level: %d. Ilość exp.: %d. Pozostały exp. do levelu %d: %d.',[ALevel,AExp,ALevel+1,AExpLeft]);
end;
Funkcje te należy umieścić gdzieś w sekcji implementation. W ten sposób mamy już funkcję formatującą komunikat, pozostało go wyświetlić.begin
Result := Round(50/3*Power(Level,3) - 100*Power(Level,2) + 850/3*Level - 200);
end;
function CalculateLeftExp: string;
var
Tibia: Cardinal;
AExp, ALevel, ANextLevelExp, AExpLeft: Cardinal;begin
{Obliczamy zawartość zmiennych}
Tibia := FindWindow('TibiaClient',nil);
AExp := MemoryReadInt(Tibia,PLAYER_EXP,Len,Rdd);
ALevel := MemoryReadInt(Tibia,PLAYER_LEVEL,Len,Rdd);
ANextLevelExp := ExpToLevel(ALevel+1);
AExpLeft := ANextLevelExp-AExp;
{Formatujemy wyjściowy ciąg tekstowy}
Result := Format('Level: %d. Ilość exp.: %d. Pozostały exp. do levelu %d: %d.',[ALevel,AExp,ALevel+1,AExpLeft]);
end;
Umieść drugi przycisk, kliknij go podwójnie i uzupełnij:
procedure TForm1.Button2Click(Sender: TObject);
begin
ShowMessage(CalculateLeftExp);
end;
5. Komunikaty statusu
Wyskakujące okienko to nie był najlepszy pomysł. Takie rozwiązanie jest niewygodne i przeszkadza w grze. Grając w Tibię, często można zaobserwować komunikaty pojawiające się na dole ekranu gry, a nad konsolą. Pokażę, jak umieścić tam własne komunikaty.
W sekcji implementation umieść procedurę:
procedure SetStatusText(Text: string; Time: Integer);
var
Written: LongWord;
I: Byte;
Tibia: Cardinal;
begin
Tibia := FindWindow('TibiaClient',nil);
for I := 0 to 255 do
MemoryWriteInt(Tibia,STATUS_TEXT+I,$00,1,Written);
MemoryWriteStr(Tibia,STATUS_TEXT,Text,Length(Text) ,Written);
MemoryWriteInt(Tibia,STATUS_TIMER,Time*10,4,Writte n);
end;
Procedura czyści bufor tekstu statusu, a następnie umieszcza nowy tekst i wyświetla go przez podaną ilość sekund. Zmieńmy więc procedurę Button2Click: var
Written: LongWord;
I: Byte;
Tibia: Cardinal;
begin
Tibia := FindWindow('TibiaClient',nil);
for I := 0 to 255 do
MemoryWriteInt(Tibia,STATUS_TEXT+I,$00,1,Written);
MemoryWriteStr(Tibia,STATUS_TEXT,Text,Length(Text) ,Written);
MemoryWriteInt(Tibia,STATUS_TIMER,Time*10,4,Writte n);
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
SetStatusText(CalculateLeftExp,10);
end;
6. Globalny hook na klawiaturębegin
SetStatusText(CalculateLeftExp,10);
end;
Co prawda komunikat nie jest wyświetlany już w osobnym oknie, ale...
Sprawdzanie stanu expa wywołujemy dalej za pomocą przycisku. Czas wziąć się za hotkeye.
Sekcję var uzupełniamy:
var
Form1: TForm1;
HookID: HHOOK;
type
PKbdDllHookStruct = ^TKbdDllHookStruct;
TKbdDllHookStruct = record
vkCode,
ScanCode,
Flags,
Time,
dwExtraInfo: Integer;
end;
const
WH_KEYBOARD_LL = 13;
Skoro zadeklarowaliśmy już odpowiednie typy i stałe, czas założyć hooka.Form1: TForm1;
HookID: HHOOK;
type
PKbdDllHookStruct = ^TKbdDllHookStruct;
TKbdDllHookStruct = record
vkCode,
ScanCode,
Flags,
Time,
dwExtraInfo: Integer;
end;
const
WH_KEYBOARD_LL = 13;
Poniższe funkcje wklejamy gdzieś w sekcji implementation (z 4programmers):
function LLKeyHookFunc(HookCode: Integer; KeyCode: wParam; KStrokeInfo: lParam): LResult; stdcall;
var
Struct: PKbdDllHookStruct;
begin
Struct := Ptr(KStrokeInfo);
if (HookCode >= 0) then
begin
{Pojedyncza funkcja}
if (Struct.vkCode = VK_F7) and(GetAsyncKeyState(VK_CONTROL)<-32766) then begin
SetStatusText(CalculateLeftExp,10);
end;
end;
{Uwaga! POPRAWIONE}
Result := CallNextHookEx(HookID, HookCode, KeyCode, KStrokeInfo);
end;
var
Struct: PKbdDllHookStruct;
begin
Struct := Ptr(KStrokeInfo);
if (HookCode >= 0) then
begin
{Pojedyncza funkcja}
if (Struct.vkCode = VK_F7) and(GetAsyncKeyState(VK_CONTROL)<-32766) then begin
SetStatusText(CalculateLeftExp,10);
end;
end;
{Uwaga! POPRAWIONE}
Result := CallNextHookEx(HookID, HookCode, KeyCode, KStrokeInfo);
end;
procedure LockSystem;
begin
HookID := SetWindowsHookEx (WH_KEYBOARD_LL, @LLKeyHookFunc, hInstance, 0);
end;
procedure UnLockSystem;
begin
UnHookWindowsHookEx (HookID);
end;
Klikamy podwójnie formę w pustym miejscu, i uzupełniamy:begin
HookID := SetWindowsHookEx (WH_KEYBOARD_LL, @LLKeyHookFunc, hInstance, 0);
end;
procedure UnLockSystem;
begin
UnHookWindowsHookEx (HookID);
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
LockSystem;
end;
Przechodzimy do zdarzenia OnClose, klikając w Object Inspectorze zakładkę Events, znajdując linijkę OnClose, i klikając ją podwójnie. W otwartej sekcji kodu uzupełniamy:begin
LockSystem;
end;
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
UnlockSystem;
end;
Wciskamy Ctrl+S, a potem F9, aby uruchomić program. Kombinacją Ctrl+F7 wyświetlimy pozostały exp do levelu.begin
UnlockSystem;
end;
7. Czytanie mapy
Ta technika przyda nam się przy robieniu bota do autofishingu. Będziemy musieli odczytać, w jakich polach znajduje się woda (chyba nie będziemy łowić trawy).
Przedstawiony przeze mnie sposób został przetłumaczony (z C++ na Pascal/z angielskiego na polski) tylko i wyłącznie we własnym zakresie. Tekst źródłowy (po angielsku / w C++) by Vanitas: http://71.18.84.43/articles/ta_art_mapread.html
Struktury rekordów w powyższym dokumencie są w C++. Przetłumaczone na Pascal wyglądają tak:
type
TObjectData = record
objectId: integer;
data1: integer;
data2: integer;
end;
TTileDef = record
stackedObjectCount: integer;
objectData: array[0..12] of TObjectData;
padding1: integer;
padding2: integer;
end;
TTileDefMap = array[0..7,0..13,0..17] of TTileDef;
var
TDMap: TTileDefMap;
Skoro mamy już strukturę danych, trzeba ją wczytać z pamięci Tibii. Adres wskaźnika na mapę to w Tibii 7.8 $606038. Niestety, funkcje z unitu Mem.pas nie przewidują wczytywania struktur. Jednak bazując na nich, napiszemy własną (poprawioną).TObjectData = record
objectId: integer;
data1: integer;
data2: integer;
end;
TTileDef = record
stackedObjectCount: integer;
objectData: array[0..12] of TObjectData;
padding1: integer;
padding2: integer;
end;
TTileDefMap = array[0..7,0..13,0..17] of TTileDef;
var
TDMap: TTileDefMap;
function MemoryReadMap(var Readed: DWORD): TTileDefMap;
var PID: Integer;
PHandle: Integer;
Buff: TTileDefMap;
WindowHandle: hWnd;
Address: Integer;
Length, Rdd: LongWord;
begin
Length := SizeOf(Result);
WindowHandle := FindWindow('TibiaClient',nil);
Address := $606038;
Address := MemoryReadInt(WindowHandle,Address,4,Rdd);
if WindowHandle = 0 then Exit;
GetWindowThreadProcessId(WindowHandle, @PID);
PHandle := OpenProcess(PROCESS_VM_READ, False, PID);
if PHandle = 0 then Exit;
ReadProcessMemory(PHandle, Pointer(Address), @Buff, Length, Readed);
Result := (Buff);
CloseHandle(PHandle);
end;
W ten sposób mapa powinna być wczytana do pamięci w postaci trójwymiarowej tablicy. Tą jednak zostawimy na potem.var PID: Integer;
PHandle: Integer;
Buff: TTileDefMap;
WindowHandle: hWnd;
Address: Integer;
Length, Rdd: LongWord;
begin
Length := SizeOf(Result);
WindowHandle := FindWindow('TibiaClient',nil);
Address := $606038;
Address := MemoryReadInt(WindowHandle,Address,4,Rdd);
if WindowHandle = 0 then Exit;
GetWindowThreadProcessId(WindowHandle, @PID);
PHandle := OpenProcess(PROCESS_VM_READ, False, PID);
if PHandle = 0 then Exit;
ReadProcessMemory(PHandle, Pointer(Address), @Buff, Length, Readed);
Result := (Buff);
CloseHandle(PHandle);
end;
8. Nick postaci
Nick postaci nie znajduje się na żadnym konkretnym miejscu. Jest on umieszczony gdzieś na battleliście. Poprzedzają go 4 bajty identyfikatora postaci. Te 4 bajty są również pod adresem $5F7930. Skanując pamięć od $5F7994 do $5FD754, można znaleźć również nick postaci.
Ważna informacja!
Niestety, w programie mogą pojawić się błędy (głównie w adresach pamięci), gdyż ta sprawa nie jest dokładnie udokumentowana. Nie odpowiadam za błędy i zmiany w kliencie, których dokonujesz na własną odpowiedzialność.
Jeżeli znajdziesz jakieś błędy, to proszę, dokładnie je opisz i wyślij do mnie na PW.
Ważne!!!
PS. Jako że nie dysponuję już plikiem Mem.pas, zamiast tego używamy poniższej funkcji MemoryReadInt
function MemoryReadInt(Address: Cardinal): Longword;
var
ProcId: Cardinal;
tProc: THandle;
NBR: Cardinal;
value: Longword;
begin
GetWindowThreadProcessId(FindWindow('TibiaClient', Nil), @ProcId);
tProc:= OpenProcess(PROCESS_ALL_ACCESS, False, ProcId);
ReadProcessMemory(tProc, Ptr($605A04), @value, 4, NBR);
CloseHandle(tProc);
Result := Value;
end;
Zakładki