Pyrdacor / Ambermoon.net

Ambermoon rewrite in C#
GNU General Public License v3.0
416 stars 20 forks source link

Ambermoon Research #64

Open Pyrdacor opened 3 years ago

Pyrdacor commented 3 years ago

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.

kermitfrog commented 3 years ago

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 commented 3 years ago

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.

Pyrdacor commented 3 years ago

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 commented 3 years ago

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.

kermitfrog commented 3 years ago

[..] 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.

Pyrdacor commented 3 years ago

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 commented 3 years ago

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

kermitfrog commented 3 years ago

[..] 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 :)

Pyrdacor commented 3 years ago

The boss flag is independent of the element.

Pyrdacor commented 3 years ago

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 commented 3 years ago

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.

kermitfrog commented 3 years ago

[..] 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:

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
Pyrdacor commented 3 years ago

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 commented 3 years ago

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

Pyrdacor commented 3 years ago

@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

kermitfrog commented 3 years ago

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.

Pyrdacor commented 3 years ago

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 commented 3 years ago

@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).

kermitfrog commented 3 years ago

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

Pyrdacor commented 3 years ago

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 commented 3 years ago

@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.

kermitfrog commented 3 years ago

[..] 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.

Pyrdacor commented 3 years ago

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

kermitfrog commented 3 years ago

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;
}
Pyrdacor commented 3 years ago

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.

kermitfrog commented 3 years ago

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).

Pyrdacor commented 3 years ago

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.

a1exh commented 3 years ago

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.

Pyrdacor commented 3 years ago

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.

kermitfrog commented 3 years ago

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

a1exh commented 3 years ago

Amberworld tools have nasty bugs so never use them.

The tools Pyrdacor made work, but they clean up the original file contents and so make it hard to diff against the originals (or other hand made patches). The Amiga tools made by MetalliC (aka Vitaly Grebennik) in 1999 are the ones I use.

Here is what I have today.

http://thalion.atari.org/games/ambermoon/amiga/patches/v1_07/v1_07_extracted.zip

It is missing "Place_data" as I've never needed that so far. I didn't even know it was an AMB file.

FYI the Amiga tools output files with hex numbering. Pyrdacor added the option for me. But you may need to convert from hex2dec on the file numbers to find the ones you want.

Pyrdacor commented 3 years ago

@kermitfrog About the ceiling spider:

You can find it in map 259 which is located inside 2Map_data.amb. The reference is the 5th character on the map. The offset is therefore at 52 (hex: 0x34).

But the reference just stores some basic information. It itself points to an object inside 2Lab_data.amb which contains the real data. Map 259 uses labdata subfile 1 (the first labdata subfile).

So inside the first subfile in 2Lab_data.amb the 23rd object is used for the ceiling spider. It is located (relative to the subfile) at offset 1462 (0x5b6).

kermitfrog commented 3 years ago

@a1exh Ok, I deleted Amberworld tools too be safe. I'm not sure what Place_data is - but it's in the directory and differs between versions. Hex or Dec numbering is not an issue (yet) - so far I only use the filename as text anyway.

@Pyrdacor so if I understand correctly, from my file perspective this would be 2Lab_data.amb/001 @ 0x5b6, which for me points to data starting with 00 00 00 ff 00 ff 01 1f 00 23, then followed by zeroes to an object length of 65. Can this be correct? What type of Data is it? At first I thought Object infos (from Labdata.md), but then It would have a texture index of 255 and only 1 Animation which feels wrong to me.

As far as progress goes: By mapping the data to the memory dumps I can now better find functions, which access specific data and already found some that access Lab_data.amb.

I also labeled the function I mentioned earlier (at 0x00243b3e) "get_object_pointer(obj_index)" and am quite sure it returns pointers to arbitary data - so far I can confirm Monsters, Characters, and objects from Lab_data, Object3D and Wall3D. As far as I can tell it does nothing more than return a pointer to a data structure identified by an 8 bit index.. which is a bit strange, as that would mean that Ambermoon only holds a maximum of 255 objects in memory - that does not seem enough. 2 Values from global variables are also read, one of which has to be some base Pointer, maybe the other has some context information.

