Campaign on https://kampanj.bregott.se/home/ranking to promote their dairy products during the halloween period. First place is guaranteed to get a nice pair of earphones. The game itself is built on Construct 2 engine which is a game engine for people new to programming. Seems generally clunky, but not that relevant for this reversing task. The goal here is to disable generation of obstacles so that no actual brain cells are required to achieve a relatively high score.
By looking at the network tab you can see two files being generated - c2runtime.js and bmg_loader.js. The obvious first assumption here is that the first file is the actual engine and whatever code is in the other file is code that belongs to the game logic.
By observing some of the code in bmg_loader.js you can notice that console.log
gets undefined on the first line, which means that the developer doesn't want the user to see all the log messages. Understandable, because then you could find your target function a lot easier.
Since there's a lot of code to go through, some general keywords could be searched to get a hint about how the code is working. By searching for the keyword "finish" you will find a lot of occurrences, but one that stood out for me was the function that had a lot of console.log
messages about the game actually ending. Next step is to set a breakpoint there and walk up the stack frame.
FINISH: function(b, e, f, h, l) {
isFinish || (isFinish = !0,
window.wrap_banner.isActive && setTimeout(function() {
window.wrap_banner.SHOWVIDEO()
}, 2E3));
common.log("Game is finished");
common.log("Game Score : " + b);
common.log("Game PlayTime : " + e);
common.log("Game Time : " + f);
if (common.getBMGType() == MV.BMGTYPE.HMG)
When walking up the stack frame you want to find a game engine function called CallFunction
. This library function is responsible for triggering different user-defined events, e.g - Initialize
, SpawnObjects
, SpawnItems
, ParallaxBackground
, AddScore
, EndGame
.
At the time of writing this, the CallFunction
function looks like this:
l.prototype.CallFunction = function(d, e) {
var a = q();
a.name = d.toLowerCase();
a.gh = 0;
Aa(a.mb, e);
this.b.trigger(pc.prototype.i.jj, this, a.name);
b--
}
When setting a regular breakpoint in CallFunction the game will pause constantly, because the function for drawing background, as well as the function for updating the distance walked, gets called all the time.
A nice thing is to blacklist those scenarios. Google Chrome has a nice feature to set conditional breakpoints which could be set to the following expression: !(d == "ParallaxBackground" || d == "UpdateDistance")
.
Because we want to disable the function call to SpawnObjects
we want this.b.trigger()
to not actually trigger anything when CallFunction
wants to trigger object spawning. To do this we want to wrap the original trigger function inside our own custom one. The idea here is that when this.b.trigger
gets called our custom function gets called instead which only executes the original trigger function if the right conditions are met. In our case, the right conditions are whenever d != "SpawnObjects"
.
Creating proxy functions in JS is pretty easy. Firstly, because this.b
is an instance of a JS class we have to step into the actual function, to then access the properties of the class itself. Once stepped in, we can copy the original trigger function to some variable and insert our own custom trigger function.
d.prototype.old_trigger = d.prototype.trigger
d.prototype.trigger = function (a, c, b) {
if(b == "spawnobstacle") {
}
else {
return this.old_trigger(a, c, b);
}
}
Fun little challenge. The game itself is client-sided so it, for the most part, trusts the client to not cheat. I took the safe way and removed the obstacles, because the game still sends the time played etc., which a more inexperienced reverser would fall for and just change the end result.