Ultima II Talk Format
The Ultima II Talk Format is an encrypted file which houses the dialogue from the game's NPCs. The files are named TLKX?? and have no file extension.
Each file begins with a null, then contains four dialogue strings, each terminating with a null. In order to be displayed properly, each dialogue can be a maximum of three rows of text. Each row can be a maximum of 30 columns. Rows end with a carriage return unless they're 30 columns (a carriage return is implied), or they're the last row in the dialogue.
Every file is exactly 384 bytes in length. If the text in the file doesn't utilize all 384 bytes, the remainder of the file will contain garbage data from the memory buffer. There is even some source code to be found in some of the files!
The file uses an encryption similar to ROT13 (AKA the Caesar Cypher). However, instead of being rotated using a modulo 13, it is rotated by 128 (the length of the ASCII set).
|BYTE||Null||Start of file null marker. Always 0x00.|
|ASCIIZ||Dialogue||Multi-lined dialogue text.|
The following FreeBASIC code will decrypt and print out the text in a specified Talk file.
' Decrypts and displays the text in Ultima 2 Talk files. ' Change this to the path and file name you want to view. Open "H:\DOS\Ultima2\tlkx23" For Binary As #1 Screen 1 Dim As UShort Char Dim As UByte ReadByte Dim As UByte Column Dim As UByte Row Dim As UByte Dialogue For Char = 1 To 384 Get #1, , ReadByte If ReadByte = 0 Then ' The game engine uses a null character as a dialogue separator. Dialogue = Dialogue + 1 Column = 0 Row = Row + 2 ' Each Talk file has 4 dialogues. Any data after the fourth ' dialogue contains garbage memory and shouldn't be read. If Dialogue = 5 Then Exit For End If Else ' Talk file uses encryption to obscure the game's dialogue. ' It is similar to ROT13, but instead, you must subtract 128 ' from each character to get the ASCII value. ReadByte = ReadByte - 128 ' The game display engine automatically wraps text at 30 ' characters, so, if we hit 31, we wrap the line as though we ' hit a carriage return. Column = Column + 1 If Column = 31 Then Column = 1 Row = Row + 1 End If Locate Row, Column Print Chr(ReadByte) If ReadByte = 13 Then ' Special trap for carriage return. Column = 0 Row = Row + 1 End If End If Next Char Close #1 Sleep
This file format was reverse engineered by TheAlmightyGuru. If you find this information helpful in a project you're working on, please give credit where credit is due. (A link back to this wiki would be nice too!)