I'll also attach one generated dataMap table (Grandpa's cellar) in case you're interested. Entries beyond the 4th column are notes indicating that get_object_pointer() returned that address during a time where the Spider was redrawn.

dataMap.zip

Pyrdacor commented 3 years ago

Place_data contains information about prices and other relevant stuff for inns, trainers, healers and other such places.

Regarding the data. Yes you understood the offset right but it points to the object and not the object info.

So you find a header word and then 8 subobjects with 4 words each. Relative x, y, z and the index of the object info (1-based). If object info index is 0 this subobject is not used.

Pyrdacor commented 3 years ago

In the case of the ceiling spider this means header=0, first sub object = { x=255, y=255, z=286, object info index = 35 }. The 35th object info in the same labdata file (so if index is zero based it would be 34).

I assumed in Ambermoon.net that a tile has a size of 510 or 512 so that 255 is the center of a 3D tile. The given position is also the center position of the sprite. At least I think so. The wall height is given in the first word of the labdata file. So the 286 should be in relation to that wall height somehow. The z coordinate point upwards from floor to ceiling. A z value of 0 means the object is on the floor.

Decoding the 3D stuff was pretty hard. Especially without reverse engineering. Most of it was guessing, data comparing and looking at ualbion. It looks pretty good regarding this. But if you could shed some more light onto it it would be great.

kermitfrog commented 3 years ago

Ah, ok.. now I understand the LabData. But think you have a mistake in the Objects discription:

The object section contains 130 bytes per object.

Shouldn't this be 66 bytes?

The lab_data related functions I have found so far often read and write global variables, which, unfortunately, makes it difficult for me to make sense of it. I'm going to abandon that path for now and rather try to find the direct drawing function by finding the data to draw in memory and looking for direct access to it. Is the little spider just a flipped and scaled version of the normal Spider combat graphics? If not, where can I find it?

I also came accross a function that sets a global variable based on the unknown value at Labdata header 0x2. I can't make any sense of it, but maybe you can...


{
  short sVar1;
  undefined4 in_D0;
  short *psVar2;
  ushort uVar3;
  lab_data *lab_dat;
  ushort uVar4;

  psVar2 = (short *)get_object_pointer(DAT_0027a831);
  sVar1 = *psVar2;
  possibly_input_check_or_play_music();
  uVar3 = sVar1 - 0xf;
  lab_dat = (lab_data *)get_object_pointer(DAT_0027a80e);
  uVar4 = (ushort)lab_dat->field_0x2;
  possibly_input_check_or_play_music();
  uVar4 = *(ushort *)((int)&DAT_0026d362 + (int)(short)(uVar4 * 2));
  if (-1 < (short)(uVar4 - uVar3)) {
    uVar4 = uVar3;
  }
  value_based_on_something_from_lab_data =
       (short)(((uint)uVar4 * 0x155) / (uint)uVar3) + -0xaa + DAT_0027b64c;
  return in_D0;
}
Pyrdacor commented 3 years ago

No the ceiling spider has its own graphic. I am not on a PC now but you find the graphic here: https://github.com/Pyrdacor/Ambermoon/tree/master/Graphics/Objects3D

I will have a look on the code later. Thanks for sharing.

kermitfrog commented 3 years ago

I think we're getting somewhere :) I'll stop for today, but here is a function with it's subfunction, that accesses the spider gfx - I'll analyze it a bit more toworrow.

where_spider_graphics_are_accessed.zip

Pyrdacor commented 3 years ago

In your latest code it seems that unaff_D3w is the 1-based object info index.

uVar4 and uVar5 then contain the 0-based object info index. It is then multiplied by 0xe (14). 14 is the size of one object info.

So puVar8 should point to the specific object info data.

But it's a bit strange that the graphic is retrieved by the same index which should be the object info index and not the graphic index.

Maybe some local vars are used inside that getpointer function? How are arguments passed to functions?

