![]() |
|
| Jak napisać własne macro do Tibii | |
« Powrót do listy Artykułów |
|
|
banner requires iframes |
|
| Autor: Yaboomaster | Data: 15.08.2007 |
| Ocena: 5 | >>Komentarze (224)<< |
Witam ;) 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) [CODE]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;[/CODE] Odczytanie liczb (integer) [CODE]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;[/CODE] Już objaśniam ich działanie [CODE] function function ReadMemInteger(Address: Cardinal): Cardinal; [/CODE] Address - wartości w kliencie Tibii mają swoje miejsca(czyt. Adresy). Adresy te wyglądają np tak "60EAC0" (w tym miejscu zapisany jest level twojej postaci). Ż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 [CODE]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.[/CODE] Teraz naciśnij Ctrl+F9 (projekt zostanie skompilowany) możesz go uruchomić wciskając F9 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 [CODE]var Form1: TForm1;[/CODE] - (zmienne globalne) I pod nimi wpisujesz: [CODE] 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 [/CODE] (daj save na wszeli wypadek Ctrl+S) 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 [CODE]procedure TForm1.Button1Click(Sender: TObject); begin end;[/CODE] I między begin i end; wpisujemy [CODE]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));[/CODE] Teraz wciskamy ctrl+F9 potem ctrl+s potem F9 ;) 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ć [CODE]label1.Caption:=inttostr(0); label2.Caption:=inttostr(0); label3.Caption:=inttostr(0); label4.Caption:=inttostr(0); label5.Caption:=inttostr(0);[/CODE] Teraz wciskamy ctrl+F9 potem ctrl+s potem F9 ;) 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: [CODE]procedure TForm1.Timer1Timer(Sender: TObject); begin end;[/CODE] Między Begin i End; wpisujemy [CODE]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));[/CODE] Jak to działa? Po otwarciu naszego programu Timer będzie co 1000 ms odczytywał nasze dane i wpisywał je do labeli. Pora zmodyfikować troszkę Buttony. Naciskasz Button1 (Start) 2 razy i wywalamy wszystko co jest pomiędzy Begin End; Zamiast tego wpisujemy to: [CODE]Timer1.Enabled:=true;[/CODE] W Button2 też wywalamy tekst i wpisujemy: [CODE]Timer1.Enabled:=false;[/CODE] Co to jest? 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. [CODE]BATTLELIST_START = $60EB30 + 4; BATTLELIST_END = $6148F4;[/CODE] Dlaczego dodałem do battlelist start 4? Z prostej przyczyny‚ chciałem aby nick był czytany jako pierwszy z battle list :) Oto obrazowe przedstawienie battle listy. |-----------------|---------------|---------------| 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 [CODE] 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;[/CODE] Jak widzicie nasze id można też odczytać z odległości -4 od nicku. No i wszystko było by fajnie gdyby nasz nick był pierwszy na liście. Niestety tak nie jest :) 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 [CODE] 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; [/CODE] No tak ale co nam z naszej pozycji na battle? Bardzo dużo!! 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 [CODE] showmessage(MemReadString(BattleList_Start+pozycja*160+Distance_name));[/CODE] 3. Ingerencja w Klienta Na wstępie podam wam funkcje pozwalające zapisać dane w adresach pamięci. [CODE]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;[/CODE] Jak używamy? 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 [CODE]MemWriteInteger(PLAYER_HP‚nowy_lvl‚1)[/CODE] Adres pod którym chcemy coś zapisać 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 [CODE]DISTANCE_LIGHT = $74; DISTANCE_LIGHTCOLOR = $78;[/CODE] Na początku po odpaleniu nie edytowanego klienta tibii powinniśmy mieć w tych wartościach 0 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; [CODE]MemWriteInteger(battlelist_start+pozycja*160+distance_lightcolor‚250‚3); MemWriteInteger(battlelist_start+pozycja*160+distance_light‚15‚2); [/CODE] Objaśniać tego chyba nie trzeba :) Oto jak teraz powinno wyglądać nasze źródło Source5. Pakiety Teraz chyba najtrudniejsza (przynajmniej do wytłumaczenia) część poradnika. Zacznijmy może od tego żeby pobrać FreeProx http://www.blackdtools.com/freedownloads.php Czym jest freeproxy? Jest to darmowe narzędzie do przechwytywania pakietów wysyłanych i przysyłanych z Klienta do Serwera i na odwrót. Jak odpalić i sprawdzić pakiet? Uruchamiasz freeproxy‚ odpalasz Tibie logujesz się do gry z okienka freeproxy wybierasz "PROXY" i na górze po lewej zaznaczasz "Log Packets" Teraz wystarczy że poruszysz postacią powiedzmy w prawo a zobaczysz coś takiego ![]() Tak wygląda pakiet odpowiedzialny za poruszenie postaci w prawo. Jeżeli chcecie sprawdzić pakiet możecie wybrać z okienka Freeproxy "TOOLS" zaznaczyć Send to Server nacisnąć SEND HEX... i wkleić pakiet :) Tak wygląda pakiet już ładnie uzupełniony i z dodanym nagłówkiem (my tych shitów nie widzimy ale są one niezbędne) Do czasu aż sami nie nauczymy się uzupełniać pakietów pomoże nam w tym biblioteka packet.dll http://www.speedyshare.com/631866607.html Po co nam to? A właśnie po to żeby uzupełnić pakiet pustymi bitami oraz dodać do niego nagłówek. Bardzo upraszcza nam to wysyłanie pakietu. Żeby móc cokolwiek wysłać przy pomocy tej biblioteki musimy dodać odpowiednią procedurę do naszego projektu. [CODE]procedure SendPacket(ProcessID: Cardinal; Packet: Pointer; Encrypt: Boolean; SafeArray: Boolean); stdcall; external 'packet.dll'; [/CODE] Wygląda ona tak i dodajemy ją przed wszystkimi funkcjami które już do tej pory mamy. Oto przykładowa funkcja wysyłająca do klienta pakiet wypowiadania tekstu [CODE]procedure say(text:string); var PacketBuffer: array [0..200] of byte; ProcessID: Cardinal; begin GetWindowThreadProcessId(FindWindow('TibiaClient'‚Nil)‚ @ProcessID); //pobranie id procesu Tibii PacketBuffer[0] := Byte(Length(text) + 4); //tu okreslana jest dlugosc pakietu bez 0 i 1 PacketBuffer[1] := $00; PacketBuffer[2] := $96; PacketBuffer[3] := $01; PacketBuffer[4] := Byte(Length(text)); PacketBuffer[5] := $00; CopyMemory(@PacketBuffer[6]‚ @text[1]‚ Length(text)); SendPacket(ProcessID‚ @PacketBuffer‚ TRUE‚ FALSE); // tu program wysyla pakiet do programu z proces id = ProcessID end;[/CODE] Znając to wszystko możemy napisać już prosty spell caster :) Zaczniemy od wstawienia Timera na forme. Ustawiamy jego Enabled na "FALSE" (po to aby działał dopiero po naciśnięciu przycisku). Teraz wrzucamy na formę komponent TEdit. Zakładka Standard obrazek z "ab". Wrzucamy też kolejnego Buttona. Ostatnim potrzebnym komponentem jest TSpinEdit‚ zakładka "Samples" czwarty od lewej. Mając już wszystkie potrzebne komponenty możemy przystąpić do pisania. Naciskamy 2 razy na timer i zaznaczamy begin oraz end; w ich miejsce wklejamy to: [CODE]var mana:integer; begin mana:=readmeminteger(player_mana); if mana > spinedit1.Value then begin say(edit1.Text); end; end;[/CODE] Jak to działa? Timer pobiera naszą aktualną mane. I jeżeli nasza mana jest większa od wartości wpisanej w SpinEdit1 wtedy wypowiada słowa z okienka Edit1 (przykładowo czar). Teraz musimy jeszcze zrobić button który uaktywni Timer. W tym celu naciskamy 2 razy na Button i między begin a end; dodajemy coś takiego: [CODE] If (Button4.Caption = 'Start') then begin timer2.Enabled := True; Button4.Caption := 'Stop'; end Else if (Button4.Caption = 'Stop') then begin timer2.Enabled := False; Button4.Caption := 'Start'; end;[/CODE] A Caption Buttona (zakładka "Properties" piąte okno od góry) zmieniamy na 'Start'. Jak to działa? Po naciśnięciu przycisku program sprawdzi czy ma on caption Start albo Stop i w zależności od tego co znajdzie zmieni Enabled Timera na True bądź False. Kompilujemy program i testujemy ;) Teraz nasz program powinien wyglądać właśnie tak: ![]() A oto jego source: Source 6. Inne funkcje na pakietach Picie mana fluidu: [CODE]procedure Manas; var PacketBuffer: array [0..200] of byte; ProcessID: Cardinal; begin GetWindowThreadProcessId(FindWindow('TibiaClient'‚Nil)‚ @ProcessID); PacketBuffer[0] := $0D; PacketBuffer[1] := $00; PacketBuffer[2] := $84; PacketBuffer[3] := $FF; PacketBuffer[4] := $FF; PacketBuffer[5] := $00; PacketBuffer[6] := $00; PacketBuffer[7] := $00; PacketBuffer[8] := $3A; PacketBuffer[9] := $0B; PacketBuffer[10] := $0A; PacketBuffer[11] := $09; PacketBuffer[12] := $D6; PacketBuffer[13] := $95; PacketBuffer[14] := $00; SendPacket(ProcessID‚ @PacketBuffer‚ TRUE‚ FALSE); sleep(500); end;[/CODE] To jest tylko przykład. Możecie sami wysniffować sobie pakiety za pomocą Freeproxy opisanego na początku rozdziału piątego. Wystarczy potem że kolejne wartości podstawicie za PacketBuffer[x] gdzie "x" to liczba tych wartości‚ zaczynająca się od 0 :) |
|
« Powrót do listy Artykułów |
|
|
banner requires iframes |
|