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.

Week 5

Welcome to this week’s blog.

This week primarily involved manually reviewing around 100+ set.dat files—excluding a few, such as Scumm and all GLK engines, since their detection entries are not yet available for seeding.

Fig.1 – Result of matching set.dat files

During the process, I fixed several issues wherever possible, improved the matching, manually removed unwanted entries, and documented everything in a spreadsheet. Some key fixes included adding additional filtering based on platform to reduce the number of candidate matches. In some cases, the platform could be extracted from the gameid (e.g., goldenwake-win). This filtering was needed because many detection entries from the seeding process were missing file size information (i.e., size = -1). While I was already filtering candidates by file size, I also had to include those with size = -1 to avoid missing the correct match due to incomplete data. However, this approach in some cases, significantly increased the number of candidates requiring manual merging. Introducing platform-based filtering helped reduce this count, though the improvement wasn’t as substantial as expected.

Another issue stemmed from duplicate files added during seeding for detection purposes. While the detection code in ScummVM intentionally includes these duplicates, I should have removed them during seeding. Cleaning them up did reduce the manual merging effort in some cases.

There were also complications caused by file paths. Initially, the filtering considered full file paths, but I later changed it to use only the filename as mentioned in the last blog. This led to situations where the same detection file appeared in multiple directories. I’ve now resolved this by designating only one file as the detection file and treating the others as non-detection files.

A significant portion of time also went into manually removing extra entries from set.dat, e.g, different language variants. These often caused dropouts in the matching process, but removing them allowed the main entry to be automatically merged.

Some smaller fixes included:

  • Ensuring all checksums are added on a match when the file size is less than the checksum size (since all checksum would be identical in that case). This logic was already implemented, but previously only applied when creating a new entry.

  • Increasing the log text size limit to prevent log creation from failing due to overly large text in the database.

Next, I’ll begin working on the scan utility while waiting for the Scumm and GLK detection entries to become available.

Week 5: Buried and Access

This week, I focused on bringing keymapper support to two more engines: Buried and Access. Both engines came with their own set of challenges


Buried

The Buried engine had an unusual approach to input: it handled most keypresses on key release rather than on key press.

In terms of actual key replacement, there wasn’t much heavy lifting to do. However, the real challenge—like in some of the previous engines—was figuring out when to activate or deactivate keymaps. Buried has different contexts where certain keymaps should be enabled or ignored, and tracing that logic took the majority of my time this week.

Once I understood those transitions, hooking up the rest of the keymapper functionality was fairly smooth.


Access

Access was easier to work with in comparison, but it did come with one major twist: it used function keys (like F1, F2, etc.) to change the current active verb in the game.

The function responsible for switching verbs didn’t rely on keycodes—it used internal numeric codes corresponding to different verbs. I couldn’t just update that function to use actions instead, because it was used in other parts of the engine where those codes were passed directly.

To solve this, I came up with a workaround: I mapped keymapper actions to these verb codes by ensuring the actions were written in a specific order, and then calculated the correct internal code by subtracting a default base. It’s not the cleanest solution, but it worked without requiring intrusive changes to the engine’s logic.


Wrap-Up

This week, I:

  • Implemented keymapper support for Buried and Access

Next week, I’ll continue expanding support to more engines—each one bringing its own surprises.

Testing, writing and then some more testing!

Continuing from last week’s progress, this week I worked on saving some more Cast related resources: palette data (‘CLUT’ resource), bitmap data (‘BITD’ resource), text data (‘STXT’ resource) and the filmloop data (‘SCVW’). Most of the time was spent on testing these implementations. A test movie for each was made in Basilisk emulator (for Mac) and 86Box emulator (for Windows) using original Director 4 and Director 5.

The ‘CLUT’ resource was straightforward, since it only stores the colors in the palette as RGB values in bunches of 3. But we convert the 16-bit colors to 8-bit colors by only keeping the upper 8 bits only. Hence while saving these colors back, I had to left shift the byte by 8 bits and save it back.

The ‘BITD’ resource and ‘STXT’ resources were particularly tricky.

The ‘STXT’ resource contains the text data for Buttons and Text Fields. But the text is encoded in font encodings like Mac Roman and Mac Japanese for Mac or Windows-932 for Windows. This is specified in the ‘STXT’ resource. We detect the encoding, extract the part of the text that is encoded and convert it to a U32 string. While saving back however, we need to save the text in its original encoding. A single text resource can be encoded in more than one formats and at random offsets even. Thankfully, we do not ignore this information, we store it in a separate string as a header, like follows:
Common::String format = Common::String::format("\001\016%04x%02x%04x%04x%04x%04x", _style.fontId, _style.textSlant, _style.fontSize, _style.r, _style.g, _style.b);
_ftext += format;
The task was to read the header \001\016 in this string and write the font style that followed. Encode the U32 string back into the encoding given by _fontId, and write the raw string.

In case of ‘BITD’ resource (bitmap data), depending on the number of bits per pixel, we load it differently, naturally. The bitmap may be external or internal. @sev told me to focus on the internal loading for the moment. Most movies have their bitmap data compressed, i.e. instead of saving the data pixel by pixel RGB values, they store R/G/B values together,  hence if neighboring pixels have the same R values, instead of writing the said R value n times, we store the R value and the number of times it is repeated – n, which is Run Length Encoding for compressing bitmaps. However, I’m ignoring that for now, and storing the pixels in a row. Testing this was a struggle and a half. I had to create movies with bitmaps in all formats 1bpp, 2bpp, 4bpp, 8bpp, 16bpp and 32bpp and test each one separately.

Lastly, I worked on writing filmloop data which is the same as the score (‘SCVW’ resource). The resource stores each frame in the filmloop, its sprite channels and the associated cast member id. Since filmloop don’t have their own cast members, but use the cast members loaded in the cast, this was easier to save than the rest of the resources above. I simply had to write it in a format that is recognizable to the Director engine. This still took some time to write and test since the data loading for filmloops for D4/D5 is slightly different.

