
A few years ago, I was happy to play a small amateur browser-based game created after the classic “Majesty: The Fantasy Kingdom Sim”. Very soon, it revealed several vulnerabilities and bugs, including very suitable for "exploitation". The story of how I wrote the exploit scripts, reported the bugs and later participated in the development of the game a bit - below. I think it will illustrate well the somewhat non-standard, geek user approach to the game, which may be useful for developers to know.
First about the game itself. I do not provide a link, but if you wish, you can find it inside the source below. The idea of the game is quite simple: the player creates one or several heroes from a choice of 16 classes (Warrior, Barbarian, Monk and others, just like in Majesty) and then explores the world around them, defeating monsters, gradually gaining levels and selecting good equipment. The interface is hypertext, graphics at least. All graphics are taken from Majesty, and, as I recall, with the permission of the copyright holders. There is no interaction between the players, except for a completely competitive arena independent of the main gameplay and one very indirect path, which I will discuss below.
The main feature of the game is that the hero begins with a reserve of 200 "moves", which are quickly spent on researching starting locations and first journeys. New moves accumulate at a rate of 1 per real hour, regardless of the player’s actions. 24 moves are collected per day, which is
very little, enough for 5-10 minutes of play. The game is free, there is no donate in it, you can not buy for “real” either moves or anything else. The only way to play more is to create a lot of heroes, and each play 5-10 minutes a day or about an hour a week. However, creating more than 16 heroes - one for each class - is not of particular interest due to the lack of in-game interaction. That's how I played - one hero of each type. This amount is enough for about an hour of quiet play per day.
')
Hero Generation
When creating a hero, the first opportunity opens for geek optimization: generation of a hero with the best characteristics. There are five stats in the game: Strength, Vitality, Intelligence, Willpower, Artifice. After a little testing, I assumed that the value of each is chosen randomly from a range of 20–25, that is, 6 possible values per characteristic. The total possible combinations are 6 ^ 5 = 7776, of which only one is ideal. Obviously, you manually re-create the hero, you need to write a script!
However, first you still need to accurately determine the ranges of stats for each character. This can be done manually (on average, 5-6 generations for each class of the hero), but this is long and uninteresting. It was more interesting and quicker for me to take the Autohotkey scripting language already a little mastered in other games, and write code to control the Opera browser. There was no captcha when creating a hero then - it was added later with my submission - therefore the scheme of work is quite simple:
- choose a class, take the starting hypothesis about the ranges of parameters (min 50, max 1);
- Login, send a form to create a new hero;
- see which stats turned out, correct the hypothesis (reduce the min, increase the max);
- if all five ranges are 6 units long, then write the ranges to a file and proceed to the next class.
Old dirty code for the curious.Login = <.. test login ..>
Password = <.. pwd ..>
Name = testing hero
nClass = 1; 1 = adept 2 = barbarian 3 = cultist 4 = dwarf 5 = elf 6 = gnome 7 = healer 8 = monk
; 9 = paladin 10 = priest 11 = ranger 12 = rogue 13 = solarus 14 = warrior 15 = WoD 16 = Wizard
nGender = 1; 1 = male 2 = female
Email = omican@yandex.ru
MinStr = 50
MinVit = 50
MinInt = 50
MinWil = 50
MinArt = 50
MaxStr = 1
MaxVit = 1
MaxInt = 1
MaxWil = 1
MaxArt = 1
Delete =
heroesofardania.net/options/DeleteHero.asp?_sn=831067546&State=1Register =
heroesofardania.net/register.asprun e: \ program files \ opera \ opera.exe
WinWait, Blank page - Opera,
IfWinNotActive, Blank page - Opera ,, WinActivate, Blank page - Opera,
WinWaitActive, Blank page - Opera,
send ^ t
sendinput
heroesofardania.net {enter}
send 2
; waiting for the full page load - changing the pixel color in Opera
loop, 300
{
sleep, 100
PixelGetColor, temp, 287, 62
if (temp = 0xFF0000)
break
if (a_index = 299)
{
clipboard = -1
exitapp
}
}
sleep, 100
send {tab}
send 2
sleep, 100
; logging in
sendinput {tab}% Login% {tab}% Password% {tab} {enter}
send 2
loop, 300
{
sleep, 100
PixelGetColor, temp, 287, 62
if (temp = 0xFF0000)
break
if (a_index = 299)
{
clipboard = -1
exitapp
}
}
sleep, 100
send 2
sleep, 100
; removing the old hero before the cycle begins
sendinput h% Delete% {enter}
send 2
loop, 300
{
sleep, 100
PixelGetColor, temp, 287, 62
if (temp = 0xFF0000)
break
if (a_index = 299)
{
clipboard = -1
exitapp
}
}
sleep, 100
send ^ +! w
; ---------------- main loop
loop
{
; ----------------
send ^ t
sendinput% register% {enter}
send 2
loop, 300
{
sleep, 100
PixelGetColor, temp, 287, 62
if (temp = 0xFF0000)
break
if (a_index = 299)
{
clipboard = -1
exitapp
}
}
sleep, 100
send 2
sleep, 100
; creating a hero
sleep, 200
send {tab}
sendinput% Login% {tab}% Password% {tab}% Password% {tab}
loop,% nClass%
send {down}
send {tab}
loop,% nGender%
send {down}
sendinput {tab}% Name% {tab}% Email% {tab} {tab} {enter}
sleep, 100
send 2
sleep, 100
sleep, 200
loop, 300
{
sleep, 100
PixelGetColor, temp, 287, 62
if (temp = 0xFF0000)
break
if (a_index = 299)
{
clipboard = -1
exitapp
}
}
send 2
; ------------------------------------------------- ------------------- hero created
sleep, 100
; drop stat definition and hypothesis correction
if (MaxStr-MinStr! = 5)
{
mouseclickdrag, l, 113, 415, 130, 420; selection of stats with the mouse
send ^ c
if (clipboard <MinStr)
MinStr =% clipboard%
if (clipboard> MaxStr)
MaxStr =% clipboard%
sleep, 100
}
if (MaxVit-MinVit! = 5)
{
mouseclickdrag, l, 113, 430, 130, 435
send ^ c
if (clipboard <MinVit)
MinVit =% clipboard%
if (clipboard> MaxVit)
MaxVit =% clipboard%
sleep, 100
}
if (MaxInt-MinInt! = 5)
{
mouseclickdrag, l, 113, 445, 130, 450
send ^ c
if (clipboard <MinInt)
MinInt =% clipboard%
if (clipboard> MaxInt)
MaxInt =% clipboard%
sleep, 100
}
if (MaxWil-MinWil! = 5)
{
mouseclickdrag, l, 113, 460, 130, 465
send ^ c
if (clipboard <MinWil)
MinWil =% clipboard%
if (clipboard> MaxWil)
MaxWil =% clipboard%
sleep, 100
}
if (MaxArt-MinArt! = 5)
{
mouseclickdrag, l, 113, 475, 130, 480
send ^ c
if (clipboard <MinArt)
MinArt =% clipboard%
if (clipboard> MaxArt)
MaxArt =% clipboard%
sleep, 100
}
; write to file and go to next class
if (MaxStr-MinStr = 5 && MaxVit-MinVit = 5 && MaxInt-MinInt = 5 && MaxWil-MinWil = 5 && MaxArt-MinArt = 5)
{
FileAppend, Class:% nClass% `n, E: \ games \ HoA Scripts \ Stats.txt
Fileappend, Str:% MinStr% -% MaxStr% `n, E: \ games \ HoA Scripts \ Stats.txt
Fileappend, Vit:% MinVit% -% MaxVit% `n, E: \ games \ HoA Scripts \ Stats.txt
Fileappend, Int:% MinInt% -% MaxInt% `n, E: \ games \ HoA Scripts \ Stats.txt
Fileappend, Wil:% MinWil% -% MaxWil% `n, E: \ games \ HoA Scripts \ Stats.txt
Fileappend, Art:% MinArt% -% MaxArt% `n`n, E: \ games \ HoA Scripts \ Stats.txt
nClass ++
MinStr = 50
MinVit = 50
MinInt = 50
MinWil = 50
MinArt = 50
MaxStr = 1
MaxVit = 1
MaxInt = 1
MaxWil = 1
MaxArt = 1
}
sendinput h% delete% {enter}
send 2
loop, 300
{
sleep, 100
PixelGetColor, temp, 287, 62
if (temp = 0xFF0000)
break
if (a_index = 299)
{
clipboard = -1
exitapp
}
}
send ^ +! w
; is ready!
if (nClass> 16)
{
MsgBox Done!
exitapp
}
; -----------------
}
; -----------------
As a result, we obtain the desired ranges for all classes and (weakly) confirmation of the hypothesis - all characteristics have exactly 6 possible values. Further, for each class, only some of the characteristics are important, and the very top of the range is not always needed, but an intermediate threshold value is sufficient. For example, the Strength parameter gives an increase of +1 to damage for every 6 units, so from a range of 21-26 we will fit 24, 25 and 26. This allows us to raise the probability of getting a suitable combination from [1 of 7776] to a reasonable value in [1 of several tens or hundreds]. This is important, because the re-creation of the hero takes 10-15 seconds, and ideal stats would be generated on average for more than a day, and we need something else besides them.
Namely, starting money is the second opportunity for geek optimization. They are few, only 200 coins, which is not enough to buy even weak equipment. It is enough only for the simplest baton and weak armor that does not save the weakest monsters in the first combat location from being bitten. Therefore, usually newly created heroes have to spend a lot of moves to heal wounds or even resurrection after death (in a game, death is the loss of a portion of money and several priceless moves). However, there is a casino in the starting city! The sizes of the bets are 50, 100, 500 and 1000 coins, the types of bets are:
- “to black”: a half chance to double the bet;
- “to red”: 1/5 chance to check the bet;
- “on gold”: 1/20 chance to “udvadtsadterit” rate.
Obviously, unlike real casinos, the expected payoff is zero. However, each bet spends one move, which makes playing in a casino ultimately unprofitable for everyone except Gnome class heroes (they have increased luck and expectation is positive). For the generation of casino heroes it is very useful: you can put the starting money for gold, and in case of failure just create a hero again. There are no bets for 200 coins, so you have to make two bets of 100 and count on winning 2000 coins (total probability is about 1/10). So many coins are still not enough for a good start, so you need to bet again: two bets of 1000 each with the hope of getting 20,000 coins (the probability is again ~ 1/10).

The corresponding script is simple:
- recreate the hero until the stats are suitable;
- go to the casino, make 2 bets of 100, if won, then another 2 of 1000;
- if you won, then go to the next class of the hero;
- in case of failure at any stage, return to re-creation.
Hero reroll code
#SingleInstance force
; suitable class stats
hero_class2 = 1
hero_name2 = Mano Rigor
hero_gender2 = 1
min_str2 = 22
min_vit2 = 21
min_int2 = 24
min_wil2 = 22
min_art2 = 0
hero_class3 = 2
hero_name3 = Tiger Grin
hero_gender3 = 1
min_str3 = 31
min_vit3 = 26
min_int3 = 11
min_wil3 = 0; 9-14
min_art3 = 15
hero_class4 = 3
hero_name4 = Tigra
hero_gender4 = 2
min_str4 = 17
min_vit4 = 14
min_int4 = 27
min_wil4 = 0; 5-10
min_art4 = 21
hero_class5 = 4
hero_name5 = Dorn Bumpkin
hero_gender5 = 1
min_str5 = 25
min_vit5 = 28
min_int5 = 15
min_wil5 = 0; 17-22
min_art5 = 29
hero_class6 = 6
hero_name6 = Mister Pronka
hero_gender6 = 1
min_str6 = 7
min_vit6 = 9
min_int6 = 18
min_wil6 = 24
min_art6 = 0
hero_class7 = 7
hero_name7 = Sister of Silence
hero_gender7 = 2
min_str7 = 7
min_vit7 = 13
min_int7 = 28
min_wil7 = 31
min_art7 = 0
hero_class8 = 8
hero_name8 = Sphinx
hero_gender8 = 2
min_str8 = 23
min_vit8 = 25
min_int8 = 26
min_wil8 = 33
min_art8 = 0
hero_class9 = 9
hero_name9 = Tessa Virtue
hero_gender9 = 2
min_str9 = 27
min_vit9 = 26
min_int9 = 20
min_wil9 = 29
min_art9 = 0
hero_class10 = 10
hero_name10 = Fess
hero_gender10 = 1
min_str10 = 8
min_vit10 = 12
min_int10 = 30
min_wil10 = 24
min_art10 = 0
hero_class11 = 11
hero_name11 = Nausika
hero_gender11 = 2
min_str11 = 18
min_vit11 = 19
min_int11 = 22
min_wil11 = 0; 19-24
min_art11 = 24
hero_class12 = 12
hero_name12 = Aleyak Sumakai
hero_gender12 = 1
min_str12 = 15
min_vit12 = 17
min_int12 = 21
min_wil12 = 0; 4-9
min_art12 = 30
hero_class13 = 13
hero_name13 = Morpheus
hero_gender13 = 1
min_str13 = 25
min_vit13 = 24
min_int13 = 22
min_wil13 = 21
min_art13 = 0
hero_class14 = 14
hero_name14 = Saudade Jim
hero_gender14 = 1
min_str14 = 24
min_vit14 = 24
min_int14 = 14
min_wil14 = 20
min_art14 = 0
hero_class15 = 15
hero_name15 = Tor Dur Bar
hero_gender15 = 1
min_str15 = 41
min_vit15 = 41
min_int15 = 8
min_wil15 = 18
min_art15 = 0
hero_class16 = 16
hero_name16 = Wahooka
hero_gender16 = 1
min_str16 = 7
min_vit16 = 10
min_int16 = 31
min_wil16 = 23
min_art16 = 0
log_file = e: \ games \ scripts \ log.txt
gold1 = 0
str1 = 0
vit1 = 0
int1 = 0
wil1 = 0
art1 = 0
; main function
reroll (i)
{
global
loop
{
send + {enter}
wait_image (“crier”, 190, 150, 370, 370); waiting for page loading by image
get_data ()
if (str1 <min_str% i% || vit1 <min_vit% i% || int1 <min_int% i% || wil1 <min_wil% i% || art1 <min_art% i%)
{
; log ("Stats:". str1. "(". min_str. "),". vit1. "(". min_vit. "),". int1. "(". min_int. "),". wil1. " (". min_wil."), ". art1." (". min_art.") ")
delete_hero ()
continue
}
break
}
log (hero_name% i%. "is ready!")
send ^ {F4}
; waiting for page load
PixelGetColor, color, 33, 147
while color! = 0xFFFFFF
{
sleep, 30
PixelGetColor, color, 33, 147
}
sleep, 50
send {tab}
sleep, 20
return
}
; string logging function
log (str)
{
global
formattime, time ,, H: mm: ss
fileappend% time%% str% `n% log_file%
}
; casino bet feature
gamble (bet)
{
send {end}
sleep, 20
send {F8}
sleep, 50
clipboard = http: //www.heroesofardania.net/Content.asp? State = 1 & Bet =% bet%
send ^ v
sleep, 30
send {enter}
wait_image ("hall", 190, 150, 370, 370)
loop, 2
{
send {end}
sleep, 20
send {F8}
sleep, 50
clipboard = http: //www.heroesofardania.net/Content.asp? State = 2 & Color = Gold & Process = Y
send ^ v
send {enter}
wait_image ("hall", 190, 150, 370, 370)
}
return
}
; delete current hero
delete_hero ()
{
send {F8}
sleep, 50
clipboard = http: //www.heroesofardania.net/options/DeleteHero.asp? _sn = 490 & State = 1
send ^ v
sleep, 30
send {enter}
wait_image ("paladin", 50, 235, 200, 400)
send ^ {F4}
PixelGetColor, color, 33, 147
while color! = 0xFFFFFF
{
sleep, 30
PixelGetColor, color, 33, 147
}
sleep, 50
send {tab}
sleep, 20
return
}
; getting the status of the hero
get_data ()
{
global
send ^ a
sleep, 30
send ^ c
sleep, 30
StringReplace, clipboard, clipboard, `r`n ,, All
RegExMatch (clipboard, "i) Gold [^ 0-9] {0,10} ([0-9] +)", gold)
RegExMatch (clipboard, "i) Strength [^ 0-9] {0,10} ([0-9] +)", str
RegExMatch (clipboard, "i) Vitality [^ 0-9] {0,10} ([0-9] +)", vit)
RegExMatch (clipboard, "i) Intelligence [^ 0-9] {0.10} ([0-9] +)", int)
RegExMatch (clipboard, "i) Willpower [^ 0-9] {0.10} ([0-9] +)", wil)
RegExMatch (clipboard, "i) Artifice [^ 0-9] {0.10} ([0-9] +)", art)
return
}
; waiting for the right picture in the right place
wait_image (name, x1 = 1, y1 = 1, x2 = 1024, y2 = 768)
{
ImageSearch, x, y,% x1%,% y1%,% x2%,% y2%, * 80 E: \ Games \ Scripts \% name% .bmp
while ErrorLevel
{
sleep, 150
ImageSearch, x, y,% x1%,% y1%,% x2%,% y2%, * 80 E: \ Games \ Scripts \% name% .bmp
}
sleep, 50
}
; script launch key
~ ^! r ::
{
IfWinNotActive, Majesty
return
sleep, 1000
loop 16
{
if A_index <2; starting number
continue
; initial filling of the hero creation form - you need only once per class
send ^ a
sleep, 50
send {del}
sleep, 20
send% hero_name% A_index%
send {tab}
sleep, 20
send {home}
loop% hero_class% A_index%
send {down}
send {tab}
sleep, 20
send {home}
loop,% hero_gender% A_index%
send {down}
loop, 3
send {tab}
reroll (A_index)
sleep, 1000
}
return
}
^! q :: exitapp
In total, the hero needs to pick up the characteristics (probability ~ 1/200 - 1/50) and win twice in a row at the casino (probability ~ 1/100). The overall probability is very small (~ 1 / 10,000), so on average the script worked for me for 20-30 hours for each character. I found it unnecessary to do several threads, and I didn’t want to load the game server and spoil the life of amateur developers. With these nice people, besides, I managed to meet, fixing up to this a few bugs. The most enchanting bugs, and how I became the Royal Catcher of Bugs, are the last part of the article.
So, in the end, I got 16 heroes, each with good stats and 20k gold. With this money for starting 200 moves, you can already buy good starting equipment, run from another city for super-useful amulets and conveniently collect a dozen levels. However, this advantage of the "improved start" ends, and then the hero is played like any other. The level to the 20-25th difference is leveled, and the main thing is already the knowledge of the game - which location to go at the current level and with the available equipment in order to effectively spend moves on the development of the hero. How to help your heroes at this stage? It turns out that there is an opportunity to make their lives a little easier.
Money laundering through the clan system
Here comes the very only indirect way of interaction between the characters. Each hero from level 5 can join a clan created by another hero, and from 10 he can create his own. In the general treasury of the clan, you can donate money and with this money you can then build buildings such as a gym, a garden for meditation, etc. These buildings allow you to spend your moves a little more efficiently - to heal faster, get temporary bonuses, even improve stats a little. All of these bonuses are small with one exception - spells from clan temples. After the construction of temples dedicated to local deities, they can donate money to temporary useful spells that extend to all the heroes of the clan. The bonuses from some of the spells are already very noticeable - the full treatment after each battle (saving moves and money), increasing armor, increasing the number of attacks. It costs a lot of money, so in a normal game even very large clans of highly developed heroes cannot afford to often activate these spells. By the way, there can be no more than 15 or 20 heroes in a clan, and the cost of spells in the temple is proportional to the number of clan members.
You can get around this problem with the help of temporary clan members, each of whom joins a clan, donates a large amount of money to the treasury, and leaves the clan - so the cost of temple spells does not increase, but the treasury grows. It is not effective to do this manually - in order to constantly maintain all useful bonuses for each beneficiary hero you need about a dozen donor heroes, and for each donor you need to remember to play in order to farm money. According to my calculations, in order to continuously provide all 16 heroes with bonuses, it was necessary to launch 200 dwarf donors into operation. It is gnomes, and not heroes of another class, because due to heightened luck, they win the starting 20k (sometimes even 40k) of gold much easier, and after
that they can
very quickly get the right level, throw money into the clan and start productively farming.
Here the script will help us again, a little more complicated:
- generate 200 gnomes with unique names and sufficient stats and 20-40k gold from a casino;
- each of them to run away from another city, buy good equipment and amulets;
- almost all of the remaining moves to spend on pumping and pharming of gold in a suitable location;
- The remaining few moves to spend on a trip to the clan city and donation of all the money to the desired clan.
The code was created only for the first item:
launcher; increment counter in the file
increment (filename)
{
fileread, count,% filename%
count + = 1
filedelete,% filename%
fileappend% count%%%
return% count%
}
; create login
gen_login (n)
{
ret = omican_cash% n%
return ret
}
; the simplest creation of the unique name of the species Lucky PQ
gen_name (n)
{
let1: = floor (n / 26) +65
let1: = chr (let1)
let2: = mod (n, 26) +65
let2: = chr (let2)
ret = Lucky% let1 %% let2%
return ret
}
; main loop
msgbox CLick OK to launch main script
loop
{
fileread, count, e: \ games \ scripts \ login_index.txt
if (count> 200)
{
break
}
login: = gen_login (count)
name: = gen_name (count)
filedelete, e: \ games \ scripts \ data.txt
fileappend% login% `n, e: \ games \ scripts \ data.txt
fileappend,% name%, e: \ games \ scripts \ data.txt
filedelete, e: \ games \ scripts \ output.txt
filedelete, e: \ games \ scripts \ index.txt
fileappend, 0001, e: \ games \ scripts \ index.txt
run e: \ games \ Scripts \ HoA main.ahk
SetTitleMatchMode, 2
Loop
{
; if everything is stuck
if (clipboard = -1)
{
sleep, 1000
winkill, opera
clipboard = 0
sleep, 3000
run e: \ games \ Scripts \ HoA main.ahk
}
sleep, 5000
; if the current gnome is ready
if (clipboard = 1)
{
sleep, 1000
winkill, opera
clipboard = 0
increment ("e: \ games \ scripts \ login_index.txt")
sleep, 3000
break
}
}
}
main cyclefilereadline, login, e: \ games \ scripts \ data.txt, 1
filereadline, Name, e: \ games \ scripts \ data.txt, 2
nClass = 6; 1 = adept 2 = barb 3 = cult 4 = dwarf 5 = elf 6 = gnome 7 = heal 8 = monk 9 = pal
; 10 = priest 11 = ranger 12 = rogue 13 = sol 14 = warrior 15 = WoD 16 = Wizard
nGender = 1; 1 = male 2 = female
Str = 0
Vit = 0
Int = 0
Wil = 0
Attempts = 0
Password = <.. pwd ..>
index_file = e: \ games \ scripts \ index.txt
Email = <.. email ..>
Delete =
www.heroesofardania.net/options/DeleteHero.asp?_sn=490&State=1Register =
heroesofardania.net/register.aspHall =
heroesofardania.net/Content.asp?_sn=3808&_dt=1%2F29%2F2009+11%3A45%3A44+AM&ContentID=251G100 =
heroesofardania.net/Content.asp?_sn=1649&_dt=1%2F29%2F2009+11%3A54%3A42+AM&State=1&Bet=100G1000 =
heroesofardania.net/Content.asp?_sn=1649&_dt=1%2F29%2F2009+11%3A54%3A42+AM&State=1&Bet=1000Gold =
heroesofardania.net/Content.asp?_sn=9942&_dt=1%2F29%2F200++% 3A56% 3A44 +
AM&State =
2&Colour =
Goldlogout =
www.heroesofardania.net/logout.asp?_sn=163&_dt=2%2F10%2F200+2% 3A09% 3A03 +
PM&; waiting for the full page load (determined by the color of the pixel in the opera)
wait_load ()
{
loop, 300
{
sleep, 100
PixelGetColor, temp, 290, 62
if (temp = 0xFF0000)
break
if (a_index = 299)
{
clipboard = -1
exitapp
}
}
}
; go to address in new tab
address_new (adr)
{
send ^ t
sleep, 200
clipboard =% adr%
sendinput ^ v {enter}
send 2
}
; go to the address in the tab number 2
address_exist (adr)
{
send 2
sleep, 200
sendinput h
sleep, 200
clipboard =% adr%
sendinput ^ v {enter}
send 2
}
; go to the address in the current tab
address_here (adr)
{
send h
sleep, 200
clipboard =% adr%
sendinput ^ v {enter}
send 2
}
; increment counter gnomes in the file
increment (filename)
{
fileread, count,% filename%
count + = 1
if (count <1000)
count = 0% count%
if (count <100)
count = 0% count%
if (count <10)
count = 0% count%
filedelete,% filename%
fileappend% count%%%
return% count%
}
flag = 0
run e: \ program files \ opera \ opera.exe
WinWait, Blank page - Opera,
IfWinNotActive, Blank page - Opera ,, WinActivate, Blank page - Opera,
WinWaitActive, Blank page - Opera,
count = 0001
send {tab}
; ---------------- main loop
loop
{
; ----------------
send ^ {F4}
sleep, 100
send ^ {F4}
address_new ("
heroesofardania.net ")
wait_load ()
send 2
sleep, 200
sendinput {tab}% Login% {tab}% Password% {tab} {enter}
sleep, 200
send 2
wait_load ()
address_exist (Delete)
wait_load ()
address_exist (Register)
wait_load ()
send 2
sleep, 200
; check for a rare bug "empty hero creation window"
loop
{
PixelGetColor, temp, 234, 715
if (temp = 0x800080)
{
send 2
sleep, 200
address_new (Register)
wait_load ()
sleep, 200
send 2
sleep, 200
}
else
break
}
; filling out a hero creation form
sleep, 200
send {tab}
sendinput% Login% {tab}% Password% {tab}% Password% {tab}
loop,% nClass%
send {down}
send {tab}
loop,% nGender%
send {down}
sendinput {tab}% Name% {tab}% Email% {tab} {tab} {enter}
sleep, 200
send 2
sleep, 200
wait_load ()
send 2
; ------------------------------------------------- ------------------- hero created
sleep, 200
; checking stats with mouse selection
if (str)
{
mouseclickdrag, l, 79, 407, 97, 407
send ^ c
if (clipboard <Str && clipboard> 0)
flag = 1
sleep, 200
}
if (Vit)
{
mouseclickdrag, l, 79, 424, 97, 424
send ^ c
if (clipboard <Vit && clipboard> 0)
flag = 1
sleep, 200
}
if (int)
{
mouseclickdrag, l, 79, 441, 97, 441
send ^ c
if (clipboard <Int && clipboard> 0)
flag = 1
sleep, 200
}
if (wil)
{
mouseclickdrag, l, 79, 458, 97, 458
send ^ c
if (clipboard <Wil && clipboard> 0)
flag = 1
sleep, 200
}
; if bad stats
if (flag)
{
count: = increment (index_file)
sleep, 200
}
; ------------------------------------------------- ------------ the end of the case of bad stats
if (! flag)
{
; winning 2000 casino
address_here (Hall)
wait_load ()
address_exist (G100)
wait_load ()
address_exist (Gold)
wait_load ()
address_exist (Gold)
wait_load ()
send 2
sleep, 200
mouseclickdrag, l, 80, 289, 120, 289; money allocation with the mouse
send ^ c
if (clipboard <1900 && clipboard> -1)
{
FileAppend,% count%:% A_Hour%:% A_Min%:% A_sec% Lose 1`n, E: \ games \ Scripts \ output.txt
count: = increment (index_file)
sleep, 200
}
if (clipboard> 1900 && clipboard <1000000)
{
; winning 20,000
address_here (G1000)
wait_load ()
address_exist (Gold)
wait_load ()
address_exist (Gold)
wait_load ()
send 2
sleep, 200
mouseclickdrag, l, 80, 289, 120, 289
send ^ c
if (clipboard> 19000 && clipboard <1000000)
{
if (Attempts <1)
{
FileAppend, Done! % clipboard% `n, E: \ games \ Scripts \ output.txt
address_here (logout)
clipboard = 1
exitapp
}
if (Attempts> 0)
{
; winning another 20,000
address_here (G1000)
loop,% Attempts%
{
if (a_index = 1)
{
wait_load ()
address_exist (Gold)
wait_load ()
}
else
{
address_here (Gold)
wait_load ()
}
send 2
sleep, 200
mouseclickdrag, l, 80, 289, 120, 289
send ^ c
if (clipboard> 22000)
break
}
if (clipboard> 22000 && clipboard <1000000)
{
FileAppend, Done! % clipboard% `n, E: \ games \ Scripts \ output.txt
MsgBox Done!
exitapp
}
FileAppend,% count%:% A_Hour%:% A_Min%:% A_sec% Lose 3`n, E: \ games \ Scripts \ output.txt
count: = increment (index_file)
}
}
if (clipboard <19000 && clipboard> -1)
{
FileAppend,% count%:% A_Hour%:% A_Min%:% A_sec% Lose 2`n, E: \ games \ Scripts \ output.txt
count: = increment (index_file)
sleep, 200
}
}
}
flag = 0
; -----------------
}
; -----------------
The remaining items were never implemented, because several events occurred simultaneously:
- I'm already tired of playing and writing exploits;
- developers have received an offer to help with the development of the game;
- crowds of suspicious gnomes were spotted by players. The fact is that writing a generator of realistic unique names for dwarves was too lazy for me, and the dwarves had names like Lucky XY, Lucky PQ, etc. It turned out that during a consecutive game with several heroes in the last 10 minutes they were all displayed in the list of online players, and my too-alike gnomes stably hung there for 5-6 pieces. A topic appeared on the forum, in which the puzzled players wondered what kind of dwarves were and who needed them. Then I realized that it was time to open up, told about the exploits and curtailed my dark activities.
A little about the moral side of the issue. Due to the lack of any opportunity to ruin the lives of other players, as well as the weak competitive component of the game at that time, my obtaining of dishonest advantages had no effect on the game society. And generally passed unnoticed. My heroes were young, because of the “1 move per hour” system they could not bear with the old-timers and were completely lost in the bottom of all ratings. In the process of my digging in the game, I found and reprimanded a lot of bugs and vulnerabilities, including those that initially exploited.
Bug catching
The first major bugs I discovered were related to things that gave + Vitality. According to the data in gaming Wikipedia, an increase in HPmax with increasing levels is subject to the formula HPmax + = floor (Vitality / 4) + rand (1, floor (Vitality / 4)). Thus, every 4 points Vitality was given an average of + 1.5HP per level. If the hero at the time of raising the level were wearing things with + Vitality, then the additional HP should have been higher. However, because of the bug, these things were not taken into account (as the developer told me, for some reason, before raising the level, all things were removed from the hero, and then put on again).
After my report, the bug was fixed, which led to completely unexpected results. Heroes with a very low Vitality ratio, who had barely survived the battles before, now began to receive much more HP per level. In particular, the gnomes, whose base Vitality lies in the range of 4–9, easily doubled their equipment and even tripled their health, and, taking into account their other abilities, turned from the most choked people into potentially strongest heroes. The same is true in the original extremely fragile Healers. It became extremely important for such heroes to find the appropriate things as low as possible in order to start receiving bonuses to HP early.
A minor flaw was in the casino: it was possible to enter into it with things that increase luck and win more often. After my report to the casino, they added a bouncer who sniffed out such things and simply did not let them inside. In a casino in another city, one could always win in one of the games by simple frauds with GET request parameters. Now there are also bouncers: "Cheaters are not welcomed here!"
The game as a whole was made on the knee, and therefore was extremely leaky. In particular, to exit from the first city, it was necessary to buy a card for 600 or 800 coins, and only then in the dialogue at the gate an option “leave the city” appeared. However, all the options in the dialogs had links of the form “... & State = n”, and selecting the desired value of the GET parameter allowed using the hidden dialog option.
The most enchanting vulnerability is associated with numerical input fields like “how many moves to rest” or “how many moves to pray to the deity”. In most cases, input negativity checking was done by Javascript on the client side. As a result, one day my hero rested -1000 moves for an experiment and ... died! “You have rested for -1000 turns-restored –6000 health points. You are killed! ” But death is only a loss of money and a few moves. Which I now have almost 1000 more ... With the same -1000 moves to the deity, instead of a heap of blessings, the hero receives no less a lot of curses.
Impressed by my reports, the developers gave me for a more effective search for the bugs of a special hero with impenetrable armor and a bunch of moves:

Later, the developers gave me access to the test branch, where I could barely mix it (it turned out that the whole game was written in a completely unknown VBScript for me) wrote a new feature - fast switching of equipment sets. The feature was “in production”, but my contribution ended there, because I still could not overcome my dislike of VBScript and continue development.
In conclusion, we can say that the geek player has a non-standard view of the game. It is more interesting for him (at least in my case) to study the game, look for unbalanced strong strategies, vulnerabilities and exploits, than just playing “as it should be”. If the results of his research, he is not too lazy to convey to the developers, the game will only benefit from this.
Of course, the new in this reasoning is not enough - the classification of players, including such a “researcher”, I think I saw a couple of years ago on Habré. However, for me personally, as well as probably for everyone in my place, it is nice to remember this time and my modest contribution to that wonderful cozy game.