puVar8 + 6 casted to a byte is the number of animation frames (checked it in labdata structure). It is checked for != 1 so that makes sense.

puVar8 + 9 is the texture height so your assumption might be right that the code possibly determines the frame offset. puVar8+2 (without int cast) is really puVar8+8 (2 uints are 8 bytes). So this is the texture width.

puVar8+10 is the mapped texture width. puVar8+3 (again without int cast is puVar8+12) is the mapped texture height.

Pyrdacor commented 3 years ago

Now I got it. puVar8+2 (which is +8 in bytes) is the texture width in pixels. As sprites on the Amiga have always widths that are multiples of 16, they use & 0xfff0 there. This ensures that the loaded value is a multiple of 16. Then they use >> 1 (which means divide by 2) cause each pixel uses 4 bits (half a byte).

So yes this is the calculation of the frame offset inside the sprite data.

Pyrdacor commented 3 years ago

The path taken by spider means "no floor object".

if ((uVar7 & 8) == 0)

uVar7 stores the first 4 bytes of the object info. So the lowest bits are the object flags. Bit 3 (=8) means "floor object" if set.

Pyrdacor commented 3 years ago

unaff_D6w should become the width for drawing and unaff_D7w the height for drawing.

Mapped texture sizes are multiplied by 256 and then divided by (unaff_D2w + 256). So unaff_D2w seems to be some scaling factor.

So in the spider example unaff_D2w seems to be 449 so the scale factor is 256/705 which is nearly 0.363. The value 449 could be 256 1.75 + 1. And so 705 = 256 2.75 + 1. So if unaff_D2w is 0 the scaling is 1 otherwise smaller. In the spider case 1/2.75 or 4/11 which is the mentioned 0.363.

If it is a floor object the height is calculated differently of course.

If I'm right the x position is stored in the lower word of in_D0 and the y position in in_D1w. They are also scaled like the sizes above.

DAT_0027b6e0 should contain the screen height or something similar as the drawing y coordinate is subtracted from it. I guess y goes from down to up inside the world but on the screen from top to bottom.

Pyrdacor commented 3 years ago

I decoded some of the drawing stuff. Will only show the end of it here:

if (!floorObject)
{
    drawWidth = MappedTextureWidth * 256 / 705;
    drawHeight = MappedTextureHeight * 256 / 705;
    drawY -= drawHeight;
}
else
{
    drawWidth = MappedTextureWidth * 256 / 705;
    drawHeight = MappedTextureHeight / 2;

    tempHeight = unaff_D2w + drawHeight + 256;

    if (tempHeight == 0) // unaff_D2w == MappedTextureHeight / 2 - 256
    {
        // jump somewhere
        // in the end this leads to returning just -1 for draw x and not fill rest of data
        // so this is most likely a "don't draw"
    }
    tempHeight = screenHeight - worldY * 256 / tempHeight - drawY
    if (tempHeight < 0) tempHeight = -tempHeight;
    drawHeight = tempHeight * 2;
    if (drawHeight < 2)
        drawHeight = 2;
    drawY -= drawHeight / 2;
}

If successful the function returns 8 values:

More precisely it fills them into 9 shorts of unaff_A4 in the given order. But I guess this is the result value. Drawing positions and sizes should be in screen coordinates I guess. At least they are scaled using the 449 or 705 value.

I will have a look at the last function as well.

I learned that dimensions are all based on 256 somehow. When I know where the 449 is coming from etc this will help a lot.

DAT_0027c360 seems to be the number of object infos.

Pyrdacor commented 3 years ago

The last function seems to adjust the x drawing offset in some situations. One part of the code seems to determine the frame index to use.

So this uses mostly known values. Can you post nearby or nearby executed functions as well? I want to decode other values of the labdata structures.

Pyrdacor commented 3 years ago

Did you find out how get_object_pointer works?

In your first code snippet with the 0x2 labdata header you read two times with it. You said that reading with DAT_0027a80e gives you the labdata. But a bit above you read at DAT_0027a831. If assuming right this offset is 35 behind the labdata which makes no sense. How does the function work and does it use more arguments?