There is a slight recurring problem that the sizes of the resources need to be calculated before writing the resource itself because, the size precedes the data. This could be avoided by calculating the size as we write it, but that requires storing the offset of where the size is supposed to go, calculating the size, jumping to the offset, writing the size and then jumping back to the current position, which is an ugly solution.

Overall, the progress this week was good, but I’m worried whether I’m taking too much time testing. The midterm evaluation is coming up in 7 days, and I’m unsure I’ve done enough to count it as a success. I hoped to have completed this task by the midterm, but… Alas!

Week 5: ADL and Parallaction

Introduction

During this week of GSoC, I opened PRs for adding text-to-speech to ADL and Parallaction. I thought that these engines would take me longer than they did, but neither of them were particularly challenging, which was a pleasant surprise.


ADL

I spent the beginning of this week finishing TTS for ADL. Fortunately, it needed very little additional work from last week: I only needed to add TTS to a few extra key presses and clean up the text, primarily by removing dashes that could interfere with voicing. Since ADL’s games are so simple, there wasn’t much to do with this engine, and I was able to make a PR early in the week.


Parallaction

After ADL, I worked on Parallaction, which has a level of complexity that is comparable to that of other engines I’ve worked with. To begin, Parallaction’s text is displayed through methods that are only called once, instead of every frame like most of the engines I’ve worked on. As a result, I didn’t have to track the previously said text for this engine, and it was rather simple to voice the text – although in certain cases, such as labels, I had to store the text to be voiced later, as all possible labels are initialized at once, and voicing them upon initialization would result in a great amount of invisible text being voiced. I was glad to find that voicing the text in these methods covered almost all instances of text, including dialogue options when hovered over.

Nonetheless, Parallaction did have a few exceptions to consider, mainly in the form of its credits and introduction. Most of the text in the introductory cutscene and the credits is in the form of an image, which required finding a good place to detect when these images are rendered. For example, the second and third opening credit lines appear after an animation plays, unlike the first line, which appears the instant the location switches. Therefore, I opted to voice this text in the on instruction opcode, as it is executed when these animations finish. A similar problem emerges with the end credits, which are in the form of images that slowly scroll upward. Trying to voice all of them the instant the credits begin results in the TTS speaking too quickly and being poorly synced with the actual credits. To fix this issue, I found that the instruction opcode inc is executed roughly when another credit moves onto screen. Thus, after voicing the first few credits at once – since inc isn’t executed for these – I voiced the credits one at a time in this opcode, resulting in much cleaner voicing. Finally, I resolved another syncing problem where the test results, displayed after picking a character, disappear too quickly for the TTS to keep up by delaying the loop that controls the location change while TTS is speaking. A similar strategy was used for some of the opening credits. The result is TTS that is better synced to the text for certain images and situations.

Another interesting task for Parallaction was switching voices for each character, which was doable because each character is differentiated by their .talk file. In most cases, a simple array with each file name was enough for switching voices during dialogue. However, some dialogue interactions, such as the one between all three playable characters at the end of the game, have more than one character per .talk file, differentiated by the mood value. This was resolved by updating the array to have extra entries, and adding the mood as an offset to the index for these special cases. Beyond that, the TTS voice needed to be switched when exiting a slide, showing a location comment (which I decided to use an extra “narrator” voice for), and switching characters, allowing for more dynamic character voicing.

Ultimately, Parallaction was an entertaining engine to add TTS to. It was mostly simple, with its exceptions – such as text in the form of an image, unique means of selecting a language, and password inputs – being not too hard to handle. Nonetheless, it’s currently only been tested with Nippon Safes, Inc. From the code, the Big Red Adventure seems to handle text similarly, allowing me to add tentative TTS to it, but I’m uncertain if it will work as well. Some translations also need verification.


Conclusion

I think that this week of GSoC was fairly successful, with PRs opened for adding TTS to ADL and Parallaction. ADL was easy, while Parallaction had very few surprises, making them both simpler than I expected. Next week, I’ll be working on adding TTS to Prince, which I’m looking forward to exploring.

Off to Adibou's Land of Sciences!

We are happy to announce that ScummVM now supports Adibou 2: Nature & Sciences, a 1998 expansion of Coktel Vision's Adibou 2.

While the original Adibou 2 game focused on reading and mathematics, Nature & Sciences explores new topics: history, geography, science & technology, the countryside, and everyday life. Each subject features six playful educational activities. Please note that the 3D car driving minigame is unfortunately not supported in our ScummVM reimplementation, as it was based on a different engine from the rest of the game.

To help us during the testing period, dig out your Nature & Sciences CD, grab a daily build and read our testing guidelines. You will also need an existing installation from the base Adibou 2: Environment CD, and to copy the contents of your Nature & Sciences CD into a freely named subdirectory of the main game directory, as described in our wiki. Then, register the expansion in-game via the "Land of Knowledge" screen, and start collecting sweets as you complete Adibou's activities…or encourage a young relative to do so! Hardworking players may even earn a diploma upon completing a module, which they can proudly display in their room!

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.

Week 4: Sword25, TeenAgent and NGI

This week, I focused on bringing keymapper support to three engines: Sword25, Teenagent, and NGI. Each had its own quirks, but none were too complex—so I was able to make steady progress across the board.


Sword25

The Sword25 engine uses script-driven input handling. That made things straightforward—no refactoring needed.

My job here was mostly investigative: figure out what each key does and document it for the keymapper. Since input behavior was defined in scripts, I didn’t need to touch much engine-level code. The entire process was smooth and quick.


Teenagent

Teenagent was also fairly simple. It involved defining a few custom actions for specific keys and setting up mouse input. The engine doesn’t have complex input modes or conditionally processed input, so once I identified the key behaviors, wiring up the keymapper didn’t take long.


NGI

The NGI engine was the most challenging of the three this week. While it didn’t introduce any new types of problems, it did have more keys and more complexity around where input is processed.

One big issue: keys pressed during menus, modals, or intros weren’t processed through the same input flow as in-game actions. That meant keymapper actions would either:

  • Be ignored in certain contexts, or worse…

  • Be interpreted in places they shouldn’t be

To fix this, I had to carefully trace the engine’s input handling paths and add logic to enable or disable the keymapper at the right times. Most of my time on NGI was spent understanding those flows and placing the toggles precisely.

Once that was in place, implementing custom actions and mapping key behaviors was straightforward.


Wrap-Up

This week, I:

  • Implemented keymapper support for Sword25, Teenagent, and NGI
  • Got the NGI keymapper PR merged
  • Got the keymapper action label normalization PR merged

Next week, I’ll continue this momentum and explore new engines.

Making our way to the destination… one measured step at a time

This week’s progress was somewhat better than the previous. Continuing from last week’s writing task, I was successfully writing the initial map, memory map, the key data and the cast data (‘CAS*’ data) successfully. Until then, we’re outputting the data as it is, without any modification. The goal was to be able to write movies for now even if there aren’t any modifications. This week I got to actually writing data that will be modified.

The first thing was the cast config data (‘VWCF’ chunk) data, this was pretty straightforward. I just had to save all the data that was being ignored and write it back in `saveConfig()`. Although, it involved calculating checksum for the config.

The next thing was the cast member data (‘CASt’ chunk), this was the tricky part. As I understand it, a ‘CASt’ chunk is made up of three sections, the first is the headers (which includes the metadata, like flags, cast member type and size of the rest of the two sections), there is info and data. The info has “strings” inside which contain information about the cast member like the name of the cast member, the file name (if it’s external), id of the script attached to it, font information, etc. Depending on the cast member, this number of strings varies. Hence, the size of the info also varies. Since I need to write the size of the info and data sections in the header, I have to calculate it from the loaded data. Hence, I ended up creating three methods getCastResourceSize(), getCastInfoSize(), and getCastDataSize(), this is useful since, I’ll need the ‘CASt’ resource size when I’m writing it in the memory map. Thankfully, this structure of info is consistent across all ‘CASt’ resources. So, I can create a single method in the base class and it works for all the cast members. The only section the cast members differ in is the data section for which I created a custom method in each derived class. (Note: Here the base class is CastMember and the derived classes are TextCastMember, PaletteCastMember, BitmapCastMember, etc.) The data section contains the cast member data (the bounding box dimensions, background color, flags, etc.). Now that I think about it, data and info are arbitrary names. Both contain different data about the castmember, but data still.

Majority of this weeks’ time was spent on testing chunks being written(even though there were only two (‘VWCF’ and ‘CASt’)). For debugging, I’ve also introduced a new channel (kDebugSaving). Some of the cast members are still haven’t been tested thoroughly.

These cast members can have children resources. Data like bitmap (pixel data in ‘BITD’ resource), text (‘STXT’ resource), are stored in these children resources. After the ‘CASt’ resource, our next step is writing these children resources which I think are going to be easier to write and test than the ‘CASt’ resource. Sidetracking, I tried writing the ‘CLUT’ (Palette data) and ‘STXT’ (Text data).

Even though, I was occupied the entire week, this still isn’t that much progress. It is still some time before this task is on its way to completion. I’ll need to speed up if I want to complete it within time.

Also, I realized my blogs are not as information rich as I’d like them. So, I’m planning on writing a mid-week blog to explain in detail, what I’ve done till now (shouldn’t take long!).

Week 4

Welcome to this week’s blog update. This week was focused on fixing bugs related to seeding and set.dat uploading, improving filtering mechanisms, and rewriting parts of the processing logic for scan.dat.

After some manual checks with the recent set.dat updates, a few issues surfaced:

1. Identical Detection Fix

Some detection entries had identical file sets, sizes, and checksums, which led to issues during the automatic merging of set.dat. Previously, we used a megakey to prevent such duplicates, but since it included not only file data but also language and platform information, some identical versions still slipped through.
To solve this, I replaced the megakey check with a more focused comparison: filename, size, and checksum only, and logging the details of the fileset that got clashed.

2. Punycode Encoding Misplacement

The filenames were being encoded using Punycode every time they were processed for database insertion. However, there was no requirement for this encoding as it should have occurred earlier — ideally at the parsing stage, either by the scanning utility that generates .dat files or on the application’s upload interface. I have removed the encoding during database updates. Though I still have to add it at the scan utility side, which I’ll do this week.

3. Path Format Normalization

Another issue was related to inconsistent file paths. Some set.dat entries used Windows-style paths (xyz\abc), while their corresponding detection entries used Unix-style (xyz/abc). Since filtering was done using simple string matching, these mismatches caused failures. I resolved this by normalizing all paths to use forward slashes (/) before storing them in the database.

4. Improving Extra File Detection with clonof and romof

While analyzing filesets, I encountered a previously unnoticed clonof field (similar to romof). These fields indicate that extra files might be listed elsewhere in the .dat file. The previous logic only looked in the resource section, but I found that:

  • Extra files could also exist in the game section.

  • The file references could chain across multiple sections (e.g., A → B → C).

To address this, I implemented an iterative lookup for extra files, ensuring all relevant files across multiple levels are properly detected.

scan.dat Processing Improvements

For scan.dat, I introduced a file update strategy that runs before full fileset matching. All files that match based on file size and checksum are updated first. This allows us to update matching files early, without relying solely on complete fileset comparisons.

Minor Fixes & UI Enhancements

  • Prevented reprocessing of filesets in set.dat if a key already exists in subsequent runs.

  • Passing the --skiplog CLI argument to set.dat processing to  suppress verbose logs during fileset creation and automatic merging.

  • Improved filtering in the dashboard adding more fields like engineid, transcation number and fileset id, and fixing some older issues.

  • Introduced a new “Possible Merges” button in the filesets dashboard to manually inspect and confirm suggested merges.This feature is backed by a new database table that stores fileset matches for later manual review.

Week 4: MADE

Introduction

During this week of GSoC, I mostly focused on adding TTS to MADE, with some work on ADL as well. A PR has been made for MADE, though there may be more work for it in the future.


MADE

Most of my week was dedicated to adding TTS to MADE. MADE – or at least, Return to Zork – offered many new challenges. For one, rather than displaying text one piece at a time, like I’ve seen in most of the engines I’ve worked with, Return to Zork displays several pieces every frame across multiple channels. This meant that I couldn’t simply track the previously said text in a single variable to avoid speech loops, as it would be repeatedly overridden. To solve this problem, I tried several different approaches. First, I tried finding the function that actually sets most of the text, which I found in the form of sfReadMenu. This function does set the text one piece at a time, which would be good for voicing. However, the issue with it is that sfReadMenu is called several times when a new scene loads, even when the text that it’s setting isn’t visible, resulting in extraneous text being voiced. I tried to check for any flags that could identify this text as invisible, but I didn’t find anything good to use, so I scrapped this idea. I then thought of tracking several different previously said texts and their channels in an array, but this seemed unnecessarily cumbersome, especially because the channels are often changing. Eventually, I settled on adding a variable for the previously said text to the individual channels themselves, and using this variable to check if text should be voiced. This worked quite well in most cases, and after accounting for exceptions where the spoken text should be queued, it resulted in good TTS for much of the game.

MADE, however, had a few additional problems. For one, it seems to handle hovering over objects completely within the game scripts, which means that there’s no easy place to detect when the cursor hovers over buttons in Return to Zork’s save/load screen. After trying to search for possible conditions I could use, I eventually decided to recreate the click boxes for the save, load, and cancel buttons, as well as the text entry boxes for new save slots. Fortunately, none of the text for these buttons is in the form of an image, which meant I could find the IDs for each individual object or menu that displays them and use these IDs to get their text, a strategy that should work across languages.

Another issue arises with the tape recorder in Return to Zork. The text for the name and numbers on each tape recorder entry are displayed within channels, but the text that corresponds to these pieces – such as “name” or “trk” – are in sfDrawText, which results in awkward, out-of-order voicing (for example, when the track is 001 and the name is Wizard Trembyle, TTS would say “trk, name, 001, Wizard Trembyle”). Fortunately, this could easily be avoided by not voicing these pieces in sfDrawText, and instead fetching this text when voicing the channel text, resulting in cleaner voicing (such as “trk: 001, name: Wizard Trembyle”). Unfortunately, the tape recorder is interactive, which further complicates matters. Voicing each piece separately can result in issues if the user switches between entries where a number is the same, such as one entry having max track 001 and the other having max track 001, since the previously said text won’t change and thus it won’t be voiced at all. Getting around this involved voicing all pieces – name, track, and max track – whenever the name changes, as it should be unique for each entry, alongside voicing the track individually in specific situations to account for the user switching tracks. Voicing the time was another matter: if it’s voiced whenever it changes, then the TTS system will repeatedly try to voice it as it ticks upward, resulting in awkward interrupts or unnecessary lag. I eventually decided that it was best to try to voice it when the sound clip ends, so it’ll only be voiced at the moment it stops moving. These techniques involved keeping track of flags and the status of the previously said variables, since they’re modified and retrieved each frame, but it seems to have resulted in more responsive voicing of the tape recorder.

In conclusion, MADE was a little more challenging than expected, since Return to Zork has several simultaneous channels and handles much of its logic within the game scripts. Nonetheless, it was a fun experience adding TTS to it. It seems finished now, though it remains to be seen how well it will handle different games or translations, and it has a few hardcoded translations that need verification.


ADL

ADL has seemed quite easy so far. As an engine for rather simple text-based games, almost all of its text is handled by Display::printString and Display::printAsciiString. It doesn’t even require tracking the previously said text, since these methods to display text are called only once, and there are no buttons that require responsive voicing. Thus, I think I’ve almost finished TTS for it, though challenges may arise later.


Conclusion

Over week 4 of GSoC, I’ve completed more TTS implementations, with MADE finished and ADL mostly finished. MADE offered the most challenges, due to its different ways of handling text, but its lack of text in the form of images was welcome. It’s been an interesting week, and I’m excited to explore more engines. Next week, I’ll be looking to finish TTS for ADL and begin work on TTS for Parallaction.

SLUDGE-based games are ready for testing

Do you want to expose the deepest secrets of the Tremendous Corporation® and save the games industry? Are you interested in helping a young man who's out of order in an alien future? Or will you join the story of Nathan, who wants to get a second chance in life?

If you answered "yes", then get ready, as we are happy to announce full support for all games based on the SLUDGE engine. The original work began back in 2017, by one of our GSoC students, Simei Yin. Now, after many years of development, the following games are available for public testing:

  • Above The Waves
  • Cubert Badbone, P.I.
  • Frasse And The Peas Of Kejick
  • The Game That Takes Place On a Cruise Ship
  • The Interview
  • Lepton's Quest
  • Life Flashes By
  • Mandy Christmas Adventure
  • Nathan's Second Chance
  • Out of Order
  • Robin's Rescue
  • The Secret of Tremendous Corporation

Essentially, this is the list of all the SLUDGE-based games we know of.

So download your free copies of the games from our game downloads page and use a daily development build to start playing. If you encounter any issues, please submit bug reports to our issue tracker.

Finally, please contact us if you are aware of other games using SLUDGE, variant releases, or releases that are not getting detected properly.

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.

Week 3: Titanic, Tetraedge, and a Sea of Strings

This week was quite busy and eventful! I had my keymapper implementations for the Supernova and Voyeur engines successfully merged, and I also completed keymapper support for Titanic and Tetraedge. In addition, I took on an extra task to help normalize all keymapper action descriptions across the project.


Titanic: The Most Challenging Engine So Far

Out of all the engines I’ve worked on so far, Titanic has definitely been the most complex.

Implementing the keymapper took me about four days, most of which were spent trying to understand how input was being handled. Unlike other engines, Titanic processes input through a system of “messages.” Key presses are encapsulated in these messages and passed around to various components that need to handle input. This indirect approach made the input flow harder to trace.

Even though I’m still not 100% confident I fully understand every detail of the mechanism, I eventually got the keymapper working by mimicking how raw keycodes were originally processed. After two days of reading and experimenting, things finally started to click — and from that point on, it was just a matter of replacing keycodes with action-based mappings and writing the action bindings.


Tetraedge: The Easiest One Yet

In contrast to Titanic, Tetraedge was a breeze. The engine only uses three keys and mouse input, so I was able to wrap up the keymapper integration in half a day. Always nice to have a quick win!


Action Label Normalization: A Multilingual Cleanup

ScummVM’s GUI is actively translated into many languages by the community using Weblate. Weblate automatically picks up strings that are explicitly marked for translation in the code.

To reduce the translators’ workload, it’s important that identical strings are reused consistently across the codebase. Weblate treats strings with even minor differences — like different capitalization — as separate entries. Unfortunately, over the years, different contributors have used varying styles (sentence case, title case, etc.) for keymapper action descriptions.

To help streamline translations, I went through and normalized 1100+ action labels to follow sentence capitalization. The task itself wasn’t difficult, but it was quite repetitive. Fortunately, sev helped by providing a git grep and awk-based workflow that made identifying and editing the labels much faster. With that in place, I was able to get through the normalization process much more efficiently.


Wrapping Up

This week, I:

  • Got my Supernova and Voyeur keymapper PRs merged 🎉

  • Implemented full keymapper support for Titanic (the hardest one so far)

  • Added keymapper support to Tetraedge (the easiest one yet)

  • Normalized 1100+ keymapper action labels for consistent translations across the GUI

Next week, I plan to keep the momentum going and tackle keymapper support for more engines. With each engine, I’m getting a better understanding of the diverse input systems in ScummVM—and figuring out how to adapt the keymapper to each one’s quirks.

The infant is learning… slowly

Quarter of GSoC period is gone… Wow, time flies!

This week I switched my task to saving Director movies in ScummVM. This was my main task of focus in my proposal. @sev explained to me that some games need to save .dir movies.

A director movie is in a containerized format. Chunks of different types of files are stored under a resource header. We can load and parse the movies in ScummVM but we cannot save them back since we throw away the things we don’t know yet. However, it is not as simple as reading the data in the file and writing it back to file. Because these games may modify the data inside these chunks. Also, the loading functionality is scattered throughout the Director engine. The ProjectorRays project by @djsrv is a great resource for this task.

So, the task is to keep the unknown data for the sake of saving the movies and to figure out how I can save the chunks that will be modified. All the other chunks that won’t be touched will be written verbatim.

This week, I started by writing the “reverse functions” to the archive loading functions in the Archive class readStream(), readMemoryMap(), readKeyMap()and readCast()  namely writeStream(), writeMemoryMap(), writeKeyMap() and writeCast(). The former load an Director movie archive whereas the latter saves the said archive. The rest of the chunks as described in the memory map (chunk ‘mmap’) are written as they are. For now, the movies are being written properly (except for a minor hiccup which I will check out today). Although I have done much testing on the windows, Director 5 movies I have, but @sev asked me to test this with a variety of movies before opening a pull request. Another idea is to store all the data for a specific chunk in a struct for consistency just like how ProjectorRays does. However, ProjectorRays doesn’t handle all chunks.

My initial idea to saving director movies was to move all the loading functions to the Archive class so that it can store them. It will have all the data and write it whenever necessary. However, @sev pointed out, that is a substantially flawed approach. There are far too many chunks to move to the Archive class, they don’t belong to the Archive class and it will be an unmaintainable mess. Thankfully, I didn’t spend much time on it.

Now, this week, I need to start writing Cast data (reversing loadCastData()).

I had to go to Pune again, for the publishing of our college magazine. Finally, that responsibility of mine is over. This week, I was logging in late, which moving forward, won’t be happening.

I’ve finally started to pick up the pace (although still slow). Things have started to look little brighter now…

Week 3

Welcome to this week’s blog update.

Most of this week was spent rewriting the logic for processing set.dat files, as the previous implementation had several inconsistencies. As shown in Figure 1, the earlier logic directly compared checksums to determine matches. However, this approach only worked when the file size was smaller than the checksum size (typically md5-5000), since set.dat files only include full file checksums. This caused us to miss many opportunities to merge filesets that could otherwise be uniquely matched by filename and size alone.

Fig.1 : Previous query used in matching.

Since set.dat files only contain entries that are already present in the detection results (with the exception of some rare variants I discovered later in the week), we should typically expect a one-to-one mapping in most cases. However, some filesets in set.dat can correspond to multiple candidate entries. This happens when the name and size match, but the checksum differs—often due to file variants. This case needs to be handled with manual merge.

Previously, the logic for handling different .dat file types was tightly coupled, making it hard to understand and maintain. I started by decoupling the logic for set.dat entirely. Now, the candidates for the match for  set.dat filesets are filtered by engine name, filename, and file size (if it’s not -1), excluding out the checksum. It is made sure that all the detection files(files with detection flag set to 1) follow the condition.

 

Initially, I was filtering out the fileset only with the highest number of matches, assuming it was correct. However, that approach isn’t reliable—sometimes the correct match might not be the largest group. So all these candidates need to go for the manual merge. Only when all checksums match across candidates can we be confident in an automatic match.

I also added logic to handle partial or full matches of candidate filesets. This can happen when a set.dat is reuploaded with changes. In such cases, all files are compared: if there’s no difference, the fileset is dropped. If differences exist, the fileset is flagged for manual merge.

Finally, I handled an issue with Mac files in set.dat. These files aren’t correctly represented there: they lack prefixes and have checksums computed for the full file rather than individual forks. So, these filesets are dropped early by checking if no candidates are found for that fileset after SQL filtering.

Other Updates

During seeding, I found some entries with the same megakey, differing only by game name or title. Sev advised treating them as a single fileset. So now, only the first such entry is added, and the rest are logged as warnings with metadata, including links to the conflicting fileset.

Other fixes this week included:

  • Removing support for m-type checksums entirely (Sev also removed them from the detections).

  • Dropping sha1 and crc checksums, which mainly came from set.dat.

Next Steps

With the seeding logic refined, the next step is to begin populating the database with individual set.dat entries and confirm everything works as expected.

After that, I’ll start working on fixing the scan.dat functionality. This feature will allow developers to manually scan their game data files and upload the relevant data to the database.

Week 3: Draci

Introduction

Another week of GSoC has passed, with more text-to-speech work done, as a PR has been opened adding TTS to Draci. In addition, TTS for WAGE and CruisE has been merged, and TTS for Cine has been more rigorously tested.


Draci

Much of my time this week was invested in adding TTS to Draci. In terms of text complexity, Draci was roughly below average: most of its text was displayed in a few places, which were easy to track down. There were, however, several considerations regarding the engine. For one, Draci’s Czech and Polish translations have full (or almost full) voiceovers, while the German and English translations only have subtitles. To account for these differences, I split TTS into subtitles and objects, my first time doing so, and had to refrain from voicing subtitles for versions with voiceovers. Beyond that, I had to implement making the subtitles last as long as the TTS is speaking, as the subtitles normally move so fast that the speech can’t keep up; TTS for missing voice clips for the Czech and Polish versions, which received its own option at the suggestion of my mentor; and recognition of cases where the text is only punctuation, which isn’t voiced by the TTS system, and is thus immediately skipped (the text remains on screen for as long as TTS is speaking, meaning if the text isn’t voiced, the system immediately believes that it’s time to move to the next subtitle). All of these were interesting exercises in timing and accommodating different versions.

In my opinion, one of the most interesting components of Draci was the encoding. Draci uses encodings not present in ScummVM’s CodePage enum, which meant I had to hunt them down and manually write a translation table. Fortunately, the Dragon History website states that the Czech, English, and German translations all use Kamenický encoding, with the German translation having an exception for ß. This simplified the process greatly, as it just required creating a translation table for Kamenický encoding and converting the bytes, a similar process to the fix for TeenAgent’s Russian translation. Unfortunately, the Polish encoding is only described by the same website as “some ridiculous proprietary encoding”. I initially tried observing the bytes of the in-game strings to figure out the encoding for each character, but I worried that I’d miss a few characters. Instead, I found the character sets in the original Draci source code, which showed the bytes of UTF-8 encoding for each character as characters in the Windows-1250 code page. This meant that all I had to do was translate these characters according to Windows-1250 encoding to bytes, then combine these bytes to obtain the UTF-8 encodings for the Polish characters. That was most of the work for encodings, aside from altering the encoding table for German and English to replace certain Czech characters with equivalents in those languages, as TTS for the credits struggled to pronounce those characters.

Ultimately, Draci’s encodings were interesting to explore, and I’m very glad that the Dragon History website had them listed.


MADE

At the end of the week, I started work on MADE, but more work must be done for it. It seems that Return to Zork handles text a little differently from what I’m used to, as it displays text in several simultaneous channels every frame. My usual solution of tracking the previously said text with a variable won’t work here, as it’ll be overridden by the other text. I’ve thought of a few possible solutions, including tracking several different previously said texts or finding where the text is set – which I may have already found, but I need to do more investigation – and I plan to explore them over this week.


Miscellaneous

Aside from Draci and MADE, I fixed a few problems with CruisE – thanks to my mentor for noticing that the encoding was wrong for some of the versions, among other necessary changes – and tested TTS for Cine for other versions. For Cine, I was worried that my method of checking image names for text in the form of an image would fail across different versions of Future Wars and Operation Stealth, but I was pleasantly surprised to not find any issues, at least in my testing. The only changes I ended up making were adding a few new translations, fixing some small caveats with the encoding for the German and French translations, adding line-by-line TTS for the Operation Stealth copy protection screen (which I initially had, but I removed due to fears that it wouldn’t work across versions), adding TTS for Future Wars’s grid copy protection screen, and adjusting for exceptions across versions (such as the US Amiga version of Future Wars using a different copy protection screen from all other Amiga versions). Cine is thus much more thoroughly tested across different game editions now.


Conclusion

This week involved more work on TTS, with testing for Cine, TTS implementation for Draci, and the beginnings of TTS implementation for MADE. Exploring Draci’s encodings was the most entertaining part, and I hope to bring over my more in-depth knowledge of encoding methods into future implementations. Next week, I’ll be finishing TTS for MADE and beginning TTS for ADL, alongside any changes I need to make to my open PRs for Cine and Draci.

All aboard! The Last Express is ready for testing!

Paris, July 24, 1914. We are excited to announce that Jordan Mechner's acclaimed real-time adventure The Last Express is now ready for testing in ScummVM!

Step into the shoes of Robert Cath, an American doctor on the run from murder charges, as he boards the legendary Orient Express for what would be its final journey before the outbreak of World War I.

What makes this adventure game truly unique is its time-based gameplay - the game takes place in real time, and characters move within the train following their own schedules, speaking in their native languages, and living their lives whether you're watching or not. But don't worry... you can always rewind time if you missed anything important!

To test this game, you'll need the original version of the game (either from the original hybrid DOS/Windows/Mac CDs or from GOG.com) and a daily development build of ScummVM. Please note that the Gold Edition available from Steam is NOT supported - only the original version is compatible at this time. The engine supports the following languages for the game: English, French, German, Italian, and Spanish. We couldn't locate the Japanese version at the moment, but please contact us if you have it!

Grab a daily build, have our testing guidelines handy, and get ready for an incredible journey through history, mystery, and one of the most innovative adventure games of the 1990s — and besides... the Orient Express is such a beautiful train, wouldn't you want to take some screenshots while you're there?

Week 2

Welcome to the weekly blog.
After wrapping up work on macfiles last week, I finally moved on to testing the first two phases of the data upload workflow — starting with scummvm.dat (which contains detection entries from ScummVM used for populating the database) and set.dat (data from older collections, provides more information).

Database Changes: File Size Enhancements

Before diving into the testing, there was a change Sev asked for — to extend the database schema to store three types of file sizes instead of just one. This was necessary due to the nature of macfiles, which have:

  • A data fork

  • A resource fork

  • A third size: the data section of the resource fork itself

This change introduced significant modifications to db_functions.py, which contains the core logic for working with .dat files. I had to be careful to ensure nothing broke during this transition.

Punycode Encoding Logic Fixes

At the same time, I fixed the punycode logic in db_functions.py. Punycode encoding (or rather, an extended version of the standard used in URL encoding) is employed by ScummVM to convert filenames into filesystem-independent ASCII-only representations.

There were inconsistencies between punycode logic in db_functions.py and the original implementation in the Dumper Companion. I made sure both implementations now align, and I ran unit tests from the Dumper Companion to verify correctness.

Feeding the Database – scummvm.dat

With those fixes in place, I moved on to populating the database with data from scummvm.dat. While Sev was working on the C++ side to add the correct filesize tags for detections, I ran manual tests using existing data. The parsing logic worked well, though I had to add support for the new “extra size” fields.

Additionally, I fixed the megakey calculation, which is used later when uploading the scummvm.dat again with updates. This involved sorting files alphabetically before computing the key to ensure consistent results.

I also introduced a small optimization: if a file is less than 5000 bytes, we can safely assume that all checksum types (e.g., md5-full_file, md5-5000B, md5-tail-5000B, md5-oneMB, or the macfile variants like -d/-r ) will be the same. In such cases, we now automatically fill all checksum fields with the same value used in detection.

Uploading and Matching – set.dat

Finally, I worked on uploading set.dat to the database, which usually contains the follwoing – metadata (mostly irrelevant), full size checksums only and filesizes.

    • scummvm.dat doesn’t contain full file checksums like set.dat, so a match between files from set.dat and scummvm.dat is only possible when a file’s size is less than the detection checksum size, generally 5000 Bytes.
    • This transitions the status from “detection” to “partial” — we now know all files in the game, but not all checksum types.

    • If there is no match, we create a new entry in the database with the status dat.

Fixes :

There was an issue with the session variable @fileset_last, which was mistakenly referencing the filechecksumtable instead of the latest entry in the filesettable. This broke the logic for matching entries.

When a detection matched a file, only one checksum was previously being transferred. I fixed this to include all relevant checksums from the detection file.

 Bug Fixes and Improvements

Fixed redirection logic in the logs: previously, when a matched detection entry was removed, the log URL still pointed to the deleted fileset ID. I updated this to redirect correctly to the matched fileset.

Updated the dashboard to show unmatched datentries. These were missing earlier because the SQL query used an inner JOIN with the game table, and since set.dat files don’t have game table references, they were filtered out. I replaced it with a LEFT JOIN on fileset to include them.


That’s everything I worked on this past week. I’m still a bit unsure about the set.dat matching logic, so I’ll be discussing it further with Sev to make sure everything is aligned.

Thanks for reading!

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.

Week 2: Supernova & Voyeur

This week, I continued working on implementing keymapper for more game engines. The highlight was getting my SLUDGE keymapper PR merged, and diving into two new engines: Supernova and Voyeur.


Supernova Engine: The Challenge of Custom Actions

The Supernova engine powers the Mission Supernova series—old-school point-and-click adventures that are surprisingly engaging (and tough!). To test my changes, I had to play through parts of the game, and I genuinely enjoyed the puzzles and storytelling, despite the lack of modern visuals. Honestly, without the walkthrough, I would’ve spent more time solving puzzles than coding the keymapper.

Unlike SLUDGE, this engine uses engine-driven input handling, which gave me the flexibility to define custom actions instead of just simulating keypresses. Why is this better? Because:

Custom actions give the engine full control over what happens when a key is pressed, while mimicking keypresses can cause issues like duplicate handling or unexpected default behavior still triggering.

The biggest challenge I faced in Supernova was how the engine handled keyboard input. It stored the raw keycode of every key press in a single variable called _key, which was then used for both gameplay actions and full-text input.

This became a problem when introducing custom actions. Unlike raw keycodes, custom actions are more abstract—they represent “what to do” rather than “what key was pressed.” But if I allowed both to be handled the same way, the engine might mistakenly store a custom action as if it were a real keypress, which would break things like text input.

The Solution

I separated the handling of keycodes and actions using two variables:

  • _key → stores the raw keycode (used only when text input is needed)

  • _action → stores the custom action (used during normal gameplay)

Then, I made sure the engine would:

  • Disable the keymapper when full keyboard input was required (e.g., entering text). This way, real keypresses would go through, and _key would be filled correctly.

  • Enable the keymapper during gameplay. In this mode, keypresses would be intercepted, converted into actions, and stored in _action.

This setup allowed me to cleanly separate when to use keycodes and when to use actions, without breaking any existing input logic. Once this system was in place, replacing the original key handling with action-based logic was pretty straightforward. All I had to do was replace hardcoded keycodes with custom actions.


Voyeur Engine: Simple, Then Smarter

Compared to Supernova, Voyeur was a breeze. The game is almost entirely mouse-driven, with very little keyboard interaction. I initially thought I could get away with two simple binds—left and right click—and be done in a few hours.

But… why stop there?

I decided to improve it by breaking the keymap into multiple context-specific keymaps, depending on where the player is in the game. For example, different menus or in-game sequences would have their own set of keybinds with clearer action labels.

The tricky part was figuring out where in the code to enable/disable each keymap. That took a whole day of reading through the engine, but once I nailed it, the final implementation was clean and intuitive.


Wrap-Up

