[v1.11b] Loading New TXT Files

This forum is for discussions on how to edit what can not be edited through the txt files, needless to say this isn't about battle net hacking.

Moderators: Nefarius, Havvoric

Post Reply

1
100%
 
Total votes: 1

User avatar
Nefarius
Retired Admin
Cherub
Posts: 11607
Joined: Sat Jun 15, 2002 8:13 pm
Location: Where the blood forever rains
Contact:

Hand-picked

[v1.11b] Loading New TXT Files

Post by Nefarius » Thu Nov 08, 2007 4:50 pm

I've decided to make this topic sticky for quite obvious reasons (I think they should be obvious anyway).

Adding new TXT files to this game is a must for more extensive changes because we'd otherwise be committing the severe crime of needless hardcoding. When something should be softcoded, hardcoded or softcoded and then cached should follow common sense. Things that are done over and over every frame are best not put into a database format that needs to be looked up constantly, it's better to cache the relevant data on game start instead of quering anew every time.

First of all, TXT file engine has linker fields, linker fields are used to resolve Id pointers ('names') to the matching Id number ('index').

Code: Select all

LINKER_BodyLocs          =          D2CommonBase+0xA079C;
LINKER_Colors            =          D2CommonBase+0xA1144;
LINKER_CompCode          =          D2CommonBase+0xA0810;
LINKER_ElemTypes         =          D2CommonBase+0xA07AC;
LINKER_Events            =          D2CommonBase+0xA0808;
LINKER_HireDesc          =          D2CommonBase+0xA0848;
LINKER_HitClass          =          D2CommonBase+0xA07B4;
LINKER_ItemFormula       =          D2CommonBase+0x43160;
LINKER_Items             =          D2CommonBase+0xA0824;
LINKER_ItemStatCost      =          D2CommonBase+0xA1360;
LINKER_ItemTypes         =          D2CommonBase+0xA1384;
LINKER_MissCalc          =          D2CommonBase+0xA07EC;
LINKER_MissFormula       =          D2CommonBase+0x17F20;
LINKER_Missiles          =          D2CommonBase+0xA12F8;
LINKER_MonAi             =          D2CommonBase+0xA081C;
LINKER_MonCompData       =          D2CommonBase+0x2DC90;
LINKER_MonMode           =          D2CommonBase+0xA07BC;
LINKER_MonPlace          =          D2CommonBase+0xA1230;
LINKER_MonProp           =          D2CommonBase+0xA13BC;
LINKER_MonSounds         =          D2CommonBase+0xA1218;
LINKER_MonStats          =          D2CommonBase+0xA120C;
LINKER_MonStats2         =          D2CommonBase+0xA1224;
LINKER_MonTypes          =          D2CommonBase+0xA13C8;
LINKER_MonUMod           =          D2CommonBase+0xA13DC;
LINKER_Overlay           =          D2CommonBase+0xA1348;
LINKER_PetType           =          D2CommonBase+0xA137C;
LINKER_PlayerClass       =          D2CommonBase+0xA0794;
LINKER_PlrMode           =          D2CommonBase+0xA07C4;
LINKER_Properties        =          D2CommonBase+0xA0838;
LINKER_PropFormula       =          D2CommonBase+0x42E70;
LINKER_Sets              =          D2CommonBase+0xA1398;
LINKER_SkillCalc         =          D2CommonBase+0xA07CC;
LINKER_SkillCodes        =          D2CommonBase+0xA0800;
LINKER_SkillDesc         =          D2CommonBase+0xA1320;
LINKER_SkillFormula      =          D2CommonBase+0x100F0;
LINKER_Skills            =          D2CommonBase+0xA132C;
LINKER_Sounds            =          D2CommonBase+0xA0928;
LINKER_States            =          D2CommonBase+0xA0850;
LINKER_StorePage         =          D2CommonBase+0xA07A4;
LINKER_Strings           =          D2CommonBase+0x0E2D0;
LINKER_SuperUniques      =          D2CommonBase+0xA1268;
LINKER_TreasureClassEx   =          D2CommonBase+0xA1148;
Lets take the function LINKER_MonStats for example, when this field is used the name 'skeleton1' will resolve to the index '0' and so on and so forth. Some of the above also have special purposes, for example LINKER_String will resolve a string key to the string index (this is the preferred way to do this, only legacy txt files like objects.txt still store the string key), LINKER_xxxFormula functions are used to encoded a calc field into a formula for the formula interpretor. There re four types of formula for Skills.txt, Missiles.txt, Weapons/Armor/Misc.txt and finally all files that store property data (MagicPrefix et al.)

Of course, linker fields alone are not enough, what about fields that don't hold other record Ids? The game has several types of fields in its database files, identified by a ENUM (as far as I can say this was a enum, not just entering numbers from memory).

Code: Select all

FIELDTYPE_DATA_ASCII      =          1,
FIELDTYPE_DATA_DWORD      =          2,
FIELDTYPE_DATA_WORD       =          3,
FIELDTYPE_DATA_BYTE       =          4,
FIELDTYPE_UNKNOWN_5       =          5,
FIELDTYPE_DATA_BYTE_2     =          6,
FIELDTYPE_DATA_DWORD_2    =          8,
FIELDTYPE_DATA_RAW        =          9,
FIELDTYPE_ASCII_TO_CODE   =          10,
FIELDTYPE_UNKNOWN_11      =          11,
FIELDTYPE_UNKNOWN_12      =          12,
FIELDTYPE_CODE_TO_BYTE    =          13,
FIELDTYPE_UNKNOWN_14      =          14,
FIELDTYPE_CODE_TO_WORD    =          15,
FIELDTYPE_UNKNOWN_16      =          16,
FIELDTYPE_NAME_TO_INDEX   =          17,
FIELDTYPE_NAME_TO_INDEX_2 =          18,
FIELDTYPE_NAME_TO_DWORD   =          19,
FIELDTYPE_NAME_TO_WORD    =          20,
FIELDTYPE_NAME_TO_WORD_2  =          21,
FIELDTYPE_KEY_TO_WORD     =          22,
FIELDTYPE_MONSTER_COMPS   =          23,
FIELDTYPE_UNKNOWN_24      =          24,
FIELDTYPE_CALC_TO_DWORD   =          25,
FIELDTYPE_DATA_BIT        =          26,



Of course, not all of these field types are compatible with all linker fields, below is a list of linker functions vs. field types.

These fields are storing actual raw data in specific formats (raw ASCII text, conversions to numbers [aka atoi] and so on), this is the actual data stored in the files, and what you'll be using most of the time.

FIELDTYPE_DATA_ASCII (1)

This will read n-characters (n being defined in BinField -> FieldSize, see below) into the binary image of the text file. Say you want to store the path anf filename of a DC6 file, you would have a char array holding the path (like szPath[32]) then you'd use FIELDTYPE_ASCII_STRING as your field type, and set BinField -> FieldSize to 31 (why 31?, so that the last character remains 0 of course).


FIELDTYPE_DATA_BYTE (4)

This tells the game that this field stores only 1 byte of data (this applies both to actual bytes, unsigned bytes and bools, it just depends on how your code uses it later!). This will convert numbers to their values (aka the ASCII string '10' becomes the value 10). FIELDTYPE_DATA_BYTE_2 seams to be the signed variant. But this makes no real difference, this makes more of a difference in the original code because of compiler warnings I guess.


FIELDTYPE_DATA_WORD (3)

This is like the above, but it tells the game this field stores bytes of data (aka 1 word). This will convert numbers to their values (aka the ASCII string '10' becomes the value 10).


FIELDTYPE_DATA_DWORD (2)

Again like the above, but this field holds 4 bytes instead of 2 (aka 1 DWORD). This will convert numbers to their values (aka the ASCII string '10' becomes the value 10).


FIELDTYPE_DATA_BIT (26)

This field is used to store a single bit, the minimum size of a bitmask in this game is one DWORD (aka 4 bytes, thus storing 32 individual flags). If you were to use 33 bit flags in a single file, the field grows to 8 bytes in length (and so on and so forth). This is a more efficient way to store booleans then allocating a single byte to each. In this case BinField -> FieldSize indicates the bit in the mask this column symbolizes. That is, say column 'warp' would be the 4th bit in a bitmask, then you'd set BinField -> FieldSize to 3 (and not 4). As you probably expect, any value other then 0 and 'blank' will be treated as true (aka the bit will be set).


FIELDTYPE_DATA_RAW (9)

This field type is used to store 4-letter codes without building a linkage table from the field (like fieldtype 10, FIELDTYPE_ASCII_TO_CODE would). Use this when having item codes in a file. UPDATE: This field type converts blanks to 0x20 also (aka space instead of 0x00), so a blank field in the *.txt becomes 0x20202020.



These fields build linkage tables, linkage tables are used by other linker functions to link a 'name' to a 'index', you should leave your fingers of these until you don't need this topic for reference anymore.

FIELDTYPE_ASCII_TO_CODE (10)

Fields: LINKER_CompCode, LINKER_PlayerClass, LINKER_BodyLoc, LINKER_StorePage, LINKER_ElemTypes, LINKER_HitClass, LINKER_Colors, LINKER_HireDesc, LINKER_MonMode, LINKER_PlrMode, LINKER_SkillCalc, LINKER_MissCalc, LINKER_ItemTypes, LINKER_Items

This field type reads 4 ASCII characters and outputs a DWORD, and does not attempt to do any conversions, that is ABCD becomes 0x44434241, these are the famous four-letter-codes you can find everywhere through the game.


FIELDTYPE_NAME_TO_INDEX (17)

Fields: LINKER_MonAi, LINKER_MonPlace, LINKER_SkillCodes, LINKER_Events, LINKER_MonTypes, LINKER_Overlay, LINKER_ItemStatCost, LINKER_Properties, LINKER_Missiles, LINKER_States, LINKER_MonStats, LINKER_SkillDesc, LINKER_Skills, LINKER_MonProp, LINKER_MonSounds, LINKER_MonStats2, LINKER_MonUMod, LINKER_Properties, LINKER_Sets, LINKER_SuperUniques, LINKER_TreasureClassEx

This field type is similar to the first, however it reads a complete ASCII character string and builds a conversion table composed of record numbers. Each index in this conversion table is of WORD size (thus anything using this field for linkage will at most support 65536 individual entries, but to be on the safe side, always consider 32767 as the maximum). In all the above cases, a value of -1 (or 0xFFFF) is considered an INVALID_CLASS.

Note: LINKER_SkillCodes builds a internal-only reference table of skill codes (actually the bin image is just overwritten later by the real skills.bin) so that other files can link to skills.bin before skills.bin itself is ever compiled.


FIELDTYPE_NAME_TO_INDEX_2 (18)

Fields: LINKER_PetTypes

This field type is only used by PetTypes.txt and nothing else, it functions like the above in general, exact difference not known, probably related to default values. Don't use it unless you know why, and if you do know why, go ahead and post 'why' here so that I can correct this entry properly.


FIELDTYPE_MONSTER_COMPS (23)

Fields: LINKER_MonCompData

This is a extremely special field, used solely for MonStats2.bin component data, it would go way too far to explain this here and this cannot be used anywhere else (hardcoded offsets in the linker function and all). For the record it puts at +0x15 of the bin image an array of 16 bytes, each holding the number of random components assigned to the unit and at +0x26 of the bin image 16 arrays of 12 arrays holding the individual components. (aka arr[16][12]).




These functions are of key importance, they allow us to link text records together using a human understandable symbolic code or name, instead of only Id numbers (like the game used to have before v1.10 in most files).

FIELDTYPE_CODE_TO_BYTE (13)

Fields: LINKER_CompCode, LINKER_PlayerClass, LINKER_BodyLoc, LINKER_StorePage, LINKER_ElemTypes, LINKER_HitClass, LINKER_Colors, LINKER_HireDesc, LINKER_MonMode, LINKER_PlrMode, LINKER_SkillCalc, LINKER_MissCalc, LINKER_Items

This field type converts a 4-letter code to a id number occupying one BYTE, the default value of these fields depends on the linker function used. For PlayerClass it is 7 (0-6 being valid classes, and 7 being CLASS_NONE). In vanilla all the above fields use this besides LINKER_ItemTypes


FIELDTYPE_CODE_TO_WORD (15)

Fields: LINKER_ItemTypes (also LINKER_CompCode, LINKER_PlayerClass, LINKER_BodyLoc, LINKER_StorePage, LINKER_ElemTypes, LINKER_HitClass, LINKER_Colors, LINKER_HireDesc, LINKER_MonMode, LINKER_PlrMode, LINKER_SkillCalc, LINKER_MissCalc, LINKER_Items)

This field type converts a 4-letter code to a id number occupying one WORD, the default value depends on the linker function used, but only ItemTypes fields use this in vanilla, and IIRC the default for those is -1.


FIELDTYPE_NAME_TO_WORD (20)

Fields: LINKER_MonAi, LINKER_MonPlace, LINKER_SkillCodes, LINKER_Events, LINKER_MonTypes, LINKER_Overlay, LINKER_ItemStatCost, LINKER_Properties, LINKER_Missiles, LINKER_States, LINKER_MonStats, LINKER_SkillDesc, LINKER_Skills, LINKER_MonProp, LINKER_MonSounds, LINKER_MonStats2, LINKER_MonUMod, LINKER_Properties, LINKER_Sets, LINKER_SuperUniques, LINKER_TreasureClassEx

This is another variant of the above, the main difference is that this handles links which are entire ASCII strings and not just 4-character strings, in addition this will output the default value -1 if the link is invalid. This is also used for the range column in skills.txt for example. LINKER_TreasureClassEx is a very special case, because TreasureClassEx.txt gets compiled in old fashion (aka pure ASCII -> BIN dump), but later this gets replaced by TreasureClassExShort, which is a internal structure where fields are properly linked with indexes rather then names.


FIELDTYPE_NAME_TO_WORD_2 (21)

Fields: LINKER_PetTypes

This is like the above, but designed for PetTypes links only, like the special field used to generate PetTypes links in the first place.


FIELDTYPE_KEY_TO_WORD (22)

Fields: LINKER_Strings

This is a special field type, it is designed for strings from the *.tbl files, this field holds a textual string key in *.txt form, which is converted to a string index by the game using this fieldtype + linker function, the main special thing here is that nonexistant strings are replaced by the 'An Evil Force' string. The size of these fields is one WORD.


FIELDTYPE_CALC_TO_DWORD (25)

Fields: LINKER_MissFormula, LINKER_ItemFormula, LINKER_PropFormula, LINKER_SkillFormula

This field is used to encode formulae (aka calc fields), this is used for all calc fields in all files, what type of formula this turns out to be depends on the linker functions. The size of these fields is always a DWORD, the default value (when the formula is invalid or blank), is -1 (which the interpretor automatically disposes as 0 when evaluated). The fact this occupies a DWORD is why formulae are limited to 255 characters.







Calling the custom text file loader is probably the most important thing, otherwise how are the new files going to be compiled ;)

Code: Select all

6FD662F3  |. /74 3B                        JE SHORT D2Common.6FD66330
6FD662F5  |. |A1 90EEDE6F                  MOV EAX,DWORD PTR DS:[6FDEEE90]
6FD662FA  |. |33C9                         XOR ECX,ECX
6FD662FC  |. |85C0                         TEST EAX,EAX
6FD662FE  |. |51                           PUSH ECX
6FD662FF  |. |74 0A                        JE SHORT D2Common.6FD6630B
6FD66301  |. |68 F6080000                  PUSH 8F6
6FD66306  |. |83C2 FC                      ADD EDX,-4
6FD66309  |. |EB 05                        JMP SHORT D2Common.6FD66310
6FD6630B  |> |68 F8080000                  PUSH 8F8
6FD66310  |> |68 3C6EDE6F                  PUSH D2Common.6FDE6E3C
6FD66315  |. |E8 F22EFFFF                  CALL <JMP.&Fog.#10046>
6FD6631A  |. |8B15 2809DF6F                MOV EDX,DWORD PTR DS:[6FDF0928]
6FD66320  |. |52                           PUSH EDX
6FD66321  |. |E8 462FFFFF                  CALL <JMP.&Fog.#10212>
6FD66326  |. |C705 2C09DF6F 00000000       MOV DWORD PTR DS:[6FDF092C],0
[color=red]6FD66330  |> \E8 ########                  CALL ########
6FD66335  |.  81C4 08010000                ADD ESP,108
6FD6633B  \.  C2 0C00                      RETN 0C[/color]
Each BinImage is created from an array of BinField structures, one for each column in the file, and an array terminator so it knows "where it ends".
The structure looks like so:

Code: Select all

struct BinField
{
  char* szFieldName;
  int eFieldType;
  int nFieldLength;
  int nFieldOffset;
  void* pLinkField;
};
szFieldName holds the name of the column.
eFieldType holds the enum value of the field type discussed above, aka FIELDTYPE_DATA_DWORD and so on.
nFieldLength holds special data, this is used only by FIELDTYPE_DATA_BITS and FIELDTYPE_DATA_ASCII for bit position and string length.
nFieldOffset holds the offset of the data within the bin structure.
pLinkField holds a pointer to the link field, for field types that need it.

A BinImage is compiled / loaded by calling D2Common.10496.
The prototype of this function looks like this: typedef void* (__stdcall * D2COMMON_10496)(void*,char*,BinField*,int*,DWORD);

You would normally call this function like this: D2Common10496(NULL,"NewTextFileName",BinFieldArray,&nRecords,sizeof(BinImageStructure));

The last entry in the BinField array must have szFieldName set to "end" for the game to know the previous column was indeed the last one. If you forget this you're welcomed to take a trip to crashisthan.


Here is an example:

Code: Select all

BinField AiParamsTable[] =
{
	{"stall",FIELDTYPE_DATA_WORD,0,0,0},
	{"stall (N)",FIELDTYPE_DATA_WORD,0,2,0},
	{"stall (H)",FIELDTYPE_DATA_WORD,0,4,0},
	{"health",FIELDTYPE_DATA_WORD,0,6,0},
	{"health (N)",FIELDTYPE_DATA_WORD,0,8,0},
	{"health (H)",FIELDTYPE_DATA_WORD,0,10,0},
	{"approach",FIELDTYPE_DATA_WORD,0,12,0},
	{"approach (N)",FIELDTYPE_DATA_WORD,0,14,0},
	{"approach (H)",FIELDTYPE_DATA_WORD,0,16,0},
	{"wander steps",FIELDTYPE_DATA_WORD,0,18,0},
	{"wander steps (N)",FIELDTYPE_DATA_WORD,0,20,0},
	{"wander steps (H)",FIELDTYPE_DATA_WORD,0,22,0},
	{"steps",FIELDTYPE_DATA_WORD,0,24,0},
	{"steps (N)",FIELDTYPE_DATA_WORD,0,26,0},
	{"steps (H)",FIELDTYPE_DATA_WORD,0,28,0},
	{"run speed",FIELDTYPE_DATA_WORD,0,30,0},
	{"run speed (N)",FIELDTYPE_DATA_WORD,0,32,0},
	{"run speed (H)",FIELDTYPE_DATA_WORD,0,34,0},
	{"run",FIELDTYPE_DATA_WORD,0,36,0},
	{"run (N)",FIELDTYPE_DATA_WORD,0,38,0},
	{"run (H)",FIELDTYPE_DATA_WORD,0,40,0},
	{"escape",FIELDTYPE_DATA_WORD,0,42,0},
	{"escape (N)",FIELDTYPE_DATA_WORD,0,44,0},
	{"escape (H)",FIELDTYPE_DATA_WORD,0,46,0},
	{"curse dist",FIELDTYPE_DATA_WORD,0,48,0},
	{"curse dist (N)",FIELDTYPE_DATA_WORD,0,50,0},
	{"curse dist (H)",FIELDTYPE_DATA_WORD,0,52,0},
	{"safe dist",FIELDTYPE_DATA_WORD,0,54,0},
	{"safe dist (N)",FIELDTYPE_DATA_WORD,0,56,0},
	{"safe dist (H)",FIELDTYPE_DATA_WORD,0,58,0},
	{"escape dist",FIELDTYPE_DATA_WORD,0,60,0},
	{"escape dist (N)",FIELDTYPE_DATA_WORD,0,62,0},
	{"escape dist (H)",FIELDTYPE_DATA_WORD,0,64,0},
	{"attack",FIELDTYPE_DATA_WORD,0,66,0},
	{"attack (N)",FIELDTYPE_DATA_WORD,0,68,0},
	{"attack (H)",FIELDTYPE_DATA_WORD,0,70,0},
	{"attack2",FIELDTYPE_DATA_WORD,0,72,0},
	{"attack2(N)",FIELDTYPE_DATA_WORD,0,74,0},
	{"attack2 (H)",FIELDTYPE_DATA_WORD,0,76,0},
	{"skill melee",FIELDTYPE_DATA_WORD,0,78,0},
	{"skill melee (N)",FIELDTYPE_DATA_WORD,0,80,0},
	{"skill melee (H)",FIELDTYPE_DATA_WORD,0,82,0},
	{"skill range",FIELDTYPE_DATA_WORD,0,84,0},
	{"skill range (N)",FIELDTYPE_DATA_WORD,0,86,0},
	{"skill range (H)",FIELDTYPE_DATA_WORD,0,88,0},
	{"skill strong",FIELDTYPE_DATA_WORD,0,90,0},
	{"skill strong (N)",FIELDTYPE_DATA_WORD,0,92,0},
	{"skill strong (H)",FIELDTYPE_DATA_WORD,0,94,0},
	{"skill dist",FIELDTYPE_DATA_WORD,0,96,0},
	{"skill dist (N)",FIELDTYPE_DATA_WORD,0,98,0},
	{"skill dist (H)",FIELDTYPE_DATA_WORD,0,100,0},
	{"skill curse",FIELDTYPE_DATA_WORD,0,102,0},
	{"skill curse (N)",FIELDTYPE_DATA_WORD,0,104,0},
	{"skill curse (H)",FIELDTYPE_DATA_WORD,0,106,0},
	{"skill buff",FIELDTYPE_DATA_WORD,0,108,0},
	{"skill buff (N)",FIELDTYPE_DATA_WORD,0,110,0},
	{"skill buff (H)",FIELDTYPE_DATA_WORD,0,112,0},
	{"skill heal",FIELDTYPE_DATA_WORD,0,114,0},
	{"skill heal (N)",FIELDTYPE_DATA_WORD,0,116,0},
	{"skill heal (H)",FIELDTYPE_DATA_WORD,0,118,0},
	{"skill corpse",FIELDTYPE_DATA_WORD,0,120,0},
	{"skill corpse (N)",FIELDTYPE_DATA_WORD,0,122,0},
	{"skill corpse (H)",FIELDTYPE_DATA_WORD,0,124,0},
	{"skill cmd",FIELDTYPE_DATA_WORD,0,126,0},
	{"skill cmd (N)",FIELDTYPE_DATA_WORD,0,128,0},
	{"skill cmd (H)",FIELDTYPE_DATA_WORD,0,130,0},
	{"apply other",FIELDTYPE_DATA_WORD,0,132,0},
	{"apply other (N)",FIELDTYPE_DATA_WORD,0,134,0},
	{"apply other (H)",FIELDTYPE_DATA_WORD,0,136,0},
	{"other dist",FIELDTYPE_DATA_WORD,0,138,0},
	{"other dist (N)",FIELDTYPE_DATA_WORD,0,140,0},
	{"other dist (H)",FIELDTYPE_DATA_WORD,0,142,0},
	{"spawn",FIELDTYPE_DATA_WORD,0,144,0},
	{"spawn (N)",FIELDTYPE_DATA_WORD,0,146,0},
	{"spawn (H)",FIELDTYPE_DATA_WORD,0,148,0},
	{"limit",FIELDTYPE_DATA_WORD,0,150,0},
	{"limit (N)",FIELDTYPE_DATA_WORD,0,152,0},
	{"limit (H)",FIELDTYPE_DATA_WORD,0,154,0},
	{"delay",FIELDTYPE_DATA_WORD,0,156,0},
	{"delay (N)",FIELDTYPE_DATA_WORD,0,158,0},
	{"delay (H)",FIELDTYPE_DATA_WORD,0,160,0},
	{"fixed spawn",FIELDTYPE_DATA_DATA,0,162,0},
	{"endless spawn",FIELDTYPE_DATA_DATA,0,163,0},
	{"wander idle",FIELDTYPE_DATA_DATA,0,164,0},
	{"skill melee first",FIELDTYPE_DATA_DATA,0,165,0},
	{"skill melee last",FIELDTYPE_DATA_DATA,0,166,0},
	{"skill range first",FIELDTYPE_DATA_DATA,0,167,0},
	{"skill range last",FIELDTYPE_DATA_DATA,0,168,0},
	{"skill strong first",FIELDTYPE_DATA_DATA,0,169,0},
	{"skill strong last",FIELDTYPE_DATA_DATA,0,170,0},
	{"skill buff first",FIELDTYPE_DATA_DATA,0,171,0},
	{"skill buff last",FIELDTYPE_DATA_DATA,0,172,0},
	{"curse first",FIELDTYPE_DATA_DATA,0,173,0},
	{"curse last",FIELDTYPE_DATA_DATA,0,174,0},
	{"heal skill",FIELDTYPE_DATA_DATA,0,175,0},
	{"corpse skill",FIELDTYPE_DATA_DATA,0,176,0},
	{"cmd skill",FIELDTYPE_DATA_DATA,0,177,0},
	{"spawn skill",FIELDTYPE_DATA_DATA,0,178,0},
	{"run dist",FIELDTYPE_DATA_WORD,0,179,0},
	{"run dist (N)",FIELDTYPE_DATA_WORD,0,181,0},
	{"run dist (H)",FIELDTYPE_DATA_WORD,0,183,0},
	{"escape steps",FIELDTYPE_DATA_WORD,0,185,0},
	{"escape steps (N)",FIELDTYPE_DATA_WORD,0,187,0},
	{"escape steps (H)",FIELDTYPE_DATA_WORD,0,189,0},
	{"escape speed",FIELDTYPE_DATA_WORD,0,191,0},
	{"escape speed (N)",FIELDTYPE_DATA_WORD,0,193,0},
	{"escape speed (H)",FIELDTYPE_DATA_WORD,0,195,0},
	{"health other",FIELDTYPE_DATA_WORD,0,197,0},
	{"health other (N)",FIELDTYPE_DATA_WORD,0,199,0},
	{"health other (H)",FIELDTYPE_DATA_WORD,0,201,0},
	{"wander",FIELDTYPE_DATA_WORD,0,203,0},
	{"wander (N)",FIELDTYPE_DATA_WORD,0,205,0},
	{"wander (H)",FIELDTYPE_DATA_WORD,0,207,0},
	{"curse frame",FIELDTYPE_DATA_WORD,0,209,0},
	{"curse frame (N)",FIELDTYPE_DATA_WORD,0,211,0},
	{"curse frame (H)",FIELDTYPE_DATA_WORD,0,213,0},
	{"buff frame",FIELDTYPE_DATA_WORD,0,215,0},
	{"buff frame (N)",FIELDTYPE_DATA_WORD,0,217,0},
	{"buff frame (H)",FIELDTYPE_DATA_WORD,0,219,0},
	{"teleport skill",FIELDTYPE_DATA_DATA,0,221,0},
	{"tele dist",FIELDTYPE_DATA_WORD,0,222,0},
	{"tele dist (N)",FIELDTYPE_DATA_WORD,0,224,0},
	{"tele dist (H)",FIELDTYPE_DATA_WORD,0,226,0},
	{"teleport",FIELDTYPE_DATA_WORD,0,228,0},
	{"teleport (N)",FIELDTYPE_DATA_WORD,0,230,0},
	{"teleport (H)",FIELDTYPE_DATA_WORD,0,232,0},
	{"err range",FIELDTYPE_DATA_WORD,0,234,0},
	{"err range (N)",FIELDTYPE_DATA_WORD,0,236,0},
	{"err range (H)",FIELDTYPE_DATA_WORD,0,238,0},
	{"strong dist",FIELDTYPE_DATA_WORD,0,240,0},
	{"strong dist (N)",FIELDTYPE_DATA_WORD,0,242,0},
	{"strong dist (H)",FIELDTYPE_DATA_WORD,0,244,0},
	{"buff flag",FIELDTYPE_DATA_DWORD,0,246,0},
	{"heal flag",FIELDTYPE_DATA_DWORD,0,250,0},
	{"corpse flag",FIELDTYPE_DATA_DWORD,0,254,0},
	{"spawn dist",FIELDTYPE_DATA_WORD,0,258,0},
	{"spawn dist (N)",FIELDTYPE_DATA_WORD,0,260,0},
	{"spawn dist (H)",FIELDTYPE_DATA_WORD,0,262,0},
	{"set boss",FIELDTYPE_DATA_DATA,0,264,0},
	{"charge skill",FIELDTYPE_DATA_DATA,0,265,0},
	{"charge ticks",FIELDTYPE_DATA_WORD,0,266,0},
	{"charge ticks (N)",FIELDTYPE_DATA_WORD,0,268,0},
	{"charge ticks (H)",FIELDTYPE_DATA_WORD,0,270,0},
	{"regen bonus",FIELDTYPE_DATA_WORD,0,272,0},
	{"charge state",FIELDTYPE_NAME_TO_WORD,0,274,LINKER_States},
	{"charge overlay",FIELDTYPE_NAME_TO_WORD,0,276,LINKER_Overlay},
	{"needs target",FIELDTYPE_DATA_DATA,0,278,0},
	{"hurt buff",FIELDTYPE_DATA_WORD,0,279,0},
	{"hurt buff (N)",FIELDTYPE_DATA_WORD,0,281,0},
	{"hurt buff (H)",FIELDTYPE_DATA_WORD,0,283,0},
	{"activate dist",FIELDTYPE_DATA_WORD,0,285,0},
	{"activate dist (N)",FIELDTYPE_DATA_WORD,0,287,0},
	{"activate dist (H)",FIELDTYPE_DATA_WORD,0,289,0},
	{"strong error",FIELDTYPE_DATA_DATA,0,291,0},
	{"range error",FIELDTYPE_DATA_DATA,0,292,0},
	{"die on end",FIELDTYPE_DATA_DATA,0,293,0},
	{"dis-engage",FIELDTYPE_DATA_WORD,0,294,0},
	{"dis-engage (N)",FIELDTYPE_DATA_WORD,0,296,0},
	{"dis-engage (H)",FIELDTYPE_DATA_WORD,0,298,0},
	{"cmd dist",FIELDTYPE_DATA_WORD,0,300,0},
	{"cmd dist (N)",FIELDTYPE_DATA_WORD,0,302,0},
	{"cmd dist (H)",FIELDTYPE_DATA_WORD,0,304,0},
	{"hurt buff skill",FIELDTYPE_DATA_DATA,0,306,0},
	{"rand tele",FIELDTYPE_DATA_WORD,0,307,0},
	{"rand tele (N)",FIELDTYPE_DATA_WORD,0,309,0},
	{"rand tele (H)",FIELDTYPE_DATA_WORD,0,311,0},
	{"rand tele dist",FIELDTYPE_DATA_WORD,0,313,0},
	{"rand tele dist (N)",FIELDTYPE_DATA_WORD,0,315,0},
	{"rand tele dist (H)",FIELDTYPE_DATA_WORD,0,317,0},
	{"anim length",FIELDTYPE_DATA_WORD,0,319,0},
	{"end",0,0,0,0},
};

