Welcome to the ScummVM planet - This aggregates the personal blogs of developers, teams members and active participants from all around the ScummVM community.
If you wish to subscribe to updates to the planet or individual blogs please use the links on the right hand side.
To add your blog to the planet contact DJWillis.
January 25, 2015
January 11, 2015
ScummVM takes you on an adventure in the world of Zork, with support for two
new games! In Zork Nemesis: The Forbidden Lands, your task is to embark on a
journey to solve the mystery of a spirit called Nemesis, and of the four
alchemists it killed. On your second journey to Zork, in Zork: Grand
Inquisitor, you must find three legendary objects that will restore all magic
to the realm and bring about the fall of Grand Inquisitor Yannick, whose goal
is to "shun magic, shun the appearance of magic, shun everything, and then shun
The ScummVM Team is proud to announce that both
Zork Nemesis: The Forbidden Lands
Zork: Grand Inquisitor
are now playable in ScummVM using the latest
daily builds, and ready for
testing. As usual, all bugs should be reported to
our bug tracker following our
bug submission guidelines.
While you play through the two games, we would also love it if you could take some
screenshots for us.
So, grab your game discs and help test! If you don't own the games already, they can be
purchased online at GOG.com:
by md5 (firstname.lastname@example.org) at January 11, 2015 12:00 AM
December 31, 2014
We are proud to announce the release of a new version of ResidualVM, bringing
Myst III: Exile support, more than thirteen years after its original release.
Initial work towards adding Myst III support began back in 2009 as a side project.
A work in progress version of the engine was included in ResidualVM
two years later. The game became completable in 2012, but only now do we feel confident
enough to release a stable version providing a feature complete experience.
Additionally, some bugs have been fixed in Grim Fandango, and game data verification
has been added on first launch. So that you'll know if your game data was
copied correctly from your CDs.
Whether you are using Windows, Linux or OS X, you can check out our
downloads page, get yourself a copy of
ResidualVM 0.2.0, and decide later if you'd rather visit the land of the dead or
the land of a revengeful trapped man. The changes in version 0.2.0 are detailed in our
Instructions to setup the games can be found on our wiki.
As usual, even though we have tested the games thoroughly you might encounter game-breaking
bugs. So, please save often and report bugs to our
Our thanks go to Presto Studios, for releasing such a great game, and to the people involved
with testing ResidualVM over the years, allowing us to provide high quality releases.
2015 already looks like a promising year for ResidualVM, thanks to the ongoing effort
for adding Escape from Monkey Island support.
by bgK (email@example.com) at December 31, 2014 12:00 AM
December 23, 2014
We are planning to release ResidualVM 0.2.0 with support for Myst III as our second supported game.
The Grim engine has had quite a few changes since 0.1.1, so to avoid any big showstoppers for Grim Fandango, we need your help.
We need people to play through Grim Fandango and Myst III, preferably testing all the optional parts
that aren't required for completion too. If you are interested,
download our 0.2.0 pre-release builds (not the unstable builds),
and start playing.
Any bugs should be reported to our issue-tracker
on GitHub, and when you complete, you should post in our forums.
Details can be found here.
by aquadran (firstname.lastname@example.org) at December 23, 2014 12:00 AM
November 30, 2014
Yes, another Broken Sword post. In previous posts I wrote about my initial work to add support for the mac version of the Broken Sword game in ScummVM
and some more work I did to fix graphical glitches with this version. At that point the game was working fine for me. But soon, we got a report on the forum
that the speech was not working. Obviously it was working for me, I would have noticed if it wasn't. So what the heck?!
Bug reports are good. They stop me getting bored. And they show that other users have the mac version of Broken Sword and benefit from my work, which is also gratifying. So let's look at that issue. And to do so, first let's rewind to my first post. I wrote that I assumed the files with the same name (including the extension) as the files of the Windows version where in the same format, and in particular used the same endianness. And the files with a different extension were big endian in the mac version and little endian in the Windows version. That proved mostly correct (a few resources had been left as little endian data in files otherwise converted to big endian).
Except that wasn't correct. So why did it work? Because I had been lucky. When I initially worked on supporting that game it looked like my guess was correct, and I therefore made quick progress as I was not distracted by some strange behaviour. But...
Let's start the story from the beginning:
In the Windows version the speech is stored in a file named speech.clu
. There are actually two such files, one on each CD, and they store the speech as 16 bits compressed mono wave data. And as you can expect the data is little endian.
In the Mac version, the files have the same name (speech.clu
). So in my initial implementation I assumed the speech data was little endian in the mac version as well. And it worked... with the version I have (the French version). Obviously it didn't work with the version of the user reporting the bug (the English version) since the user reported hearing static noise instead of speech.
The two files (Windows and Mac versions, both English) have the same size:
But opening them in an hex editor shows differences:
Do the differences remind you of something?
If not go back and read again the first two posts in this Broken Sword mac support series.
Before looking at the differences, I will give a short explanation of the speech file format.
The speech files are a collection of sound resources. Each resource contains the wave data for spoken sentence and is organised as follow:
4 bytes: 'data
' (i.e. hexadecimal values 64 61 74 61)
4 bytes: number of samples
n bytes: wave data (16 bits mono)
So quite simple, but maybe no as simple as you might think. If you are thinking that n is the number of samples multiplied by 2 (since each sample takes 2 bytes) you are wrong. Because the data is compressed. This is not really important for now so I will keep the description of the compression for later.
What is important here is that we can see that the first 4 bytes after 'data
' are identical (in the image above hexadecimal values 8E E6 01 00 - which, since we know it is little endian, means 0x0001E68E = 124558 samples). But the values that follow are obviously a series of 2 bytes values for which the bytes have been swapped.
At this points, here is a small reminder in case you are not following me and did not go back to my previous posts: big endian and little endian are conventions used to interpret the bytes making up a data word (more at http://en.wikipedia.org/wiki/Endianness
). For example 42 in hexa is 2A, or when using two bytes 002A. When using the big endian convention, this would be stored as 00 2A. But when using the little endian convention this would be stored as 2A 00.
So this should be obvious to you now that both the Windows and the Mac version store the number of samples of the sound resource as little endian values, but in the mac version the data samples themselves are stored using big endian convention. Why mix endianness in the same file? Why do this for the mac English version but not the mac French version? Don't ask me, I have no idea.
Since some mac version use little endian and others use big endian, we need to know which one it is. Does it depend on the language, i.e. all French versions use little endian and all English versions use big endian? Maybe. But I don't trust statistics on a set of two samples. And what of the German versions?
Therefore we decided to use a heuristic to find out if the mac version the player has uses big endian data or little endian data. The heuristic works by computing the average difference between two consecutive samples (using absolute values). Using the assumption that a sound wave is smoother than picking values at random, the lower average difference is considered to be the correct endianness.
If we take the 13 samples from the example image above, assuming big endian for the mac version gives us the following curve:
The average difference value from one sample to the next is 425.17.
If we assume little endian data the curve is:
And the average difference value is 9344.75.
So in this case the heuristic tells us the data is big endian, which it is. Of course in the actual source code we use more than 13 samples to get a statistically valid heuristic value.
The original patch that adds the heuristic code can be found in the patch tracker: https://sourceforge.net/p/scummvm/patches/956/
But the story does not ends here. A new bug report
very similar to the original one (i.e. speech sounds like static noise) was reported a few months ago. It was quite obvious that the heuristic did not work for that user and the wrong endianness was used. Why is that? It turns out there were several issues with the original heuristic code.
And that is where explaining how the speech data compression works will help to understand what was wrong. The data for one sound resource is broken in blocks, each one starting with a number of samples followed by the sample values. When you have consecutive samples with the same value it uses a negative size and the value is stored only once.
So for example the following sequence:
0 0 0 0 0 1 2 3 4 5
would be stored as (where the brackets indicate the blocks):
-5 0] [5 1 2 3 4 5]
All those numbers (number of samples and sample value) are stored on 2 bytes.
Here is the original source code (if you don't see the source code visit the blog as it may not be visible in RSS feeds).
I will not show the uncompressSpeech() code (yet). The only thing you need to know is that it uses the value of _bigEndianSpeech as either big endian or little endian data. So what the code above does is get the data assuming little endian data and then compute the heuristic value for the samples it gets and for the same samples to which a byte swap is applied, which should be the value we would get assuming big endian data. Right?
Wrong! This heuristic forgets something: the data is compressed, and when uncompressing it always assume little endian when reading the number of samples for each block. But if the data are big endian this number would be different and the blocks would have different sizes, and because the block boundaries would be wrong it would cause number of samples to be interpreted as sound samples and some sound samples to be interpreted as number of samples.
For example let's look at the resource of 10 sample with the compressed data -5 0 5 1 2 3 4 5.
Assuming the data is stored in big endian, in hexadecimal values with two bytes per value this give us: 80 05 00 00 00 05 00 01 00 02 00 03 00 04 00 05
If we read this with the heuristic above, because the number of sample is always read assuming little endian data we get 80 05 = 1408 samples instead of -5 for the first block. So the code will get the following samples: 0 5 1 2 3 4 5 followed by 4 garbage values read beyond the end of the resource instead of getting 0 0 0 0 0 1 2 3 4 5. So if the data are stored using big endian, the heuristic values gets biased. Almost always it still gets a lower score as reading it as little endian though.
The solution here is to call uncompressSpeech() twice, once assuming little endian and once assuming big endian.
But there is still an issue with this heuristic.
When reading with the wrong endianness, since we may read the wrong length, it may for example be a big negative number. Because we are using a relatively small finite number of samples, statistically we could end up with a small heuristic value because it has a lot of consecutive samples with the same value. For this reason I made an additional change to skip consecutive samples with the same value when computing the average difference.
After that commit the value for the heuristic with the wrong endianness is consistently about 21000, i.e. 1/3rd of 16 bits integer range (65 535 / 3 = 21845). As noted by wjp: the average absolute difference between two random numbers drawn independently from a uniform distribution between 0 and N is indeed N/3. So this is quite reassuring.
So everything was correct after this change? No, that would be too easy. We want complex puzzles spanning several rooms, not some kind of hidden objects game. And the user reporting the bugs confirmed the bug was still present after that change. So let's look at a different room, or rather a different function.
Here is the code from uncompressSpeech():
Note something relevant? No? Look closer. You see it now? Yes, this function always give us the sound data in little endian format, whatever the endianness in which it is stored, and more importantly whatever the endianness of the computer on which the code is run.
And if you look at the heuristic code above, it assumes it gets data in the native endianness (i.e. the endianness of the computer on which the code is run). So when running on a computer using big endian convention the heuristic was wrong. Let's look again at our previous example and how it was interpret on a big endian computer:
Read with the correct endianess: 0 1 2 3 4 5 (duplicate values have been removed)
Was interpreted as: 0 256 512 768 1024 1280
Read with the incorrect endianess: 0 1280 256 512 768 1024 1280
Was interpreted as 0 5 1 2 3 4
So in the heuristic code, on a big endian computer we need to swap the bytes of the two set of data to get the correct value. This brings us to the final code, in which the heuristic computation was also moved to a separate function to avoid code duplication (since it is done twice):
The user reporting the bug confirmed he was using ScummVM on a big endian computer (a G4 mac) and that the speech was correct after that final change.
by Thierry Crozat (email@example.com) at November 30, 2014 09:38 PM
November 29, 2014
November 23, 2014
September 28, 2014
In my very first post on this blog I wrote how I came to be involved with the ScummVM
project by adding support to the mac version of Broken Sword 1. I wrote I had been lucky, and you will have to wait a bit longer to know why (yes I know, I am milking this one, but I promise I will explain it soon). I expertly avoided however to reveal that I had also been lazy. When I submitted the initial patch I knew the support was not perfect. I already mentioned it lacked support for AIFF music (and I will take this opportunity to correct myself: apparently the support was added by eriktorbjorn, at least according to the history on github
, and not by sev as I mistakenly wrote in my first post). But more importantly there were graphical glitches. Yes! GRAPHICAL GLITCHES! Oh, the horror! And I can't even claim I had not noticed them. That would mean admitting I was blind (or at least color blind).
The first one is visible every time you visit Nico in her apartment, which is quite often (just a shame you can't use that big bed). Notice anything wrong (no, not the bed)?
|George, don't leave! Have you seen what is waiting for you out there? A corridor painter in red! Stuff of nightmare! And the psychopath who painted that might still be lurking in a corner!|
Just in case the image above appears normal to you, here is what it should have looked like:
|No light in the corridor? I guess they forgot to pay the electricity bill.|
The second glitch is even bigger, although maybe not as obvious. I had not played the game for a few years myself when I added support for it in ScummVM, and while something was bugging me during my tests I was not sure what it was initially.
|Bull's Head Hill, Syria, on a murky day. The sky, the color of a swamp, was empty of any birds. And I was about to jump into the void.|
And here is what is should have looked like:
|A Sunny day in Syria. Maybe I will live after all. Not that it will stop me jumping though.|
So what is wrong? This scene in Syria has a background parallax layer, on top of which the foreground is drawn, with transparency where we should see the background. And you have probably noticed by now that the background was not visible in the mac version.
The game sometimes uses parallax layers to give a sense of depth to the scene. When the characters move on screen, the foreground and background will move at different speed. See wikipedia
if you have never heard of a parallax before.
This is the only scene in the game that has a background parallax layer. And as such it has a special logic for the draw code. Other scenes may have a foreground parallax layer however, as is visible in the video below.
The two glitches are caused by two different bugs. But they are somewhat related. The game uses 256 colors with a different palette for each scene. That means each scene defines a list of 256 colors, and then the image data is defined using the indexes (stored on 1 byte) in that list instead of using directly the colors.
The palette for each scene is actually defined in two separate resources: one that defines the palette for the scene itself and contains 184 colors (indexes 0 to 183), and one for the sprites that contains 72 colors (indexes 184 to 255). The first color (at index 0) is actually reserved for the top bar (inventory) and bottom bar (dialog options) area when the bars are hidden. It is forced to black in all the scenes, whatever the color defined in the data file. This is also the color used for the door in Nico's room. And it is used for the transparent part of the foreground image in the bull's head hill scene. And this is the index used for the transparency in the sprite data as well.
You have probably guessed it by now: the mac version does not use color index 0 for the door in Nico's room and for the transparency in the bull's head hill scene. After a bit of debugging it turned out it is actually using color index 255 (i.e. the last color of the palette instead of the first one). In Nico's apartment that color happens to be red, and in the Bull's Head Hill scene it happens to be some sort of brownish dark green. Once I knew what the problem was, it was fixed with a simple patch
Other than that the Mac version is identical to the Windows version. It still uses the first 184 colors of the palette for the background and the last 72 colors for the sprites. And it still uses color index 0 for the top and bottom bars area and the transparency in the sprite data. So I have no idea why they made that change for the two cases described above.
Here is the code to get the palette when loading a new room. As explained above it is called twice, once for the first 184 colors and a second time for the remaining 72 colors. We force color 0 to be black. Lines 6 to 9 corresponds to the fix for the mac version, in which we also force color 255 to be black.
And here is the beginning of the draw code. As I wrote above the Bull's Head Hill, which is screen 54, has a special handling. We first draw the background parallax and then draw the screen on top, skipping pixels with color 0 (which here means transparent). On line 21 we have the fix for the mac version, for which we also skip pixels with color 255.
And that is all for today. In the next post I will speak of the speech data, and I will explain why I was lucky in my initial implementation.
by Thierry Crozat (firstname.lastname@example.org) at September 28, 2014 06:22 PM
September 19, 2014
Hey everybody! :)
Finally, Sfinx got the point (with the English translation in parallel), that it's available for testing! :)
You can read the official announcement here
So if you have the time and you are eager to try something new, feel free to test our new engine!
by uruk (email@example.com) at September 19, 2014 06:54 AM
September 18, 2014
In this post I will continue to write about translating games for the ScummVM project. This is the last part of a three parts series.
Part 3: Translate a game into a new language
Some of the games for which we released a freeware version are from eastern Europe and were not released in English. So to give them a wider audience we decided to add an English translation.
The first such game was Dragon History
, a Czech game for which a GSoC
student added support in ScummVM in 2009, with the help of the original developer. The game was only released in Czech and Polish originally, but German and English translations have been added. If you want to know more about this game, see the official web site: http://www.ucw.cz/draci-historie/index-en.html
Since I don't know much about Dragon History myself, in this post I will focus on two Polish games from LK Avalon
. The first one Soltys
, is supported since ScummVM 1.5. It is available to download for free on our web site
, and in addition to the original Polish version, we have an English and Spanish translation.
The second game I will write about is Sfinx
. It is very similar to Soltys in the way it works, and support for it in ScummVM was added during this year GSoC
. We are currently working on the English translation and very soon (maybe tomorrow?) we intend to make it available so that non-Polish ScummVM users can test the game, report bugs and also suggest improvement to the translation.
Edit: the call for tests is now live
Both Soltys and Sfinx have two data files named vol.dat and vol.cat. The latter is a catalog that lists the files present in the former and at which offset they start. So when the game needs a file, it can look into the catalog where to start reading it in the vol.dat file. To edit the data files however, we need to extract those. Then we can repackage them into a new vol.dat file, generating a new catalog file as well in the process. We have two tools
to perform the extraction and packaging, and they work for both Soltys and Sfinx (despite some minor differences in the file format).
Once uncompressed, you will have a lot of files. All the dialogs are in a file named CGE.SAY. The hotspots names are in the files with the SPR extension. The other files can be ignored (they will be needed when repackaging the game though.
So what does the CGE.SAY look like? Here is a small portion of it that shows almost everything there is to know:;--Anna above. 1:22=Oh, what a nice pussy!|I would love to have one;--Vincent in the dark 1:31=Where's the light? I can't see 1:32=There should be a shutter,|let's try to lift it
;--Vincent about the cleaning stuff 2:01=Cleaning? Never!|It's for the girls!;--Anna about the cleaning stuff 2:02=Isn't there a gentleman around?
Lines starting with a semi column are comments. There are a lot of them, which is a great help.
Dialog lines start with xx:yy as you can see above. The xx is the room number. So in the example above we have a portion of the dialogs for the first two rooms. The yy is the text number in this room.
The pipe indicate a line break. So for example the first text of the second room will look like this in game:
Simple, isn't it?
Now let's have a look at one of the SPR files, for example 02ZSYP.SPR. As the name suggest this is one of the hotspots in the second room. The start of the file look like this in the polish version:Type=AUTOName=zsyp na <98>mieci
[seq] 0 -2 0 0 0 8 1 3 84 2 127 8 .OTWIERA 1 0 85 2 127 8 .ZAMYKA
2 -2 0 0 0 8
[ftake]say -2 2:5 brudny
[mtake]reach -2 2:7 . zsypSOUND 2:7 2:84pause -1 72SAY -2 2:4NEXT -1 0 . smiec popycha
The name is what appears on screen when moving the cursor to the hotspot. We can now also see that the file is named after the hotspot name. This makes it easy to find a file when you know the hotspot name... in Polish (not so easy when you know it in English ;) ).
The <98> is the way my text editor displays non ASCII characters using their hexadecimal value (so in decimal we have here character 152). In this case the character is ś. The game is using the CP852 encoding
(with only the example above it could also have been using the mazovia encoding
, but other characters allow to make the distinction). Fortunately English does not use many non ASCII characters, so we don't have to deal with this much.
So, the polish name is zsyp na śmieci
. Google translate
tells me (I don't speak Polish myself) that it translates into garbage chute
. So let's modify the second line in the file and see how it looks:Type=AUTOName=garbage chute
For Sfinx, the bulk of the work was done by Strangerke and then I made a couple of passes to improve the English and fix spelling mistakes. Uruk, the GSoC student who worked on the engine, also made some modifications.
For Soltys, the Polish to English translation was done by neutron and the Spanish version is from IlDucci and The FireRed. I am currently working on a French translation as well.
The process I explained above is therefore very similar to what I explained in the previous post to improve an existing translation for Drascula:
- Unpack the data file.
- Edit the dialogs and hotspot names.
However there is one major difference. Because the game was only released in Polish in the first place, the font data does not contain all the characters we need for other languages. For English this is not an issue, unless you happen to use a word loaned from French, such as déjà vu or café. When translating to French however you need those accentuated characters. So there is one more step to do: modify the font data (which was done by Strangerke on Soltys).
The font is stored in a file called CGE.CFT. This is a simple bitmap font, for which each pixel is black (or another color) or transparent. So we need one bit to store a pixel. If the bit is 1, the pixel is visible, and if the bit is 0, the pixel is not visible. The height of the font is 8 pixels, which conveniently can therefore be stored on one byte (because in case you don't already know, 1 byte contains 8 bits). The width is variable, and if for example a character is 4 pixels wide, thus 4x8 pixels, its data is coded on 4 bytes. And there are 256 possible characters.
The font file starts with the width, coded on one byte, for each characters. That takes the first 256 bytes. Then the bitmap starts. Here is the start of the file for Sfinx displayed with hexadecimal values. The first column is the address (also in hexadecimal). We have 16 bytes on each line. A star denotes one or more lines that are identical to the previous line.0000000 04 06 06 06 06 06 06 04 04 04 04 04 04 04 04 040000010 04 04 04 04 04 04 04 04 04 04 04 04 04 04 04 040000020 04 02 04 06 04 05 05 02 04 04 03 04 02 03 02 030000030 04 04 04 04 04 04 04 04 04 04 02 02 04 04 04 050000040 05 05 05 05 05 05 05 05 05 02 04 05 04 06 05 050000050 05 06 05 05 06 05 04 06 04 06 05 03 03 03 04 050000060 04 05 04 04 04 05 03 04 04 02 03 04 03 06 04 040000070 04 04 04 05 03 04 04 06 04 04 04 04 02 04 06 060000080 04 04 04 04 04 04 04 04 04 04 04 04 04 05 04 050000090 04 04 04 04 04 04 04 05 05 04 04 04 04 04 04 0400000a0 04 04 04 04 05 05 04 04 05 05 04 04 04 04 04 0400000b0 04 04 04 04 04 04 04 04 04 04 04 04 04 05 04 0400000c0 04 04 04 04 04 04 04 04 04 04 04 04 04 04 04 04*00000e0 05 04 04 05 04 04 04 04 04 04 04 04 04 04 04 0400000f0 04 04 04 04 04 04 04 04 04 04 04 04 04 04 03 040000100 00 00 00 00 1e 29 2f 29 1e 00 1e 2b 2f 2b 1e 000000110 0e 1f 3e 1f 0e 00 0c 1e 3f 1e 0c 00 1c 5b 7f 5b0000120 1c 00 1c 5e 7f 5e 1c 00 ff ff ff 00 ff ff ff 000000130 ff ff ff 00 ff ff ff 00 ff ff ff 00 ff ff ff 00*0000180 ff ff ff 00 ff ff ff 00 ff ff ff 00 00 00 00 000000190 2f 00 03 00 03 00 14 7f 14 7f 14 00 26 7f 32 0000001a0 13 0b 34 32 00 1a 25 1a 28 00 03 00 3c 42 81 0000001b0 81 42 3c 00 06 06 00 08 1c 08 00 60 00 08 08 0000001c0 20 00 38 07 00 3f 21 3f 00 22 3f 20 00 3b 29 2f00001d0 00 31 25 3f 00 0f 08 3f 00 37 25 3d 00 3f 25 3d00001e0 00 01 3d 03 00 3f 25 3f 00 37 25 3f 00 24 00 6400001f0 00 08 14 22 00 14 14 14 00 22 14 08 00 02 29 050000200 02 00 1e 21 2d 0e 00 3c 0a 09 3f 00 3f 25 26 180000210 00 1f 21 21 12 00 3f 21 22 3c 00 3f 25 25 20 000000220 3f 05 05 01 00 1e 21 29 19 00 3f 04 04 3f 00 3f
If we look at the first few lines, we can see that the characters are between 2 and 6 pixels wide.
Let's try to have a look at the start of the alphabet. In the ASCII table
, we can see the value of the letter A is 65, and since values start at 0, that means this is the 66th character. So first we will compute the sum of the widths of the first 65 letters.
That would be 4 + 6 + 6 + 6 + ... + 4 + 4 + 4 + 5 = 263
So if we skip the first 256 bytes (the character widths) and then the next 263 bytes, we should get the data for letter A. So let's look at the data that starts at address 256 + 263 = 519 (207 in hexadecimal).
I have highlighted in red above the width for the 66th characters, which as we can see is 5, and the 5 bytes starting at address 0x207.
Let's write them, with the corresponding binary representation below (with the least significant bit at the top): 3c 0a 09 3f 00 0 0 1 1 0 0 1 0 1 0 1 0 0 1 0 1 1 1 1 0 1 0 0 1 0 1 0 0 1 0 0 0 0 0 0 0 0 0 0 0
So now a bit of ASCII art: we replace the 1 by a @ and the 0 by a space @ @ @ @ @ @ @ @ @ @ @ @ @ @
You recognize something?
Just for fun, let's do the same for the next two letters: @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @
So we can edit the font file using an hexadecimal editor for example. This involves some ASCII art (exciting :-), and it can be challenging to fit an accentuated characters on 5x8 pixels), some additions on hexadecimal numbers and some conversions between binary and hexadecimal (boring :-( ).
This concludes my three parts posts on translating games for ScummVM. I hope you found it interesting. Now I will take some rest while you start testing Sfinx. There is one last thing though: ScummVM is a community effort, and it does not only involves software developments. You can contribute in other ways, such as translating freeware games, translating ScummVM itself or helping with the user manual
. So if you are motivated to help us, please get in touch for example on our IRC channel (#scummvm on irc.freenode.net) or forum
by Thierry Crozat (firstname.lastname@example.org) at September 18, 2014 09:23 PM
We recently added support for a new game, Sfinx, in ScummVM, and we can now proudly announce that it is ready to be tested. Thanks to the original developers, this game is now freeware and can be downloaded on our web site. And in addition to the original Polish version we are also proposing an English version! This version is not final, so we are looking for people who speak English and/or Polish and who are not afraid of spotting spelling and grammar mistakes in the texts of the game (English only), while also looking for different bugs, glitches and other evil things.
To sum up: We need testers!
Grab our daily build and the data files of the game, and go for it!
You can report bugs on our bug tracker following our bug submission guidelines. If you have the time for it, screenshots are also very welcome!
by uruk (email@example.com) at September 18, 2014 12:00 AM
September 16, 2014
In this post I will continue to write about translating games for the ScummVM project. This is the second part of a three parts series.
Note: This post contains an embedded sourc code example that is not visible on the RSS feed.
Part 2: Improve the original translation of a game
Sometime the official translation of a game could be mistaken with the result you would get from the AltaVista translation of the 90s
. And I am not exaggerating.
The French version of Drascula
was an example of this, and I am told the Italian version was not better - but my limited knowledge of the Italian language does not allow me to confirm. While this was hilarious in its own way, it distracted from the game, so we decided to provide an improved translation for both Italian and French. I didn't work at all on the Italian translation, so the examples I will take are all from the French translation. But most of the explanation would work for the other translations.
Here the strings are partly in the game data file and partly in the original executable. We extracted the string from the original executable and instead in ScummVM they are in the drascula.dat file that we provide with ScummVM. So improving the translation meant both modifying this drascula.dat file and modifying the the game data files. Sometimes it also meant adding new strings, as for example the subtitles for some languages were missing in the Von Braun cutscenes
Modifying the strings in the drascula.dat file is easy. The strings are hardcoded in the source code of the tool used to generate that file
. So we just need to modify that source code. The only little difficulty is that non-ASCII characters (e.g. accentuated character, and we have a lot of those in French) are using the Code Page 850 encoding
. And in C we need to use the octal number in the string preceded with a backslash. So for example, to have an è, the decimal value in the CP850 encoding is 138, which in octal is 212. So the string would be '\212'. Therefore to get "Chèvre" ("Goat" in English, I think my brain was permanently damaged by working on Broken Sword) I would need to write "Ch\212vre".
Modifying the data file is not much harder. Those files are actually ARJ archives
. So you can easily decompress them using a tool that supports this compression. Files with strings are those with the extension CAL (which contain the dialogs) and ALD (which contain the hotspots). But you cannot edit them directly; that would be too simple. They use a simple encryption: each byte is x'ored with 0XFF
For example the letter A in ASCII has a value of 65. In binary this gives 01000001.When you x'or it with 0XFF (11111111 in binary) this gives: 10111110 (190 in decimal).
To get back to the original text you just need to X'or it again by 0xFF.
So I quickly wrote a simple C program to decrypt and re-encrypt the files:
To give you an idea of how bad it was, here are some of the hotspots from the original version and the corresponding ones from my improved version.
|PUIT||PUITS||A simple typo you might think. Maybe, if it had been the only one...|
|CIMTEIERE||CIMETIERE||Anagrams now? Maybe that was actually designed as a puzzle?|
|CAISSON||TIROIR||Where did that come from??? Canadian French maybe?|
|CERVEAUS||CERVEAUX||You may need a brain to know that the plural of words ending in 'eau' takes a X and not an S.|
|TRONC||COFFRE||Maybe my favorite. It make me think that the "translator" may have been working from the English text and not the Spanish one. TRONC is a tree trunk. COFFRE is a chest... or a car trunk.|
|ARMARIO||ARMOIRE||OK, they forgot to translate that one.|
|BAUL||COFFRE||And that one.|
|ESPEJO||MIROIR||And also that one.|
|PUERTA||PORTE||Did I download the Spanish version by mistake?|
And you have many more like this. And the dialogs were not much better. For those who understand french here are a few examples of original dialogs:
- Quelle merde de jeu dans lequel le personnage principal meurt! Un instant, qu'y a-t-il de mon dernier désir?
- Et bien merci et au revoir. Que tu la dormes bien.
- Non rien. Je m'es allais déjà.
- Comment peux-je tuer un vampire?
- Qu'est-ce qu'on suppose que tu fais?
I will stop there. But I could fill pages like that. So if you speak french and fancy a good laugh, feel free to download the original french version (not the updated one) from our web site
and play the game.
Another game for which we improved an existing translation is Mortville Manor
. This is a French game that was also released in German and English. Except the DOS version was never released in English. Strangerke (one of the developer who worked on the engine in ScummVM) extracted the English strings from the Amiga and Atari version. But it was still missing all the dialogs. Strangerke created a Google Doc spreadsheet with the French and English strings and with a ScummVM user named Hugo we started fixing the existing English translation and translating the missing strings. Then we implemented a small tool
to generate a data file from these strings (mort.dat, which is distributed with ScummVM) so that users can play in English using the game data files from the DOS French or German version.
For Mortville Manor, we actually also bundled the French and German strings and the data for the menu in the mort.dat data file. That way we can easily improve those languages as well. But for now they have not been improved and only the original French and German versions are available. I have been told the German one is not perfect. So if you like this game, speak German, would like to improve the German translation, and have a lot of free time on your hands you can contact me ;-)
See you tomorrow for part 3.
by Thierry Crozat (firstname.lastname@example.org) at September 16, 2014 07:23 PM
September 03, 2014
Adding support for Myst III: Exile to ResidualVM is nearly done.
Now, we only need your help to find the last rough edges by playing the game thoroughly and reporting any issues you encounter.
If you are interested, locate your copy of the game, and download a daily build (not the 0.1.1 release build).
Instructions to setup the game can be found in our README.
Bugs should be reported to our issue tracker on GitHub.
Once you have finished, please report your testing results in our forums.
by bgk (email@example.com) at September 03, 2014 12:00 AM
August 19, 2014
After the rotation fixes, one weird issue remained with Guybrush, when getting on the raft in the set mot
. When Guybrush got on the raft, he would strangely rotate in a circular motion once on the raft.
To debug the problem, I commented out the lines in the set file mot.lua
regarding guybrush getting on the raft and found that it was a call to setrot
which caused the issue to occur. In this call, setrot
was called with TRUE
as the fourth parameter, causing Guybrush to rotate over time instead of snapping to the rotation. Due to some difficulties with getting the rotation before Guybrush turned out of the retail version, I used a textObject
in the lua script before setrot
was called to display the values. Here's the retail version and ResidualVM:
|Guybrush's rotation before getting on the raft in the Retail Version|
|Guybrush's rotation before getting on the raft in ResidualVM|
As can be seen, the yaw parameter is inverted! So, is this the issue that's causing the weird rotation? I tried setting Guybrush's rotation before calling setrot(0, 0, 0, TRUE)
with guybrush:setrot(180, 69.8599, 180)
and found that the weird rotation still occurred. So, it seems there are actually two problems here:
- When rotating, Guybrush is sometimes rotated in axes that he shouldn't be when setrot is called without snapping to the new angles
- Guybrush's rotation is incorrect, with the yaw axis returning a negative value when it should be positive
To inspect the first problem, I tried setting Guybrush's rotation to 0 in each of the axes independently and inspecting the result and found that no axis was directly responsible for this rotation. I then tried setting them all to 0 with a snap and found that this looked correct (which was expected, obviously). The next experiment was to set each of the angles to a part of the rotation. I tried (0, 69.8599, 0)
first and found that the result looked right, indicating that the pitch and roll axes might be causing the problem. I then tried (45, 69.8599, 45)
and found that it also looked correct! However, at (90, 69.8599, 90)
, the issue re-appeared. In fact, any value below 90 seemed to work okay, while values greater than 90 caused problems. Out of curiosity, I then tried (-45, 69.8599, -45)
and found that it also worked fine! In fact, values between -90 and 90 (non-inclusive) seemed to produce the correct result, while values outside that range produced incorrect behavior.
This pointed to a problem with the conversions between Euler angles and other rotation forms in that angles between (-90, 90) were calculated correctly, but larger angles were not. First, I checked the functions that I wrote by implementing a simple perl program to compute the rotation matrices from Euler Angles and back again. This program can be found here
and produces results demonstrating the correct equations for setting a rotation matrix from Euler Angles and retrieving the Euler Angles from the matrix:
|Output from computing the Euler -> Matrix conversion for ZYX|
Fortunately (or unfortunately for finding the bug!) the results matched and there wasn't an obvious problem, aside from the absence of checking for singularities or gimbal lock checking. However, I was having trouble keeping the X's for the axis and the X's for the Euler Angles straight, so as a part of this work, I reworked the Euler Angle nomenclature to make the naming more consistent with the usage.
With the nomenclature straightened out, I then added a fix for singularities that arise from when it's impossible to determine the correct result. For an Euler Order of ZYX, we can see that when the Y Euler Angle is pi/2
degrees), it will be impossible to differentiate the other two angles. We can see this by inspecting the figure above, where we can see that if the result of Cy
is 0, then the components that are used by the arctan
cannot be used to determine the result. Cy is 0 when the cosine of the Y axis angle is pi/2
. This state results in a condition called gimbal lock. To work around this issue, we can check to see if the conversion will produce gimbal lock and if so, chose a single rotation.
So, does correcting for gimbal lock fix any of our problems? We can be fairly certain that it won't just by inspection. In the scenario above, Guybrush's coordinates would not cause a singularity because the second angle (yaw
) is not +/- 90
degrees. Still, it was a necessary fix!
So, what is really going wrong here? Let's try some more experimentation! First, let's rotate Guybrush from (0,0,0)
to (180, 0, 0)
and (0,0,0) to (0, 0, 180)
|Pitch 180 Degrees - (0,0,0) to (180, 0, 0)|
|Roll 180 Degrees - (0,0,0) to (0, 0, 180)|
|Rotation of (0,0,0) to (180, 0, 180)|
We can see that the sum of these rotations is going to result in Guybrush facing 180 degrees opposite his position on the yaw axis from the (0,0,0)
rotation. So, we can see that a rotation from (180, 0, 180) to (0, 0, 0) should only produce rotation in the yaw axis instead of rotating through the pitch and roll axes as well.
What causes this extra rotation? In ResidualVM, the turn methods of the Actor use Euler Angles to compute the rotation by making the angles equal. If instead, a Quaternion is used for rotation, this problem should be resolved. Now that the problem has been identified, in the next post, I'll address the issue.
by Joe Jezak (firstname.lastname@example.org) at August 19, 2014 12:09 PM
August 18, 2014
Continued from the previous entry
In the previous entry, we started debugging the issues with the lava puzzle and found a problem with Guybrush colliding with the logs. We also found that a similar problem was occurring with the monkey Timmy, on the beach at Monkey Island. In this post, we'll try to figure out why Timmy isn't colliding with Guybrush and just running right through him instead.
|Timmy on the Beach, Running Wild!|
To begin with, I examined the script that controlled Timmy's running behavior. When an actor has a monkey that's running around the actor's position, a variable named monkey_range
is set, which describes the maximum distance that the monkey can run from the actor in each direction. A random value within this range is chosen for the monkey to go to. The monkey runs to this location using the runto
method, which is implemented by the Lua command WalkActorTo
, which ultimately runs the Actor::walkTo method in ResidualVM. Within this method, the actor checks for collisions while plotting the path.
So, why isn't Timmy colliding with Guybrush? If we comment the check to print out when actors are checked for collision, we find that we can make Timmy collide with Guybrush if Guybrush walks into him, but not if Timmy runs into Guybrush when Guybrush isn't moving. If we inspect the logic that makes Timmy move, we find that there's a method in the Actor class called updateWalk
which updates the character's position over the duration of the walk. If we add some code here to force a collision check when the actor is moving, Timmy stops as expected when he runs into Guybrush.
I suspect that this fixes the lava puzzle collisions too, but currently the logs in the puzzle aren't showing. I'll tackle this in the next post! This work is submitted as PR #1050
by Joe Jezak (email@example.com) at August 18, 2014 11:20 PM
With the integrations of my text patches, the text rendering is much closer to the original version. Unfortunately, it was found that they also caused memory corruption, resulting in segfaults when using the TinyGL rendering path.
First, I wasn't sure exactly what was causing the segfaults, just that they were happening at seemingly random intervals and that the backtrace usually involved a text function such as drawing or freeing a text object. I put the game into gdb and found that I couldn't reliably trigger the bug to investigate the problem. I then tried running ResidualVM in valgrind
and found that when creating the text object with the function createTextObject
in the TinyGL rendering path, when creating the text bitmap, there was a buffer overrun. To be sure, I added an assertion to this path that asserted when the bitmap offset was larger than the bitmap storage. This confirmed the issue!
So, what changed to cause the bitmap size to be smaller than expected? If we look at the code, it appears that the TinyGL engine is allocating the text object bitmap using the kerned height and width. As it turns out, this isn't actually enough space to hold the completed text because there's an additional piece of information, the column offset. When a letter is printed without using the whole kerned space (as in the character "1"), there's an offset added to the starting column to account for this, letting the game use less storage for the character. When I accounted for this extra width, along with adding a new function to take the y offset into consideration as well, the segfault was fixed and text was rendered properly in TinyGL again! This was submitted in PR #1024
by Joe Jezak (firstname.lastname@example.org) at August 18, 2014 11:20 PM
August 17, 2014
While working on an as-of-yet-unnamed engine last year, I realized I needed to dither some videos. My only hope was that it wouldn't be as painful as DrMcCoy had it
several years ago (and I'm pretty sure the "beauty" part was sarcastic). Looking at how the game dithers the graphics, I figured out that it relied on Video for Windows to handle dithering. VFW promptly handles it by making the codec handle it.
For this game, that codec was Cinepak. The Cinepak decoder has been in ScummVM since 2009 (I wrote it in late 2007 or so, so it's really even older). I refused to use some other dithering algorithm that would have been different and given different results. If I was going to implement this, I was going to do it to match the original 100%. That meant it was time to figure out what it does.
Basically, the algorithm is based on pre-dithered tables that are for a given hardcoded palette. For custom palettes, it finds the nearest (using a simple distance equation) color in it and maps from the Cinepak palette index to the custom one. It then uses the pre-dithered tables to generate 4x4 blocks based on the contents of the codebook which is then mapped to the custom palette.
I pushed the code
for the curious.
QuickTime also does something similar (but with a different dithering algorithm in Cinepak, of course), which I'll be working on for Myst.
Here's the result, using one of the Cinepak samples from the MPlayer samples archive
(in this case, the Lara Croft one):
|Normal decode to 24bpp|
|Dither to 8bpp|
The result looks pretty decent. I was mostly glad it wasn't a ridiculous amount of extra code.
by clone2727 (email@example.com) at August 17, 2014 11:14 PM
I finally got around to do a proper playthrough of EMI in ResidualVM with all the fixes and improvements made during GSoC. I'm happy to say the game can now be played from start to finish with only some minor issues :)
The biggest issue that remains right now is a bug with the lava boat puzzle on Monkey island. The boats on the lava field are not visible, which makes the puzzle difficult to complete. JoseJX
is working on this issue though, so hopefully it will be resolved soon.
Other than that, there are a few graphical issues. Animations occasionally still seem to snap to the wrong frame. This happens for example when jumping out of the bank window on Lucre island, and when Guybrush gets a grog at the Micro-Groggery on Jambalaya island. Also, the text in the end credits flickers and the text color doesn't match the original.
Pathfinding is not perfect yet. Actors tend to zig-zag around before reaching their destination, which looks silly sometimes.
On the audio side, footstep sounds are sometimes wrong. For example, when walking on the Jambalaya island beach, it sounds like Guybrush is walking on wood. Also, voiceovers sometimes tend to get cut short, which is a bit annoying.
These issues will have to be resolved after GSoC though. I'm pleased that all of the issues I spotted are fairly minor, and the game is very much playable in the current state. I'm confident that support for EMI could be included in the stable release of ResidualVM fairly soon.
As GSoC soon comes to a close, I want to thank my mentors and the ScummVM/ResidualVM community for your support and for providing me this great opportunity to bring another classic adventure back to life. This has been a fun summer!
by Akz (firstname.lastname@example.org) at August 17, 2014 07:54 PM
August 16, 2014
With the DVD/GOG version of Pegasus Prime, there was a slight problem before release. We had an invalid function call entering the three new chase sequences when compiled in gcc with optimizations. I was unable to figure out the exact cause at the time and I ended up writing a hack around it in final release.
Since a bad function was getting called, I had feared gcc was overwriting a return address somewhere and sending the program counter where it shouldn't be. valgrind wasn't helping and only showed the after-effects of the bad function call. It was pretty hard to pinpoint in gdb too, due to the calling function being called numerous times during execution without breaking. I had shelved the issue for some time so I could return later, perhaps with another idea of tackling it. I found my hope in the AddressSanitizer
Armed with my shiny new PC and gcc 4.8.1, I recompiled with the address sanitizer to see what I would get. The game would now crash as soon as the sequence would start, due to the sanitizer kicking in. The information the sanitizer gave helped in really one way: I had a way to make it stop as soon as it broke from the stack buffer overflow
error. Perhaps not quite the way the tool was intended to be used, but it was enough of a hint for me.
With some logging to a file, I saw that it crashed here
the first time _inputHandler changed. Going with LordHoto
's suggestion to check the vtable of the pointer, I noticed something funny: It was the vtable for the wrong class!
Once I saw where the _inputHandler field was populated
, I quickly saw what my mistake was. Instead of relying on the compiler to upcast from the subclass to the InputHandler class, I had a manual C-style cast in there. The Neighborhood pointer (only known through forward declaration) was being cast to the InputHandler pointer. Normally this would be OK, as long as the compiler knew about the class hierarchy (in this case, with multiple inheritance and virtual functions), and then generate a static_cast. But if it didn't know that, it would have to go with a reinterpret_cast. The code was doing a reinterpret_cast and throwing away the hierarchy, and therefore causing undefined behavior. It just so happened that it called into the wrong vtable in this case.
But why did it happen only during optimization? Probably because the function was getting inlined. If the include order had Neighborhood defined in the translation unit before getting to the constructor of GameInteraction, it would have output the correct static_cast. It's likely one other place had this situation and that version ended up being the actual used function.
Definitely one of the hardest bugs I've had to track down.
by clone2727 (email@example.com) at August 16, 2014 01:09 PM
August 13, 2014
Yes, yes, yes! :) CGE2 engine is finally merged, the fruit of this summer grew ripe! :)
This week I was busy with fixing issues regarding my merge request and other problems reported by Coverity
. Also, I found the sound system of the game still pretty buggy, but after a day of work, it finally works as intended. Other than that, the engine is really
complete now, we are only waiting for a decent translation, and that's all, it will be free to download from our site
About Avalanche: as I said, I started to work on Avalanche engine again. This week I spent my time with the objectifying of the dropdown menu's code, and now I can say that even if it's not perfect, but it's much cleaner than before and easier to read. Now, during the remaining two days of this GSoC, I plan to implement the keyboard handling of the dropdown menu. We'll see if I'll have enough time for that...
Now only the end of the program remains, so I'd like to say to you guys as a closing a big thank you for this another great summer! I am especially thankful for the work of Arnaud, Paul, David and Thierry. I wouldn't be here without you guys!
I am still very proud to be a member of ScummVM. :)
by uruk (firstname.lastname@example.org) at August 13, 2014 08:08 PM
August 12, 2014
Great news! The Prince and the Coward is now completable! This means that we are able to play from beginning to the end of the game without skipping any important part of it. Here we have a screenshot from ending sequence to give you a proof:
First thing that I had to change was hero drawing and movement to implement second hero displaying. I made some changes in Hero class (hero shadow drawing, zooming) and also update pathfinding functions for second hero and now it's working fine:
|Second hero implementation|
Displaying and movement
I also implement drawing "power meter" and bind it with mouse and keyboard buttons, so now last mini-game - "Escape from jail" is working correctly:
|"Escape from jail" |
Next things that I had to code were credits loading and scrolling function for them. They are displayed after game ending animations and they look like this:
Scrolling after ending animations
I also work on graphics functions to add
anti-aliasing code that exist in original version of game for sprite drawing functions. Prince is using 256 colors palette and its edge smoothing is based on 256 x 256 table of colors transition. Algorithm is checking if currently drawing pixel is located next to the edge of sprite and if it makes jagged line. If it does then this pixel is set with color from transition table calculated of original background pixel color and sprite's edge pixel color. Personally I don't see a big difference, but I think that it is visible mostly on not moving sprites, like shovel. You can compare it on screenshots below:
|Anti-aliasing comparison - first location|
(from left to right) Original game, ScummVM - AA on, ScummVM - AA off
|Anti-aliasing comparison - shovel sprite|
(from left to right) Original game, ScummVM - AA on, ScummVM - AA off
Most of work is done now, but there are still glitches that I have to fix (e.g. correct shadow drawing, fix drawing of last frames of FLC animations) and some ScummVM's Advanced Engine Features that I'd like to implement before merging of the engine.
by lukaslw (email@example.com) at August 12, 2014 07:37 PM
Continued from the previous entry.
After all of the previous work on attaching and detaching and getting it pretty close to working, I found that there were still a few issues, like the cuffs not attaching properly to Guybrush's wrists in the set wed
. Additionally, after a code review by Akz, he suggested that the code could be made simpler and that instead of trying to match the internal matrices in the retail version, I should just aim to match the output. Of note was that I needed to insert a large number of transposes to make the intermediate values work.
As such, I scrapped the previous code and instead tried to fix the problem of attaching and detaching with all of the new information that I'd learned over the course of debugging this problem. This new version of the attach and detach code is much less intrusive and achieves the same result with far fewer calculations. This code was re-submitted as PR #948
. So, how does the pole in the set pph
|The set pph after getting off the raft|
Looks pretty close, the pole is in the right position, but let's compare it with the original:
|Difference between retail and ResidualVM|
It's a little hard to see, but there's still a different in the orientation of the pole. I'm not exactly sure where this comes from, but I suspect that it has to do with the joint rotation of Guybrush's hand. Still, the result is much better than it was before.
The next outstanding rotation issue that needed to be looked into was Issue #958
. I thought that I had fixed this bug, but it appears to still be broken. The best item for displaying the issue is the itty-bitty brass screw
, which is rotating on the wrong axis.
As you can see, the screw starts in the wrong orientation and is upside down! Additionally, the rotation of some items in the inventory are incorrect with rotation being applied to the wrong axis. As it turns out, the orientation was incorrect because the rotation was applied backwards. Inverting the rotation Quaternion result of GetRotationQuat
made this problem go away, but the resulting angle was wrong for the attached actor. This was because GetRotationQuat
was being used as part of the attach and detach sequence. Iverting those usages as well found that the result now matched what we had before, except that the screw was rotated correctly.
The pole in the set pph
is effected by the rotation of the hand joint in Guybrush's hand. As it turns out, the code that I had written for the attach and detach had neglected the position of the attached joint. Incorporating this rotation and translation slightly improved the position and rotation of the pole in the set mot
At this point, I was hoping that all of the issues were resolved enough to move on, but two major issues remain:
- The boats in the lava puzzle were placed incorrectly, appearing (well, not actually appearing) under the lava instead of on top.
- When Guybrush rotates on the raft after boarding, he spins on a wrong axis.
In the next post, we'll tackle these issues!
by Joe Jezak (firstname.lastname@example.org) at August 12, 2014 12:51 PM
August 11, 2014
As I've spent the last week fixing some minor bugs and documenting code, I wanted to analyse better the effects of this system on the current games.
I've done some profiling by measuring fps in two modes: analysis and release;
Analysis build is a build with less optimizations enabled and debug symbols whilst release mode is the classic o3 build with every possible optimization enabled.
The scenes used for these tests are the following:
EMI - ship scene, lucre island and act 1 beginning.
Grim demo: first scenes of the demo.
Here are some screenshots for clarity.
And here are some results.
Before dirty rectangle system (analysis / release):
Ship scene: 13.50 / 57 fps
Lucre Island: 9 / 47 fps
Act 1 Beginning: 25 / 135 fps
Grim scene 1: 50 / 160 fps
Grim scene 2: 62 / 220 fps
Grim scene 3: 57 / 243 fps
Grim scene 4: 60 / 205 fps
After dirty rectangle system (analysis / release):
Ship scene: 12 / 55 fps
Lucre Island: 9 / 45 fps
Act 1 Beginning: 24 / 133 fps
Grim scene 1: 23 / 136 fps
Grim scene 2: 62 / 500 fps
Grim scene 3: 27 / 180 fps
Grim scene 4: 42 / 250 fps
As we can see dirty rects introduces an heavy overhead, especially with analysis build; but release build is somewhat balanced: the fps is pretty much the same for crowded scenes whereas it goes up by quite a bit if the scene has only a few animated objects (like grim scene 2 or scene 4, where animated objects are small and dirty rects yield some performance advantage).
In my personal opinion dirty rects should only be employed on some specific scenarios, as its overhead generally slows down the code and it only shines in some cases.
Dirty rects is a system that is probably better off being used in 2D games where screen changes are more controllable and there is no need to perform more calculation to know which region of the screen is going to be affected.
Developing this system was quite challenging and it took a lot of time but I think that the overall task was beneficial because it gave us an insight on how this could have affected performance: I think that implementing this system on an higher level of abstraction might result in being more effective but more research would be required for doing so (such system would not be applicable for this project though as the engine has to support a vast variety of games).
by Subr3v (email@example.com) at August 11, 2014 10:07 PM
August 10, 2014
This year is the 20th anniversary of the release of the classic video game Inherit the Earth: Quest for the Orb. The Wyrmkeep Entertainment Co. has announced that it is developing a sequel entitled Inherit the Earth: Sand and Shadows.
This all-ages adventure game is set in a world of humanoid, anthropomorphic animal characters. The story follows Rif the Fox as he tries to confirm the rumors that an important relic of the legendary humans has survived and who is responsible for the relic's recovery. Unfortunately, there are sinister forces involved in the rumors — ones that endanger Rif, his friends, and his society.
"Inherit the Earth: Sand and Shadows will be enchanting and thrilling game for all to enjoy, with subtleties and hidden depths that can be appreciated by older players," said company president, Joe Pearce. "With the help of the backers to the funding drive, we expect to release a great game mid-2015."
A video demo has been released on the game's website at http://InheritTheEarth2.com. The video provides a preview of the art, animation and play experience that will appear in the product.
In conjunction with this announcement, the company has launched a funding drive on Kickstarter to help in completing development of the game. Information about this drive can be found at http://InheritTheEarth2.com/kickstarter.
by sev (firstname.lastname@example.org) at August 10, 2014 12:00 AM
August 08, 2014
With all the major feature implementation tasks completed, this week I focused mainly on fixing any remaining bugs I could find. Klusark was kind enough to do a full playthrough of the game in ResidualVM, which revealed a number of issues of varying severity. Some of these issues were blockers which prevented progression in the game at certain points. These should all be resolved now, and on current master the game should be completable from start to finish again.
Here's a brief summary of the issues I resolved this week:
- Guybrush got stuck when attempting to lure the fish on Lucre island into the scupperware box, preventing progression in the game
- The game got stuck when giving the earrings to the Dainty Lady figurehead
- Saving and then restoring a savegame caused lighting to break
- Saving and restoring caused a crash due to broken shadow initialization
- Guybrush faced the wrong way after the Voodoo Lady summons the items from the heirloom chest
- Some of the ships and the water at the harbor on Melee island were rendered incorrectly
- Guybrush was invisible in the heirloom cave, and the light shaft effects were drawn incorrectly
The last two issues were the most interesting ones, so I'll discuss those a bit next. Let's look at a before-and-after comparison from the Melee Island harbor.
Several issues can be seen in the screenshot of the old version on the left. The color of the waves is too dark, the water splash sprites are drawn over the ship and the rowboat, and the bottom of the ship is not clipped correctly.
In debugging these issues, I used Apitrace
in order to figure out how exactly the original game forms the final picture. Since the game has an OpenGL renderer (activated with the parameter -gl), we can use Apitrace to extract the list of OpenGL function calls as well as the OpenGL state on each drawn frame. By examining the trace I found that ResidualVM was drawing some of the sprites in a different order than the original game, and for some sprites depth testing was enabled while it wasn't in ResidualVM. Also, the original game draws mask sprites to clip the ship and the rowboat to the correct shape, which were not drawn in ResidualVM at all.
|The mask sprites visualized. With additive blending the black bits become transparent.|
When actors (including sprites) are about to be drawn in EMI, they are first sorted into descending order by their sort order
, which is a per-actor integer field that can be controlled from the game scripts. When an actor is attached to another, the actor's sort order will be overridden by the parent actor's sort order. This is what happens when the water splash sprite is attached to the ship sprite. However, after the sprite is attached, the scripts then set a new sort order for the sprite. In the original engine this value overrides whatever was derived from the parent, but in ResidualVM the sort order was not updated, which caused the sprites to be drawn in wrong order. Updating the sort order even if the actor is attached fixed this issue.
The rest of the issues were mostly related to incomplete parsing of sprite and model data files. I discovered flag fields in the sprite data, which control whether depth and alpha testing should be enabled for that sprite, and whether additive blending
should be used or not. I also found a similar flag that controlled the blending mode in the model data. I discovered these mostly through trial-and-error, by changing suspicious-looking bits in the data files slightly and then loading up the original game to see how the changes effect the end result. Setting the correct blend mode for models fixed the water color.
To enable drawing of the mask sprites, I had to disable a piece of old, temporary code that simply skipped drawing of any sprites with "mask" in the name. I assume this was done because if incorrectly rendered, the mask sprites looked ugly and only cluttered the image (see the image above). Now with the blending and sort order fixes in place, the masks did produce the correct result, though.
This is the heirloom cave before and after my changes. The left one is fairly obviously broken: Guybrush is invisible, the light shaft effects look ugly, and shadows are missing. Fortunately the light shaft effect was corrected by the previous model blend mode fix.
Guybrush's invisibility was caused by another seemingly temporary hack in ResidualVM, which disabled drawing to the color buffer if an actor's sort order was greater or equal to 100. In the cave, Guybrush's sort order is set to 100 in the scripts, which caused him to become invisible. The original engine doesn't seem to do anything similar, so I concluded that this behavior was wrong.
The cave set also led me to discover another previously unknown field in the set shadow data. The field is a string, which specifies a name of a light in the set. When this string is present, the shadow projection point should be the light's position, instead of whatever is stored in the shadow data. After parsing the light name and implementing this behavior in ResidualVM, the shadows now work correctly in the cave also.
These fixes can currently be found in this branch
by Akz (email@example.com) at August 08, 2014 10:47 PM