Skip to content

Instantly share code, notes, and snippets.

@ArtemGr
Last active June 29, 2018 22:40
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ArtemGr/7ce9cb629a48a71ab1c0 to your computer and use it in GitHub Desktop.
Save ArtemGr/7ce9cb629a48a71ab1c0 to your computer and use it in GitHub Desktop.
Eador: Masters of the Broken World - savegame trainer
// 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());}
}
@SirZee
Copy link

SirZee commented Jun 29, 2018

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment