When I ran Ultima, I had lots of questions regarding how could Lord British make the game, and this one was the first question. How to program such a nice handwriting at the very beginning of Ultima IV intro sequence. I could only guess, but I didn’t have a clue.
But things have changed. Now we have internet, and many smart guys out there who tried to reverse engineer the wonderful game uploaded their findings on the internet for you to see. If you look into the ergonomy-joe’s decompiled source, you can find how things worked inside of the game. But still, even though there are lots of information available, if you don’t have enough time and energy, it’s not easy to dig down the gold mine and actually get the gold out. If your coding skill is not good enough and/or you are not familier with C language, that is out of question. So, I will try to explain how it works behind the surface.
In the ergonomy-joe’s decompiled source, take look at the SRC-TITLE/DATA.C. In the file, our ergonomy-joe was kind enough to comment the portion where the Lord British’s handwriting is, and that is the unsigned character array D_346E. It contains the x, y coordinate of the handwriting.
So, to make a long story short, the program has the x, y points data inside. When right time comes, it just draws the points sequencially, that’s all.
Now here’s a python source that draws the handwriting. The python version is 3.x, and the pygame version I use is 1.9.6.
# Lord British's handwritings, a reproduction of Ultima IV's introductory sequence
# the data is from ergonomy-joe's decomiled source
# program by Dr. Roach (https://drroach.game.blog/), 2020-02-23
data = [
0x54,0xBD,0x55,0xBC,0x57,0xBC,0x59,0xBC,0x5B,0xBC,0x5C,0xBD,0x5C,0xBE,0x5B,0xBF,
0x59,0xBF,0x58,0xBE,0x58,0xBD,0x57,0xBB,0x56,0xBA,0x56,0xB9,0x55,0xB8,0x55,0xB7,
0x54,0xB6,0x54,0xB5,0x53,0xB4,0x53,0xB3,0x52,0xB2,0x52,0xB1,0x51,0xB0,0x4F,0xB0,
0x4E,0xB0,0x4D,0xB1,0x4D,0xB2,0x4E,0xB3,0x50,0xB3,0x51,0xB2,0x54,0xB2,0x55,0xB1,
0x56,0xB1,0x57,0xB0,0x59,0xB0,0x5B,0xB0,0x5D,0xB0,0x5F,0xB0,0x61,0xB0,0x63,0xB0,
0x65,0xB0,0x67,0xB0,0x69,0xB0,0x6B,0xB0,0x6D,0xB0,0x6F,0xB0,0x71,0xB0,0x73,0xB0,
0x75,0xB0,0x77,0xB0,0x79,0xB0,0x7B,0xB0,0x7D,0xB0,0x7F,0xB0,0x81,0xB0,0x83,0xB0,
0x85,0xB0,0x87,0xB0,0x89,0xB0,0x8B,0xB0,0x8D,0xB0,0x8F,0xB0,0x91,0xB0,0x93,0xB0,
0x95,0xB0,0x97,0xB0,0x99,0xB0,0x9B,0xB0,0x9D,0xB0,0x9F,0xB0,0xA1,0xB0,0xA3,0xB0,
0xA5,0xB0,0xA7,0xB0,0xA9,0xB0,0xAB,0xB0,0xAD,0xB0,0xAF,0xB0,0xB1,0xB0,0xB3,0xB0,
0xB5,0xB0,0xB7,0xB0,0xB9,0xB0,0xBB,0xB0,0xBD,0xB0,0xBF,0xB0,0xC1,0xB0,0xC3,0xB0,
0xC5,0xB0,0xC7,0xB0,0xC9,0xB0,0xCA,0xB0,0xCB,0xB1,0xCC,0xB1,0xCD,0xB2,0x5E,0xB8,
0x5E,0xB7,0x5D,0xB6,0x5D,0xB5,0x5C,0xB4,0x5C,0xB3,0x5D,0xB2,0x5F,0xB2,0x60,0xB2,
0x61,0xB3,0x61,0xB4,0x62,0xB5,0x62,0xB6,0x63,0xB7,0x63,0xB8,0x62,0xB9,0x60,0xB9,
0x5F,0xB9,0x69,0xB9,0x6A,0xB8,0x6A,0xB7,0x69,0xB6,0x69,0xB5,0x68,0xB4,0x68,0xB3,
0x67,0xB2,0x6B,0xB8,0x6C,0xB9,0x6E,0xB9,0x77,0xB9,0x75,0xB9,0x74,0xB9,0x73,0xB8,
0x73,0xB7,0x72,0xB6,0x72,0xB5,0x71,0xB4,0x71,0xB3,0x72,0xB2,0x74,0xB2,0x75,0xB3,
0x76,0xB4,0x77,0xB5,0x77,0xB6,0x78,0xB7,0x78,0xB8,0x79,0xB9,0x79,0xBA,0x7A,0xBB,
0x7A,0xBC,0x7B,0xBD,0x7B,0xBE,0x76,0xB3,0x77,0xB2,0x8B,0xBE,0x8B,0xBD,0x8A,0xBC,
0x8A,0xBB,0x89,0xBA,0x89,0xB9,0x88,0xB8,0x88,0xB7,0x87,0xB6,0x87,0xB5,0x86,0xB4,
0x86,0xB3,0x85,0xB2,0x8C,0xBF,0x8E,0xBF,0x8F,0xBF,0x90,0xBE,0x90,0xBD,0x8F,0xBC,
0x8F,0xBB,0x8E,0xBA,0x8E,0xB9,0x8C,0xB9,0x8F,0xB8,0x8F,0xB7,0x8E,0xB6,0x8E,0xB5,
0x8D,0xB4,0x8D,0xB3,0x8C,0xB2,0x8A,0xB2,0x88,0xB2,0x87,0xB3,0x96,0xB9,0x97,0xB8,
0x97,0xB7,0x96,0xB6,0x96,0xB5,0x95,0xB4,0x95,0xB3,0x94,0xB2,0x98,0xB8,0x99,0xB9,
0x9B,0xB9,0xA1,0xB9,0xA0,0xB8,0xA0,0xB7,0x9F,0xB6,0x9F,0xB5,0x9E,0xB4,0x9E,0xB3,
0x9D,0xB2,0xA2,0xBC,0xA2,0xBB,0xA9,0xBC,0xA9,0xBB,0xA8,0xBA,0xA8,0xB9,0xA7,0xB8,
0xA7,0xB7,0xA6,0xB6,0xA6,0xB5,0xA5,0xB4,0xA5,0xB3,0xA6,0xB2,0xA8,0xB2,0xA9,0xB2,
0xAA,0xB3,0xA5,0xB9,0xA6,0xB9,0xAA,0xB9,0xB2,0xB9,0xB1,0xB8,0xB1,0xB7,0xB0,0xB6,
0xB0,0xB5,0xAF,0xB4,0xAF,0xB3,0xAE,0xB2,0xB3,0xBC,0xB3,0xBB,0xBB,0xB8,0xBA,0xB9,
0xB8,0xB9,0xB7,0xB9,0xB6,0xB8,0xB6,0xB7,0xB7,0xB6,0xB8,0xB5,0xB9,0xB4,0xB9,0xB3,
0xB8,0xB2,0xB6,0xB2,0xB5,0xB2,0xB4,0xB3,0xC5,0xBE,0xC5,0xBD,0xC4,0xBC,0xC4,0xBB,
0xC3,0xBA,0xC3,0xB9,0xC2,0xB8,0xC2,0xB7,0xC1,0xB6,0xC1,0xB5,0xC0,0xB4,0xC0,0xB3,
0xBF,0xB2,0xC5,0xB9,0xC6,0xB9,0xC7,0xB8,0xC7,0xB7,0xC6,0xB6,0xC6,0xB5,0xC5,0xB4,
0xC5,0xB3,0xC6,0xB2,
]
import pygame
SCREEN_WIDTH = 320
SCREEN_HEIGHT = 200
color = (80, 80, 255)
black = (0, 0, 0)
pygame.init()
pygame.display.set_caption("Lord British Handwriting")
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
screen.fill(black)
clock = pygame.time.Clock()
i = 0
running = True
while running:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if i + 1 < len(data):
x = 10 + data[i]
y = 260 - data[i + 1]
pygame.draw.line(screen, color, (x, y), (x + 1, y), 1)
i += 2
pygame.display.update()
pygame.quit()
Line number 65 and 66 extract the x and y coordinate of the pixel to draw from the data. Why x is not just data[i] but 10+data[i], and why y is not just data[i+1] but 240-data[i+1]? It’s just to make the signature at the center of the screen at the right orientation. The points data that is stored is made that way, so we have to draw that way. I needed to do some experiments to make things work right, which anybody can do.
Actual drawing happens at line number 67. It doesn’t draw a pixel but a two-pixel-length line, again, because the points data the ultima IV has are stored that way.
I hope this post is helpful to the ones who really wanted to know how things work inside of the game.