Skip to content

Instantly share code, notes, and snippets.

@richyk1
Last active October 10, 2020 00:38
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 richyk1/6de5cf3d14b55e9c701401717051444d to your computer and use it in GitHub Desktop.
Save richyk1/6de5cf3d14b55e9c701401717051444d to your computer and use it in GitHub Desktop.
Bus eller bregott

Bus eller bregott

Description

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.

Initial moves

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);
  }
}

Conclusion

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.

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