int nRecords = 0;
AiParams* pAiParams = (AiParams*)Common10496(NULL,"monaiparam",AiParamsTable,&nRecords,sizeof(AiParams));


And likewise, we can't just load things and 'hope for the best', we also have to unload them later

Code: Select all

6FD66104  |. /74 2F                        JE SHORT D2Common.6FD66135
6FD66106  |. |A1 90EEDE6F                  MOV EAX,DWORD PTR DS:[6FDEEE90]
6FD6610B  |. |33C9                         XOR ECX,ECX
6FD6610D  |. |3BC6                         CMP EAX,ESI
6FD6610F  |. |56                           PUSH ESI
6FD66110  |. |74 14                        JE SHORT D2Common.6FD66126
6FD66112  |. |68 F6080000                  PUSH 8F6
6FD66117  |. |83C2 FC                      ADD EDX,-4
6FD6611A  |. |68 3C6EDE6F                  PUSH D2Common.6FDE6E3C
6FD6611F  |. |E8 E830FFFF                  CALL <JMP.&Fog.#10046>
6FD66124  |. |EB 0F                        JMP SHORT D2Common.6FD66135
6FD66126  |> |68 F8080000                  PUSH 8F8
6FD6612B  |. |68 3C6EDE6F                  PUSH D2Common.6FDE6E3C
6FD66130  |. |E8 D730FFFF                  CALL <JMP.&Fog.#10046>
[color=red]6FD66135  |> \E8 ########                  CALL ########
6FD6613A  |.  5E                           POP ESI
6FD6613B  \.  C3                           RETN[/color]

To unload your BIN image, you need to call Fog.10046, which is blizzards wrapper for GlobalFree, you could just pass it to globalfree and be done with it, but there are all kinds of memory management gadgets in the game, so we better don't think but do what they've done before.

The prototype for this function is: typedef BOOL (__fastcall * FOG10046)(void*,void*,char*,int,DWORD);
To use it: Fog10046(NULL,pMemoryToFree,__FILE__,__LINE__,0); The first argument is the memory pool used, but D2 never uses pools other then none, so just pass NULL here (thats what they also do).
Last edited by Nefarius on Fri Nov 23, 2007 7:12 am, edited 2 times in total.
''(...) The game can basically be considered unhackable. '' - Blizzard Entertainment (30th May 2000)
Black Omen Productions | MetalStorm: Progress Report | Screenshots

User avatar
CorniI
Dark Alliance Beta Test
Champion of the Light
Posts: 371
Joined: Wed Apr 12, 2006 9:02 am

Post by CorniI » Sun Dec 23, 2007 3:26 pm

Hi,
how can I *access* the data in the text, aka getting a pointer to the first table entry + an int with how long it is?
Corni
Linux is like a Wigwam - NO Windows, no Gates and Apache inside

User avatar
Nefarius
Retired Admin
Cherub
Posts: 11607
Joined: Sat Jun 15, 2002 8:13 pm
Location: Where the blood forever rains
Contact:

Hand-picked

Re: [v1.11b] Loading New TXT Files

Post by Nefarius » Sun Dec 23, 2007 3:40 pm

typedef void* (__stdcall * D2COMMON_10496)(void*,char*,BinField*,int*,DWORD);
It's returned by the function, the pointer to int will store the record count being compiled.
''(...) The game can basically be considered unhackable. '' - Blizzard Entertainment (30th May 2000)
Black Omen Productions | MetalStorm: Progress Report | Screenshots

User avatar
Necrolis
Senior Admin
Throne
Posts: 9125
Joined: Sat Mar 25, 2006 1:22 pm
Location: The Land of the Dead
Contact:
South Africa

Hand-picked

Re: [v1.11b] Loading New TXT Files

Post by Necrolis » Sun Dec 23, 2007 6:16 pm

Nefarius";p="371953" wrote:
typedef void* (__stdcall * D2COMMON_10496)(void*,char*,BinField*,int*,DWORD);
It's returned by the function, the pointer to int will store the record count being compiled.
A practical example might help :)
First you load the file and get the pointer to the image and store it for later use:

Code: Select all

struct TownsTXT
{
       BYTE Act;
	   WORD StartTown;
       WORD TownId[8];
};
	BinField TownsTXTTable[] =
	{
		{"Act",FIELDTYPE_DATA_BYTE,0,0,0},
		{"StartTown",FIELDTYPE_DATA_WORD,0,1,0},
		{"Town1",FIELDTYPE_DATA_WORD,0,3,0},
		{"Town2",FIELDTYPE_DATA_WORD,0,5,0},
		{"Town3",FIELDTYPE_DATA_WORD,0,7,0},
		{"Town4",FIELDTYPE_DATA_WORD,0,9,0},
		{"Town5",FIELDTYPE_DATA_WORD,0,11,0},
		{"Town6",FIELDTYPE_DATA_WORD,0,13,0},
		{"Town7",FIELDTYPE_DATA_WORD,0,15,0},
		{"Town8",FIELDTYPE_DATA_WORD,0,17,0},
		{"end",0,0,0,0},
	};
	int TownsRecords = 0;
	TownsTXT* Towns = (TownsTXT*) D2CompileText(NULL,"Towns",TownsTXTTable,&TownsRecords,sizeof(TownsTXT));
	DeathTXTs->nTowns = TownsRecords;
	DeathTXTs->pTowns = Towns;
then you need to retrieve the pointer and the required info:

Code: Select all

