Categories
Uncategorized

Week 8: MacVenture II

This week I continued to work on MacVenture engine. I managed to fix a few blocking bugs and complete to the end Déjà Vu, Uninvited and Shadowgate. I also improved text input dialogs, made exit buttons clickable (as a shortcut to “Go” and “Open” commands), fixed the bug where titles are not displayed at saves. There are still a few things that needs to be implemented: diploma window, clean/mess up functionality and to fix how certain item bitmaps are displayed.

The first Déjà Vu went fairly well, and at the start of the week I managed to get through the end of it. After that, I went with playtesting Uninvited, and this is were I encountered the first problem. To progress further in the game, I needed to go through the door, but two deadly dogs were in front of it, blocking the path.

To pass them, you need use the “Speak” action to say the magic words and thus use the spell. The magic words are: “Instantum Illuminaris Abraxas”. I typed the words, however, that did not help and I died:

I tried over and over, with lowercase variants, checked if I needed some other item in inventory, however, everything was right according to walkthrough guides. This is when I started checking the scripts, as clearly, something wasn’t triggering. Luckily, there are only three opcodes involving strings: ScriptEngine::opadEQS, ScriptEngine::opaeCONT and ScriptEngine::opafCONTW. The one responsible for the situation is opafCONTW, which checks for the containing word. So I looked up, I discovered it was checking for the lowercase variant of the magic word: “instantum illuminaris abraxas”. So, fortunately, this turned out to be one line fix. Interestingly, when I debugged and printed out the strings from that method, as I discovered the game (apparently all games made with the engine) was containing a very huge variety of curse words. I won’t write them down here, but it has every word you can think of 🤪.

At the same time, I changed the way input text dialog worked. Before the changes, it was essentially an ordinary dialog, with no cursor of the text, and contained the same hardcoded title for all dialogs – “What would you like to say?”. This was also easy to fix, as the I essentially copied the code for cursor from MacTextWindow, and I added a line to fetch the text from the object, not from the hardcoded global table. The end result looks like this:

Another feature I finished was pressing on the exit buttons to go between the rooms. This is optional feature, as you could use “Open” command to open the doors and “Go” to actually go through the door. However, when playing the games this quality of life feature really comes handy, and was worthwhile implementing. Especially, when the code was all there, and I just had to copy it from Webventure repository.

After finishing Uninvited to the end, I went with playtesting Shadowgate. This game took a little longer, as there is a slightly annoying game mechanic with disposable torches, which are needed to be managed. There was one little blocking element, when reaching the room with the sphynx. Essentially, you have to solve the riddle of the sphynx by giving him the right item. However, when I operated the item on it, the game kept crashing. After looking for the cause a bit, I figured that the reason was dead simple: the engine had two versions of focusObjWin(ObjID objID) function. The safe version focusObjectWindow(ObjID objID) that checked if objID really referred to existing window, and unsafe that did not have this check. And of the course, when the code related to sphynx was executed, the unsafe version was run. The fix was just get rid of the unsafe version.

Lastly, I made sure the main game window and inventory windows kept their titles preserved when loading a save file. This took a bit of rewiring and adding for methods, as the code for managing the windows we have now is a little different from the original sources. In the original, main game window and all inventory windows where closing and opening at the start of the game, winning, losing, and save loading. I added back things for the inventory window, however kept the main game window unchanged for the most part. I make a workaround fix to set it’s objID to zero, so that when script executes, it updates the window info. The reason was that a decent portion of code needed to altered to adhere to the change, but since that one line fixes the issue I decided to leave it as is.

Categories
Uncategorized

Week 7: MacVenture

Last I week I started working on the MacVenture engine. The engine is based on the javascript implementation. It is very close to completion, what’s left is to fix a few leftover bugs and playtest the games, so that they are completable. I started with the first Déjà Vu game and close button was not working, drag (lasso) selection is missing, and in certain places the game window was not displaying the correct images. Throughout the week I tackled these problems.

As said above, first thing I noticed is that the close button was not responding, e.g. after clicking on it, nothing was happening. It took quite some time to figure out what was going wrong, but eventually I found the reason for it. The problem was that the code for identifying whether the mouse was inside close button was looking like this:

bool MacWindow::isInCloseButton(int x, int y) const {
    int bLeft = kBorderWidth;
    int bTop = kBorderWidth;
    if (_macBorder.hasOffsets()) {
       bLeft = _macBorder.getOffset().left;
       bTop = _macBorder.getOffset().top;
    }
    return (x >= _innerDims.left - bLeft && x < _innerDims.left && y >= _innerDims.top - bTop && y < _innerDims.top);
}

As can be seen, it returns true if mouse position is at the corners of window borders. And it works correctly if close button is indeed at one of the corners, as in WAGE games:

Standard window with close button on the top left

However, here the close button has a little offset to right and bottom from the left top position of border:

Window with different close button

So to make it work, I added offset fields for the closeButton, and made  MacWindow::isInCloseButton() method to look at those offsets, if they are present.

Next problem was absence of drag (or lasso) selection. It is needed to select multiple items in the inventory at once, as can be seen here:

However, making it works wasn’t that easy and took me a couple of tries and rewrites, and to the moment of writing, there is a slight bug that needs to be fixed. But the idea is simple: on the mouse button down event save the pivot point of the selection, on the mouse move event update the the second point, and lastly, on mouse up event create a rectangle from the two points, look if any inventory objects are inside and mark them as selected. The difficulties was there because of the code flow structure which was altered and become noticebly different from the original code.

What really helped me here and in the later problem is that I managed to get original code running and made it debuggable in browser. I updated the jQuery version the fresh one, fixed a few type errors and imported the main game code as module, so that when it is compiled with closure compiler, the separate mac.js is generated.

With this, I stepped through the js code, looked how the lasso was done, and made it essentially working:

 

Afterwards I worked on fixing the issue right at the start of the game. What happened is that, when examining the mirror object, the engine would not pause and quickly skip the image of character. It happened, because the engine was not pausing after MacVentureEngine::clickToContinue() function was called. I added an additional flag that is set when clickToContinue is true, so that the game pauses, and the main game window shows the correct images. This is how it worked before and after the fix:

There are two more bugs left from the last week which I am fixing now. First one related to window resize – when making the window smaller, it stops showing borders:

The second also probably related to it. Clicking at the resize button selects the object if that object happens to be there.

For this week, I plan to continue the work on the MacVenture and fix the above mentioned bugs. Afterwards, I will solely focus on playtesting.

Lastly, I want to mention that I succesfully passed the Midterm evaluation for GSoC. I want to thank my mentor – Eugene. He has been very supportive and always there for help and explanations. Also, the tasks and engines were all diverse and very interesting to work on. I am very enjoying contributing to ScummVM.

Categories
Uncategorized

Week 6

Last week I continued to work on TeenAgent, in particular implementing the support for voiceovers. I first wasn’t sure about that since I could not find anything related to voicing in the sources we had. However, because the engine can load the voices and play them, I decided to map the voice indices to each text and play the voices in the same places where Text-To-Speech’s sayText() are called. Apart from this, there was a bug report for the qdEngine game, so I quickly returned to it to add support for punycode.

The idea for implementing the voiceovers is as I said to map the voice indices (1..2043) to each text. I played with the sounds to see how the voice resources are located and figure out the ranges. For the most part, it was sequential. The ranges are as follows:

  • 1 – 333 Messages
  • 334 – 566 Scene objects descriptions
  • 567 – 591 Combination messages
  • 592 – 683 Item descriptions
  • 902 – 2040 Dialogs

Interestingly the range [684 – 901] between Items and Dialogs is empty. Implementing the first four items were relatively easy. For the display message methods I just added an additional argument for voice ids, for others I extracted that from the newly added method – getVoiceIndex(). However, the dialogs were hardest to get right. At the moment of writing, I am still working on it, most of them are working, but still some parts are wrong. The main problem I discovered is that the voice resources for dialogs are not really ordered, not every dialog line is voiced and some voice resources are empty. For the empty voices, I discovered that checking the size, i.e, if its too small is enough. That is, all of those voice resources are 4 bytes in size and I got the list of them and implemented isVoiceIndexEmpty() helper method. But the fact that some voices are not ordered, really became an issue and I had to manually find, listen and check every problematic dialog/lines. Because of this, the method for computing voice indices became a mess, and I had to rewrite it over and over.

Despite the difficulties presented above, I am close to finishing it, and once the dialogs are done, all text in the game will have a voiceover. Good thing is that I have every line of text extracted from the previous weeks, so even if I don’t know Polish, I still can with relatively no problem find the the exact text from the voices I hear.

For this week, I first plan to finish this task (today) and then move to working on the next engine – MacVenture.

Categories
Uncategorized

Week 5: Teenagent, Agent Mlíčňák, Юнагент

This week I continued to work on adding Russian, Polish and Czech strings. Last week was spent with extracting those strings from the executables, and this time I worked on making the engine load them. The task was a bit challenging and it took a lot of trials and errors the get certain things right. Let’s break down everything I did.

First of all, I had to think how I would store and then load the language specific data. Initially I thought maybe I could fit the data into in their respective locations in the original .dat file, but when looking at Polish and Czech executables, which are much larger, I realized that would not work. So I ended with adding all the strings at the end of original .dat file. In the engine, I added Segment object for each of the items, and made the engine to read from them instead of the data segment.

I started with loading and displaying Credits and Item names, as these two were the easiest. For the loading part, I introduced two Segment objects containing these two resources and made them read the data from .dat file:

I also added Resources::precomputeCreditsOffets() and Resources::precomputeItemOffset() to precompute offsets after the loading is done. This is identical to how dialog offsets are computed and is needed so that the engine can get to correct location where credit/item data start when it asks for a particular credit/item number.

Next, I added message strings. Adding them was also quite easy, and took the same approach as the one above. The only thing that needed to be carefully taken care of was combine error message. In the .exe they don’t come together with the rest of the messages. In fact this message is the last part of the combining table section. However, for the consistency, I placed this message as the last item in messages segment.

Speaking of combination messages, I added them after this. Each combination consists of the following members:

struct {

   byte _obj1Id;

   byte _obj2Id;

   byte _newObjId;

   Common::String _combinationString;

}

I split this structure to two: one containing the first three bytes which are the same for all languages, and the one containing just the combination strings.

After that, I worked on dialogs. Initially I thought it would be the same as with other strings, but at the time I forgot about dialog stacks. Because of this, the dialogs for English and Russian versions were fine, but some dialogs in Polish and Czech versions were completely off the context. Turned out, that some dialogs should be popped from dialog stacks and shown only when certain events are triggered. They were working for English and Russian versions, as the code for reading the dialog stack data was still reading it from data segment, not from the segment that was dedicated to it. After realizing this, I added stack dialog stacks as a resource to .dat, added code for loading it and correct dialogs started popping out.

Lastly, I worked on adding scene object names and descriptions. This part was the trickiest. First of all, I realized during extraction, I made an error, and didn’t not consider cases with items with default description. That is, after the null byte, these items contain 01 byte, which indicated that the objects will be given the default description name – “Cool.” (English), “Miodzio.” (Polish), “Bezva.” (Czech), “Вещь.” (Russian). Once I realized this, I fixed it here.

Another important thing to point out is that this kind structure is a part of savefile, and the objects and their members (including names) are modified in runtime. The last part about the names is crucial, as certain objects, namely four – “girl”, “robot”, “boy”, “bowl” are modified changed to their “real” names – “Anne”, “Mike”,  “Sonny or whatever”, “body”. I implemented the support for this, but forgot crucial thing – I didn’t set enough space for the initial names, so that when they are changed, it does not overwrite description part. Because of this, I was getting constant crashes when trying to load the older saves (before changes in the pr). After finding about this, I was able to fix the issue and thus, preserve compatibility with old saves.

Categories
Uncategorized

Week 4

Last week I started working on creating multilanguage dat file for the Teen Agent. Currently, only the English version is supported, and Polish, Czech and Russian versions needs to be added.

The task is to extract language specific data, strings from the executables, put them in the create_teenagent utility and finally generate the .dat file for the use of engine. The extraction tools for the game were already in hand, although I had to write custom python scripts to create cpp code from them. The strings that needed to be extracted are the following:

  1. Names and descriptions of inventory items
  2. Dialogs
  3. Messages
  4. Character’s thoughts
  5. Credits
  6. Names and descriptions of scene objects

