Ambermoon.net icon indicating copy to clipboard operation
Ambermoon.net copied to clipboard

Ambermoon Research

Open Pyrdacor opened this issue 4 years ago • 378 comments

I want to gather all unclear things here so we can track and research them. I will update this initial post to have an up to date list on top.

Pyrdacor avatar Dec 23 '20 16:12 Pyrdacor

Found functions for lockpick, find trap and disarm trap checks. They are all simple % rolls after all, with a Dex check after failed lockpick/disarm, that triggers the trap if failed. I'm not completely rulling out, that some locks use a different function - but maybe Ambermoon is just simpler than Amberstar in that regard.

While looking at the dissassembly, I also learned not to trust ghidra's decompiler too much.. it's much more readable than Assembler, but may, on occasion, not accurately represent what is going on..

kermitfrog avatar Dec 27 '20 13:12 kermitfrog

In Items, the byte at offset 0x20 (and probably those before) contains item flags. After successfully opening a lock with a key it is checked with & 0x10. If that bit is set, the key is destroyed. It is not clear, which effect it has on other items. "Lantern" has it, but is not destroyed on use of last charge.

kermitfrog avatar Dec 27 '20 14:12 kermitfrog

In Items, the byte at offset 0x20 (and probably those before) contains item flags. After successfully opening a lock with a key it is checked with & 0x10. If that bit is set, the key is destroyed. It is not clear, which effect it has on other items. "Lantern" has it, but is not destroyed on use of last charge.

I decoded it as "destroy after usage". Maybe it is actually "consume charge and destroy if no charge is left unless the item is rechargeable"?

Pyrdacor avatar Dec 27 '20 16:12 Pyrdacor

FYI at byte 0x20 are the item flags and at byte 0x21 the item slot flags which are also used in item slots separately. It's already documented in my other repo. But some things are not clear yet.

But if you find out what the item flags 0x08 and 0x40 are for and maybe 0x80 (but I guess this is unused) it would be great.

Pyrdacor avatar Dec 27 '20 16:12 Pyrdacor

[..] It's already documented in my other repo. [..]

I really should run git pull more often on that one...

I found the Spell Info locations in AM2_CPU at 0x26be64 (relative memory address as shown in ghidra) or 0x43FC4 (byte offset). Version is 1.07 Eng.

Its 7 times 30 entries of 5 bytes, that I decoded as this:

Offset Type Description
0x01 ubyte use condition flags
0x02 ubyte SP
0x03 ubyte SLP
0x04 ubyte target flags
0x05 ubyte probably element / type of effect or something like that

Use conditions

Bit Description
0 World map
1 2D? this is never set alone
2 3D
3 Camp
4 Battle
5 Lyramion
6 Wood Moon
7 Morag

Target

Bit Description
0 1 Party member
1 unused
2 Whole Party
3 1 Enemy
4 Row of Enemies
5 All Enemies
6 Item
7 Blink (only used for that spell)

Elements / Effect

Every spell has exactly one bit set here, with the sole exception of whirlwind (=0, probably bug). The other elemental combat spells use it as follows:

Bit Description
4 Earth
5 Wind
6 Fire
7 Ice

all other spells have one of Bits 0-3 set, but I don't understand the meaning.

kermitfrog avatar Dec 29 '20 13:12 kermitfrog

Nice finding. I think there are spells that are only usable in dungeons so there should be a distinction between 3D and 3D dungeon imho. Maybe bit 1 is about that? Which spells have that bit set?

The unused target bit should be "Row of party members/allies". I found messages for such a target inside the data so I guess it was planned but no such spell was added. Blink is the only spell which targets a battle field so I guess the associated target bit means "location" or "empty battle field".

Have a look at elements I already decoded here. Monsters have an element as well. They make immune to some spells. Maybe if a monster has the same element as a spell it gets immunity? Unfortunately fire monsters are still affected by fire spells etc. But the "ghost" element actually grants immunity to the spell ghost weapon which I would expect to have the same element.

Maybe bits 0-3 are the same elements I suggested in the other repo. Bit 0 is yet not decoded so maybe we could decode it by looking at the spells which use them. Maybe it is just the neutral element or means "non-elemental".

Is there a spell which uses multiple elements?

Bit 3 is most likely the element "undead" or its anti-element "holy". Is this bit only set for anti-undead spells or healing spells?

Pyrdacor avatar Dec 29 '20 13:12 Pyrdacor