void InitTownArray()
{

	int nlvls = GetBlizzardTXT()->nLevels;
	TownsTXT* Towns = GetDeathTXT()->pTowns;

	for (int l = 0; l < 8; l++)
	{
		isTown[Towns[0].TownId[l]] = true;
		isTown[Towns[1].TownId[l]] = true;
		isTown[Towns[2].TownId[l]] = true;
		isTown[Towns[3].TownId[l]] = true;
		isTown[Towns[4].TownId[l]] = true;
	}
}
what I use to store ptrs and counts:

Code: Select all

DeathTXT* DeathTXTs;
struct DeathTXT
{
       Monstats3TXT* pMonstats3;
       int nMonstats3;
       TownsTXT* pTowns;
       int nTowns;
       ItemTypes2TXT* pItemTypes2;
       int nItemTypes2;
       MercDescTXT* pMercDesc;
       int nMercDesc;
       void* pMessages;
       int nMessages;
       LevelUpTXT* pLevelUp;
       int nLevelUp;
       LightMapTXT* pLightMap;
       int nLightMap;
       CharCreationTXT* pCharCreation;
       int nCharCreation;
       HellFireOilsTXT* pHellFireOils;
       int nHellFireOils;
	   NoSockTXT* pNoSock;
	   int nNoSock;
	   LevelsExTXT* pLevelsEx;
	   int nLevelsEx;
	   MonUITXT* pMonUI;
	   int nMonUI;
}; 
and to retrieve the TXT struct

Code: Select all

DeathTXT * __fastcall GetDeathTXT()
      {
        return DeathTXTs;
      } 
that should answer any questions :)
and a tip: when linking/using the linker make sure your txt is loaded AFTER the txt your gonna link to...(with nefs loc that should happen)
Last edited by Necrolis on Mon May 19, 2008 2:29 pm, edited 2 times in total.
Image
Netiquette, Do you USE it?!?! | Nefarius' Fixed TXT Files | Terms Of Service
Blackened | Day of Death | D2GFEx
"What was yours is mine. Your land, your people, and now your life." - Lim-Dul, the Necromancer
Judgement is Final, Death is Eternal

User avatar
CorniI
Dark Alliance Beta Test
Champion of the Light
Posts: 371
Joined: Wed Apr 12, 2006 9:02 am

Re: [v1.11b] Loading New TXT Files

Post by CorniI » Sun Dec 23, 2007 7:40 pm

Nefarius";p="371953" wrote:
typedef void* (__stdcall * D2COMMON_10496)(void*,char*,BinField*,int*,DWORD);
It's returned by the function, the pointer to int will store the record count being compiled.
ah ok, thanks .) I should read a text more carefully/try it before asking questions like this :D thx anyway ;)
Corni
Linux is like a Wigwam - NO Windows, no Gates and Apache inside

User avatar
Necrolis
Senior Admin
Throne
Posts: 9125
Joined: Sat Mar 25, 2006 1:22 pm
Location: The Land of the Dead
Contact:
South Africa

Hand-picked

Post by Necrolis » Mon May 19, 2008 2:42 pm

The 1.10 TXT linkers :)
(btw these aren't actually DWORD's, they're D2TXTLinkageStrc** [see below], its just easier to use them as such)

Code: Select all

	TXTItemTypeLink			= D2CommonBase+0xAA1FC;
	TXTMonstatsLink			= D2CommonBase+0xAA084;
	TXTClassLink			= D2CommonBase+0x0A960C;
	TXTStringLink			= D2CommonBase+0x94D0;
	TXTPropLink				= D2CommonBase+0xA96B0;
	TXTStatLink				= D2CommonBase+0xAA1D8;
	TXTStateLink			= D2CommonBase+0xA96C8;
	TXTHireDescLink			= D2CommonBase+0xA96C0;
	TXTTreasureClassLink	= D2CommonBase+0xA9FC0;
	TXTSkillDescLink		= D2CommonBase+0xAA198;
	TXTSkillDescCalcLink	= D2CommonBase+0x9E40;
	TXTElemTypeLink			= D2CommonBase+0xA9624;
	TXTMissileDescLink		= D2CommonBase+0xAA170;
	TXTSkillsCalcLink		= D2CommonBase+0x9D20;
	TXTOverlayLink			= D2CommonBase+0xAA1C0;
	TXTEventLink			= D2CommonBase+0xA9680;
	TXTPetTypeLink			= D2CommonBase+0xAA1F4;
	TXTMonModeLink			= D2CommonBase+0xA9634;
	TXTSkillLink			= D2CommonBase+0xAA1A4;
	TXTMissileLink			= D2CommonBase+0xAA170;
	TXTSoundLink			= D2CommonBase+0xA97A0;
	TXTBodyLocLink			= D2CommonBase+0xA9614;
	TXTItemLink				= D2CommonBase+0xA969C;
	TXTItemCalcLink			= D2CommonBase+0x151E0;
	TXTUniqueItemCalcLink	= D2CommonBase+0x17740;
	TXTMonstatLink			= D2CommonBase+0xAA084;
	TXTMonstatExLink		= D2CommonBase+0xAA09C;
	TXTMonTypeLink			= D2CommonBase+0xAA240;
	TXTMonPropLink			= D2CommonBase+0xAA234;
	TXTMonAILink			= D2CommonBase+0xA9694;
	TXTSkillModeLink		= D2CommonBase+0x24CD0;
	TXTCubeParamLink		= D2CommonBase+0x12FC0;
	TXTCubeInputCalcLink	= D2CommonBase+0x123E0;
	TXTCubeOutputCalcLink	= D2CommonBase+0x128D0;
	TXTHitCodeLink			= D2CommonBase+0xA962C;
	TXTSuperUniqueLink		= D2CommonBase+0xAA0E0;
	TXTCompCodeLink			= D2CommonBase+0xA9688;
	TXTStorePageLink		= D2CommonBase+0xA961C;
	TXTColourLink			= D2CommonBase+0xA9FBC;
	TXTPlayerModeLink		= D2CommonBase+0xA963C;
	TXTMonPlaceLink			= D2CommonBase+0xAA0A8;
	TXTSkillCalc2Link		= D2CommonBase+0xA9644;
	TXTMissCalcLink			= D2CommonBase+0xA9664;
	TXTUniqueLink			= D2CommonBase+0xAA228;
	TXTSetLink				= D2CommonBase+0xAA210;
	TXTSetItemLink			= D2CommonBase+0xAA21C;
	TXTUniqueMonNameLink	= D2CommonBase+0xA9FC0;
	TXTMonSoundsLink		= D2CommonBase+0xAA090;
	TXTMonSeqLink			= D2CommonBase+0xAA184;
	TXTMonUModLink			= D2CommonBase+0xAA254;
	TXTMercDescLink		    = D2CommonBase+0xA96C0;
D2 has 2 main TXT callback locs, one in d2common and one in d2win, which you can hook @0x3B44, it loads when the char select screen is opened. D2Win also has it own txt compile func, which is just a copy of the d2common one(i assume...)
D2WinCompileText :: D2WinBase + 0x2CE0
it takes the same params.
___________________________
New TXT Linkers :D
we need 2 funcs for this:

Code: Select all

D2FUNC(void,__stdcall,D2DestroyTXTLinkage,(D2TXTLinkageStrc* ptLink),D2FOG_10212);
D2FUNC(D2TXTLinkageStrc*,__stdcall,D2BuildTXTLinkage,(char* szFile, int nLine),D2FOG_10211);

Code: Select all

struct TXTFuncLinksTXT
{
	DWORD nLink;
};

struct TXTFuncLinksTest
{
	DWORD nLink;
	WORD nStr;
};

struct DeathTXT
{
	int TXTLoadCount;
    //...
    TXTFuncLinksTXT* pTXTFuncLinks;
    int nTXTFuncLinks;
    D2TXTLinkageStrc* lTXTFuncLinks;
};

struct D2TXTLinkageStrc
{
	void* pLink;
	DWORD Unk[3];   //don't care to check
};
first we need to create and init a linker:

Code: Select all

	BinField TXTFuncLinksTXTTable[] =
	{
		{"Code",FIELDTYPE_ASCII_TO_CODE,0,0,(DWORD)&DeathDataTables->lTXTFuncLinks},
		{"end",0,0,0,0},
	};

	int TXTFuncLinksRecords = 0;
	DeathStormDataTables->lTXTFuncLinks = D2BuildTXTLinkage(__FILE__,__LINE__);
	TXTFuncLinksTXT* TXTFuncLinks = (TXTFuncLinksTXT*)D2CompileText(NULL,"TXTFuncLinks",TXTFuncLinksTXTTable,&TXTFuncLinksRecords,sizeof(TXTFuncLinksTXT));

    if (TXTFuncLinksRecords == NULL)
      D2Assert("pRecord != NULL",__FILE__,__LINE__);
now we can use the link :)