Most of them were not difficult extract and put in the code, except for the scene objects. I started with the Russian version, and the problem was that some string could not fit in the original positions, so they had to be copied to the free space of the data segment. That includes the pointers to those objects. So this caused a little issue, and I am in process of writing it properly.

Screenshot the Russian (fan-translated) version

In parallel, I was also looking at the Polish (CD) version. This version brought more issues, which I am yet to solve. The problem is that Polish executable is larger than the English, and has two more scene objects. The sizes of texts are also generally bigger, so I would not be able to fit them into the dat file. I am thinking of putting them after the data segment and dialogs. However, that would require also changing in a couple of places in the engine code, since it relies on the data segment. This means that save system also needs to be adapted for this, as the engine is currently writing the save section part of the data segment. I’ve already tried this idea and separated the credits section the data segment, and put it separately at the end. This allowed to display the Polish text in the credits section:

Similarly, I’ve experimented with the dialogs, and they also started showing up properly:

However, there is also a Dialog stack, which is modified at runtime by the game, so I probably will need to put as a resource in different segment as well.

Categories
Uncategorized

Week 3

In the third week of GSoC, I worked on finishing the WAGE engine. The main things that needed to be done for the engine were: 1) check save/load system and fix the game bugs; 2) to redump the games and add them to detection table.

The save/load system was mostly working when I started, but a there were a few missing cases that needed to be handled. 1) When trying to load a save after loosing the in the game, the console window showed the text from the previous session. The fix was to simply reload the state() and then run the “Look” command, so that we get the initial text about the scene (surroundings) where the main character in located.

Other interesting bug to solve was the crash I was getting after loosing in several games. For example, I would always crash after throwing the “frag grenade” and destroying the spacecraft in the game Bug Hunt. Since we have the sources for the engine, I first tried to check the code there. It was the same. I run the original Java sources in Eclipse, but as it turned out, the game also crashes in that scene. So that meant I had to go deeper in the code logic and find what exactly went wrong there. The problem was that when destroying the spaceship, the script would move the character to “storage scene”, an empty, temporary scene where characters are located during the startup. Since the Scene class contains Rect pointer needed for drawing and the for the storage scene it’s null, the game crashed when trying the draw the scene. Of course, we could add the checks for the NULL pointer, and fix the crash, but that would not be sufficient, since the “Game Over” dialog would not show up. The solution I came up with is to check if the player was moved to this storage scene after making the turn and set the gameOver flag: commit.

After that, I fixed the remaining bugs and the text in the About dialog, which took several iterations since I would discover new problems as run more and more games with different text sizes, fonts, etc.

Then, it was a time to do the re-dumping task. Thanks to my mentor, I already had my Basilisk II setup ready, and it was not that difficult to do the task. My workflow was as follows: I would decompress the game archives inside Basilisk, then move them to place that will be visible from the host (my OS), then use dumper-companion the make sure the file names are nicely puny-encoded. After that, I would check if there are any issues with the games in ScummVM itself, and later add them to detection tables.

Categories
Uncategorized

Week 2

My second week of GSoC is over and has been full of different activities: I wrote two news items, took screenshots for qdEngine and SLUDGE games, started working on a new engine (WAGE) and had a video meeting with my mentor.

As I wrote in my previous posts, the main work on qdEngine is done, but few things were needed before the announcement: preparing screenshots, creating wiki pages for the games and writing the announcement post itself. And I started the week this those tasks. Taking the screenshots turned out to be quite useful as I discovered the regression caused by one of commits. In particular, when playing “Wait for it! Issue 3. Song for a Hare” I found that the game went black screen and stalled after playing a video. After probing the packets with ffprobe, I found that it happened because of a packet with invalid timestamp, which I did not consider in my PR. I quickly fixed the issue, and the videos are playing normally now. After the screenshots, I created a wiki page for the newly added games and I wrote a news item to announce the full support for qdEngine.

Following the qdEngine, I did the same kind of tasks for the SLUDGE engine. Again, taking the screenshots were useful as I discovered that I did not cover one case with save/load system – autosaves. By that time, I was very comfortable with the saves, so it was an easy fix.

After the SLUDGE, I started working on WAGE engine. This engine is also very close to completion with only a few small things left to be done. However, to properly implement the leftover features and fix the bugs, I needed to compare with the original, which wasn’t that easy to set up due to intricacies of the file system of Macintosh. But that wasn’t that big of an issue, thanks to ScummVM project leader and my mentor – Eugene (sev), who kindly guided and explained every step for me in video call.

