Reverse engineering Diablo v1.09b (last patch)
#21
Damn you guys ar going down a very very old road.

This work has already been done for the most part.
Reply
#22
Hej Vortex!

I always feel happy to hear someone else interested in reversing Diablo 1, just for the joy of learning how the game engine works!

Quote:Does anyone here have a compiled list of any sort of data regarding Diablo? It does not have to be 'of interest' (which is relative from person-to-person).

We've been trying to compile such a list for some time :)

These notes are collaborated on at https://github.com/sanctuary/notes and are also made available through http://sanctuary.github.io/notes/

Brief status update as of 2018-02-15:

* 35% of the function signatures have been documented.
* 95% of the read-only global variables have been documented.
* 98% of the read-write global variables have been documented.
* 91% of the uninitialized global variables have been documented.

Type definitions of structures and enumerates have also been documented.

Quote:If you are very familiar with reverse engineering Diablo: I'd love to pick your brain on a few topics of how Diablo was written.

Any particular topics you have in mind? Would be happy to try to figure it out together :)

Cheerful regards,
The members of sanctuary
Reply
#23
(01-30-2018, 12:23 AM)TheKillerVortex Wrote: Here's an explicit question:

Are 0x1D and 0x19 hard-coded values describing the "entry" of the maze-generation? To guarantee that the stairs themselves are in a room with possible exiting cells?

They are indeed hard-coded values.

The actual entrance is added in trigs_init_warps_town:

Code:
warps[0].entrance_x = 25; // ref: 0x4619C1
warps[0].entrance_y = 29;

and for the description text on mouse hover, the mouse X-Y map coordinates are set in trigs_set_stairway_text_town.

Code:
strcpy(description_line, "Down to dungeon");
cursor_x = 25; // ref: 0x462049
cursor_y = 29;

Also, take a look at the global variable warps and the Warp struct.
Reply
#24
(01-30-2018, 02:10 AM)TheKillerVortex Wrote:
Code:
00686D8C - 03 00 00 00

This represents the graphic image to load for your character (00 = no weapon, 01 = sword, 02 = axe, 03 = bow, 04 = mace; I guess?).

Yes. That address points to the item type of an equipped item on the player.

The Player struct is not currently documented, as it's huge. However, some fields are known. For instance, the equipped items.

Code:
typedef struct {
   ...
   // offset: 037C (2576 bytes)
   Item body_items[7];
   ...
} Player;

The player global variable has been documented (but is currently commented out, until the Player struct is defined), and it is located at address 0x686448. Since it is commented out, it does not appear at http://sanctuary.github.io/notes/#variable/players yet.

Code:
// address: 0x686448
//
// players contains the player characters of the current game.
Player players[4];

And the Item struct has been documented, thus its size (0x170) is known and the offset to its member.

From this information, we can calculate what the address 0x686D8C corresponds to.

Code:
0x686D8C - 0x686448 = 0x944 // offset into the player struct.
0x944 - 0x37C = 0x5C8       // offset into the body_items member of players[0].
0x5C8 % 0x170 = 0x8         // offset into the Item struct; players[0].items[4].type

Looking at http://sanctuary.github.io/notes/#struct/Item we can locate the member at offset 0x8, which corresponds to the item type.

Code:
typedef struct {
   ...
   // offset 0008 (4 bytes)
   item_type  type;
   ...
} Item;

Lastly, the item_type enum is defined at http://sanctuary.github.io/notes/#enum/item_type

Code:
// Item types.
typedef enum {
    ITEM_TYPE_MISC         =  0, // Potions, scrolls, books and quest items.
    ITEM_TYPE_SWORD        =  1,
    ITEM_TYPE_AXE          =  2,
    ITEM_TYPE_BOW          =  3,
    ITEM_TYPE_MACE         =  4,
    ITEM_TYPE_SHIELD       =  5,
    ITEM_TYPE_LIGHT_ARMOR  =  6,
    ITEM_TYPE_HELM         =  7,
    ITEM_TYPE_MEDIUM_ARMOR =  8,
    ITEM_TYPE_HEAVY_ARMOR  =  9,
    ITEM_TYPE_STAFF        = 10,
    ITEM_TYPE_GOLD         = 11,
    ITEM_TYPE_RING         = 12,
    ITEM_TYPE_AMULET       = 13,
    ITEM_TYPE_14           = 14, // NOTE: Unused?
    ITEM_TYPE_NONE         = -1,
} item_type;

I hope this may provide useful for your future reversing adventures :)

As you will notice, there is still a lot left to be documented in the game. Care to join us at sanctuary? :)

Cheers!
Reply
#25
[Image: Diablo_Game_20180216_121609.jpg]

I was messing around with the pre-release demo and found some leftover debug/cheat code. Interesting stuff, since some of it appeared in Mock-up shots from 1995-1996. I sent this info to Mystery over at Diablo-Evolution so hopefully it can be more documented. Below is the info to recreate it yourself. I wonder how much of this could still remain in the final game, but not activated?