Code: Select all

	BinField TXTFuncLinksTestTable[] =
	{
		{"TXTFuncLinks",FIELDTYPE_CODE_TO_WORD,0,0,(DWORD)&DeathDataTables->lTXTFuncLinks},
		{"String",FIELDTYPE_KEY_TO_WORD,0,4,TXTStringLink},		
		{"end",0,0,0,0},
	};

	int TXTFuncLinksTestRecords = 0;
	TXTFuncLinksTest* TXTFuncLinksTester = (TXTFuncLinksTest*)D2CompileText(NULL,"TXTFuncLinksTest",TXTFuncLinksTestTable,&TXTFuncLinksTestRecords,sizeof(TXTFuncLinksTest));

    if (TXTFuncLinksTestRecords == NULL)
      D2Assert("pRecord != NULL",__FILE__,__LINE__);
Image
Netiquette, Do you USE it?!?! | Nefarius' Fixed TXT Files | Terms Of Service
Blackened | Day of Death | D2GFEx
"What was yours is mine. Your land, your people, and now your life." - Lim-Dul, the Necromancer
Judgement is Final, Death is Eternal

User avatar
CorniI
Dark Alliance Beta Test
Champion of the Light
Posts: 371
Joined: Wed Apr 12, 2006 9:02 am

Re: [v1.11b] Loading New TXT Files

Post by CorniI » Mon May 19, 2008 7:29 pm

and in case you need longer Lookup Names than 4 chars, like TCex-Names, the same applies for them, so

Code: Select all

BinField directionsBin[]=
{
	{"name",FIELDTYPE_NAME_TO_INDEX,0,0,(DWORD)&(txtMod.directionsLinker)},
	{"end",0,0,0,0},
};
for the init and

Code: Select all

static BinField spoiler :P[]=
{
	///
	{"direction1",FIELDTYPE_NAME_TO_WORD,0,18,(DWORD)&(txtMod.directionsLinker)},
	{"direction2",FIELDTYPE_NAME_TO_WORD,0,20,(DWORD)&(txtMod.directionsLinker)},
	{"direction3",FIELDTYPE_NAME_TO_WORD,0,22,(DWORD)&(txtMod.directionsLinker)},
	{"direction4",FIELDTYPE_NAME_TO_WORD,0,24,(DWORD)&(txtMod.directionsLinker)},
	{"end",0,0,0,0}
};
for the usage :)
Linux is like a Wigwam - NO Windows, no Gates and Apache inside

User avatar
Necrolis
Senior Admin
Throne
Posts: 9125
Joined: Sat Mar 25, 2006 1:22 pm
Location: The Land of the Dead
Contact:
South Africa

Hand-picked

Post by Necrolis » Mon May 19, 2008 9:16 pm

My example is actually not limited to 4 chars(the test linkage has links like addskill etc). There are a few linker types, but most use names and not DWORDs, then there is string linkage which is a special case.
Image
Netiquette, Do you USE it?!?! | Nefarius' Fixed TXT Files | Terms Of Service
Blackened | Day of Death | D2GFEx
"What was yours is mine. Your land, your people, and now your life." - Lim-Dul, the Necromancer
Judgement is Final, Death is Eternal

User avatar
Nefarius
Retired Admin
Cherub
Posts: 11607
Joined: Sat Jun 15, 2002 8:13 pm
Location: Where the blood forever rains
Contact:

Hand-picked

Re: [v1.11b] Loading New TXT Files

Post by Nefarius » Thu Jul 10, 2008 4:41 pm

Heres the whole linker structure layed out, I haven't seen the two DWORDs given as dw1 and dw2 used by the game (yet). Probably one of them is actually the mem pool.

Code: Select all

struct IdNode
{
	char szName[32];
	int nId;
	IdNode* pLow;
	IdNode* pHigh;
};

struct IdLink
{
	int nCount;
	DWORD dw1;
	DWORD dw2;
	IdNode* pBase;
};
The link parser is using a tree, ordered by strcmp return (ie for lesser and higher then the current name) from what I saw.

You have to free the structure using Fog10212 at end of session to avoid memory leaks. Fog10213 is used to get the Id for a name. (arg1=hLink, arg2=text, arg3=0 where checked).

---

EDIT: new parsers, a la cubemain.txt

One thing that wasen't mentioned on the initial post is that adding new parsers is very simple.

first of all, the parsing callback function prototype

void (__fastcall* TXT_PARSER)(char* src, void* dst, int offset, int pos, int row, int col);

src is the raw text for this cell, its copied into a new buffer by calling code, so no need to mess with tabs here

dst = base of the current record (ie the compiled data is stored in an array of records, this is merely the pointer to the record, ie array + i), this is _NOT_ a pointer to the member itself but to the record itself.

offset = the offset for the field as passed in the table data struct

pos = the argument (count, flag index) passed to the table data struct

row = seams to be row #

col = seams to be col # (when it resets, row is incremented by 1, so obviously this)

How to add parsing to a field?

simple, you use field type 23 (above given erroneously as FIELDTYPE_MONSTER_COMPS, its just extra parser in reality).

Instead of a link field you pass a pointer to your parser function, like so:

Code: Select all

{"buff flag",			LINK_SPECIAL,0,246,XYTXTS_MonAiFlags},
{"heal flag",			LINK_SPECIAL,1,250,XYTXTS_MonAiFlags},
{"corpse flag",			LINK_SPECIAL,2,254,XYTXTS_MonAiFlags},
After this, you can basically do whatever you want.

Sample:

Code: Select all

void __fastcall XYTXTS_MonAiFlags(char* src, void* dst, int nOffset, int nPos, int nRow, int nColumn)
{
	// translates the textual flags
	// from the txt file into real flags
	// ADDED 10th July, 2008 (by Nefarius)

	AiParams* pAiParams = (AiParams*)dst;
	ASSERT(pAiParams);

	DWORD flags = 0;

	char szBuffer[64];

	while (src)
	{
		src = GetTxtSeg(src, szBuffer, ARRSIZE(szBuffer));
		flags |= MONAI_GetLinkerFlags(szBuffer);
	}

	ASSERT(nPos >= 0 && nPos <= 2);

	pAiParams->flags[nPos] = flags;
}
...

Basic functions for parsing the text (not optimized), it will copy words seperated by 0x2C (ie comma).

Code: Select all