By the way do you know the hunk plugin for ghidra? I didn't used it yet but maybe it helps to find data offsets in relation to the data hunk beginning which would be awesome for loading data from AM2_CPU regardless of the game version.

https://github.com/lab313ru/ghidra_amiga_ldr

Pyrdacor avatar Dec 29 '20 13:12 Pyrdacor

[..] Maybe bit 1 is about that? Which spells have that bit set?

Every spell, that has bit 0 set (3D) with the exception of Call Eagle and Magical.

[..] "Row of party members/allies" [..] "location" or "empty battle field" [..]

Yes, that makes sense.

Maybe bits 0-3 are the same elements I suggested in the other repo [..]

At first, I thought so, too. But don't think it fits exactly... Boss is Immune too Fear(0x01), Paralyze(=Lame, I think 0x04), Petrify (0x08), DissolveVictim(0x02), Madness(0x01), Drugs(0x04), Irritation(0x01)... but not to Sleep(0x01) 0x02 is also used by magic missile/arrows, blind and all Alchemy spells except LP-Stealer (0x04) - ok could be Alchemy. 0x01 is also used by Remove Fear/Panic and all Mystic spells, except Mystic Globe (0x04) and Show monster LP (0x08) - Mystic equals Psychic would make sense.. Then 0x04 .. - all LP healing and many other Healing spells have this (interestingly also Remove Madness) 0x08 is used for Petrify, cause (but not stop - 0x04) Aging, as well as all Anti-Undead spells and spells that cure Disease, Poison, Forms of death and Petrification

Special spells use 0x02, except for Lockpick & Magical Map (0x01), Decrease Age & Stinking Mushroom (0x04)

Is there a spell which uses multiple elements?

Nope

[..] By the way do you know the hunk plugin for ghidra? [..]

Yes! And let me tell you: it makes a Huge difference in how useful ghidra is :)

kermitfrog avatar Dec 29 '20 14:12 kermitfrog

The boss flag is independent of the element.

Pyrdacor avatar Dec 29 '20 15:12 Pyrdacor

FYI: There is a spell type immunity flag on monsters. First I thought it is the same as the spell school so for example a monster can be immune to all healing or all destruction spells. But when testing it, it didn't fit exactly. For example the spell ghost weapon is spell school Alchemistic but the immunity bit for destruction spells did block it. Maybe the element bits is an additional spell category which is used for the explicit spell type immunity flag on monsters.

See byte 0x10 here: https://github.com/Pyrdacor/Ambermoon/blob/master/FileSpecs/Characters.md

Maybe monsters can regulate their immunities (beside elements) by this relation (byte 0x10 of character and 5th byte of spell).

Pyrdacor avatar Dec 29 '20 16:12 Pyrdacor

Basically you could modify a monster to have different values for byte 0x10 and check if then all spells are blocked with the same bit in 5th byte.

Pyrdacor avatar Dec 29 '20 16:12 Pyrdacor

[..] Maybe monsters can regulate their immunities (beside elements) by this relation (byte 0x10 of character and 5th byte of spell).

could be.. at some point, I should be able to find some code for this...

Basically you could modify a monster to have different values for byte 0x10 and check if then all spells are blocked with the same bit in 5th byte.

Yes, but at the moment, I'd rather concentrate an analyzing the code in a spread out way. I expect to have less time for this project soon and next to none from late January to maybe April or May. So my plan is to find out some of the harder parts before then. I believe spreading out (meaning: take a few hours per problem, then move to the next), seems more efficient at this point, as this allows me to label more functions and data locations in ghidra, and by that making things easier to understand on a second pass.

I also plan to write a guide about the whole approach I'm using here.

In that spreading spirit, I dedicated a few hours to unraveling the mysteries of time - in other words: the function, that handles stuff when time advances.

A lot of global variables are modified, which I haven't explored in detail... but what I found is this:

Ambermoon actually tracks the progress of months (12*30 days) and years. but first 2 things:

  • let's call the, so far unknown, Attribute/Ability value the "Backup value".
  • lets define "affected by time" as: not (dead (any variant), petrified, or 0x80 (ageless? another variant of dead?))

Now for things that happen:

Every year the following check is made:

if character is affected by time {
    increase age by 1
    if age + age_bonus >= age_max {
        kill character
    }
}

fun fact: you can avoid aging, by beiing dead or a statue at the right time :)

every day:

if character is affected by time and diseased {
    choose random Attribute*(not Age) and if it is != 1, decrease it by 1
    do 0 damage to character (probably to trigger the damage notifier graphic on the portrait)
}

