Translating Serpent Isle in your language HOWTO

(note: I'm not finished writing this. All contact should be sent to artaxerxes2 at

If, like me, you were disappointed not to be able to play Serpent Isle in your own mother tongue, especially if the first part, Black Gate, was translated, and if you are not afraid to spend hours at it, you might want to translate Serpent Isle yourself. It took a team of 3-4 people over a year to do a usable translation, doing it during their free time, and about the same for the proofreading. This document will address the biggest questions you might face: where to start? what to do?

Important note: you cannot use Unicode. If your language requires Unicode, you cannot translate Serpent Isle the way I describe here. I do not know of any other way you could translate Serpent Isle. Sorry!

Some background information

A fully translated Serpent Isle fits those criteria:

  1. descriptions are translated
  2. conversations are translated
  3. graphics and menus are translated
  4. introduction and endgame sequences are translated
  5. voices are translated

This document will help you out with descriptions, conversations and graphics. The rest will be briefly discussed at the end of this document.


You will need some tools for the translation. The Exult Team made available a package which contains some very important tools that you must acquire to translate Serpent Isle, otherwise you'll have to write your own! This package is available on their download page and is called "Exult Tools". You will also need some perl scripts you can download on **** TODO: PUT LINK HERE ****.


This is what you see when you single click on an item, NPC or, weirdly enough, when you are cold or hungry and a few other situations. All of that is found in the file text.flx, from your static/ directory. A flex file shouldn't be edited directly. Since it holds text, use the tool textpack, from the Exult tools you downloaded earlier. Convert the file into a text file using the following command:

textpack -x text.flx text.txt

The first few lines of the resulting text.txt are as follow:

# Written by Exult Textpack tool
3:icy water
6:icy water

You are now ready to edit the file text.txt using a regular text editor. Do not alter the numbers in front of each line! Only change the text. This file is roughly split in three categories, grouped by line numbers:

Once you are finished translating this file (do not use Unicode, it is not supported and won't work!), you need to make it again a flex file. Use the following command:

textpack -c text.flx text.txt

It's time to try out your file! Edit your exult.cfg file and add a <patch> section under the <serpentisle> section if you did not have one already. My configuration file looks like that:

   ... rest of config file here ...

Under Windows, it might be:

   ... rest of config file here ...

Copy your brand new text.flx to your patch/ directory as set in your configuration file. Under Linux

cp text.flx /home/aurelien/jeux/u7p2/patch
Under Windows:

copy text.flx C:\games\ultima\serpentisle\patch

Now start Exult, and start playing a bit of Serpent Isle. Click on the gangplank (line #150 in the text.flx file) once and see the name in your language appear! If it doesn't, make sure you have setup a <patch> directory in your exult.cfg file, make sure Exult uses the right exult.cfg file and make sure you read Patch : /home/aurelien/jeux/u7p2/patch in either stdout.txt or on your standard output. Also, make sure you copied the translated text.flx into this directory! If it is still not working, contact me via email (address at the bottom of this document).

About non-ASCII characters

It might happen that you used non-ASCII characters in your text.flx file and while playing, you quickly realised they didn't show in the descriptions. For instance, you translated "light" into "lumière", but when you click on the light in the floor of the cavern (where the bed roll is), it shows "lumire" instead. The reason is simple: each character you typed in the text.txt file has an character-code. Character-code in the ASCII table have an associated bitmap in the file fonts.vga but the non-ASCII character codes don't. To show the non-ASCII character codes, you will have to create the bitmaps yourself, or use my patched version of fonts.vga which contains the (almost complete) iso8859-1 table bitmaps. If you use another character table (like iso8859-2 or others), or if you don't want to use my patched version of fonts.vga, you will have to alter it yourself.

The simplest and fastest way I have found to alter fonts.vga is to use yet another tools provided by the Exult Team, expack. We will concentrate more on the default yellow text, but the same applies to all the fonts used in Serpent Isle (11 fonts in total!). Type the following command:

expack -x fonts.vga

This will create 12 files, named 00.u7o, 01.u7o, [...], 11.u7o. Rename 00.u7o to 00.shp and edit it using the Gimp or Photoshop. You will need to install the plugins to edit a .shp file first. The plugins can be obtained from Exult's download page. I do not recommend putting bitmaps in the lower (<32) frames otherwise you will have troubles later on. Simply add layers to the image, each layer showing one bitmap. The layer number must represent the character-code you wish to use. For instance, the letter "é" is character-code #233 (0xE9) in iso8859-1. So it must the 233rd layer in your 00.shp file. You should never have more than 256 characters in your character code. Exult does not support Unicode.

Once you are finished, you need to repack the file into a usable fonts.vga. Rename the file 00.shp to 00.u7o first, then use the following command:

expack -c fonts.vga ??.u7o

Copy this new fonts.vga to your patch/ directory and start Exult. Your new bitmaps should show now! (note: you could use this technique to use a different font if the default Gothic yellow gets on your nerves.)


This is the core of your translation effort. All conversations (when you double-click on most NPCs for instance) are found in the file usecode from your static/ directory. The tools you downloaded earlier will help you to translate this file. At this point you have an alternative. Either you translate the content of the usecode file using:

In either case there is some preparation to do. Put the usecode file into a directory, say translation/, as well as the script (don't ask about the name) and the Exult Tools wud, wuc and rip. From this translation/ directory, type the following command:

perl -prepare

After a few minutes, you'll see a large amount of new files and a few new directories. That's OK. The script splitted the usecode file into a whole lot of individual compiled usecode functions (using rip), decompiled each function (using wud) and separated the data section from the code section in each of those decompiled usecode functions. For information, the new directories are:

Information about translating conversations

In either method, you must be very careful about certain pitfalls we encountered in our translation work. Here are some random notes you should get acquainted with.

The command line method

This method works (we used it for the whole translation) but requires some rigor in your procedures. Also, unless you create a CVS repository, this method shouldn't be used if the translating team is more than 1 person. Pick a file from the todo/ directory, translate it and when you are done, move it to the french_data/ directory. Keep doing this until there is no more files in the todo/ directory.

Analysis of an example

An extract of what you would look at would be for instance:

                .funcnumber     0401H
L0000:  db      '@Dupre...@'
L000A:  db      00
L000B:  db      '@Yes, '
L0011:  db      00
L0012:  db      '?@'
L0014:  db      00
L0015:  db      '@Bring to me a woman!@'
L002B:  db      00
L002C:  db      '@I will slay you all!@'
L0042:  db      00
L0043:  db      '@Fulfill thy desires!@'
L0059:  db      00
L005A:  db      '@I must have ale!@'
L006C:  db      00
L006D:  db      'leave'
L0072:  db      00
L0073:  db      'join'
L0077:  db      00
L0078:  db      '"How good to see thee again, '
L0095:  db      00
L0096:  db      '! Knowing that thou wouldst soon return, I have waited for t'
        db      'hee at this establishment."'
L00ED:  db      00
L00EE:  db      '@And he hath developed quite a bar tab!@'
L0116:  db      00
L0117:  db      '"One that I shall pay, worry thou not!"'
L013E:  db      00
L013F:  db      '"And I have good news for thee, '
L015F:  db      00
L0160:  db      '."'
L0162:  db      00
L0163:  db      'good news'
L016C:  db      00
L016D:  db      'join'
L0171:  db      00
L0172:  db      'leave'
L0177:  db      00
In this case, this function is from the file and thus is for Dupre (NPC # + 0x400 = usecode function). You must not change the Lxxxx: db part and ignore the lines where the text is only 00 (do not delete them though!).

Notice the first seven lines of actual text (L0000, L000B, L0012, L0015, L002C, L0043 and L005A) are barked. Also, the lines you reply are L006D, L0073, L0163, L016D and L0172, the rest is said by the NPC. You could also infer L00EE is said by another NPC given what is being said. This line is not barked however, but the actual NPC's face is shown and this text is displayed, unfortunately no visual clues will inform you of that.

Also, notice L000B and L0012: in this case, you can infer the Avatar's name will be inserted between the two. Same thing between L013F and L0160.

L0096 is split on two lines since it is too big to display on one. This limitation is due to wud and is only for display purposes. When you translate, you can make a line almost as long as you want. Consequently, you could end up with the following for this line:

L0096:	db	' ! Comme je savais que tu allais revenir bientôt, j'ai décidé de t'attendre dans cet établissement."'
L00ED:	db	00

Very important in this case, L006D and L0172 are identical, same for L0073 and L016D. If you make them different, you won't be able to click on them during the game. Try it once so that you will know what the symptoms are when that mistake is done.

Finally, there is a connection between L013F and L0163. When Dupre said "And I have good news for thee", the answer 'good news' is added. Try to keep it consistent.

Testing it

Frequently, you want to check out the result of the translation. You must recreate the usecode file using the newly translated data first. For this, you can again use my perl script

perl -creation
This command will use the files found in the french_data/ directory when recombining the data part from the code part, will recompile each function (using wuc) and put all usecode functions into one usecode file (using rip).

Simply copy this newly created usecode file to your patch/ directory and try it out! I recommend you to get started with simple and short functions first, until you get the hang of it. You also might want to first translate either Iolo, Shamino or Dupre, so that you can see immediate results. For instance, replace every instance of 'bye' from your answers (= what you reply, thus surrounded only with simple quotes) into whatever way you say "bye" in your language. Run the command above and try it out (don't forget to put the translated version in the french_data/ directory).


If you intend to use the command line method and to share the work with others, you will need to setup a way to have a common repository fron which you will pull files to translate and push translated files. This is already possible is using along with a CVS server. Before every translating sessions, run:

perl -synchronise
This will update your local tree. You can then proceed to translate files in the todo/ directory, or adjust files in the french_data/ directory. When finished, you need to update the CVS tree. Do it by typing:
perl -commit
However, you need to discuss with the other team members how the workload will be shared, otherwise you might unnecessarily overstep each other.

The web browser method

If you have access to a web server that supports PHP and MySQL, you might want to have a look at my package called tisane. This package is a set of PHP pages that will help you translate Serpent Isle. This is the recommended method, since you will save yourself a lot of time. Once everything is installed, all you will have to do is to point your browser to some web page, select a function to translate, edit the file, press a button and bam! you've just finished a function! You can also search through the original or the translated text for some words, download one or all usecode functions, review changes through a ChangeLog, create a list of recommended words for certain common words.

For instance, you can hint the translator that the recommended word used to translate "MageLord" has been chosen to be "Seigneur Mage". Each time the original text will show the word "MageLord", it will look different and putting the mouse cursor on top will pop the word "Seigneur Mage" in a tooltip.

The page where you edit a usecode function will show the original text on the left and either nothing on the right (if nothing was done), or the currently translated text. Each Lxxxx line to translate or proofread will be in its own textbox and you don't even have to worry anymore about the "Lxxxx", the "db", the "00" and the rest since only the text to translate is alterable.

To help even more, a description can be given to each usecode function, as well as some flags to identify functions, like: isNPC, isProofread, needCorrection, etc.

Finally, the page to edit a usecode function will show at the bottom of the screen a list of quick links in case you need help to translate. You enter a word in the box, select the search type and press the button. Current search types are: Definition (English), Definition (French), Synonymes (French), Conjugaison (French). If you know of a site that can be used to help in your translation, you can also add it there. The only requirement is that it must be possible to access the result page right away, via a GET or a POST method, so if the site requires cookies, tough! If parameters are required, they can be set in advance and are filled on-the-fly when you choose the associated search type.


There is a bit of work to do, but you will have to do it only once. Roughly, the steps are:

  1. Install the web pages and prepare the database.
  2. Prepare the usecode file.
  3. Massage the data and upload it to the database.

Installing the web pages

Copy the web pages to a directory your web server can serve from. Point your browser to this directory and more specifically to the file install.php. Follow the instructions. When you are finished, you should end up with a fully prepared database. On to the next step.

Preparing usecode

As is, you cannot use the usecode file. Fortunately, if you've followed the steps recommended in this documentation, you should have almost everything ready. You ran the command:

perl -prepare

This command has done most of the work for you. The data/ directory contains all the original text and the french_data/ contains the translated version. Using my perl script, you can massage the data so that it becomes database friendly. Run it like this:

cat data/* | perl > english.txt
cat french_data/* | perl > french.txt


Coming soon