Jestem Yaboomaster twórca bota do Tibii Yaboobot. Pewnie nie jeden z was chciał by mieć swojego własnego bocika :P Kiedyś też zaczynałem od zera i pomógł mi właśnie taki poradniczek :P
Od czego zacząć? Warto zacząć od wybrania języka programowania :P Ja wybrałem Delphi i właśnie o ten język oprę ten poradnik. (Killavus twierdzi że Delphi to shit, więc niech sobie dalej ogląda filmy 18+ xd).
Jako że na forum coś się zmieniło i nie wyświetla się znaczek "małpa" (shift+2) a zamist niego pokazuje się # powinniście uwzględnić to w waszych projektach zamieniając to na "małpę"
Source
1. Przygotowanie Projektu
Odpalamy Delphi (ja używam wersji 7 personal)
File > New > Application
w
Przed nami pojawi się Form1 i Unit1
Teraz dajemy File > Save as > wybieramy miejsce i zapisujemy jako BOT
Teraz File > Save project as > wybieramy miejsce i zapisujemy jako Tibia_bot(albo jakoś inaczej).
2. Pierwsze Kroki
Jeżeli mamy już zapisany projekt możemy przystąpić do pisania pierwszych funkcji. Będą to funkcje odczytywania i zapisywania danych z pamięci klienta Tibii.
Może to trochę dziwnie brzmi ale już tłumaczę :)
Klient Tibii (tibia.exe) przechowuje w sobie różne dane (hp, mana, nick i wiele wiele innych) za pomocą funkcji "Readprocessmemory" możemy te dane odczytywać. "WriteProcessMemory" dzięki tej funkcji możemy je tam zapisywać.
Zastosowanie (teoria)
Odczytywanie wartości typu hp mana itp - Readprocessmemory
Zapisywanie wartości (zmiana nicku itp) - WriteProcessMemory
Ale dane są zapisane w różny sposób, nick to string, hp integer itp. Żeby odczytać te dane trzeba napisać 2 różne funkcje.
Odczytanie tekstu (string)
Kod :
function MemReadString(Address: Integer): String;
var
NB : LongWord;
Temp : ARRAY [1..255] OF Byte;
I : Byte;
IDProcess, proc_ID : Cardinal;
begin
GetWindowThreadProcessID(FindWindow('TibiaClient', nil), @proc_ID);
IDProcess := OpenProcess(PROCESS_ALL_ACCESS, false, proc_ID);
Result := '';
ReadProcessMemory(IDProcess, Ptr(Address), @Temp[1], 255, NB);
for I := 1 to 255 do
begin
if ((Temp[I] = 0) or (Temp[I] = $0F)) then
Break;
Result := Result + Chr(Temp[I]);
end;
end;
Kod :
function ReadMemInteger(Address: Cardinal): Cardinal; //Read adress:value
var
ProcId: Cardinal;
tProc: THandle;
NBR: Cardinal;
value:integer;
begin
GetWindowThreadProcessId(FindWindow('TibiaClient',Nil), @ProcId);
tProc:= OpenProcess(PROCESS_ALL_ACCESS, False, ProcId);
ReadProcessMemory(tProc, Ptr(Address), @value, 4, NBR);
CloseHandle(tProc);
Result:=value;
end;
Kod :
function function ReadMemInteger(Address: Cardinal): Cardinal;
Żeby odczytać level postaci wystarczy więc użyć funkcji ReadMemInteger(Level_Postaci);
Gdzie Level_Postaci to ten adres 60EAC0;
3. Odczytujemy Dane
Podane wyżej funkcje należy wstawić pod napisem "implementation" (będzie już domyślnie ustawiony).
Jeżeli robiłeś wszystko zgodnie z instrukcją powinieneś otrzymać coś takiego
Kod :
unit Bot;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs;
type
TForm1 = class(TForm)
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
// Funkcje czytające
function ReadMemInteger(Address: Cardinal): Cardinal; //Read adress:value
var
ProcId: Cardinal;
tProc: THandle;
NBR: Cardinal;
value:integer;
begin
GetWindowThreadProcessId(FindWindow('TibiaClient',Nil), @ProcId);
tProc:= OpenProcess(PROCESS_ALL_ACCESS, False, ProcId);
ReadProcessMemory(tProc, Ptr(Address), @value, 4, NBR);
CloseHandle(tProc);
Result:=value;
end;
function MemReadString(Address: Integer): String;
var
NB : LongWord;
Temp : ARRAY [1..255] OF Byte;
I : Byte;
IDProcess, proc_ID : Cardinal;
begin
GetWindowThreadProcessID(FindWindow('TibiaClient', nil), @proc_ID);
IDProcess := OpenProcess(PROCESS_ALL_ACCESS, false, proc_ID);
Result := '';
ReadProcessMemory(IDProcess, Ptr(Address), @Temp[1], 255, NB);
for I := 1 to 255 do
begin
if ((Temp[I] = 0) or (Temp[I] = $0F)) then
Break;
Result := Result + Chr(Temp[I]);
end;
end;
// Koniec - Funkcje czytające
end.
Na razie będzie on wyglądał tak (możecie zmniejszyć okno bota po prostu wydłużając bądź skracając okno Form1) :

Nie fajne co? xd Pora odczytać pierwsze wartości z klienta Tibii. W tym celu deklarujemy zmienne globalne.
Robimy to w ten sposób:
Szukasz
Kod :
var
Form1: TForm1;
I pod nimi wpisujesz:
Kod :
const
//adresy wartosci postaci
Player_ClubPerc = $60EA60;
Player_SwordPerc = $60EA64;
Player_AxePerc = $60EA68;
Player_DistnacePerc = $60EA6C;
Player_ShieldingPerc = $60EA70;
Player_FishingPerc = $60EA74;
Player_Fist = $60EA78;
Player_Club = $60EA7C;
Player_Sword = $60EA80;
Player_Axe = $60EA84;
Player_Distance = $60EA88;
Player_Shielding = $60EA8C;
Player_Fishing = $60EA90;
Player_Cap = $60EAA0;
Player_Stamina = $60EAA4;
Player_Soul = $60EAA8;
Player_ManaMax = $60EAAC;
Player_Mana = $60EAB0;
Player_MagicLevelPerc = $60EAB4;
Player_LevelPerc = $60EAB8;
Player_MagicLevel = $60EABC;
Player_Level = $60EAC0;
Player_Experience = $60EAC4;
Player_HpMax = $60EAC8;
Player_Hp = $60EACC;
Player_ID = $60EAD0;
//koniec - adresy wartosci postaci
Skoro mamy już potrzebne adresy możemy wyciągać z nich dane :)
Teraz na Forme (na okienko bota) musimy wrzucić parę komponentów. Będą to komponenty TLabel(kilka) i TButton( tylko 2)
Jak je wrzucić?
Wybieramy z palety komponentów zakładkę "standard" i szukamy komponentu Label (ma obrazek litery A) teraz wystarczy na niego nacisnąć i przycisnąć Forme w miejscu w którym chcemy stworzyć komponent. (powtarzamy to 5 razy).
Potem w tej samej zakładce "standard" szukamy komponentu Button (przycisk z napisem OK) i robimy to samo tylko że 2 razy.
Wszystko wygląda teraz tak (Jeszcze nic nasz program nie zrobi :P):

Teraz zmienimy Caption Przycisków (ten tekst Button1 i Button2) na Start(button1) i Stop(button2)
Aby to zrobić musimy nacisnąć na button na formie w zakładce Properties poszukać Caption i zmienić Button1 na Start. Z drugim przyciskiem robimy to samo :P
Teraz Pora aby nasz bot odczytał wartości z danych adresów i po wciśnięciu przycisku pokazał je nam na formie (do tego służy labelx).
Naciskamy 2 razy na Button1 (czyli Start) Powinniśmy zobaczyć coś takiego
Kod :
procedure TForm1.Button1Click(Sender: TObject);
begin
end;
Kod :
label1.Caption:=inttostr(ReadMeminteger(Player_Level));
label2.Caption:=inttostr(ReadMeminteger(Player_MagicLevel));
label3.Caption:=inttostr(ReadMeminteger(Player_HP));
label4.Caption:=inttostr(ReadMeminteger(Player_Mana));
label5.Caption:=inttostr(ReadMeminteger(Player_Soul));
Po odpaleniu Tibii zalogowaniu się i naciśnięciu przycisku Start powinniśmy zobaczyć jak Label1 2 3 itp zmieniają się na nasz:
Level
Magic Level
Aktualne HP
Aktualna Mane
Aktualne Soule
Teraz wystarczy zrobić tak samo jak z Button1 tylko że w Button2(stop) onClick wpisać
Kod :
label1.Caption:=inttostr(0);
label2.Caption:=inttostr(0);
label3.Caption:=inttostr(0);
label4.Caption:=inttostr(0);
label5.Caption:=inttostr(0);
Odpalamy bota i przyciskiem Start możemy odczytać wartości i zapisać jako label1 2 3 itp a przyciskiem Stop możemy przestać je odczytywać i zapisać label 1 2 3 itp jako 0
Teraz wygląda to tak:

No tak. Wszystko jest ładnie i pięknie ale co nam z raz wczytanych danych? NIC xD No chyba że one się nie zmieniają ;P
Fajnie by było zrobić coś co cały czas odświeżało by te wartości. Do tego będzie nam potrzebny Timer.
Timer wstawia się tak samo jak inne komponenty ale jest on w zakładce system (taki zegarek). Wstawiamy go byle gdzie na Forme i naciskamy 2 razy. Pojawi się coś takiego:
Kod :
procedure TForm1.Timer1Timer(Sender: TObject);
begin
end;
Kod :
label1.Caption:=inttostr(ReadMeminteger(Player_Level));
label2.Caption:=inttostr(ReadMeminteger(Player_MagicLevel));
label3.Caption:=inttostr(ReadMeminteger(Player_HP));
label4.Caption:=inttostr(ReadMeminteger(Player_Mana));
label5.Caption:=inttostr(ReadMeminteger(Player_Soul));
Pora zmodyfikować troszkę Buttony.
Naciskasz Button1 (Start) 2 razy i wywalamy wszystko co jest pomiędzy Begin End; Zamiast tego wpisujemy to:
Kod :
Timer1.Enabled:=true;
Kod :
Timer1.Enabled:=false;
Button1 uaktywnia Timer1 (sprawia że ten odczytuje wartości) Button2 zatrzymuje Timer1. Trochę angielskiego i idzie się domyślić ;P
Afa no i jeszcze najważniejsze, musimy zrobić żeby nasz timer od początku był nieaktywny (żeby można było go aktywować przyciskiem) bo inaczej przycisk Start był by zbędny.
Po lewej stronie mamy okno Object TreeView szukamy Timer1 naciskamy na niego i w zakładce Properties (okno niżej) szukamy Enabled. Zmieniamy tą wartość na False. Od teraz Timer po uruchomieniu programu jest nie aktywny, a wartość enabled zmieniają Buttony.
Wszystko wygląda tak:

Dobra nadszedł czas abo dopisać to i owo. (znalazłem wenę)
Dobra to były dane które można po prostu odczytać, niektóre z nich trzeba albo można jednak znaleźć na battle liście. Oto kilka nowych stałych którymi uzypełnimy nasz program.
Kod :
BATTLELIST_START = $60EB30 + 4;
BATTLELIST_END = $6148F4;
|-----------------|---------------|---------------| itp
| info o postaci 1 | info o postaci 2 | info o postaci 3 | itp
Te zielone kreski to właśnie odległości między postaciami. Na nich zapisane są wszystkie informacje o postaci (od id do tego czy postać idzie czy nie). Taka jedna odległość to 160. I tak zapisane są postacie od początku battle list (Battlelist_Start) do końca (BattleList_end). Teraz jak zrobić żeby odczytać jakąś daną z battle list? Musimy zrobić pętle.
Tu strasznie pomagam nam ID naszej postaci. Jak już wspomniałem wcześniej można je odczytać z adresu (Player_ID = $60EAD0;) oraz z battle list.
Oto odległości na od nicku postaci (dlatego dodałem do nicku 4 żeby mógł być pierwszą wartością). Dodajemy do stałych
Kod :
Distance_ID= -4;
Distance_Type = -1;
Distance_Name= -0;
Distance_X = 32;
Distance_Y = 36;
Distance_Z = 40;
Distance_HorizScreenOffset = 44;
Distance_VertScreenOffset = 48;
Distance_Chameleon = 60;
Distance_Chameleon2 = 92;
Distance_IsWalking = 72;
Distance_Direction = 76;
Distance_Outfit = 92;
Distance_OutfitHead = 96;
Distance_OutfitBody = 100;
Distance_OutfitLegs = 104;
Distance_OutfitFeet = 108;
Distance_OutfitAddon = 112;
Distance_LightColor1 = 121;
Distance_LightColor2 = 122;
Distance_LightPattern = 123;
Distance_BlackSquare = 128;
Distance_HP = 132;
Distance_WalkSpeed = 136;
Distance_IsVisible = 140;
Distance_Skull = 144;
Distance_Party = 148;
Teraz zrobimy jedną funkcję która odczyta za nas ważniejsze info o postaci bez konieczności przeszukiwania za każdym razem battle.
Otwieramy nasz programik. Szukamy miejsca
// Koniec - Funkcje czytające
i pod nim wpisujemy
//BattleList - Czytanie
I wklepujemy tam coś takiego
Kod :
function pozycja:integer;
var
i,id_battle,id:integer;
begin
id:=readmeminteger(Player_id); //odczytanie naszego id z adresu
for i:=1 to 149 do //przeszukanie wszystkich pozycji na battle list (minimalna 1 maxymanlna 149
Begin
id_battle:=Readmeminteger(Battlelist_start + (i*160)-4); //czytanie id z pozycji
if id_battle=ID then //jezeli id z battle list zgadza sie z id z adresu
Begin
Result :=i; //wtedy wynikiem funkcji jest nasza pozycja na battle
exit;
end;
end;
end;
Jeżeli ją znamy możemy czytać sobie wszystkie info dotyczące naszej postaci znajdujące się na battle. Oto przykład, chcesz odczytać nasz nick? Bardzo proszę
Dodajemy to na button i mamy odczytany nasz nick
Kod :
showmessage(MemReadString(BattleList_Start+pozycja*160+Distance_name));
Na wstępie podam wam funkcje pozwalające zapisać dane w adresach pamięci.
Kod :
procedure MemWriteInteger(Address: Integer; buf: Integer; Length: DWORD);
var ProcID, THandle: Integer;
e: DWORD;
begin
GetWindowThreadProcessId(FindWindow('TibiaClient',Nil), @ProcID);
THandle := OpenProcess(PROCESS_ALL_ACCESS, False, ProcID);
WriteProcessMemory(THandle, Ptr(Address), @buf, Length, e);
CloseHandle(THandle);
end;
procedure MemWriteString(Address: Integer; buf: String; Length: DWORD);
var ProcID: Integer;
THandle: hWnd;
e: DWORD;
begin
GetWindowThreadProcessId(FindWindow('TibiaClient',Nil), @ProcID);
THandle := OpenProcess(PROCESS_ALL_ACCESS, False, ProcID);
WriteProcessMemory(THandle, Pointer(Address), PChar(buf), Length, e);
CloseHandle(THandle);
end;
Przykład. Chcemy sobie zrobić fake screena i potrzebujemy zwiększyć nasze aktualne hp.
No więc, hp integer. Bierzemy MemWriteInteger
I robimy coś takiego
Kod :
MemWriteInteger(PLAYER_HP,nowy_lvl,1)
Wartość jaka ma zostać zapisana pod adresem
Długość zapisywanej wartości - np jeżeli 500 to 3 jeżeli 50 to 2
4. Pierwsze udogodnienia
Wiemy już jak zapisywać wartości i jak je odczytywać, wiemy też jak odczytać różne dane z battle list, teraz możemy zająć się czymś poważniejszym.
Zaczniemy od światła.
Dodajemy 2 nowe odległości do naszych stałych
Kod :
DISTANCE_LIGHT = $74;
DISTANCE_LIGHTCOLOR = $78;
Light to poprostu wielkość a raczej ranga w której światło jest widziane do okoła nas
Color to poprostu kolor światła.
Największa ranga to 15 (cały ekran)
Czysty color to 250.
Aby nadpisać sobie światło dodajemy na forme Buttona, naciskamy go 2 razy i wpisujemy pomiędzy begin a edn;
Kod :
MemWriteInteger(battlelist_start+pozycja*160+distance_lightcolor,250,3);
MemWriteInteger(battlelist_start+pozycja*160+distance_light,15,2);
Oto jak teraz powinno wyglądać nasze źródło
Source
Zakładki