swfans / swars-re-helpers

0 stars 1 forks source link

Syndicate Wars Playstation Debug Symbols #1

Open Moburma opened 2 years ago

Moburma commented 2 years ago

Hi, Not sure if you are aware of this, but last year a prototype of the PlayStation version of Syndicate Wars was posted on archive.org (https://archive.org/details/SyndicateWarsJun21997prototype). What's relevant about this is that unlike the final game it appears to have the debug symbols file MAIN.SYM in its root. While this is of course for the substantially different PS1 version, it also has a LOT of entries for things that were cut out of that version, so possibly this may have been adapted from and be useful to the original DOS version. Maybe this would be helpful to the project.

mefistotelis commented 2 years ago

Thank you, I didn't know that. Will take a look.

mefistotelis commented 2 years ago

Ok looks like Ida Pro loads the PSX binary without issues, and there are already tools to convert symbols to a format which Ida can read.

The team cracking Diablo already prepared relevant tools to read the MND symbol file (other people made their tools as well, ie. DumpSym2IdaPython or symdump, but these are more basic).

So to use the Diablo tool, it should be possible to install sym_dump with one of these:

go get -u github.com/sanctuary/sym/cmd/sym_dump
go get -u github.com/Happy-Ferret/sym/cmd/sym_dump

Then reading symbols should work:

# Dump type definitions
sym_dump -types MAIN.SYM

# Dump type definitions, variable and function declarations
sym_dump -c MAIN.SYM

# IDA Python scripts
sym_dump -ida MAIN.SYM

The symbol file looks ok, seem to have not only functions but also structs and enums defined. Very useful stuff. Will try the dumper when I have more time.

Moburma commented 2 years ago

Good to see it seems to be useful! I'm not much of a programmer so can't really help more, unfortunately. By the way, I've also been doing a lot of research into the game's files, and reverse engineered some of the mission file details. I'm not sure if this is useful to you or the game on modern OS project (probably not), but you can see here and here. I also wrote a little on the PlayStation version here.

mefistotelis commented 2 years ago

I improved the symbol extractor: https://github.com/mefistotelis/psx_mnd_sym

and did some work on these - all display-related routines (drawing, screen composition) and interface are different, but game mechanics are very similar. The PSX version is slightly simplified in few places.

Interestingly, PC version has some code for the mech tank (in moon level), while PSX version has all that cut. The mech initialization function places double rocket and quad laser as its weapons. So no new weapon, but still pity it doesn't fully work.

Moburma commented 2 years ago

Awesome. One thing I noticed is there seemed to still be symbols for cut stuff like the email system, is there any of that left in the code? Also the mech is interesting, I think that stuff actually does work. Messing around with it, it's possible to make it fire four homing lasers that work the same as the ones the Graviton Gun fires. It's extremely hard to trigger it, but it's something to do with double clicking slowly and holding the right mouse button down. If you do it right it lifts its arms up and shudders for a moment before thrusting forward and shooting four lasers, see below: pic1 pic2 pic3

mefistotelis commented 2 years ago

Wow, I was sure using that mech is not possible.

Mail system - no, I didn't found that part.

mefistotelis commented 2 years ago

I named some of the functions and variables in PC version. A lot of data is now far easier to read. Ie. reading .MIS file:

  sprintf(&str, "%s/all%03d.mis", levels_dir[0], (signed __int16)num);
  fh = FileOpenInclCD(&str, 2u);
  if ( fh != -1 )
  {
    LbFileRead(fh, &out, 4); // not sure what this is, other that if above 30, another block is read below
    LbFileRead(fh, &engine_mem_len, 2);
    LbFileRead(fh, engine_mem_alloc_ptr + engine_mem_alloc_size - 64000, (unsigned __int16)engine_mem_len);
    LbFileRead(fh, &next_mission, 2);
    LbFileRead(fh, mission_list, 76 * next_mission); // Each mission entry has 75 bytes; the struct Mission is fully defined in debug info
    LbFileRead(fh, &next_used_objective, 2);
    LbFileRead(fh, used_objectives, 32 * next_used_objective); // Each entry has 32 bytes; the struct Objective is fully defined in debug info
    if ( out > 0x1E )
      LbFileRead(fh, engine_mem_alloc_ptr + engine_mem_alloc_size - 1353, 1320);
    LbFileClose(fh);
Moburma commented 2 years ago

That's fantastic. I did some work on reverse engineering the .mis files already here. One thing I found is the playstation version has each mission defined in only 72 bytes rather than 76. I also worked out big chunks of the objectives data, but not all. Is it known where the weapon loadouts for missions are stored? I never figured this out, I assumed it was not in the level files as changing the level that is loaded did not seem to change the loadout of the level played, but I couldn't see where that data would be stored in the .mis. Also is it known how the game knows what missions to load in the campaign? By the briefing numbers set in .mis? But even then the zealot campaign starts at a weird number (24) that changed during development, so it must be specified somewhere (main executable?). I couldn't work this out.

mefistotelis commented 2 years ago

I also worked out big chunks of the objectives data, but not all.

If you're going that deep, you should try looking at the extracted symbols from PSX.

// size: 0x1C
struct Objective {
    // offset: 0000
    unsigned short Next;
    // offset: 0002
    unsigned char Map;
    // offset: 0003
    unsigned char Level;
    // offset: 0004
    unsigned short Status;
    // offset: 0006
    unsigned short Type;
    // offset: 0008
    unsigned short Flags;
    // offset: 000A
    short Thing;
    // offset: 000C
    short X;
    // offset: 000E
    short Y;
    // offset: 0010
    short Z;
    // offset: 0012
    short Radius;
    // offset: 0014
    unsigned char Pri;
    // offset: 0015
    unsigned char Arg2;
    // offset: 0016
    unsigned short StringIndex;
    // offset: 0018
    unsigned short UniqueID;
    // offset: 001A
    unsigned char ObjText;
    // offset: 001B
    unsigned char dummy91;
};

// size: 0x48
struct Mission {
    // offset: 0000
    unsigned char *TextName;
    // offset: 0004
    unsigned short TextId;
    // offset: 0006
    unsigned short StartHead;
    // offset: 0008
    unsigned short SpecialEffectID;
    // offset: 000A
    unsigned short SuccessHead;
    // offset: 000C
    unsigned short FailHead;
    // offset: 000E
    unsigned short SourceID;
    // offset: 0010
    unsigned short SuccessID;
    // offset: 0012
    unsigned short FailID;
    // offset: 0014 (3 bytes)
    unsigned char SpecialTrigger[3];
    // offset: 0017 (3 bytes)
    unsigned char SuccessTrigger[3];
    // offset: 001A (3 bytes)
    unsigned char FailTrigger[3];
    // offset: 001D
    unsigned char BankTest;
    // offset: 001E
    unsigned short SpecialEffectFailID;
    // offset: 0020
    unsigned short SpecialEffectSuccessID;
    // offset: 0022
    unsigned short StringIndex;
    // offset: 0024 (3 bytes)
    unsigned char StartMap[3];
    // offset: 0027 (3 bytes)
    unsigned char StartLevel[3];
    // offset: 002A (3 bytes)
    unsigned char SuccessMap[3];
    // offset: 002D (3 bytes)
    unsigned char SuccessLevel[3];
    // offset: 0030 (3 bytes)
    unsigned char FailMap[3];
    // offset: 0033 (3 bytes)
    unsigned char FailLevel[3];
    // offset: 0036
    unsigned char MapNo;
    // offset: 0037
    unsigned char LevelNo;
    // offset: 0038
    unsigned char BankTestFail;
    // offset: 0039
    char Complete;
    // offset: 003A (5 bytes)
    unsigned char MissionCond[5];
    // offset: 003F
    unsigned char ReLevelNo;
    // offset: 0040
    unsigned short CashReward;
    // offset: 0042
    unsigned char PANStart;
    // offset: 0043
    unsigned char PANEnd;
    // offset: 0044
    unsigned short WaitToFade;
    // offset: 0046
    unsigned short PreProcess;
};

Is it known where the weapon loadouts for missions are stored?

I didn't looked into default loadouts.

Also is it known how the game knows what missions to load in the campaign?

You mean level file name? Here you go (code from PC):

  if (lvno < 0)
  {
    lvno = -lvno;
    word_1C8446 = 1;
    if (lvno <= 15)
      sprintf(str, "%s/c%03dl%03d.dat", levels_dir[0], current_map, lvno);
    else
      sprintf(str, "%s/c%03dl%03d.d%d", levels_dir[0], current_map, (lvno - 1) % 15 + 1, (lvno - 1) / 15);
    if (lvno > 0)
      word_1C844A = lvno;
  }

The lvno comes from mission_list[].LevelNo. And if that number is non-negative - it looks like file name isn't initialized at all? that could lead to nasty things - trying to open a file with its name being a random binary string.

Moburma commented 2 years ago

Ahh, I didn't realise those structures were right in front of me all along! That's great. I was assuming that missions entries started after the map/level bytes (offset: 0036 above), but this shows I was wrong (I thought this might even be the case) and actually there are more bytes for each level definition afterwards. Very useful!

What I meant for the campaign was how the game knows which of the 109 missions is the first one of the campaign, which is the second, and so on. Offset: 000E in Mission above is what mission briefing the mission uses (which counts up from 1, so it could use that to know this is the start of the campaign. This is complicated by some levels like the unreleased Unguided demo level having a briefing of 1 set as well). Especially the Church of the New Epoch campaign, that starts at briefing 24 and mission 48 - this must be hardcoded somewhere.

it's interesting as well that there seems to be some provision for the number in ALLXXX.MIS changing in the code above, it's not just hardcoded. Looks like they really could have done an expansion disc that way (PC Zone speculated the Unguided campaign would be an add-on disc). I guess also Multiplayer uses a separate file, so maybe it's just for support of that.

Moburma commented 2 years ago

I found the default weapon loadouts, they actually are just set in the level file entry for each agent. It's the WeaponsCarried DWORD in the _139fake structure. If you set this to FFFFFFFF it gives that agent all weapons in the game, which includes the two cut weapons I'd seen before that appear as cannisters. Slightly more interestingly, it also gives you what seems to be the cut Napalm Mine, but it doesn't work (uses the Ion Mine icon). This normally can't be hacked in just by editing saved games.

Using this information I might try and update the old SWLevelEd app by Tomasz Liz.

mefistotelis commented 2 years ago

Interestingly, there are parts of level editor in the PC binary. Apparently there was originally a button to enter the editor; but there are only parts of the feature remaining in the code, and the button is hidden.

  init_screen_button(&main_MAP_EDITOR_button, 0x104u, 0x183u, gui_strings[443], 6, sprites_Font_Qot, 1, 0);
[...]
  main_MAP_EDITOR_button.X = 319 - ((signed int)main_MAP_EDITOR_button.Width >> 1);
[...]
  main_MAP_EDITOR_button.Border = 3;
[...]
  main_MAP_EDITOR_button.CallBackFn = main_do_map_editor;

Weapons - when we have the symbols re-created, it should be possible to implement missing parts. Though the game lacks community - and community is crucial for such projects.

Moburma commented 2 years ago

Ahh, I had wondered about that. There's a lot of talk in the interviews in the official strategy guide about how much effort was put into the editor. I would imagine the early demo is most likely to have it working (if it survives), as that has things like the "Simulated Levels" working properly that are broken in the final game. I also noticed in the early demo there is a file called MEDIT-0.ANI that is missing from the other demo and final game - Editor graphics? These files were used in older Bullfrog games like Syndicate and Theme Park, but I don't know how to view/what they contain.

Also I realised the Sonic Blast weapon can be equipped by editing the weapon loadout as per my previous post, but it's not very exciting, it just appears as the rocket launcher and can't be fired.

You're right about community, it's sadly probably way too late for many people to care at this point. I did a deep dive into the release of the complete development files for the Amiga game Hired Guns, and even among the still fairly animated Amiga community literally about 2 people cared. There might be people on the port Google group that would help. I'd love it if they could release the original files for this game, especially all the Unguided stuff (missing briefings!), but I'd imagine EA would not like that at all...

mefistotelis commented 2 years ago

The .ANI files look like a variation on DAT/TAB format - the data seem to use the same RLE transparency encoding. So it shouldn't be hard to make an extractor, if a need arises.

It's very possible EA doesn't even have the original files. And former Bullfrog employees do not want to release materials which is copyrighted by EA. Maybe things will get published when these people retire. This is another area where community would help - the devs love to talk about the old days, but someone has to have an interest in listening. The Dungeon Keeper community contacted the devs, and got many interesting stories, pictures etc.

For figuring out names of DOS-related functions, I'm comparing the code with Dungeon Keeper beta - it uses very similar libraries around graphics, sound, network support etc. I also found an old Smacker library and Spaceball joystick library, and am naming functions from these. But there are quite large discrepancies around the network code, and it looks like Spaceball library is not the only joystick lib included. Do you know of any other Bullfrog binaries with debug info, from the similar time period? Or maybe you know more about the libraries used?

Moburma commented 2 years ago

I tried to find other debug versions etc - they usually lead back to forum posts from yourself! I'm really not a programmer unfortunately, I can just muddle through with a hex editor and making basic scripts to get as far as I have. I had a quick look at the prototypes for Populus the Beginning on the PlayStation, but they don't have any symbols etc so don't seem useful. There are lots of early versions of Theme Hospital, but I don't know how useful they are and you've probably already exhausted that alley.

Changing the subject, one thing I would like to see is the zooming changed in Syndicate Wars. The artist Mike Man talks here about how the game was originally not supposed to have the zoom limited by what weapon you use, and early screenshots showed it zoomed out even further than e.g. the LR rifle goes. It would be awesome to restore the zoom control to be fully player controlled and zoomed out. I assume it must have a per-weapon setting, so I've been trying to find that to hack to its maximum value for all guns.

mefistotelis commented 2 years ago

Changing how zoom works isn't that hard, but expanding zoom is a different beast - not an easy task. The renderings you refer to were made using special build of the game, with all rendering arrays enlarged, probably like 100x. Expanding these arrays manually would require most of the engine to be rewritten in C.

Mike Man mentions Bikers - ha, I was wondering why Unguided campaign desktop image starts with "b":

void reload_background()
{
[...]
  else if ( background_type == 0 )
  {
    LbFileLoadAt("qdata/s-proj.dat", back_buffer);
  }
  else if ( background_type == 1 )
    {
      LbFileLoadAt("qdata/z-proj.dat", back_buffer);
    }
    else if ( background_type == 2 )
    {
      LbFileLoadAt("data/b-proj.dat", back_buffer);
    }

The network library - it seem to be based on "Radica" library by "Diadem Inc.". But I didn't found any information about such library. It looks like Bullfrog adopted this library in 1995 or early 1996, and then modified it over time, so in Dungeon Keeper only the low level functions match.

Moburma commented 2 years ago

Wow, that's a good spot on the Bikers background! It's interesting it was in data/ rather than QData/ like the other two. I don't know if you've seen these early screenshots but they show the main menu in an early (I think the first) iteration, and specifically this one shows that the Unguided were called "Bikers" in-game at one point. These are also interesting as they show something called the "Public Access Network" in them - have you seen any references to that? I looked in the symbols file but didn't see it. I'd guess this would be some kind of in-game internet for advancing the plot that was removed early on. There's also a weird "S" shape icon in early menu screenshots at the bottom. The graphics for this exist in the final game but I don't know what it was used for.

The library information is very interesting, I'll have a look out. Not found anything so far though. I notice GeneWars also uses it. Did you ever look at Hi-Octane? I've never looked into that game, but the time frame it was released in and the rushed development to me seems it might be a good target for leftover stuff in it that shouldn't be there that might be helpful.

mefistotelis commented 2 years ago

I actually just started getting names from Genewars - there is a nice debug executable for it. It doesn't use the Radica thing, but many network routines are still very similar. Looks like Radica calls were just added to normal network routines, executed before the actual transfer. Maybe Radica is not a whole network library, but some kind of add-on to it?

For Hi-octane - I don't have any pre-release / debug executables for that.

For Public Access Network - there is a game objective "Use PANet":

const char *gameobjctv_names[] = {
    "",
    "GAME_OBJ_P_DEAD",
    "GAME_OBJ_ALL_G_DEAD",
    "GAME_OBJ_MEM_G_DEAD",
    "GAME_OBJ_P_NEAR",
    "GAME_OBJ_MEM_G_NEAR",
    "GAME_OBJ_P_ARRIVES",
    "GAME_OBJ_MEM_G_ARRIVES",
    "GAME_OBJ_ALL_G_ARRIVES",
    "GAME_OBJ_PERSUADE_P",
    "GAME_OBJ_PERSUADE_MEM_G",
    "GAME_OBJ_PERSUADE_ALL_G",
    "GAME_OBJ_TIME",
    "GAME_OBJ_GET_ITEM",
    "GAME_OBJ_USE_ITEM",
    "GAME_OBJ_FUNDS",
    "GAME_OBJ_DESTROY_OBJECT",
    "GAME_OBJ_PKILL P",
    "GAME_OBJ_PKILL G",
    "GAME_OBJ_PKILL ALL G",
    "GAME_OBJ_USE_PANET",
    "",
    "GAME_OBJ_PROTECT_G",
    "GAME_OBJ_P_PERS_G",
    "GAME_OBJ_ALL_G_USE_V",
    "",
    "",
}
Moburma commented 2 years ago

That is fantastic! I already worked out what most of the objective types were some time ago, it's good to see I was mostly right! I did an analysis of the .mis files previously and for the record the following objective types are not used by the final game:

GAME_OBJ_PKILL G was actually used by a mission in the .ben file, but was changed for later versions of the file, so presumably was deprecated instead for similar older objective types.

I notice in the symbols for the Playstation version there are PANetItem definitions (helps when you know what to search for!). The fact there was an objective to use the PAN in-game is pretty mindblowing though - that would imply at some point you would be able to return to the main menu and use the option there? That would also solve issues like not knowing how much research had occured during a mission as you could just check it. I assume it was some kind of cyberpunk BBS/internet type thing that would give you messages/hints/objectives. Really fascinating implications to how that would change the game.

mefistotelis commented 2 years ago

Here's something interesting - rewards after mission. I always thought if you end with civilians persuaded, you get the cost of the weapon they carry, but it looks like there's per-civilian sum instead.

      switch ( thing->SubType )
      {
        case SubTT_PERS_AGENT:
          add_agent(*(_DWORD *)&thing->U[88], *(_WORD *)&thing->U[36]);
          ingame.Credits += 1000;
          break;
        case SubTT_PERS_UNKN2:
          ingame.Credits += 1000;
          break;
        case SubTT_PERS_UNKN3:
          ingame.Credits += 150;
          break;
        case SubTT_PERS_SCIENTIST:
          ++research.Scientists;
          break;
        default:
          ingame.Credits += 100;
          break;
      }

For using the PAN - I don't think it was meant to get you into menu, I would assume there was a minigame of some kind planned there. But who knows.

Moburma commented 2 years ago

That is interesting. I wonder what the other cases are/were for? Perhaps persuading unguided? Odd that something else would get you 1000 as well. Yeah, the PAN thing might just be a terminal on the map or something. It definitely had a fully fleshed out mode in the menus though. I recently paid way too much money for some old issues of GamesMaster magazine, that had a making of Syndicate Wars diary each month and most of it is not scanned on the internet, so hopefully there will be a few interesting things that might shine a light on this from there.

mefistotelis commented 2 years ago

Shouldn't be hard to recognize the person types, based on config or sprite numbers. Ie.:

struct PeepStat peep_type_stats[17] =
{
  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
  { 1000, 1024, 2048, 1024, 20, 5, 512, 0, 0, 0 }, // 1=agent
  { 1000, 1024, 2048, 1024, 20, 1, 512, 0, 0, 0 },
  { 600, 524, 2048, 601, 10, 2, 312, 0, 0, 0 },
  { 100, 100, 400, 300, 0, 1, 172, 0, 0, 0 },
  { 100, 100, 400, 300, 0, 1, 160, 0, 0, 0 },
  { 600, 600, 900, 601, 15, 3, 312, 0, 0, 0 },
  { 4000, 1000, 800, 2001, 9999, 2, 362, 0, 0, 0 },
  { 600, 400, 800, 601, 6, 2, 362, 0, 0, 0 },
  { 600, 524, 2024, 601, 10, 2, 312, 0, 0, 0 },
  { 200, 100, 300, 200, 0, 1, 212, 0, 0, 0 }, // 10=scientist
  { 600, 524, 2024, 601, 0, 1, 312, 0, 0, 0 },
  { 2000, 1024, 2048, 1024, 20, 5, 512, 0, 0, 0 },
  { 100, 100, 400, 300, 0, 1, 150, 0, 0, 0 },
  { 110, 100, 400, 300, 0, 1, 212, 0, 0, 0 },
  { 100, 100, 400, 300, 0, 1, 212, 0, 0, 0 },
  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
};
Moburma commented 2 years ago

Isn't the above the same as was figured out in swleveled? The second entry is zealots, and third is female Unguided then civilians, etc. I would have thought persuading a zealot should also increase the agent count though.

Also do you know of any other unused graphics? I noticed there are these icons, possibly they were from the editor? https://ibb.co/yyHxnZ9

mefistotelis commented 2 years ago

Oh right, I forgot about that code. I wrote it like 15 years ago. EDIT: I seem to have errors there. High priest is clearly #12, and numbers 13 & 15 share the same visual sprites - so they are probably the 2 variants of blonde woman.

Unused graphics - sorry, can't remember. I worked on the graphics years ago as well.

mefistotelis commented 2 years ago

Here are my work folders from the times when I worked on the game graphics. You will find there a few old websites, various pictures, fan drawings, and some never finished things. It's a big mess, if you're interested in digging through it.

https://mega.nz/file/cRlHgC5I#jnSKHZ8Y8N0RutDr_7mqV2tGG71AkLePX2Znb3sy89E https://mega.nz/file/xcdUjJBR#Wqc5Wg3IsSyN6nYtiF3Xb5hCosyPHZRJcKH4gD1Ibu0 https://mega.nz/file/8csiBSiT#YtH0yB5_Iu90wnRXT1yGr3lJL_qbHSsyuRcDO-w5Ugo https://mega.nz/file/kcMCGabQ#FohXUlpKKsrFK57HePYvCRJEFs3bVI_k6Zfg7zs3W18

Let me know when you're done downloading. pass: swars

Moburma commented 2 years ago

Awesome, thanks a lot! I've got it all now. Not had time to look in these, but do you have the old "banned" radio advert? Trying to find that has driven me bonkers, I am sure I had a copy of it downloaded before back int he day, but can't find it, and it's totally missing from the internet. I really vividly rememeber it and thought it was on my old Amiga's hard drive, but I even re-imaged it to PC a few weeks ago but it's not on there. Would love to preserve it somewhere!

mefistotelis commented 2 years ago

I've heard about that radio advert, but no I don't have it. There should be magazine advert "better to give than to receive" in these materials, though.

mefistotelis commented 2 years ago

I don't get it.. there's a special code executed if you press F while a level is being loaded?

    if ( lbKeyOn[KC_F] )
      unkn_f_pressed_func();

Keep it pressed when accepting mission, then within the game do R for restart, and your weapon loadout will change. But I can't understand what that function is supposed to do.

Moburma commented 2 years ago

Does this work in the final game? I tried it just now but it didn't seem to work, I held it down from both the briefing and map screen but nothing seemed to be different. One thing it DID do I didn't know about was that pressing F in the mission briefing screen changes between 3 different font sizes! Maybe the weapon thing was to override the play weapons to use the ones set in the mission itself for testing?

I've not been doing much Syndicatey, but I did try one thing: The demo has some different sounds. Specifically it has more Police speech, and also a load of background "spot effects" it plays for atmosphere. These are removed from the final game sound.dat, but after experimenting with Theme Hospital (where I found that you can actually add in cut speech that is in the playstation version and some will work), I tried swapping the demo sound.dat in. The spot effects still do not play, so that code must really have been removed. However, the extra police speech does play!

mefistotelis commented 2 years ago

That's from final game, yes. The game clears key presses when they're used, that's why it didn't worked.

  1. Log in for new game
  2. Sell agent 1 uzi
  3. read the mail, go to map
  4. Press F just before accepting the mission
  5. Wait for the mission to start with F pressed
  6. Release the key when in game
  7. press R

.. And agent 1 will have weapons back. I don't think the change caused by that key is reverted anywhere. So whatever it was supposed to do, it persists until you restart the game process.

EDIT: Ok I was wrong. I just tested that without pressing "F" and got my weapons reset as well. I guess this is Syndicate Wars Port bug. So then, I have completely no idea what pressing the "F" does. But it does execute the additional function.

Moburma commented 2 years ago

Ahh, that makes more sense. I was testing the original game in DOS and it doesn't reset your weapons there. Don't forget that F seems to change the graphic settings in-game, was it related to that at all?

I also finally got the issues of GamesMaster magazine today with the making of diary in it. Not that interesting, but it does talk about them getting the game working in Windows. I've seen rumours on Usenet that there was going to be a "Windows 95 patch" but no proof. It is weird that it was the last of their games to be DOS only, but it seems they did genuinely intend to make a Windows version like Genewars onwars that was abandoned. Also it talks about characters being able to "Open Armoured Domes". I assume these are the big shell type things you see opening and closing in the early pre-alpha rolling demo. They seem to exist in the final game but are just part of the scenery and don't do anything. It also states the game went to Alpha status with "all missions complete" in May 1996, which seems about right, the ALLMISS.BEN file is from the start of May, although that's only close to finished, lots was changed since then. This seems to be the zoomed out build with the weapons only on the left of the screen.

mefistotelis commented 2 years ago

Don't forget that F seems to change the graphic settings in-game, was it related to that at all?

The function seem to be for loading a level, not for playing it. But I may be misinterpreting when it's run.

Alt-F seem to be used to fail a level in cheat mode:

      if ( ingame__Cheats & 4 && lbKeyOn[KC_C] && lbShift == 4 )
      {
        lbKeyOn[KC_C] = 0;
        mission_result = 1;
      }
      if ( ingame__Cheats & 4 && lbKeyOn[KC_F] && lbShift == 4 )
      {
        lbKeyOn[KC_F] = 0;
        mission_result = -1;
      }

The domes - that's interesting. So I'd guess there was supposed to be some kind of access card? It must've been a tough decision to abandon that idea - it would be a nice game mechanic. But I'm not sure why the domes are so big - in London mission with the professor taking an item near the dome (I guess originally it was supposed to be inside the dome), it would be enough to have much smaller structure. Maybe these domes were supposed to hide larger objects, like vehicles?

Moburma commented 2 years ago

I notice there seem to be traces for this in the demo symbols:

 <SYMBOL ADDRESS="0x155205" NAME="aOpenDomeD" />
 <SYMBOL ADDRESS="0x155212" NAME="aCloseDomeD" />
mefistotelis commented 2 years ago

That beta version will help in recovering enumerations. Ie the strings you found are part of printing scripted tasks for persons:

char __usercall __spoils<eax,edx,ebx> person_command_to_text@<al>(char *out@<eax>, __int16 cmd_idx@<dx>, char a3@<bl>)
{
  char *v3; // esi
  char result; // al
  Command *cmd; // edx
  int v6; // [esp+0h] [ebp-12h]

  v3 = out;
  HIWORD(v6) = cmd_idx;
  if ( a3 == 1 )
    return a3 ^ 1;
  cmd = &commands[v6 >> 16];
  if ( cmd->Flags & 0x10000 )
  {
    sprintf(out, "%d ", v6 >> 16);
    v3 += strlen(v3);
  }
  if ( cmd->Flags & 0x20000 )
  {
    sprintf(v3, "U ", v6 >> 16);
    v3 += strlen(v3);
  }
  if ( cmd->Flags & 0x40000 )
  {
    sprintf(v3, "IU ", v6 >> 16);
    v3 += strlen(v3);
  }
  if ( cmd->Flags & 0x80000 )
  {
    sprintf(v3, "!!!");
    v3 += strlen(v3);
  }
  switch ( cmd->Type )
  {
    case 2:
      sprintf(
        v3,
        "GO TO POINT X%d Y%d Z%d",
        *(_DWORD *)&cmd->OtherThing >> 16,
        *(_DWORD *)&cmd->X >> 16,
        *(_DWORD *)&cmd->Y >> 16);
      result = 1;
      break;
    case 3:
      sprintf(v3, "GO TO PERSON %d", *(unsigned __int16 *)&things[*(_DWORD *)&cmd->Next >> 16].field_48[6]);
      result = 1;
      break;
    case 4:
      sprintf(v3, "KILL PERSON %d", *(unsigned __int16 *)&things[*(_DWORD *)&cmd->Next >> 16].field_48[6]);
      result = 1;
      break;
    case 5:
      sprintf(v3, "KILL %d MEM GROUP %d", *(_DWORD *)&cmd->Arg2 >> 16, *(_DWORD *)&cmd->Next >> 16);
      result = 1;
      break;
    case 6:
      sprintf(v3, "KILL ALL GROUP %d", *(_DWORD *)&cmd->Next >> 16);
      result = 1;
      break;
    case 7:
      sprintf(v3, "PERSUADE PERSON %d", *(unsigned __int16 *)&things[*(_DWORD *)&cmd->Next >> 16].field_48[6]);
      result = 1;
      break;
    case 8:
      sprintf(v3, "PERSUADE %d Mem G %d", *(_DWORD *)&cmd->Arg2 >> 16, *(_DWORD *)&cmd->Next >> 16);
      result = 1;
      break;
    case 9:
      sprintf(v3, "PERSUADE All G %d", *(_DWORD *)&cmd->Next >> 16);
      result = 1;
      break;
    case 0xA:
      sprintf(v3, "BLOCK PERSON %d", *(unsigned __int16 *)&things[*(_DWORD *)&cmd->Next >> 16].field_48[6]);
      result = 1;
      break;
    case 0xB:
      sprintf(v3, "SCARE PERSON %d", *(unsigned __int16 *)&things[*(_DWORD *)&cmd->Next >> 16].field_48[6]);
      result = 1;
      break;
    case 0xC:
      sprintf(v3, "FOLLOW PERSON %d", *(unsigned __int16 *)&things[*(_DWORD *)&cmd->Next >> 16].field_48[6]);
      result = 1;
      break;
    case 0xD:
      sprintf(v3, "SUPPORT PERSON %d", *(unsigned __int16 *)&things[*(_DWORD *)&cmd->Next >> 16].field_48[6]);
      result = 1;
      break;
    case 0xE:
      sprintf(
        v3,
        "PROTECT PERSON %d idx %d",
        *(unsigned __int16 *)&things[*(_DWORD *)&cmd->Next >> 16].field_48[6],
        *(_DWORD *)&cmd->Next >> 16);
      result = 1;
      break;
    case 0xF:
      sprintf(v3, "HIDE");
      result = 1;
      break;
    case 0x10:
      sprintf(v3, "GET ITEM %d", *(unsigned __int16 *)&sthings[*(_DWORD *)&cmd->Next >> 16].field_24[22]);
      result = 1;
      break;
    case 0x11:
      sprintf(v3, "USE WEAPON %s", *((_DWORD *)&off_145200 + (*(_DWORD *)&cmd->Next >> 16)));
      result = 1;
      break;
    case 0x12:
      sprintf(v3, "DROP SPEC ITEM %d", *(unsigned __int16 *)&sthings[*(_DWORD *)&cmd->Next >> 16].field_24[22]);
      result = 1;
      break;
    case 0x13:
      sprintf(v3, "AVOID PERSON %d", *(unsigned __int16 *)&things[*(_DWORD *)&cmd->Next >> 16].field_48[6]);
      result = 1;
      break;
    case 0x14:
      sprintf(v3, "WAND AVOID GROUP %d", *(_DWORD *)&cmd->Next >> 16);
      result = 1;
      break;
    case 0x15:
      sprintf(v3, "DESTROY BUILDING %d", *(_DWORD *)&cmd->Next >> 16);
      result = 1;
      break;
    case 0x16:
      goto LABEL_13;
    case 0x17:
      sprintf(v3, "USE VEHICLE %d", *(unsigned __int16 *)&things[*(_DWORD *)&cmd->Next >> 16].field_48[6]);
      result = 1;
      break;
    case 0x18:
      sprintf(v3, "EXIT VEHICLE");
      result = 1;
      break;
    case 0x19:
      sprintf(v3, "CATCH TRAIN %d", *(_DWORD *)&cmd->Next >> 16);
      result = 1;
      break;
    case 0x1A:
      sprintf(v3, "OPEN DOME %d", *(_DWORD *)&cmd->Next >> 16);
      result = 1;
      break;
    case 0x1B:
      sprintf(v3, "CLOSE DOME %d", *(_DWORD *)&cmd->Next >> 16);
      result = 1;
      break;
    case 0x1C:
      sprintf(v3, "DROP WEAPON %s", *((_DWORD *)&off_145200 + (*(_DWORD *)&cmd->Next >> 16)));
      result = 1;
      break;
    case 0x1D:
      sprintf(v3, "CATCH FERRY %d", *(_DWORD *)&cmd->Next >> 16);
      result = 1;
      break;
    case 0x1E:
      sprintf(v3, "EXIT FERRY %d", *(_DWORD *)&cmd->Next >> 16);
      result = 1;
      break;
    case 0x1F:
      sprintf(v3, "PING EXIST");
      result = 1;
      break;
    case 0x20:
      sprintf(
        v3,
        "GOTOPOINT FACE %d X%d Z%d",
        *(_DWORD *)&cmd->Next >> 16,
        *(_DWORD *)&cmd->OtherThing >> 16,
        *(_DWORD *)&cmd->Y >> 16);
      result = 1;
      break;
    case 0x21:
      sprintf(v3, "SELF_DESTRUCT");
      result = 1;
      break;
    case 0x22:
      sprintf(v3, "PROTECT MEM G %d", *(_DWORD *)&cmd->Next >> 16);
      result = 1;
      break;
    case 0x23:
      sprintf(v3, "RUN TO POINT X%d Z%d", *(_DWORD *)&cmd->OtherThing >> 16, *(_DWORD *)&cmd->Y >> 16);
      result = 1;
      break;
    case 0x24:
      sprintf(v3, "KILL EVERYONE");
      result = 1;
      break;
    case 0x25:
      sprintf(v3, "GUARD OFF");
      result = 1;
      break;
    case 0x26:
      sprintf(v3, "Execute Coms (plyr)");
      result = 1;
      break;
    case 0x33:
      sprintf(v3, "WAIT P/V DEAD %d", *(unsigned __int16 *)&things[*(_DWORD *)&cmd->Next >> 16].field_48[6]);
      result = 1;
      break;
    case 0x34:
      sprintf(v3, "WAIT %d MEM G DEAD %d", *(_DWORD *)&cmd->Arg2 >> 16, *(_DWORD *)&cmd->Next >> 16);
      result = 1;
      break;
    case 0x35:
      sprintf(v3, "WAIT ALL G DEAD %d", *(_DWORD *)&cmd->Next >> 16);
      result = 1;
      break;
    case 0x36:
      sprintf(v3, "WAIT P/V/I NEAR %d", *(unsigned __int16 *)&things[*(_DWORD *)&cmd->Next >> 16].field_48[6]);
      result = 1;
      break;
    case 0x37:
      sprintf(v3, "WAIT %d MEM G %d NEAR ", *(_DWORD *)&cmd->Arg2 >> 16, *(_DWORD *)&cmd->Next >> 16);
      result = 1;
      break;
    case 0x38:
      sprintf(v3, "WAIT ALL G %d NEAR ", *(_DWORD *)&cmd->Next >> 16);
      result = 1;
      break;
    case 0x39:
      sprintf(
        v3,
        "WAIT P/V/I ARRIVES %d X%d Y%d Z%d",
        *(unsigned __int16 *)&things[*(_DWORD *)&cmd->Next >> 16].field_48[6],
        *(_DWORD *)&cmd->OtherThing >> 16,
        *(_DWORD *)&cmd->X >> 16,
        *(_DWORD *)&cmd->Y >> 16);
      result = 1;
      break;
    case 0x3A:
      sprintf(v3, "WAIT %d MEM G ARRIVE %d", *(_DWORD *)&cmd->Arg2 >> 16, *(_DWORD *)&cmd->Next >> 16);
      result = 1;
      break;
    case 0x3B:
      sprintf(v3, "WAIT ALL G ARRIVE %d", *(_DWORD *)&cmd->Next >> 16);
      result = 1;
      break;
    case 0x3C:
      sprintf(v3, "WAIT P PERSUADED %d", *(unsigned __int16 *)&things[*(_DWORD *)&cmd->Next >> 16].field_48[6]);
      result = 1;
      break;
    case 0x3D:
      sprintf(v3, "WAIT %d MEM G PERSUADED %d", *(_DWORD *)&cmd->Arg2 >> 16, *(_DWORD *)&cmd->Next >> 16);
      result = 1;
      break;
    case 0x3E:
      sprintf(v3, "WAIT ALL G PERSUADED %d", *(_DWORD *)&cmd->Next >> 16);
      result = 1;
      break;
    case 0x3F:
      sprintf(v3, "WAIT MISSION SUCC %d", *(_DWORD *)&cmd->Next >> 16);
      result = 1;
      break;
    case 0x40:
      sprintf(v3, "WAIT MISSION FAIL %d", *(_DWORD *)&cmd->Next >> 16);
      result = 1;
      break;
    case 0x41:
      sprintf(v3, "WAIT MISSION START %d", *(_DWORD *)&cmd->Next >> 16);
      result = 1;
      break;
    case 0x42:
      sprintf(
        v3,
        "WAIT OBJECT DESTROYED %d X%d Y%d Z%d",
        *(_DWORD *)&cmd->Next >> 16,
        *(_DWORD *)&cmd->OtherThing >> 16,
        *(_DWORD *)&cmd->X >> 16,
        *(_DWORD *)&cmd->Y >> 16);
      result = 1;
      break;
    case 0x43:
      sprintf(v3, "WAIT TIME %d", *(_DWORD *)&cmd->Time >> 16);
      result = 1;
      break;
    case 0x47:
      sprintf(v3, "WAND P/V DEAD %d", *(unsigned __int16 *)&things[*(_DWORD *)&cmd->Next >> 16].field_48[6]);
      result = 1;
      break;
    case 0x48:
      sprintf(v3, "WAND %d MEM G %d DEAD ", *(_DWORD *)&cmd->Arg2 >> 16, *(_DWORD *)&cmd->Next >> 16);
      result = 1;
      break;
    case 0x49:
      sprintf(v3, "WAND ALL G DEAD %d", *(_DWORD *)&cmd->Next >> 16);
      result = 1;
      break;
    case 0x4A:
      sprintf(v3, "WAND P/V/I NEAR %d", *(unsigned __int16 *)&things[*(_DWORD *)&cmd->Next >> 16].field_48[6]);
      result = 1;
      break;
    case 0x4B:
      sprintf(v3, "WAND %d MEM G %d NEAR ", *(_DWORD *)&cmd->Arg2 >> 16, *(_DWORD *)&cmd->Next >> 16);
      result = 1;
      break;
    case 0x4C:
      sprintf(v3, "WAND ALL G %d NEAR ", *(_DWORD *)&cmd->Next >> 16);
      result = 1;
      break;
    case 0x4D:
      sprintf(
        v3,
        "WAND P/V/I ARRIVES %d X%d Y%d Z%d",
        *(unsigned __int16 *)&things[*(_DWORD *)&cmd->Next >> 16].field_48[6],
        *(_DWORD *)&cmd->OtherThing >> 16,
        *(_DWORD *)&cmd->X >> 16,
        *(_DWORD *)&cmd->Y >> 16);
      result = 1;
      break;
    case 0x4E:
      sprintf(v3, "WAND %d MEM G ARRIVE %d", *(_DWORD *)&cmd->Arg2 >> 16, *(_DWORD *)&cmd->Next >> 16);
      result = 1;
      break;
    case 0x4F:
      sprintf(v3, "WAND ALL G ARRIVE %d", *(_DWORD *)&cmd->Next >> 16);
      result = 1;
      break;
    case 0x50:
      sprintf(v3, "WAND P PERSUADED %d", *(unsigned __int16 *)&things[*(_DWORD *)&cmd->Next >> 16].field_48[6]);
      result = 1;
      break;
    case 0x51:
      sprintf(v3, "WAND %d MEM G PERSUADED %d", *(_DWORD *)&cmd->Arg2 >> 16, *(_DWORD *)&cmd->Next >> 16);
      result = 1;
      break;
    case 0x52:
      sprintf(v3, "WAND ALL G PERSUADED %d", *(_DWORD *)&cmd->Next >> 16);
      result = 1;
      break;
    case 0x53:
      sprintf(v3, "WAND MISSION SUCC %d", *(_DWORD *)&cmd->Next >> 16);
      result = 1;
      break;
    case 0x54:
      sprintf(v3, "WAND MISSION FAIL %d", *(_DWORD *)&cmd->Next >> 16);
      result = 1;
      break;
    case 0x55:
      sprintf(v3, "WAND MISSION START %d", *(_DWORD *)&cmd->Next >> 16);
      result = 1;
      break;
    case 0x56:
      sprintf(
        v3,
        "WAND OBJECT DESTROYED %d X%d Y%d Z%d",
        *(_DWORD *)&cmd->Next >> 16,
        *(_DWORD *)&cmd->OtherThing >> 16,
        *(_DWORD *)&cmd->X >> 16,
        *(_DWORD *)&cmd->Y >> 16);
      result = 1;
      break;
    case 0x57:
      sprintf(v3, "WAND TIME %d", *(_DWORD *)&cmd->Time >> 16);
      result = 1;
      break;
    case 0x6E:
      sprintf(v3, "LOOP COM %d", *(_DWORD *)&cmd->Next >> 16);
LABEL_13:
      result = 1;
      break;
    case 0x6F:
      sprintf(v3, "UNTIL P/V DEAD %d", *(unsigned __int16 *)&things[*(_DWORD *)&cmd->Next >> 16].field_48[6]);
      result = 1;
      break;
    case 0x70:
      sprintf(v3, "UNTIL %d MEM G DEAD %d", *(_DWORD *)&cmd->Arg2 >> 16, *(_DWORD *)&cmd->Next >> 16);
      result = 1;
      break;
    case 0x71:
      sprintf(v3, "UNTIL ALL G DEAD %d", *(_DWORD *)&cmd->Next >> 16);
      result = 1;
      break;
    case 0x72:
      sprintf(v3, "UNTIL P/V/I NEAR %d", *(unsigned __int16 *)&things[*(_DWORD *)&cmd->Next >> 16].field_48[6]);
      result = 1;
      break;
    case 0x73:
      sprintf(v3, "UNTIL %d MEM G %d NEAR ", *(_DWORD *)&cmd->Arg2 >> 16, *(_DWORD *)&cmd->Next >> 16);
      result = 1;
      break;
    case 0x74:
      sprintf(v3, "UNTIL ALL G %d NEAR ", *(_DWORD *)&cmd->Next >> 16);
      result = 1;
      break;
    case 0x75:
      sprintf(
        v3,
        "UNTIL P/V/I ARRIVES %d X%d Y%d Z%d",
        *(unsigned __int16 *)&things[*(_DWORD *)&cmd->Next >> 16].field_48[6],
        *(_DWORD *)&cmd->OtherThing >> 16,
        *(_DWORD *)&cmd->X >> 16,
        *(_DWORD *)&cmd->Y >> 16);
      result = 1;
      break;
    case 0x76:
      sprintf(v3, "UNTIL %d MEM G ARRIVE %d", *(_DWORD *)&cmd->Arg2 >> 16, *(_DWORD *)&cmd->Next >> 16);
      result = 1;
      break;
    case 0x77:
      sprintf(v3, "UNTIL ALL G ARRIVE %d", *(_DWORD *)&cmd->Next >> 16);
      result = 1;
      break;
    case 0x78:
      sprintf(v3, "UNTIL P PERSUADED %d", *(unsigned __int16 *)&things[*(_DWORD *)&cmd->Next >> 16].field_48[6]);
      result = 1;
      break;
    case 0x79:
      sprintf(v3, "UNTIL %d MEM G PERSUADED %d", *(_DWORD *)&cmd->Arg2 >> 16, *(_DWORD *)&cmd->Next >> 16);
      result = 1;
      break;
    case 0x7A:
      sprintf(v3, "UNTIL ALL G PERSUADED %d", *(_DWORD *)&cmd->Next >> 16);
      result = 1;
      break;
    case 0x7B:
      sprintf(v3, "UNTIL MISSION SUCC %d", *(_DWORD *)&cmd->Next >> 16);
      result = 1;
      break;
    case 0x7C:
      sprintf(v3, "UNTIL MISSION FAIL %d", *(_DWORD *)&cmd->Next >> 16);
      result = 1;
      break;
    case 0x7D:
      sprintf(v3, "UNTIL MISSION START %d", *(_DWORD *)&cmd->Next >> 16);
      result = 1;
      break;
    case 0x7E:
      sprintf(
        v3,
        "UNTIL OBJECT DESTROYED %d X%d Y%d Z%d",
        *(_DWORD *)&cmd->Next >> 16,
        *(_DWORD *)&cmd->OtherThing >> 16,
        *(_DWORD *)&cmd->X >> 16,
        *(_DWORD *)&cmd->Y >> 16);
      result = 1;
      break;
    case 0x7F:
      sprintf(v3, "UNTIL TIME %d", *(_DWORD *)&cmd->Time >> 16);
      result = 1;
      break;
    case 0x80:
      sprintf(v3, "WAIT OBJ:-");
      result = 1;
      break;
    case 0x81:
      sprintf(v3, "WAND OBJ:-");
      result = 1;
      break;
    case 0x82:
      sprintf(v3, "UNTIL OBJ:-");
      result = 1;
      break;
    case 0x83:
      sprintf(
        v3,
        "WITHIN AREA X%d Y%d Z%d",
        *(_DWORD *)&cmd->OtherThing >> 16,
        *(_DWORD *)&cmd->X >> 16,
        *(_DWORD *)&cmd->Y >> 16);
      result = 1;
      break;
    case 0x84:
      sprintf(v3, "WITHIN OFF");
      result = 1;
      break;
    case 0x85:
      sprintf(v3, "LOCK BUILD %d", *(_DWORD *)&cmd->Next >> 16);
      result = 1;
      break;
    case 0x86:
      sprintf(v3, "UNLOCK BUILD %d", *(_DWORD *)&cmd->Next >> 16);
      result = 1;
      break;
    case 0x87:
      sprintf(v3, "SELECT WEAPON %s ", *((_DWORD *)&off_145200 + (*(_DWORD *)&cmd->Next >> 16)));
      result = 1;
      break;
    case 0x88:
      sprintf(v3, "HARD AS AGENT");
      result = 1;
      break;
    case 0x89:
      sprintf(v3, "UNTIL G %d NOT SEEN", *(_DWORD *)&cmd->Time >> 16);
      result = 1;
      break;
    case 0x8A:
      sprintf(v3, "start danger music");
      result = 1;
      break;
    case 0x8B:
      sprintf(v3, "PING P/V %d", *(unsigned __int16 *)&things[*(_DWORD *)&cmd->Next >> 16].field_48[6]);
      result = 1;
      break;
    case 0x8C:
      sprintf(v3, "CAMERA TRACK %d", *(unsigned __int16 *)&things[*(_DWORD *)&cmd->Next >> 16].field_48[6]);
      result = 1;
      break;
    case 0x8D:
      sprintf(v3, "UNTRUCE GROUP %d", *(_DWORD *)&cmd->Next >> 16);
      result = 1;
      break;
    case 0x8E:
      sprintf(v3, "PLAY SAMPLE %d", *(_DWORD *)&cmd->Next >> 16);
      result = 1;
      break;
    default:
      result = 0;
      break;
  }
  return result;
}
Moburma commented 2 years ago

Ahh I see, I noticed many of those before. Particularly the Ferry ones, it seems at one point it was intended to be able to use boats in the same way as you can use Trains to get across maps. I'm not sure this is possible in the final game, I've noticed some of the cut levels have boardable boats moving around, but I don't think they come close enough to the shore to get in (they have arrows to show you can get in on mouse over).

Moburma commented 2 years ago

Not sure if you're interested, but I scanned the making of diary articles and uploaded them. I only have parts 2,3 and 5 out of 5 so far, however. https://ibb.co/2gWTv5W https://ibb.co/9pdNrCg https://ibb.co/Rz0LgDL https://ibb.co/jHMZWbf https://ibb.co/wpgrKyc https://ibb.co/GFdmRsD

mefistotelis commented 2 years ago

Thanks, that's nice to have.

I tied searching for parts of the level editor; wile single functions which were used for that exist in betas and final, there isn't enough to even know how the editor worked. The only version with more of editor code included is the alpha demo.

There's a menu with editor options, it probably looked the same as cheat menu in Dungeon Keeper:

dseg02:000CAF0C mapedit_menu    dd offset asc_C267A     ; " "
dseg02:000CAF10                 dd offset aChoosePoly   ; "CHOOSE_POLY"
dseg02:000CAF14                 dd offset aChooseCol    ; "CHOOSE_COL"
dseg02:000CAF18                 dd offset aChooseText   ; "CHOOSE_TEXT"
dseg02:000CAF1C                 dd offset aChooseMode   ; "CHOOSE_MODE"
dseg02:000CAF20                 dd offset aPlaceObject  ; "PLACE_OBJECT"
dseg02:000CAF24                 dd offset aEditObject   ; "EDIT_OBJECT"
dseg02:000CAF28                 dd offset aPickTexture  ; "PICK_TEXTURE"
dseg02:000CAF2C                 dd offset aPlaceTexture ; "PLACE_TEXTURE"
dseg02:000CAF30                 dd offset aChooseObject ; "CHOOSE_OBJECT"
dseg02:000CAF34                 dd offset aEditPrimObject ; "EDIT_PRIM_OBJECT"
dseg02:000CAF38                 dd offset aPlaceLight   ; "PLACE_LIGHT"
dseg02:000CAF3C                 dd offset aEditLight    ; "EDIT_LIGHT"
dseg02:000CAF40                 dd offset aPickBrush    ; "PICK_BRUSH"
dseg02:000CAF44                 dd offset aPlaceBrush   ; "PLACE_BRUSH"
dseg02:000CAF48                 dd offset aPickTile     ; "PICK_TILE"
dseg02:000CAF4C                 dd offset aPlaceStaticSpr ; "PLACE_STATIC_SPRITE"
dseg02:000CAF50                 dd offset aTestCar      ; "TEST CAR"
dseg02:000CAF54                 dd offset aEditCommands ; "EDIT COMMANDS"
dseg02:000CAF58                 dd offset aEditAnimTexts ; "EDIT ANIM TEXTS"
dseg02:000CAF5C                 dd offset aFineTuneObject ; "FINE TUNE OBJECT"
dseg02:000CAF60                 dd offset aBuildShuttle1 ; "BUILD SHUTTLE1"
dseg02:000CAF64                 dd offset aBuildRoad1   ; "BUILD ROAD1"
dseg02:000CAF68                 dd offset aEditTraffic  ; "EDIT TRAFFIC"
dseg02:000CAF6C                 dd offset aEditLoadMap  ; "EDIT_LOAD_MAP"
dseg02:000CAF70                 dd offset aEditSaveMap  ; "EDIT_SAVE_MAP"
dseg02:000CAF74                 dd offset aEditAmbientLig ; "EDIT_AMBIENT_LIGHT"
dseg02:000CAF78                 dd offset aEditNewMap   ; "EDIT_NEW_MAP"
dseg02:000CAF7C                 dd offset aEditMapAlt   ; "EDIT_MAP_ALT"
dseg02:000CAF80                 dd offset aEditMapSplit ; "EDIT_MAP_SPLIT"
dseg02:000CAF84                 dd offset aEditMapSort  ; "EDIT_MAP_SORT"
dseg02:000CAF88                 dd offset aEditMapLoadSav ; "EDIT_MAP_LOAD_SAVE"
dseg02:000CAF8C                 dd offset aEditAltMode  ; "EDIT ALT MODE"
dseg02:000CAF90                 dd offset aUnused       ; "UNUSED"
Moburma commented 2 years ago

Wow, that's very interesting, I guess entirely expected that it's been cut out. Did you do much looking into the pre-alpha demo? I've never looked into it much as it seems radically different from the final game, I assumed it would have a very different codebase etc so not actually that useful. There's also that Annual Report video of the Playstation version that seems to be a PS1 version of it with some changes.

mefistotelis commented 2 years ago

The PS1 version is cut down substantially. It's good for copy-pasting symbols which exist there, but there is a lot of stuff which is cut:

So I basically already used everything there was to use from PSX version.

Now the alpha - it is different. File formats are not as well developed there, controls are unfinished. And the original exe blocks you into packet read mode. But that's the only version we have with substantial parts of the editor included.

Also, here's my little mod which removes the forced options (packet mode and network disable). So with it, you can play normally, and use IPX network from command line params.

https://mega.nz/file/9Es3nKIL#jFstUJXnF_lfrZq7yJ8ixSgx5YEpo-zsia6fGPYQ-r8

run syn2 -p1 >dbg to run the original demo (replay of packet file). Or run syn2 -m1 >dbg to play on your own. Control keys are also moving the camera unfortunately; but I'm sure keys can be rebinded somehow - either there's key bindings file or a camera mode switch.

Moburma commented 2 years ago

Wow, now that's really amazing! Not had much time to play with it yet, been out with the family today. Definitely a lot to take in. One thing I noticed previously is it has the .xmi file for the original Syndicate's music and some sound effects with it that are not used that I found odd.

mefistotelis commented 2 years ago

This annoying view rotation is hard-coded - I will make a patch for it, as that removes a lot of enjoyment from exploring the map.

But what's more interesting is - if you run the game without net mode and without single map mode (ie. no params), it initializes the editor.

That currently ends with black screen, so obviously something is preventing the editor from running (assuming its main loop is even present). Unfortunately, I can't get a log from that - I'm redirecting logs to dbg file, but since I can't exit cleanly from the black screen, DOSBox isn't flushing the buffers on exit and log file is empty.. Anyway it's possible that the log would explain the issue which is preventing editor from running.

Moburma commented 2 years ago

Yeah, I noticed as well it tries to load something else if you run with no arguments. Obviously it complains textwalk.dat is missing, but I assume it's something more complex than that going on. Also, the file pre-alpha Frames.h has definitions of animation frames. Not that exciting, but it does have two interesting things - there are definitions for a Female version of the "Shady Guy" (the uncommon NPCs with the big Chinese style hats on). There's also an NPC defined called "Off". It's after the NPC "Army" (presumably the generic Soldiers seen in the final game), so possibly these were some kind of cut military officers? Oh, and you probably already know but Editicon dat/tab contains what are pretty obviously the editor button graphics.

mefistotelis commented 2 years ago

The animation frames probably changed their indexes in final game.

Anyway, here's the updated exe. I had to modify relocations, so it was more complicated than I though.

https://mega.nz/file/ZRVCwJwJ#znOsScolrPF-A3tlqIGe2wqnC1CCp3oiuaJ9BF2cLrs

The basic controls:

Interaction in packet replay mode:
ESC     - exit (only in replay mode)
I       - zoom in (faster with Shift)
O       - zoom out (faster with Shift)
R       - show more screen area
Shift-R - show less screen area
Home    - Reset camera

Interaction in map play mode:
Arrows  - move agent
Alt-Q   - change weapon
3       - stop/start game action
Insert/PgUp - change camera tilt (only in modded version)
Delete/PgDn - rotate camera (only in modded version)

Program parameters:
-c - toggles a switch which does not seem to do anything
-d - initialize alternate DOS keyboard handler
-h - hi-resolution
-m<n> - start map number <n>
-n<n> - start network game, with <n> players
-p<n> - play packet file number <n>
-q - generate DAT/TAB files from LBMs
-r<n> - record packet file number <n>
-s<name> - network session name (for multiple games over same network)
-t - recompute and save colour tables
-u<name> - user name used for per-developer level files
Moburma commented 2 years ago

Incredible work! It's interesting to see high res mode was in there and fully working all along. Turning that on, zooming out and increasing the draw distance with R lets you see for absolutely miles (although it seems to crash if you increase the distance too much). It actually looks really nice already this way. I guess this is also how they took those zoomed out map pictures in the press pack. Much to take in here!

mefistotelis commented 2 years ago

For the editor - all the task-specific routines are there (ie. changing ground, recomputing lights, placing and modifying objects), and they are merged with functions which allow to select these from a menu. But functions for displaying and handling these menus are never called - they're unused in the code.

This looks like a lot of work to make the editor to useable state. I have no idea in which order or where exactly these functions are supposed to be called. And these relocations make everything even more irritating.

I wonder if the game would work if I removed the relocation table. The exe is loaded into a fresh address space created by DOS extender, so in theory it shouldn't need relocations..

mefistotelis commented 2 years ago

I will try to enable the editor. It would be easier if I knew how it originally looked or worked..

There are 6-10 top level functions which are never called - and they either draw something, or react to some input. The problem is we don't know in which order and under which conditions they were called.

They actually seem to be several separate editors? Like map editor, level editor, mission editor, etc. It looks like each of those was actually created by a different person - coding is a bit different in each, and sometimes they invent their separate own ways to do the same things.

Another issue is the mouse - I think it will be hard to use all the menus without cursor visible. So I have to show the cursor, somehow.

I created a "blank slate" function which I now can use to experiment with the call order:

obraz

Moburma commented 2 years ago

Fantastic! I can't really help at all with this I'm afraid, but good luck. Something else I just discovered is that there really is full sound in the Alpha. I don't know if you knew this or not, but originally it was released in something called the "Christmas Demo" pack by Bullfrog, which was on at least issue 37 of PC Zone magazine. When run from here there is a generic Bullfrog sound setup program you're meant to run first that copies all the sound drivers to the right place to give all the demos sound. Without that, none of them work properly! I tihnk all the copies of the separate demos on the internet are missing this, so they are all silent no matter what you do. There is an article on TCRF about how Creation has all this cut audio... it IS all played, you just can't hear it if you don't install the sound drivers from the separate program first! it's honestly quite scary how much knowledge of early 90s PC games has already been lost.

mefistotelis commented 2 years ago

I knew it's from Christmas Demo, but had no idea about the magazine.

For the sound - I configured it myself; DOSBox by default simulates old sound blaster so originally the music didn't sounded right for me though - sound font wasn't switched.

Here's my version, with DOSBox config; you can press "g" when in -m1 mode to see how I'm trying to make the editor work. https://mega.nz/file/9UdlEY5J#osFJNmigDHgknBKT5HI5Rq79zyzCMl_4kwlAVYdwZoo

mefistotelis commented 2 years ago

Oh wow, in that version they defined an array of 64000 to store lights. Other games supported like, 4 lights at a time, so light in a level had to be spaced and there was up to 30 of them. Here the array has 64000. If it was 1500 elements more, then would be enough lights to put one on every floor tile on a level.

But in the final game, the ambient light is quite bright, so the light sources are not very pronounced. I guess the idea was to create Blade Runner like environments, but that had to be scaled down to run on computers of that era.

Moburma commented 2 years ago

Yeah, I got that impression, the early panoramic map pictures have extremely stark lighting compared to what was used in the final game. The PlayStation version is much darker and grittier lighting-wise in comparison, but of course that has many other compromises graphically. There is later footage of the alpha version that seems to have proper headlights on the cars etc as well.