... well ...at least, this is what I believe, should happen... actually, the dissassembly says, the pointer beginning at STR (offset 0x002A) is increased by 6 instead of 8, so it would actually decrease: 0: current STR 1: backup STR 2: bonus INT 3: max DEX 4: current SPD .. and so on

should be confirmed by testing..

every hour:

if character is affected by time and not exhausted and a certain, yet unclear, condition is met:
    set exhausted flag and
    for each Attribute and Ability:
        save current value to __backup value__
        right shift current by 1 (= divide by 2)
    do poison and exhaustion damage as already found

kermitfrog avatar Dec 29 '20 21:12 kermitfrog

Yeah Metibor found in the savegame data that year, month and day are also stored and increased. But I didn't know that it really increased the ages which makes sense of course.

I never knew what the disease status did so nice finding. Makes sense that it is bugged cause I never saw a reasonable effect.

The condition about exhaustion should be "if hours without sleep >= 36". The savegame stores the hours without sleep as well. After 24h you get warning messages, after 36 you get the exhaustion status.

Pyrdacor avatar Dec 30 '20 00:12 Pyrdacor

So the 4th value for each attribute/ability is used to backup the current value while exhaustion or a similar effect is active?

Pyrdacor avatar Dec 30 '20 01:12 Pyrdacor

@kermitfrog If you don't mind I have another thing that isn't 100% clear to me. It's about the 3D map structure. Each map object (barrels, monsters, jars, etc) have some coordinates. But to position them properly I guessed some things. For example the size of each block (tile) in 3D and the height of walls. Can you reverse engineer 3D map rendering? At least how objects are positioned and how the camera height (player dependent) is determined? At the moment my 3D rendering fits pretty well but isn't right in all cases. For example in grandfather's cellar the small spiders on the ceiling are not touching the ceiling. So my assumptions about coordinates is not right completely. The same is true for collision detection. There are also some unknown values in the map object data inside the labdata. Some assume it's about collision but I couldn't make sense out of them. Some flags of 3D objects are unknown or unclear too. Without reverse engineering I'm a little bit lost there.

If you have the time you can have a look at the data I mentioned here: https://github.com/Pyrdacor/Ambermoon/blob/master/FileSpecs/Labdata.md

Pyrdacor avatar Jan 02 '21 18:01 Pyrdacor

So the 4th value for each attribute/ability is used to backup the current value while exhaustion or a similar effect is active?

Yes, exactly. Although I suspect there are no other effects that use it.

[..] about the 3D map structure [..]

Well, I can try... but I may need a somewhat different approach than before. I'll have a look at the Labdata stuff later and probably come back with questions when I have something like the start of a plan.

kermitfrog avatar Jan 03 '21 11:01 kermitfrog

Thanks. Maybe you can find out where the labdata file is read and what is done with those values. Especially the wall height is of interest for me. It is the first word in each labdata.

Pyrdacor avatar Jan 03 '21 14:01 Pyrdacor

@kermitfrog The buff effects are very simple. I documented them in the alchemistic spell list here: https://github.com/Pyrdacor/Ambermoon/blob/master/FileSpecs/Enumerations/Spells.md

Basically the magic attack/defense spells just increase your attack damage or defense by 10%/20%/30% for the buff duration. The anti-magic spells seem to increase your deflect chance by 15%/25%. So the total deflect chance should be A-M plus 15/25.

The light spells use a light radius of 1-3.

The durations range from 150 to 900 ingame minutes. The best versions of each spell use 900 ingame minutes. Alchemistic globe activates the best version of each of the 4 non-mystic buffs (light, barrier, attack and anti-magic).

Pyrdacor avatar Jan 05 '21 09:01 Pyrdacor

Thanks. Maybe you can find out where the labdata file is read and what is done with those values.[..]

Starting from loading the file sounds like a good idea - I'll try that.

[..] The buff effects are very simple [..] [..] The light spells use a light radius of 1-3. [..]

Sounds good. If I am not mistaken, there are differences in light intensity in 3D as well - did you figure these out as well?

I finally uploaded my tool, along with a longer-than-originally-intended readme. You can find it here: https://github.com/kermitfrog/Amiga-Re-Engineering

kermitfrog avatar Jan 05 '21 12:01 kermitfrog

Wow very nice! Thanks for sharing your efforts and knowledge.

About the light intensity: No I haven't dealt with it yet. But I know the higher values will have other effects in 3D as well.

Pyrdacor avatar Jan 05 '21 13:01 Pyrdacor

@kermitfrog One question about spell targets. What target do spells like "Mystical Map" have? Cause they don't have a real target at all. Basically most mystic spells have no target.

Pyrdacor avatar Jan 08 '21 10:01 Pyrdacor

[..] What target do spells like "Mystical Map" have? Cause they don't have a real target at all [..]

In that case the flags are simply all 0

As for the 3D stuff.. I found a function at 0022f13c that seems to take care of loading data for 3D, loading from disk in it's second and seventh direct subroutine. However there is a lot going on there. In total (If I understand it all correctly) there are 13 calls to open, 27 calls to read and, surprisingly, just 1 call to seek. So far I have not even an idea which files are accessed. In total it looks like over a hundred subroutines are called here and it may take weeks to analyze :/ - so I'll leave that for now.

Instead I'll try to find the function that actually renders a frame - maybe this will be easier.

kermitfrog avatar Jan 08 '21 11:01 kermitfrog

Ok thank you. Yeah don't put too much time into it if it's too complex.

Pyrdacor avatar Jan 08 '21 12:01 Pyrdacor

I found the function that actually draws a spider-on-ceiling, or more specific: how you see it in game, changes from one animation frame to the next between the start and the first recursive call of the function - but it might just be drawing prerendered stuff. I'll paste the decompiled version here, just in case you can make sense of it.

Can you tell me the exact offsets and files I can find the associated object data, spider animation, etc? Maybe I can find their locations in memory and then find something more useful. Otherwise I'll leave it at that.

It would be great if someone with actual knowledge of Amiga graphics programming would have a look at it...

void 3D_object_animated_draw_start(void)

{
  int iVar1;
  int iVar2;
  int iVar3;
  uint unaff_D6;
  ushort unaff_D7w;
  int *in_A0;
  int *piVar4;
  int *piVar5;
  int *piVar6;
  
  piVar6 = (int *)((int)(short)((unaff_D7w - 1) * (short)unaff_D6) + (int)in_A0);
// 3D_object_animated_draw_recursive() is the same fuction, but starting from here
  if ((int)in_A0 < (int)piVar6) { 
    piVar5 = in_A0;
    if ((int)((int)piVar6 - (int)in_A0) <= (int)((unaff_D6 & 0xffff) * (uint)unaff_D7w)) {
      do {
        while (piVar4 = (int *)(unaff_D6 + (int)piVar5), *piVar5 <= *piVar4) {
          piVar5 = piVar4;
          if (piVar6 == piVar4) {
            return;
          }
        }
        iVar1 = *piVar4;
        iVar2 = piVar4[1];
        piVar5 = piVar4;
        do {
          piVar5 = (int *)((int)piVar5 - unaff_D6);
          if ((int)piVar5 < (int)in_A0) break;
          *(int *)((int)piVar5 + unaff_D6) = *piVar5;
          *(int *)((int)piVar5 + unaff_D6 + 4) = piVar5[1];
        } while (iVar1 < *piVar5);
        *(int *)((int)piVar5 + unaff_D6) = iVar1;
        *(int *)((int)piVar5 + unaff_D6 + 4) = iVar2;
        piVar5 = piVar4;
        if (piVar6 == piVar4) {
          return;
        }
      } while( true );
    }
    iVar1 = *in_A0;
    iVar2 = *piVar6;
    do {
      while (*in_A0 < (int)(iVar1 + iVar2 + 1U >> 1)) {
        in_A0 = (int *)(unaff_D6 + (int)in_A0);
      }
      while ((int)((uint)(iVar1 + iVar2) >> 1) < *piVar6) {
        piVar6 = (int *)((int)piVar6 - unaff_D6);
      }
      if ((int)piVar6 <= (int)in_A0) break;
      iVar3 = *in_A0;
      *in_A0 = *piVar6;
      *piVar6 = iVar3;
      iVar3 = in_A0[1];
      in_A0[1] = piVar6[1];
      piVar6[1] = iVar3;
      in_A0 = (int *)(unaff_D6 + (int)in_A0);
      piVar6 = (int *)((int)piVar6 - unaff_D6);
    } while ((int)in_A0 < (int)piVar6);
    3D_object_animated_draw_recursive(in_A0,piVar6);
    3D_object_animated_draw_recursive();
  }
  return;
}

kermitfrog avatar Jan 08 '21 16:01 kermitfrog