Right now I am continuing my work on WAGE. I’ve already fixed a couple of bugs, and there is only problem with the text (About dialog) left for now. After that, I plan to redump the games which is also part of the tasks for this engine.

Categories
Uncategorized

Week 1

In the first week, I started with finishing the work on qdEngine, then began working on the SLUDGE engine.

With qdEngine, what needed to be done was to improve the performance in the game Dog-n-cat: In the Footsteps of Unprecedented Beasts. After a little investigation, it turned out that whenever the tiled animations were drawn, the uncompress() method was called. This was very costly, and the solution was to cache the already uncompressed animations.

After finishing with this task, I was assigned to finish the SLUDGE engine. The engine is almost finished, with only a couple of small things to be done. I began with implementing the support for extended saves, which turned out to be a little harder than I expected. In particular, there was a problem with the saving the files in the ScummVM format. The saves with simple names are in this format: %s.%03d, where d is slot number. At first, I didn’t know how to get the slot number from the name of a save. This was needed for loading the save files in the games. The problem was that the SLUDGE game interpreter is based on stack machine and the only thing which is pushed to stack when loadGame function is called is the name (description) of the save.

This problem took quite a while to solve, but after many unsuccessful attempts and tries, I finally came up with the solution. I realized that almost every game when loading/saving the game shows the save file list:This meant that I could create a lookup table that maps the save name to slot number of the save file and use it for saving/loading procedures. And indeed, it worked out.

There was also a little interesting task was to testing transition modes of the engine. For that, I needed to create a demo with SLUDGE development kit, run it on original interpreter and on ScummVM, compare, and see if they are different. The transition modes appeared to be the same, so there was no additional work needed from my side.

After that, I quickly implemented return-to-launcher feature, fixed a bug in thumbnail loading and wrote a code to simulate the right click of a mouse. Lastly, I made it so message box with a specific message set by a game pops up when exiting the game:

Message box in Cubert Badbone, P.I.
Message box in Verbcoin 2.
Categories
Uncategorized

Highlights of my work on the qdEngine

In this post I would like to go over some of the highlights of the changes I made to qdEngine.

Adding support for RGBA8888 format

At first, there wasn’t any intent to add a support a 32bpp color format. The engine was using RGB565, and it was pretty much fine with most of the games. However, when I began playtesting Pilot Brothers 3D, and got to a specific place in the game, I quickly noticed that something was off:

The location in the game where I noticed the bug in shadows.

When looking more closely, you can see that the shadows under actor’s bodies are dark green:

dark green shadows  

This problem took quite some time to solve. I was checking every line of the drawing functions, comparing with the original source code, but was unsuccessful. Then, I tried to change things, in particular, tried to remove alpha_blend_565() inside the methods, and realized that it was responsible for the shadow color. I looked again at the the original, it saw that it was RGB888. But we were using RGB565, which of course has less bits and a thus created noise for the shadows. Since there is an extra bit for green channel, the shadow was a little greenish. Thus, I proceeded with adding the support for this color format, and indeed got the shadows fixed:

fixed shadows

Although it took about 2 days at the time of fixing that, I now much understand to engine’s drawing functions and learning few things about color formats. Here is the PR for the fix: https://github.com/scummvm/scummvm/pull/6552.

Checking different game (engine) versions and learning Ghidra

One of the aspects of working on QdEngine was comparing the code across different engine versions. The engine developing over time, which meant that because of the changes made to the engine, things such as pathfinding, inventory selection and collision system could work different across different the games. We had sources for multiple versions of the engine, so once the sources are compared and the change is found, a typical fix could be like this:

here the date (number against which g_engine->_gameVersion) indicates the engine version where the a certain change was introduced.

When the sources were unavailable, we had to rely on reverse engineering. For example, we didn’t have the sources for the engines of two games. To find the exact cutoff date, I would write the exact the method and how it was changed to Sev, and he would reverse and tell me the date. Thanks for his help, we were able to fix a decent amount of bugs and make the games completable to the end.

