Technical info
First a warning: you don't need to read this unless you wish to know more about internal working of SWOS and SWOS++. I'm also assuming that readers have at least some programming knowledge. Even then it might be confusing.
Contents:
About SWOS
Sprites Map
About SWOS++
I will not discuss much about Linear Executable format. For more info about it see http://www.wotsit.org. Very useful utility is DumpLX from Tenth Planet Software. For hex editing I used great hex editor HIEW (Hacker's View). Sorry, no link (I got it from some compilation), but Google should find it.
Basic info about executable:
filename: sw(o)s.exe
filesize: 2135087 bytes
file format: Linear Executable
offset to LE header: 0x2c90
fixup section size: 657660
offset to fixup page table: 0x694 (in file 0x3324)
offset to fixup record table: 0xc30 (in file 0x38c0)
number of pages: 358
There are two objects (in LE terms) in file: code and data.
Virtual size of code segment: 658297
Relocation address: 0x10000 <- important
Attributes: readable, executable, has preload pages, nonpermament
Page map index: 1
Page map entries: 0xa1
Virtual size of data segment: 810944
Relocation address: 0xc0000 <- important
Attributes: readable, writeable, has preload pages, nonpermament
Page map index: 0xa2
Page map entries: 0xc5
In file, offset for start of code is 0xa3e00, and offset for data is 0x144e00.
From now on, each offset I write will be relative either to code or data start,
depending on context.
PC version of SWOS was written as a conversion from Amiga. It can be deduced
by analyzing the code, and there are even some left over strings such as "INSERT
BLANK DISK IN DF0:". There are 15 memory locations (dwords) that are used
as registers. Their start is at 0x3146d. These dwords correspond to Amiga's
MC68000 processor registers: data registers D0-D7 and address registers A0-A7.
This is not a mistake (15 locations for 16 registers) because register A7 (stack
pointer) is mapped directly to Intel's esp register. This means that PC version
of SWOS is using so called static emulation, where whole program is translated
into host machine language and then executed. Other type of emulation is dynamic
emulation, where an interpreter is used that interprets original code run-time
(this is the way most emulators operate). Since SWOS is a commercial game, it
was more feasibile to use the first approach. I think they did the following:
first Amiga's source code (which I believe was mostly written in assembler,
or some optimizing C compiler - somebody correct me if I'm wrong) was converted
into C code, probably using some third party utility. Than they rewrited some
heavily used routines completely in x86 assembler for speed, and rewrited non-portable
routines (file handling, input handling, video) to get fully running x86 executable.
Compiler used to put all this together was Watcom C - sw(o)s.exe contains whole
Watcom run-time library, both 16 and 32-bit versions.
Consequences of this are that the executable is relatively large. It is also
missing bss sections commonly found in other executables, and every static array
is put in data section filled with zeroes. Heavy use of memory locations instead
of processor's registers brings to code size explosion. For example instruction:
mov dword [tmp10], aDataEurocup_tm
is coded in 10 bytes: c7 05 91 14 0f 00 2a 73 0C 00 + fixup record while instruction:
mov eax, aDataEurocup_tm
is coded in only 5 bytes: b8 2a 73 00 00 + fixup record.
Some MC68000 registers could be mapped directly to x86 registers to shrink size
a little.
For sound AIL (Audio Interface
Library) version 3.03 was used. Unfortunately it is commercial software, publicly
unavailable, so I didn't have access to informations about it.
I will enlist some of the most interesting locations in the game, and short
descriptions, if available. 0001 means offset is relative to start of code section,
and 0002 means relative to data section.
(Note: MC68000 registers are marked as tmp01 - tmp15)
section:offset | symbolic name | description |
0001:00000010 | main_ | C main() routine - start of program. |
0001:00005A0D | MainGameLoop | Game's main loop. |
0001:000074FD | ViewHighLightsMain | Main routine for viewing highlights. |
0001:00007DC3 | LoadCommentary | Load commentary from CD-ROM. |
0001:000092BE | Initialization | Main program initialization. |
0001:00009870 | Int9KeyboardHandler | Keyboard interrupt handler. |
0001:0000C7E2 | DrawSprite | Draw sprite. In: tmp01 - sprite number, tmp02 - x, tmp03 - y |
0001:0000D672 | ShowMenu | Show menu. In: tmp15 -> menu struct |
0001:0000D826 | InitMainMenu | Main menu initialization |
0001:0000F1CE | DrawMenuText | In: tmp02 - x, tmp03 - y, tmp04 - char color, tmp09 -> text, tmp10 -> chars table |
0001:0000F55F | ZeroOutStars | Overwrite stars in strings with zeros. |
0001:0001193D | QuitToDosMenu | Show quit to DOS menu. |
0001:00012E41 | EditCustomTeamMenu | Show edit custom teams menu. |
0001:000229C3 | LoadTeamFile | Loads requested team file. In: tmp01 - team file extension number (100 = customs.edt) Out: tmp01 - 0 = ok, 1 = error zero flag: set = all ok, clear = error |
0001:00023413 | LoadHilFile | Load highlight file into buffer. |
0001:0002F588 | ShowPlayMatchMenu | Show play match menu. |
0001:0003018B | ViewOpponentsSelect | Called when view opponent is selected. |
0001:0003E7E3 | ImportTactics | Show import tactics menu. |
0001:00041FD2 | LoadTactics | Shows menu and loads tactics. |
0001:00059389 | CheckControlsInGame | / |
0001:0005B89E | DrawSubstitutesMenu | Draws substitutes menu during the game. |
0001:0005DE4C | not named | very important routine, initializes everything before the game |
0001:0006EA0C | not named | probably the most important routine in the game; currently it is only slightly explored, but I suspect it contains AI, players updating, ball updating and a lot more |
0002:0003E152 | lin_adr_384k | Important pointer to memory block that holds virtual screen, title and fill.256 during menus, and pitch during the game |
0002:0003E192 | working_palette | / |
0002:0003EA98 | key_count | number of keys in buffer |
0002:0003EA9A | key_buffer | 10 bytes key buffer |
0002:0003EAA6 | scan_code | scan code of most current key |
0002:0003EAD0 | converted_key | ascii code of pressed key (1 = escape) |
0002:0003ECCF | play_game | play game while this is true |
0002:0003ED10 | char_tables | two pointers to char tables (small and big) |
0002:00054B56 | game_min_substitutes | / |
0002:00054B58 | game_max_substitutes | / |
0002:000B119B | commentary_table | pointers to commentary filenames |
0002:000B2A38 | teams_country_numbers | byte at offset 0 in team is used as index into this table; value from table is added to offset 1 in team, forming team general number |
0002:00070AFF | pitch_number | decides which pitch to load |
0002:000BBDC8 | club_messages_table | table of pointers to messages from club and chairman |
0002:000BDA7C | game_time | game time in BCD format |
For Intel 80x86 assembly language proabably the best reference is book "Art of Assembly language", available at http://webster.ucr.edu. For Amiga assembly language, I used "AMIGA MACHINE LANGUAGE - FROM ABACUS BOOKS", available somewhere on the Net (archive name: amigamachine.lha).
Sprite 1286 contains an error: width is erroneously set to 5 pixels. It should be 16 pixels.
0-162 characters
0-56 small set:
0 - x in a box
1 - '
2 - (
3 - )
4 - ,
5 - -
6 - .
7 - /
8-17 - numbers
18-43 - letters
44 - box (cursor)
45 - *
46 - pound
47 - longer dash
48 - ?
49 - :
50 - +
51 - %
52 - ;
53 - A with two accents above
54 - U -||-
55 - O -||-
56 - vertical bar
big set follows, for big character add 57 to small character index (except for number 56, it is unique)
113-122 stars, for player skills
123 - picture of a ball, probably for edit tactics menu
small players with corresponding color and shirt stripes; used for editing tactics (corresponding pictures are
copied into them)
124 - white guy
125 - red guy
126 - black guy
small goalkeepers, used for editing tactics
127 - white guy
128 - red guy
129 - black guy
small players with plain shirts
130 - white guy
131 - red guy
132 - black guy
small player with colored sleeves shirts
133 - white guy
134 - red guy
135 - black guy
small players with vertical stripes shirts
136 - white guy
137 - red guy
138 - black guy
small players with horizontal stripes shirts
139 - white guy
140 - red guy
141 - black guy
small goalkeepers
142 - white guy
143 - red guy
144 - black guy
149 - cursor while editing
tactics, frame 1
150 - -||- 2
151 - -||- 3
181, 182 - two empty sprites, they are located in edit teams menu, in invisible entries near the end
183 - up arrow (for scrolling)
184 - down arrow
player faces, during edit tactics (maybe used someplace else too)
186 - white guy
187 - red guy
188 - black guy
189 - empty sprite, also used in edit teams menu as a last, invisible entry
trophies
215 - national league champion
216 - invitation tournament winner
217 - national cup winner
218 - unknown trophy
219 - european champions cup
220 - european cup-winners cup
221 - UEFA cup
222 - world championship
223 - european championship
227-234 first scrolling advertisement
235-242 second -||-
243-250 third -||-
225-270 numbers + player names, written in upper left corner during the game, marking player with ball, team 1
271-286 numbers + player names for team 2
These sprites are filled before the game, they're intially empty.
307 - First team name
308 These two sprites have special properties, and special drawing function
is used
330 - current game time (118. mins at the beginning, for maximum width)
This is still incomplete and under construction. If you find errors or wish to contribute to list, let me know.
SWOS++ consists of three parts contained in files: patchit.com, loader.bin and swospp.bin. This partitioning was done because of the nature of SWOS executable itself - it is in Linear Executable format. This means that the program is loaded by DOS extender to some, before loading unknown address. Address varies, and is different almost every execution time. Due to this fact, loader must do a process called "fix up". It consists of adding load address to every reference to data or code, which is invalid - therefore fixing it. To be able to do so, executable besides code and data also contains "fixup records" that are pointing to places that need to be fixed up. Fixup records point to various places scattered around the file, making direct patching by hand extremly labourous - besides patching, fixup records also need to be updated.
First part is program which will patch SWOS executable to allow loading of the rest of the program. After patching, SWOS executable will load loader.bin and execute it. Code itself isn't very interesting.
Second part is loader.bin. It is in some way similar to boot loader. It will load the main part - swospp.bin, fix it up and do patches to SWOS. swospp.bin has a strict structure, which enables loader to do all this and check file correctness.
Third part is swospp.bin, the main part. This file contains acutal code and data, relocation offsets and patch instructions. It is in custom made binary format.
Whole this process is somewhat similar to functioning of dynamic load libraries in Windows, or generaly dynamic libraries in operating systems.
Most of the code was written in Intel x86 assembler using NASM (Net-wide assembler) and some small parts were written using excellent Watcom C compiler (which was by the way used for compiling original PC SWOS). Because of very flexibile nature of build system even C++, Pascal and perhaps some other comilers could be used too.
SWOS++ binary format makes making patches much easier than before - there is absolutely no need for hex editing. All patching is done after the program is loaded into memory and fixed up by loader, so there is no need to worry about fixup records overwritting code.