The monsters on 3D maps and all other stuff like walls, NPCs and map objects are stored inside the labdata files. Ambermoon files are all compressed and/or encrypted. But beside this each labdata file starts with a 7 byte header. The first two bytes are most likely the wall height. This is already an important value.

After the header there are 2 bytes which give the number of objects and then such amount of objects follow. Each object itself starts with a 2 byte header and then 8 subobjects follow with 8 bytes each (4 words). So each object has 66 bytes in total. Maybe this offset can be found easily?

So for example the first object in a labdata starts at offset 9. And then the next at 75, next at 141 and so on. All offsets in dec here.

After the objects there are object information blocks (14 bytes each). But again the section starts with 2 bytes which give the number of blocks.

So for example if the labdata has 3 objects, the first object info block starts at 7 + 2 + 3*66 + 2 = 209.

The offset calculation should be something like this: objectInfoOffset[i] = 7 + 2 + numObjects * 66 + 2; // or 11 + ...

After the object info blocks there are the walls. Again starting with 2 bytes for amount of blocks. Each has 8 bytes but the last byte gives an amount of wall overlays which then immediately follow the wall block. So you can't just read each wall with 8 bytes. Overlays then have 6 bytes each.

So if you find a code part where 66, 14, 8 and 6 are used as lengths, you will be close.

Pyrdacor avatar Jan 08 '21 18:01 Pyrdacor

I found several parts of code that use 66, but none of them seem to use the other values and I do not believe that analyzing these functions will get me far at this point.. but there is still hope.

There is a function that I long assumed to calculate a pointer to character or monster data, but now believe to return a pointer to any type of struct. It is used all over the place (called from 341 different locations according to Ghidra), including code I think has to do with 3D. Understanding it completely, should help with a lot of stuff, but I'm not quite there yet.

It would probably help if I understood which data is placed where in Ambermoon memory, so I could make more sense of it's input and output. My plan to get this information is:

  1. unpack and decrypt all data files to a directory structure ("unpacked_data/filename.amb/001" and so on)
  2. use FS-UAE to get a few memory dumps
  3. compute a map of occurrences of all data per memory dump
  4. make sense of it

For step one, I could use your help: can you upload a tool that simply unpacks any .amb file to a directory? (a reverse AmbermoonPack).

kermitfrog avatar Jan 12 '21 11:01 kermitfrog

Well I can help with point 1. You can find alle extracted german files here: https://github.com/Pyrdacor/Ambermoon/blob/master/ExtractedGameFiles.zip

I also could add a tool but as I already have all extracted files I guess it is easier this way. Moreover I am very busy with work and finalizing battles. ;) If this is not enough, I could of course add the feature to the packer.

Pyrdacor avatar Jan 12 '21 14:01 Pyrdacor

I've unpacked and decrypted all the v1.00, v1.01 & v1.07 data files if you want them uploaded somewhere?

I have the Amiga tools to decrypt, unpack, split and combine etc.

a1exh avatar Jan 12 '21 15:01 a1exh

A german guy called Lyra made a good Ambermoon walkthrough on youtube. It's german though but he showed a description of the effects of all attributes and abilities.

He said that INT increases the SLP and TP you gain per level. Will test that. Maybe someone else can confirm that?

According to him DEX only adds the chance to avoid triggering traps and LUK only adds the chance of avoiding the damage of triggered traps. STR and STA adds LP while STR of course also adds max weight. Anti-Magic is the spell block chance as we know.

I invited him to have a look at this project. Maybe he has some valuable input or can help playtesting.

Pyrdacor avatar Jan 12 '21 19:01 Pyrdacor

Thanks for the quick response :) Didn't have a lot of time to make use of it yet.

@Pyrdacor Yup, thats exactly how I need them. A few files are different from the ones I need to check against, but this should already help.

@a1exh if you could upload an archive with the unencrypted & extracted files listed below from 1.07 I would have everything to possibly check against - everything else is identical in the german version. Not sure I need the Amiga tools then. I tried to use the ones from Amberworld before, but could not get them to work on Linux :/.

And before I forget about it.. which data files (with index) contain the Spider on ceiling?

1Map_texts.amb 2Map_data.amb 2Map_texts.amb 3Map_data.amb 3Map_texts.amb Monster_char_data.amb NPC_char.amb NPC_texts.amb Object_texts.amb Party_texts.amb Place_data

kermitfrog avatar Jan 15 '21 10:01 kermitfrog