Hex edits to Diablo.exe, each value is a 32-bit BOOL (1 for TRUE 0 for FALSE)
Code:
0xBA958 Debug mode on/off
0xBA95C 1.0 or Pre-release
0xBA960 Show intro videos
0xBA964 Music enabled
0xBA968 Cheats on/off
0xBA96C Display framerate
0xBA970 Display yellow tile grid
0xBA974 Unknown
0xBA978 Sets lowest possible light radius
0xBA97C Unknown
0xBA980 Unknown
0xBA984 Start game paused
0xBA988 Enable all songs or just title screen
0xBA98C Unknown
Debug controls (controls prefixed with cheat only work with cheats enabled). Debug information is display in the message box between the orbs:
Code:
[CHEAT] 'P': You can hold 'P' while paused to keep moving and be invincible
[CHEAT] '*': Levels up the character one time
[CHEAT] 'M': Displays whether or not you are invincible
[CHEAT] 'T': Displays character X/Y coordinates
[CHEAT] 'L': Toggles full radius on/off
[DEBUG] 'D': Displays transparency value
[DEBUG] 'E': Displays EFlag
[DEBUG] 'R': Displays map seed
[DEBUG] 'V': Displays game version (Either V1.0 or PRE-RELEASE)

Also, here's what I have so far for the PlayerStruct (info taken from PS1 debug version). It's not fully finished yet, 0x54D8 bytes in size.
Code:
struct PlayerStruct
{
  int _pmode;
  char walkpath[24];
  int plractive;
  int destAction;
  int destParam1;
  int destParam2;
  int destParam3;
  int destParam4;
  int plrlevel;
  int WorldX;
  int WorldY;
  int _px;
  int _py;
  int field_48;
  int field_4C;
  int field_50;
  int field_54;
  int field_58;
  int field_5C;
  int field_60;
  int field_64;
  int field_68;
  int field_6C;
  int _pdir;
  int field_74;
  int field_78;
  int field_7C;
  int field_80;
  int field_84;
  int field_88;
  int field_8C;
  int field_90;
  int field_94;
  int field_98;
  int field_9C;
  int field_A0;
  int field_A4;
  int field_A8;
  int field_AC;
  int field_B0;
  int field_B4;
  int field_B8;
  int field_BC;
  __int16 field_C0;
  char _pSplLvl[36];
  char field_E6[34];
  int field_108;
  int field_10C;
  int _pMemSpells;
  int _pAblSpells;
  int field_118;
  int field_11C;
  int field_120;
  int _pSplHotKey[4];
  char _pSplTHotKey[4];
  int _pwtype;
  char _pBlockFlag;
  char _pInvincible;
  char _pLightRad;
  char _pLvlChanging;
  char _pName[32];
  int _pClass;
  int _pStrength;
  int _pBaseStr;
  int _pMagic;
  int _pBaseMag;
  int _pDexterity;
  int _pBaseDex;
  int _pVitality;
  int _pBaseVit;
  int _pStatPts;
  int _pDamageMod;
  int _pBaseToBlk;
  int _pHPBase;
  int _pMaxHPBase;
  int _pHitPoints;
  int _pMaxHP;
  int _pHPPer;
  int _pManaBase;
  int _pMaxManaBase;
  int _pMana;
  int _pMaxMana;
  int _pManaPer;
  char _pLevel;
  char _pMaxLvl;
  __int16 field_1BA;
  int _pExperience;
  int _pMaxExp;
  int _pNextExper;
  char field_1C8;
  char _pMagResist;
  char _pFireResist;
  char _pLghtResist;
  int _pGold;
  int field_1D0;
  int _pVar1;
  int _pVar2;
  int _pVar3;
  int _pVar4;
  int _pVar5;
  int _pVar6;
  int _pVar7;
  int _pVar8;
  char _pLvlVisited[17];
  char _pSLvlVisited[10];
  char align_20F;
  int field_210;
  int field_214;
  int field_218;
  int _peqN[8];
  int _pNFrames;
  int _pNFNum;
  int _peqW[8];
  int _pWFrames;
  int _pWFNum;
  int _peqA[8];
  int _pAFrames;
  int _pAFNum;
  int frame_9_unk;
  int _peqS2[8];
  int _peqS1[8];
  int _peqS3[8];
  int _pSFrames;
  int _pSFNum;
  int frame_A_unk;
  int _peqH[8];
  int _pHFrames;
  int _pHFNum;
  int _peqD[8];
  int _pDFrames;
  int _pDFNum;
  int _peqB[8];
  int _pBFrames;
  int _pBFNum;
  ItemStruct InvBody[7];
  ItemStruct InvList[40];
  int _pNumInv;
  char InvGrid[40];
  ItemStruct SpdList[8];
  ItemStruct HoldItem;
  int _pIMinDam;
  int _pIMaxDam;
  int _pIAC;
  int _pIBonusDam;
  int _pIBonusToHit;
  int _pIBonusAC;
  int _pIBonusDamMod;
  int field_5444;
  int field_5448;
  int field_544C;
  int field_5450;
  int field_5454;
  int _pISplLvlAdd;
  int field_545C;
  int field_5460;
  int _pIFMinDam;
  int _pIFMaxDam;
  int _pILMinDam;
  int _pILMaxDam;
  int _pOilType;
  char pTownWarps;
  char pDungMsgs;
  char pLvlLoad;
  char field_547B;
  int field_547C;
  int field_5480;
  char field_5348[84];
};
Reply
#26
Life/mana are stored with 6 hidden bits of precision as a 32-bit little endian number. Monster HP is also stored this way. To convert real data into displayed data, divide by 0n64 (or 0x40). Conversely, to convert displayed data into in-memory data, multiply by 0n64.

Items morph because they are stored on disk (and sent over the network) as the minimum data required to rerun the code that generated the item when it was first dropped. Items are regenerated from that data on receipt over the network or from disk. If you change a derived field, it reverts when the item is regenerated. If you change the data tables (such as modifying qlevels), then subsequent regeneration derives different results, hence the items morph.

If you have other specific questions, post them separate from your speculation and results. I may be able to address them.
Reply


Forum Jump:


Users browsing this thread: 2 Guest(s)