In beginning of May, I started with playtesting Brother Pilots 3D-2 game and could not even run it. This was because one of the tags in the script specific to this game (engine) version was missing. At the time, Sev couldn’t help me, as his laptop was in repairment. So I thought to myself, maybe I could try fixing it myself. I downloaded Ghidra and uploaded three games (including Brother Pilots 3D-2) with consecutive engine versions. Then I found the place where the the tags registered:

Decompiled code of qdscr_XML_Parser() function

After that I took decompiled code of this function, pasted to my editor and diffed between the games:

Comparison of decompiled code of “Features of National Fishing” and “Pilot Brothers 3D-2. Kennel Club Secrets”

In the image above, I compare the code for Brother Pilots 3D-2 and the game that came before it. As can be seen from the image, rotation_angle_per_quant tag is added, so the I adjusted in the code the date and was able to run the game. This was a small change, but quite useful for me, as I learned a little about reverse engineering and Ghidra.

Fixing file loading and adding support for advanced minigames games

This was supposed to be my of the main tasks for this GSoC. I needed to change how the interface for “advanced” minigames worked. Not that they are called advanced, but they are complex in comparison to the ones in other games, and added more things to previous interface.

To understand the problem better, I first tried to run the games, and see where I crash, or where things are supposed to not work. I took the first game, Dog-n-cat: In the Footsteps of Unprecedented Beasts and tried to run it and noticed that I can’t get past the logo image. What’s more important, the memory usage panel in Visual Studio was showing 3.8 Gigabytes:

That was quickly fixed by adding back early end-of-stream check from the original sources:

But something was still missing. I was still getting this warning: MinigameManager::init(): Game could not be initialized. This meant that state (file) loading was unsuccessful and returned false. The code for reading from file looked something like this:

while (!file->eos()) {
       index.read(*file);
       if (file->eos()) {
          delete file;
          return false;
       }
       .
       .
       .

At this stage, the method could only return false only from code branch I just introduced. So it was reaching the end of stream every time. I did not realize ScummVM’s ReadStream::eos() would return true only when reading beyond file size. In the original, however, their eof() would return true once file size bytes are read, and of course the code in this section relied on that. But once I figured out and adding small fixes, the minigames started showing up. We first though that more complex approach and rewiring would be needed, however, it was much simpler. After that, unstubbing the constructors the each minigame was quite easy and I was able to finish the work early.

Categories
Uncategorized

Fixing triangle animation

In this post, I would like to share a story of solving one of the recent bugs I had in qdEngine.

First, let me tell you about the bug. The problem was with the minigame in the game called “Dog-n-cat: In the Footsteps of Unprecedented Beasts”. In the minigame, players swap and move around triangle pieces to assemble the picture of an animal. The problem was that when swapping triangles, the triangles would move to swapped positions, instead of folding/unfolding in place. And the animation itself had artefacts. This is how it looked before the fix:

and how it looks now, after the fix:

 

I first thought the problem was with the code logic of this minigame. Even though we already had the sources of the minigames, as it turned out, some of the parts of code were not present there. This led me to think that maybe some crucial piece of code responsible for the rotation was absent. I told this to my mentor – Eugene Sandulenko (aka sev), and he provided me with the decompiled code of the dll for the minigame. But, it turned out to be the same. Then, he went with reversing the several games which were developed in later versions of the engine to see if important differences were introduced. Again, differences that could help were not found.

We started to lose hope on this, so I started to think about the problem from beginning and try a different approach. Sev adviced to look again at what exactly was responsible for rotation and tranformation of the triangle. And I looked very closely at qdScreenTransform::change(), the method that changes object’s angle and scale when transformation happens. In our case, it was called when triangle is transformed during swapping. I noticed that the angle was always 0.0 (wasn’t changing), but the scale in y-axis was decreasing. What’s important is that I was testing it by swapping the triangles vertically. And then, it really came to my mind, that no rotation should not even be happening in this case. Turned out, to simulate the effect of folding, the game was only scaling down the y-component of the triangle object. This meant that I only needed to check the drawing methods with scale parameter. And indeed, after comparing with the original source code, the issue was in the wrong conditional operator:

This bug a took quite some time to solve, because of wrong a wrong assumption I had. However, with the help of my mentor and an insight that came to me eventually, the problem was successfully solved.

Thanks for reading.