__inline char* SkipBad(char* src, char c)
{
	// trims all bad characters
	// ( space and quotes )
	// ADDED 10th July, 2008 (by Nefarius)

	while (*src != 0x00 && (*src == 0x20 || *src == 0x22 || *src == c))
	{
		src++;
	}

	return ((*src == 0x00) ? NULL : src);
}

char* __fastcall GetTxtSeg(char* src, char* dst, size_t size)
{
	size--;

	src = SkipBad(src, 0x2C);
	
	if (!src)
	{
		return NULL;
	}

	size_t pos = 0;

	while (*src != 0x00 && *src != 0x20 && *src != 0x22 && *src != 0x2C)
	{
		dst[pos] = *src;
		src++;
		pos++;

		if (pos >= size)
		{
			break;
		}
	}

	if (pos <= size)
	{
		dst[pos] = 0x00;
	}
		else
	{
		dst[size] = 0x00;
	}

	return SkipBad(src, 0x2C);
}
Last edited by Nefarius on Thu Jul 10, 2008 10:46 pm, edited 3 times in total.
''(...) The game can basically be considered unhackable. '' - Blizzard Entertainment (30th May 2000)
Black Omen Productions | MetalStorm: Progress Report | Screenshots

User avatar
Necrolis
Senior Admin
Throne
Posts: 9125
Joined: Sat Mar 25, 2006 1:22 pm
Location: The Land of the Dead
Contact:
South Africa

Hand-picked

Re: [v1.11b] Loading New TXT Files

Post by Necrolis » Tue Mar 29, 2011 8:34 am

For anyone use new formulae fields thats using the blizzard compiler for the formulae (skills/missiles/skilldesc/items formulae linkers), you may have noticed that when running in -txt mode, all is fine, in mpq mode, everything breaks, well thats cause in -txt mdoe, the block size and IR code block are dynamically updated, but they are only ever saved once*, and thats after the files that use them have been compiled.

To fix this you need to save them again when in -txt mode, this will fix them up for mpq mode, doing so is simple, when done for all formulae for a specific cache, just do this:

Code: Select all

	if(INLINE_GetBlizzardTXT()->bCompileTXT)
	{
		LOG_Write("DATATABLES_LoadAll(): Updating Formulae Caches");
		DATATABLES_SaveSkillsCode();
		DATATABLES_SaveItemsCode();
	}
and saving the cache is done by:

Code: Select all

/* 
	Date: Tue Mar 29 09:03:22 2011
	Author: Necrolis
	Function: DATATABLES_SaveSkillsCode
	Address: 
	Comments: 
*/

void __stdcall DATATABLES_SaveSkillsCode()
{
	FILE* pSkillsCode = fopen("DATA\\GLOBAL\\EXCEL\\skillscode.bin","wb");
	if(pSkillsCode == NULL)
	{
		LOG_Write("DATATABLES_SaveSkillsCode(): Unable to Open Skills Formulae Cache");
		return;
	}

	BlizzardTXT* pDataTables = INLINE_GetBlizzardTXT();
	fwrite(pDataTables->pSkillsCode,sizeof(BYTE),pDataTables->dwSkillsCodeSize,pSkillsCode);
	fclose(pSkillsCode);
	LOG_Write("DATATABLES_SaveSkillsCode(): Updated Skills Forumlae Cache");
}
*the way D2 does it is terrible, they save the cache, free the memory then load it all back up again instead of branching correctly...
Image
Netiquette, Do you USE it?!?! | Nefarius' Fixed TXT Files | Terms Of Service
Blackened | Day of Death | D2GFEx
"What was yours is mine. Your land, your people, and now your life." - Lim-Dul, the Necromancer
Judgement is Final, Death is Eternal

User avatar
dav92
Forum Regular
Angel
Posts: 505
Joined: Thu Oct 26, 2006 2:53 pm
Contact:

Re: [v1.11b] Loading New TXT Files

Post by dav92 » Tue Mar 29, 2011 6:35 pm

Doesn't D2 do that automatically? Or do we have to do that again, because our new txts are loaded after the original ones?

User avatar
Necrolis
Senior Admin
Throne
Posts: 9125
Joined: Sat Mar 25, 2006 1:22 pm
Location: The Land of the Dead
Contact:
South Africa

Hand-picked

Re: [v1.11b] Loading New TXT Files

Post by Necrolis » Tue Mar 29, 2011 8:04 pm

I said i my previous post that they are only saved once, and thats after the standard d2 files that use them, hence why you need to save them again(idealy one would also remove the first superfluos save too)
Image
Netiquette, Do you USE it?!?! | Nefarius' Fixed TXT Files | Terms Of Service
Blackened | Day of Death | D2GFEx
"What was yours is mine. Your land, your people, and now your life." - Lim-Dul, the Necromancer
Judgement is Final, Death is Eternal

lihaifeng55
Posts: 6
Joined: Thu Aug 08, 2019 6:21 am

Re: [v1.11b] Loading New TXT Files

Post by lihaifeng55 » Thu Aug 08, 2019 6:37 am

Nefarius wrote:
Sun Dec 23, 2007 3:40 pm
typedef void* (__stdcall * D2COMMON_10496)(void*,char*,BinField*,int*,DWORD);
It's returned by the function, the pointer to int will store the record count being compiled.
The question is, where can I find the memory pointer after the binary file is read into memory?

lihaifeng55
Posts: 6
Joined: Thu Aug 08, 2019 6:21 am

Re: [v1.11b] Loading New TXT Files

Post by lihaifeng55 » Thu Aug 08, 2019 6:41 am

Necrolis wrote:
Tue Mar 29, 2011 8:34 am


But how can I find the definition of INLINE_GetBlizzard TXT()?

I'm trying to read the information in MonStats2.txt or MonStats2.bin.

User avatar
Necrolis
Senior Admin
Throne
Posts: 9125
Joined: Sat Mar 25, 2006 1:22 pm
Location: The Land of the Dead
Contact:
South Africa

Hand-picked

Re: [v1.11b] Loading New TXT Files

Post by Necrolis » Fri Aug 09, 2019 7:46 pm

Please don't double post, put all your questions into a single post.
lihaifeng55 wrote:
Thu Aug 08, 2019 6:37 am
Nefarius wrote:
Sun Dec 23, 2007 3:40 pm
typedef void* (__stdcall * D2COMMON_10496)(void*,char*,BinField*,int*,DWORD);
It's returned by the function, the pointer to int will store the record count being compiled.
The question is, where can I find the memory pointer after the binary file is read into memory?
The void* returned by the function is the pointer to the bin file in memory, you need to do the appropriate typecast yourself to use it.
Image
Netiquette, Do you USE it?!?! | Nefarius' Fixed TXT Files | Terms Of Service
Blackened | Day of Death | D2GFEx
"What was yours is mine. Your land, your people, and now your life." - Lim-Dul, the Necromancer
Judgement is Final, Death is Eternal

lihaifeng55
Posts: 6
Joined: Thu Aug 08, 2019 6:21 am

Re: [v1.11b] Loading New TXT Files

Post by lihaifeng55 » Mon Aug 12, 2019 3:00 am

Necrolis wrote:
Fri Aug 09, 2019 7:46 pm
Please don't double post, put all your questions into a single post.
lihaifeng55 wrote:
Thu Aug 08, 2019 6:37 am
Nefarius wrote:
Sun Dec 23, 2007 3:40 pm
It's returned by the function, the pointer to int will store the record count being compiled.
The question is, where can I find the memory pointer after the binary file is read into memory?
The void* returned by the function is the pointer to the bin file in memory, you need to do the appropriate typecast yourself to use it.
Problem solved. Thanks.

Post Reply

Return to “Code Editing”