I wonder how arguments are passed in the dissambly code. Looks like good ol C but function arguments look weird. Maybe you can help me out to understand this better.

kermitfrog commented 3 years ago

Great! I had a feeling you would understand the gfx stuff better that me.

I got more C code related to drawing for you: draw_funcs.zip along with the warning, I wanted to give you along with it, even before reading your latest post, which is: do not trust Ghidra when it comes to parameters!.

psVar2 = (short *)get_object_pointer(DAT_0027a831); should, as far as I understand be: psVar2 = (unknown_object_type *)get_object_pointer(in_D0); or possibly psVar2 = (unknown_object_type *)get_object_pointer(in_D0, DAT_0027a831);

I am not yet 100% sure if DAT_0027a831 is used there at all (probably not) - but if it is, it's most likely containing a related pointer, so DAT_0027a80e is not the start of Lab_data, but rather the start of a struct (or pointer to a struct), containing a pointer to it.

Figuring out the actual call parameters has, so far, been one of the most difficult problems I've encountered when working with Ghidra and requires looking at the disassembly. Recently, I discovered an optional ananysis Feature called "Decompiler Parameter ID" that might help. I'll try it soon, but judging from the warning in it's help text, it might take hours (which would be ok) or days (which would not..) to complete.

I wonder how arguments are passed in the dissambly code. Looks like good ol C but function arguments look weird. Maybe you can help me out to understand this better.

generally, it goes like this:

What makes it more difficult, is that sometimes functions communicate by setting global values, such as DAT_00something.

In any case, this might be a good time for you to start using Ghidra, as it can show you the disassembly alongside the C code.

Ideally we would have a Ghidra-server, but I can just send you my project files. I am however using version 9.3_DEV (needed to get rid of the blinking cursor in the decompiler and used whatever git pulled without additional parameters to implement a workaround), so it might not be compatible with 9.2.

Also: I relabeled "possibly_input_check_or_play_music()" to "decrease_object_offset_0x13()". All it seems to do is get a pointer to some object and decrease the byte value at its 0x13 offset by 1 (if it's not 0). Given that the function is called from pretty much anywhere (drawing , skill checks, etc. - it's actually the most referenced function in the program) I don't think it's related to drawing specifically.

kermitfrog commented 3 years ago

ok... forget about "Decompiler Parameter ID" - that helps with the detection of variable types, but not determining which are actual input for the function.

Pyrdacor commented 3 years ago

Thanks for the info. I think your new code is drawing the 3D map. Have no PC in range since Friday and decoding all on a smartphone without tools is quiet time consuming. Will have a look at all of it on PC on Monday. ;)

Pyrdacor commented 3 years ago

Funny finding. There is a test for equality with -0x532. Interpreting this as unsigned short it is the hex value FACE. I guess this is a funny magic number to mark something as a face. :D Decoding this is a bit like being sherlock holmes. 8)

I think the face value is inside the unknown word at the beginning of each object. Maybe it means that the object is drawn on a wall? Have to check the data if there is something like 0xface in it.

The same value is also checked for -0x5413. Interpreted as unsigned short this is ABED like "a bed".

Pyrdacor commented 3 years ago

@kermitfrog If you have enough of the 3D stuff the 2D tile data isn't researched good enough too.

The icon_data files start with a word which gives the number of entries. Then this amount of entries follows. Each 8 bytes in size.

First 4 bytes (32 bits) are some kind of tile flags. They control movement blocking, sit and sleep behavior and also control some rendering. But it isn't really decoded. I guessed all of it and most of it works quiet well but many bits are unknown and those which are known could have a different meaning. I had to use some tricks to make them really work. Moreover I have no clue what the 8th byte is used for.

You can find the related info here: https://github.com/Pyrdacor/Ambermoon/blob/master/FileSpecs/Maps2D.md section Icon data.

Looked at your ghidra guide today but my time was too limited and I guess it would be much easier on Linux so I will postpone that for a while.