Last active
June 29, 2018 22:40
-
-
Save ArtemGr/7ce9cb629a48a71ab1c0 to your computer and use it in GitHub Desktop.
Eador: Masters of the Broken World - savegame trainer
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Cygwin: cd /tmp; g++ -O3 -fno-inline-functions -Wall -std=c++11 /c/spool/Promo/personal/artemgr/games/eador.cc -o eador -lz | |
// MinGW: g++ -static -O3 -fno-inline-functions -Wall -std=c++11 -I/boost/include/boost-1_53 eador.cc -o eador.exe -lz | |
#include <stdlib.h> | |
#include <stdio.h> | |
#include <sys/stat.h> | |
#include <unistd.h> | |
#include <ctype.h> | |
#include <errno.h> | |
#ifdef __MINGW32__ | |
# include <windows.h> // Sleep | |
#else | |
# include <sys/mman.h> | |
#endif | |
#include <fstream> | |
#include <iostream> | |
using std::cout; using std::cerr; using std::endl; | |
#include <memory> | |
#include <stdexcept> | |
using std::runtime_error; | |
#include <string> | |
using std::string; | |
#include <thread> | |
#include <zlib.h> | |
#include <boost/xpressive/xpressive_static.hpp> // http://www.boost.org/doc/libs/1_53_0/doc/html/xpressive.html | |
/** Read file into string. */ | |
inline string slurp (const string& path) { | |
std::ostringstream buf; std::ifstream input (path.c_str(), std::ios::binary | std::ios::in); buf << input.rdbuf(); | |
if (!input.good()) throw runtime_error ("Error reading from " + path + ": " + strerror (errno)); | |
return buf.str(); | |
} | |
static const char* UNPACK_END_MAGIC = "section.end"; | |
string unpack (string save) { | |
cout << "Looking for the compressed piece..." << endl; | |
string::size_type endPos = save.find (UNPACK_END_MAGIC); | |
if (endPos == string::npos) throw runtime_error ("'" + string (UNPACK_END_MAGIC) + "' not found."); | |
string::size_type pos = endPos + strlen (UNPACK_END_MAGIC); | |
for (string::size_type end = save.size(); pos < end; ++pos) { | |
char ch = save[pos]; if (ch != '\n' && ch != '\r') break; | |
} | |
if (save.size() - pos < 1024) throw runtime_error ("Compressed piece is too small..."); | |
cout << "Found compressed piece of size " << (save.size() - pos) << ". Unpacking..." << endl; | |
#ifdef __MINGW32__ | |
uLongf bufSize = 300 * 1024 * 1024; // 300 mebibyte. | |
char* buf = (char*) malloc (bufSize); | |
if (buf == nullptr) throw runtime_error ("!malloc"); | |
std::shared_ptr<char> free (buf, [](char* buf) {::free (buf);}); | |
#else | |
uLongf bufSize = 1024 * 1024 * 1024; // 1 gibibyte. | |
char* buf = (char*) mmap (nullptr, bufSize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); | |
if (!buf) throw runtime_error ("!mmap"); | |
std::shared_ptr<char> unmap (buf, [bufSize](char* buf) {munmap (buf, bufSize);}); | |
#endif | |
if (uncompress ((unsigned char*) buf, &bufSize, (unsigned char*) save.data() + pos, save.size() - pos) != Z_OK) throw runtime_error ("!uncompress"); | |
cout << "Unpacked " << bufSize << " bytes." << endl; | |
return string (buf, bufSize); | |
} | |
void patch (const char* savePath, size_t fileSize) { | |
printf ("Loading...\n"); | |
string bulk = slurp (savePath); | |
printf ("Loaded %i bytes\n", bulk.size()); | |
if (bulk.size() != fileSize) throw runtime_error ("Bad file size"); | |
if (bulk.find (UNPACK_END_MAGIC) != string::npos) bulk = unpack (std::move (bulk)); | |
printf ("Patching...\n"); | |
auto igold = bulk.find ("igold="); if (igold == string::npos) {printf ("No `igold=`\n"); return;} | |
// Find the second "igold" in order to know for sure that we're not training an enemy. | |
auto igold2 = bulk.find ("igold=", igold + 1); int igoldDelta = (int) (igold2 - igold); | |
printf ("igold2: %i, delta: %i.\n", (int) igold2, igoldDelta); | |
using namespace boost::xpressive; | |
auto patch = [&](const char* label, sregex regex, int maxLength, int from, int till) { | |
sregex_iterator cur (bulk.begin() + igold + from, bulk.begin() + igold + till, regex), end; | |
for (; cur != end; ++cur) { | |
sub_match<string::const_iterator> match = (*cur)[1]; | |
printf ("%s matched, length: %i; value: %s;", label, match.length(), match.str().c_str()); | |
if (match.length() <= maxLength) { | |
for (string::iterator it = bulk.begin() + (match.first - bulk.begin()); it != match.second; ++it) *it = '9'; | |
printf (" patched.\n"); | |
} else printf (" skipped.\n"); | |
} | |
}; | |
int safeTill = 32000; if (igold2 != string::npos) safeTill = std::min (safeTill, igoldDelta); | |
// http://www.boost.org/doc/libs/1_55_0/doc/html/xpressive/user_s_guide.html#boost_xpressive.user_s_guide.creating_a_regex_object.static_regexes.static_xpressive_syntax_cheat_sheet | |
patch ("Experience", ~_w >> as_xpr ("exp=") >> (s1= +_d), 5, 0, safeTill); | |
patch ("Gold", ~_w >> as_xpr ("gold=") >> (s1= +_d), 5, -1000, 1000); | |
patch ("Gems", ~_w >> as_xpr ("gem=") >> (s1= +_d), 5, -1000, 1000); | |
patch ("Current health", ~_w >> as_xpr ("chp=") >> (s1= +_d), 3, 0, safeTill); | |
patch ("Health level", ~_w >> as_xpr ("hlth=") >> (s1= +_d), 3, 0, safeTill); | |
patch ("Magick", ~_w >> as_xpr ("mag=") >> (s1= +_d), 1, 0, safeTill); | |
patch ("Lead", ~_w >> as_xpr ("lead=") >> (s1= +_d), 1, 0, safeTill); | |
patch ("F?CurLife", ~_w >> !as_xpr('F') >> as_xpr ("CurLife\x02\x00\x00\x00") >> (s1= +_d), 3, 0, 14000); | |
// Find a hero. | |
sregex levelAndType = ~_w >> as_xpr ("lvl=") >> (s1= +_d) >> repeat<300,400>(_) >> "heroName." >> (s2= +_w); | |
for (sregex_iterator cur (bulk.begin() + igold, bulk.begin() + igold + safeTill, levelAndType), end; cur != end;) { | |
string hLevel ((*cur)[1].str()), hType ((*cur)[2].str()); int32_t pos = (*cur)[0].first - bulk.begin(); | |
int32_t heroEnd = igold + safeTill; | |
++cur; if (cur != end) heroEnd = std::min (heroEnd, (int32_t) ((*(cur))[0].first - bulk.begin())); | |
cout << "Skill patcher. Hero level: " << hLevel << ", type: " << hType << ", position: " << pos << '-' << heroEnd << '.' << endl; | |
// On level 30 upgrade skills to 5. SkillID....19........Level....3 | |
sregex skillLevel = as_xpr ("SkillID") >> repeat<4> (~_w) >> (s1= +_d) >> repeat<8> (~_w) >> "Level" >> repeat<4> (~_w) >> (s2= +_d); | |
int8_t skillCount = 0; for (sregex_iterator sl (bulk.begin() + pos, bulk.begin() + heroEnd, skillLevel), end; sl != end; ++sl) { | |
if (++skillCount > 11) break; | |
string sId ((*sl)[1].str()), sLevel ((*sl)[2].str()); cout << " SkillID " << sId << " Level " << sLevel; | |
if (hLevel == "30") { | |
if (sLevel != "5") {cout << " patching to 5"; bulk[(*sl)[2].first - bulk.begin()] = '5';} | |
// 11 Willpower; 12 Atletics; 15 Magic; 16 Concentration; 17 Summoning; 20 Wand; 21 Necromancy; | |
// 22 Marksmanship; 23 Reaction; 25 Archery; 26 Path; 27 Diplomacy; 28 Loot. | |
if (hType == "scout" && sId == "11") {cout << " 11->15"; int p = (*sl)[1].first - bulk.begin(); bulk[p] = '1'; bulk[p+1] = '5';} | |
if (hType == "scout" && sId == "12") {cout << " 12->21"; int p = (*sl)[1].first - bulk.begin(); bulk[p] = '2'; bulk[p+1] = '1';} | |
if (hType == "scout" && sId == "23") {cout << " 23->16"; int p = (*sl)[1].first - bulk.begin(); bulk[p] = '1'; bulk[p+1] = '6';} | |
if (hType == "scout" && sId == "28") {cout << " 28->17"; int p = (*sl)[1].first - bulk.begin(); bulk[p] = '1'; bulk[p+1] = '7';} | |
//if (hType == "wizard" && sId == "20") {cout << " 20->26"; int p = (*sl)[1].first - bulk.begin(); bulk[p] = '2'; bulk[p+1] = '6';} | |
//if (hType == "wizard" && sId == "??") {cout << " ??->27"; int p = (*sl)[1].first - bulk.begin(); bulk[p] = '2'; bulk[p+1] = '7';} | |
} cout << endl; | |
} | |
// Upgrade spells. http://forums.playground.ru/masters_of_the_broken_world/kak_vzlomat_zoloto_cherez_artmoney-782070/#comment-10815733 | |
sregex spellLevel = as_xpr ("SpellID") >> repeat<4> (~_w) >> (s1= +_d) >> repeat<8> (~_w) >> "SlotLevel"; | |
for (sregex_iterator sp (bulk.begin() + pos, bulk.begin() + heroEnd, spellLevel), end; sp != end; ++sp) { | |
string spell ((*sp)[1].str()); cout << " Spell " << spell; | |
auto replace = [&](const char* with) { | |
cout << " Replacing with " << with; int p = (*sp)[1].first - bulk.begin(); bulk.replace (p, strlen (with), with);}; | |
// 88 Succubus | |
if (spell == "1") replace ("4"); // Spark -> Arrow. | |
else if (spell == "3") replace ("4"); // Inspiration -> Arrow. | |
else if (spell == "10") replace ("74"); // Curse -> Slave. | |
else if (spell == "11") replace ("39"); // Bless -> Word Of Life. | |
else if (spell == "12") replace ("37"); // Heal -> Cure. | |
else if (spell == "13") replace ("63"); // Fear -> Cloud of Fear. | |
else if (spell == "14") replace ("55"); // Skeleton -> Ghost. | |
else if (spell == "15") replace ("61"); // Zombie -> Vampire. | |
else if (spell == "16") replace ("72"); // Dispell -> Polymorph. | |
else if (spell == "17") replace ("71"); // Astral energy -> Dragon form. | |
else if (spell == "18") replace ("40"); // Web -> Ice Word. | |
else if (spell == "31") replace ("76"); // Gargoly -> Golem. | |
else if (spell == "32") replace ("66"); // Stone rain -> Fire tempest. | |
else if (spell == "33") replace ("60"); // Stone Skin -> Invulnerability. | |
cout << endl; | |
} | |
// Upgrade items. | |
// 18 basic long spear, 0; | |
// 20 Alebard, two-handed, first strike, 3; | |
// 29 basic bow, 0; | |
// 30 basic hunting bow, 0; | |
// 33 Hand Arbalet, 3; | |
// 35 Heavy Crossbow, 3+; | |
// 36 Guardian Crossbow, 4-; | |
// 46 basic arrows, 0; | |
// 49 heavy arrows, 3+; | |
// 51 basic leather jacket, 0; | |
// 52 studded leather jacket, 0; | |
// 53 quilt leather jacket, 1; | |
// 58 mythril chainmail, 0-3; | |
// 63 basic toga, 0; | |
// 64 basic tunic, 0; | |
// 66 gown, 0; | |
// 69 jupe (кафтан), 0; | |
// 111 basic cloth bracers, 0; | |
// 117 basic leather belt, 0; | |
// 120 basic cloak (походный плащ), 0; | |
// 122 cloak of pathfinder, 1; | |
// 137 Lightning-fast dagger, attack without retaliation, 4; | |
// 138 Betwixt dagger, 3; | |
// 149 Rapier, one-handed, 4-; | |
// 153 Sword of the Ancients, one-handed, first strike, 5; | |
// 162 Guardian halberd, two-handed, first strike, 4; | |
// 163 Spear of Retribution, two-handed, first strike, 4+; | |
// 179 Demon sword, two-handed, soul stealing, curse, 5; | |
// 180 Sword of Blood, two-handed, vampirism, 5; | |
// 201 Arrowcaster, 5-; | |
// 240 Gown of Incarnations, 4, summoning+; | |
// 247 Jacket of Cockatrice, 4; | |
// 248 Jacket of Manticore, 4; | |
// 249 Jacket of Unicorn, 5-; | |
// 274 Speed Boots, light, 3; | |
// 344 Bracers of the Mountains, 5; | |
// 347 Belt of Incarnations, 4, summoning+; | |
// 349 Belt of levitation, 4; | |
// 352 Belt of the Hill Giant, bonus to attack, 5; | |
// 409 Bandit Arrows, 3. | |
sregex itemDur = as_xpr ("ItemID") >> repeat<4> (~_w) >> (s1= +_d) >> repeat<8> (~_w) >> "Durability" >> repeat<4> (~_w) >> (s2= +_d); | |
for (sregex_iterator it (bulk.begin() + pos, bulk.begin() + heroEnd, itemDur), end; it != end; ++it) { | |
string item ((*it)[1].str()), durability ((*it)[2].str()); | |
if (item == "0") continue; | |
cout << " Item " << item << " with durability " << durability; | |
auto replace = [&](const char* with) { | |
cout << " Replacing with " << with; int p = (*it)[1].first - bulk.begin(); bulk.replace (p, strlen (with), with);}; | |
if (item == "29") replace ("36"); // Basic bow -> Guardian Crossbow. | |
else if (item == "46") replace ("49"); // Basic arrows -> heavy arrows. | |
else if (item == "111") replace ("344"); // Basic cloth bracers -> Bracers of the Mountains. | |
else if (item == "117") replace ("352"); // Basic leather belt -> Belt of the Hill Giant. | |
cout << endl; | |
} | |
} | |
// TODO: Replace "UpgradeID 10" with "UpgradeID 12", "UpgradeID 13" with "UpgradeID 15", 16 with 18, 4 with 6, 7 with 9. | |
// (http://forums.playground.ru/masters_of_the_broken_world/kak_vzlomat_zoloto_cherez_artmoney-782070/#comment-10815972) | |
printf ("Saving...\n"); | |
FILE *fh = fopen (savePath, "wb"); | |
size_t sent = fwrite (bulk.data(), 1, bulk.size(), fh); | |
fclose (fh); | |
if (sent != bulk.size()) throw runtime_error ("Error writing to file!"); | |
printf ("Done.\n"); | |
} | |
int main (int argc, char** argv) { | |
try { | |
const char* savePath = argc >= 2 ? argv[1] : nullptr; | |
if (!savePath) throw runtime_error ("Usage: eador {savePath}"); | |
printf ("Live patching of file: %s\n", savePath); | |
time_t lm = 0; | |
struct stat sb; | |
auto stat2sb = [&]() {if (stat (savePath, &sb)) throw runtime_error (string ("!stat: ") + savePath);}; | |
for (;;) { | |
stat2sb(); | |
if (lm != sb.st_mtime) { | |
#ifdef __MINGW32__ | |
Sleep (400); | |
#else | |
std::this_thread::sleep_for (std::chrono::milliseconds (400)); // Give Eador some time to save the game. | |
#endif | |
try { | |
stat2sb(); | |
patch (savePath, sb.st_size); | |
stat2sb(); | |
lm = sb.st_mtime; | |
} catch (const std::exception& ex) {printf ("%s\n", ex.what());} | |
} | |
#ifdef __MINGW32__ | |
Sleep (100); | |
#else | |
std::this_thread::sleep_for (std::chrono::milliseconds (100)); | |
#endif | |
} | |
} catch (const std::exception& ex) {printf ("%s\n", ex.what());} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
zlib.h where do i get this from?
$ g++ -O3 -fno-inline-functions -Wall -std=c++11 eador-unpack.cc -o eador-unpack -lz eador-unpack.cc:30:10: fatal error: zlib.h: No such file or directory #include <zlib.h> ^~~~~~~~ compilation terminated.