This week, I:

  • Got my SLUDGE keymapper PR merged 🎉

  • Implemented full keymapper support for Supernova (with custom actions)

  • Added smarter, context-aware keymapping to Voyeur

Next week, I plan to continue this momentum and bring keymapper support to more engines. The work is getting smoother now that I’m more familiar with ScummVM’s input systems—and every engine brings a new twist to solve.

Trying to walk when you can’t crawl!

This week, I actually got to implementing the Movie castmembers in ScummVM. The idea behind this was to use the MovieCastMember as a wrapper around a Movie member. This Movie class variable will handle the updating of frame number, execution of Scripts, handling events, etc. just like how a the main movie handles it. The tricky part was that it does not have a window. The main movie has a dedicated window where it can render its Sprites. The Movie castmember needs to be rendered in a box inside this main window. For this purpose when the main window updates (renders the next frame in the main movie) it also asks the MovieCastMember for its updated sprites using a function called getSubChannels(). So, the idea was simple, before returning these sprites, we ask the Movie castmember to “step”, update its own state.

But, this simple idea lead to me facing many difficulties. First of all, I had to include a check at each place the _window member was accessed in all functions in classes Movie and Score. The class Window also tracks the the Lingo State, which was now inaccessible for our MovieCastMember. So, the Window class needed refactoring.  I separated all Lingo State keeping functionality into a separate class LingoStateKeeper and migrated all the relevant functions  from Window. I added a member _stateKeeper to Window and Score. There was also a question of whether MovieCastMembers can switch movies using the go to movie ... command. I created a movie as a test in the original Director to check this. @sev asked me to add it to the director-tests repository.

This task is still incomplete. I’m able to see externally linked movies in ScummVM’s Director but they are stuck. They still do not allow execution of their own scripts.

What was thought to be a two day job, ate my entire week. I didn’t have my Director basics down. I hadn’t read the Manuals that were given to me a weeks ago.  I made obvious logical mistakes, I got stuck at multiple points… to the point that @sev has asked me to take a step away from this task.  I was working on processing Lingo script events the last. He will continue from here. He asked me to focus on my main task of  saving Director Movies. Hopefully, I’ll learn enough to come back to this task and be able to finish it.

I’m worried at this pace, whether I’ll be able to complete my project or rather make a meaningful contribution to ScummVM in a reasonable time period. I wanted to work yesterday (Sunday) but I thought it’d best to take the day off and read the Interactivity Manual (even though its a bit late now) and start working today (Monday) with a fresh mind.

This week I’ll focus on studying ProjectorRays to start writing director files. Study the different chunk types in a .dir file.

So… the first two weeks for me have been much less than perfect. I’m too focused on the greener side of the river forgetting there is deep water in between, gotta pass through that first or I’ll drown… But I’m optimistic. Even though I couldn’t keep the promise of having a good week from last week, I hope to have a comeback week starting today.

Week 2: CruisE

Introduction

Another week of GSoC has passed, and I’m fairly satisfied with my progress. This week, I mostly focused on adding TTS to CruisE, and I’ve opened a PR for it. However, I also spent some time working on Draci, and on updating older PRs.


CruisE

I began this week with adding TTS to CruisE. Fortunately, CruisE wasn’t too difficult to work with, as it has very few cases of text in the form of an image. In addition, most of Cruise for a Corpse’s text is displayed from renderText, which made it much easier to identify where text is displayed in the code. The difficulty with Cruise for a Corpse, however, came from making it user-friendly. For example, text can’t simply be voiced from renderText, as there are cases when the text appears on screen a significant amount of time after renderText is called, or cases where the text isn’t visible, such as when the user tries to open the inventory in the copy protection screen. In a similar manner, text can’t be voiced from createMenu, because there are times when it shouldn’t be voiced (mainly when the inventory is empty). It also can’t be voiced from createTextObject, which handles much of the game’s text, because freezeCell is sometimes called on objects created with this method, which means that the text appears on screen later. Voicing text for CruisE, therefore, required addressing these exceptions accordingly, such as by storing the text from createTextObject and voicing it when the cell is unfrozen; queuing the text of the first menu item hovered over after a menu is opened instead of interrupting the current speech, so the title of the menu is always voiced; and checking for button input in Op_GetMouseButton to stop TTS when skipping through dialog or a cutscene. I believe that these changes make the TTS for CruisE more responsive and more accurate to the gameplay.

Ultimately, actually finding where text was displayed in CruisE wasn’t that difficult. The more time-consuming part was finding and accounting for the many exceptions, skips, and places in the code that freeze, show, or hide text, in order to create a user-friendly experience. It was entertaining to look through the code and ponder the best implementation, as its code was more spread out than that of Cine or WAGE.


Miscellaneous

After CruisE, I worked on various components related to my project. For one, I revisited my WAGE PR, which required a fair number of changes, including resolving some difficulties across games that I missed (in some cases, different WAGE games handle the same circumstances surprisingly differently, such as Eidisi only calling renderSubmenu when hovering over a new item, while Ray’s Maze and Another Fine Mess call it every frame, requiring a check for the last submenu item that was hovered over in these games). I also worked on fixing TTS for TeenAgent’s Russian translation, which uses a custom encoding that has to be replicated for proper voicing – fortunately, it seemed that this encoding followed a simple pattern of adding a number to the original character to obtain a Cyrillic character in UTF-8, though since I don’t speak Russian, I’m uncertain if this works in all cases.

Beyond that, I started work on TTS for Draci. So far, Draci seems even simpler than CruisE, with fewer exceptions and oddities. Good progress has been made on Draci, but there is always a chance that something unexpected will emerge.


Conclusion

CruisE was a somewhat more complex engine to add TTS to than WAGE or Cine, and it was entertaining to hunt down its different exceptions and learn how it handles its inputs and menus. A PR has been opened for it, and while I imagine that there’ll be more work for it in the future, the hardest part for it is done. Next week, I’ll be continuing work on Draci, and I look forward to completing TTS support for it.