[1.10] Custom Portals with Cube Recipes

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

0
No votes
 
Total votes: 0

User avatar
VillageIdiot
Posts: 44
Joined: Thu Sep 22, 2005 5:46 am
Location: Bay Area, CA

[1.10] Custom Portals with Cube Recipes

Post by VillageIdiot » Thu Sep 22, 2005 6:11 am

Introduction
I am the Village Idiot and I’m a bit a newbie so please bear with me. I wanted to open the new Realm areas in 1.11 so I had to create a custom red portal to a new area with a cube recipe. I really could not find anything really documented on how to do it. I searched the Phrozen Keep forums and saw Kingpin mention it a couple of times, but there really was not any tutorial or really any posts describing how to do it. So, being a relatively resourceful person, I decided to try it myself. When I started I knew nothing about the internals of D2 and the Phrozen Keep forums and knowledge base have been indispensable in answering my questions even though I’m pretty much a lurker. Kingpin’s Red Portal was also a great help. I chose D2 1.10 because there was the most information on that version. Anyway, let’s get started shall we?

Adding your text strings to the parsing code
You should start with the extended levels plug-in modified D2Common and D2Client files. It's also a really good idea to use a rebased D2Common file. The first thing you have to do is get the game to recognize your specific output code which will spawn your portal. Of course you add the specific recipe to cubemain.txt and in the output code column; you insert a custom string such as ‘Pandemonium Portal’. In my case, I used the v1.11 cubemain which adds the following:
description enabled version op numinputs input 1 input 2 input 3 Output
Pandemonium key 1 100 28 3 pk1 pk2 pk3 Pandemonium Portal
Pandemonium Finale key 1 100 28 3 dhn bey mbr Pandemonium Finale Portal

Once that’s done, you need to edit the parser for cubemain to recognize your text string. This function is located in D2Common.dll. The parser basically checks all of the output codes looking for item codes or ‘special’ cube recipe code and then tokenizes it in the cube recipe structure stored in memory.
From what I can tell, each cube recipe has a structure in memory which is read in by parsing the cubemain text file. It looks something like this.

Code: Select all

CubeRecipeStruct
+0x14	1st Cube input code
+0x00	Item Properties (this is a bit field)
+0x02	Item code from Master list (0xFFFF = any)
+0x06	Item quality
+0x1C	2nd Cube input code
+0x24	3rd Cube input code
+0x2C	4th Cube input code
+0x34	5th Cube input code
+0x3C	6th Cube input code
+0x44	7th Cube input code
+0x4C	1st Cube Output Code
+0x00	Properties (this is a bit field)
+0x02	Item Code from Master List
+0x06	Item Quality
+0x08	Type
+0x12	Prefix Code
+0x18	Suffix Code
+0xA0	2nd Cube Output Code
+0xF4	3rd Cube Output Code
For input codes:
11 10 9 8 7 6 5 4 3 2 1 0
NRU ELI EXC BAS UPG ITEM NOE ETH SOCK NOS CODE

For output codes
10 9 8 7 6 5 4 3 2 1 0
RCH RED ELI EXC REG REM UNS ETH SOCK MOD

For the output types, we have the following values:
1 Cow Portal
2 Pandemonium Portal
3 Pandemonium Finale Portal
4 Soft Portal

FF Use Type
FE Use Item
FC Item Code Found
For item quality, use the same values that you would find in the items saved in a character file.
I added the actual text strings to the data section of the dll at offset 0x0A6CF0 which loads at 0x6F6A6CF0. So, it looks like this in Ollydebug once you enter the strings:

Code: Select all

000A6CF0  50 61 6E 64 65 6D 6F 6E 69 75 6D 20 50 6F 72 74  Pandemonium Port
000A6D00  61 6C 00 50 61 6E 64 65 6D 6F 6E 69 75 6D 20 46  al.Pandemonium F
000A6D10  69 6E 61 6C 65 20 50 6F 72 74 61 6C 00 00 00 00  inale Portal....
Now you have to add the code which will look at your codes and put them into the cube output structure. The original cubemain parsing code code looks like this. I used red to signify changed code and green to signify new code.

Code: Select all

6F612962   68 8486696F    PUSH D2Common.6F698684         ; ASCII "Cow Portal"
6F612967   55             PUSH EBP
6F612968   E8 D31E0700    CALL D2Common.6F684840
6F61296D   83C4 08        ADD ESP,8
6F612970   85C0           TEST EAX,EAX
6F612972 . 75 42          JNZ SHORT D2Common.6F6129B6
Now we add a jump into our custom code. I tried to leave the original code intact as much as I could, so I changed it to a short jump to an unused location and then a long jump to the real custom code. See the change highlighted below.

Code: Select all

6F612962   68 8486696F    PUSH D2Common.6F698684         ; ASCII "Cow Portal"
6F612967   55             PUSH EBP
6F612968   E8 D31E0700    CALL D2Common.6F684840
6F61296D   83C4 08        ADD ESP,8
6F612970   85C0           TEST EAX,EAX
[color=red]6F612972  ^75 92          JNZ SHORT D2Common.6F612906[/color]
In addition, add the long jump to the custom code. The original code is like this:

Code: Select all

6F612906   90             NOP
Change it to long jump to our custom code at 0x6F68D57C:

Code: Select all

[color=red]6F612906   E9 71AC0700    JMP D2Common.6F68D57C[/color]
Now we add our custom code at 0x6F68D57C. The parser will now use type 2 and type 3 in the cube recipe structure to signify our custom portal codes.

Edit: The code below depends on D2Common relocating to 0x03DF0000 otherwise you will get an access violation when you load a character. If you are customizing your own DLL you will need to change the addresses accordingly. I am looking into modifying the relocation table so these values get fixed up when the D2Common relocates.

Edit: Changed the addresses to the base address of the DLL and not the relocation address for my system. Updated the code below to NOT rely on the fixup table and to use the known locations of the strings left on the stack from the previous call.

Edit: Changed location so edits will fit with Extended Levels Plug-In

Edit: Changed locations to match load locations for rebased D2Common.

Code: Select all

6F68D57C   814424 FC 6CE60000    ADD DWORD PTR SS:[ESP-4],0E66C                  ; Get Cow Portal String location from stack
6F68D584   83EC 04               SUB ESP,4                                       ; And add offset to our strings 0xE66C and emulate push.
6F68D587   55                    PUSH EBP                                        ; String to compare to
6F68D588   E8 B372FFFF           CALL D2Common.6F684840                          ; Case insensitive string compare
6F68D58D   83C4 08               ADD ESP,8                                       ; Clean up Stack
6F68D590   85C0                  TEST EAX,EAX                                    ; See if we have a match
6F68D592   75 0E                 JNZ SHORT D2Common.6F68D5A2                     ; If not goto next test
6F68D594   C646 08 02            MOV BYTE PTR DS:[ESI+8],2                       ; Set Output to type 2 (Pandemonium Portal)
6F68D598   A3 F0FFD46F           MOV DWORD PTR DS:[6FD4FFF0],EAX                 ; Clear Pandemonium Quest Status
6F68D59D  ^E9 D653F8FF           JMP D2Common.6F612978                           ; Jump back to original code for more processing
6F68D5A2   834424 FC 13          ADD DWORD PTR SS:[ESP-4],13                     ; Add offset for next custom string (pand finale)
6F68D5A7   83EC 04               SUB ESP,4                                       ; Emulate push of string location
6F68D5AA   55                    PUSH EBP                                        ; String to compare to
6F68D5AB   E8 9072FFFF           CALL D2Common.6F684840                          ; Case insensitive string compare
6F68D5B0   83C4 08               ADD ESP,8                                       ; Clean up stack
6F68D5B3   85C0                  TEST EAX,EAX                                    ; See if we have a match
6F68D5B5   ^0F85 0054F8FF        JNZ D2Common.6FD529B6                           ;  If not then goto next test in original code
6F68D5BB   C646 08 03            MOV BYTE PTR DS:[ESI+8],3                       ; Set output to type 3 (Pand Finale Portal)
6F68D5BF   A3 F0FFD46F           MOV DWORD PTR DS:[6FD4FFF0],EAX                 ; Clear Pandemonium Quest Status
6F68D5C4  ^E9 AF53F8FF           JMP D2Common.6F612978                           ; Jump back to original code for more processing
Opening the portals with the cube
Now D2Common will recognize your custom portal codes in the text file and set the structures properly in memory, but you still will not be able use them in game until you modify the cube code in D2Game to recognize the new output types. Also, you need somewhere to go, so I used the levels.txt file from v1.11 since it has the additional entries for the pandemonium areas.
Basically D2Game uses a lookup table to a jump vector with the output type as an index into the table. It also does a bit of boundary checking to make sure the index is within range. Since in the original code, the only tokenized value is 1, the only index that works is 1. We need to change this. First of all, we need to add a jump table to the rdata section of D2Game. Our custom code will go in 2 locations 0x6FC90005 and 0x6FC9000A. The new jump table looks like this at 0x6FD2BC20 or offset 0x0FBC20.

Code: Select all

6FD2BC20  00 00 00 00 00 00 C9 6F 05 00 C9 6F 0A 00 C9 6F  ......Éo .Éo..Éo
The first entry is the existing Cow Portal Code and the next two are my custom portal routines at the addresses mentioned above. Here are the jump vectors referenced from the output table. I needed to add the jumps to the additional 2 entries into the jump table. The original code is below:

Code: Select all

6FC90000   E9 ABC50000      JMP D2Game.6FC9C5B0  ; Original Cow Portal Code
6FC90005   90               NOP
6FC90006   90               NOP
6FC90007   90               NOP
6FC90008   90               NOP
6FC90009   90               NOP
6FC9000A   90               NOP
Now with my 2 additional jumps added to my custom code. You will notice that I am jumping to the same code for indexes 2 and 3 in the table. For the completed mod, there will be 2 separate routines for each entry, but for testing purposes I used the same routine. Since everything is pretty much setup though, I can just change the jump once I decide to insert additional code.

Code: Select all

6FC90000   E9 ABC50000      JMP D2Game.6FC9C5B0  ; Original Cow Portal Code
[color=red]6FC90005   E9 966A0900      JMP D2Game.6FD26B9A
6FC9000A   E9 916A0900      JMP D2Game.6FD26AA0
[/color]
Now we need to actually change the cube recipe code to use our new jump table and allow indexes 2 and 3 as valid values. The original code is like this:

Code: Select all

6FC90224   84C0             TEST AL,AL                  ; AL has the type code
6FC90226   74 22            JE SHORT D2Game.6FC9024A
6FC90228   3C 02            CMP AL,2                    ; 0 < AL < 2 else leave.
6FC9022A   73 1E            JNB SHORT D2Game.6FC9024A
6FC9022C   25 FF000000      AND EAX,0FF
6FC90231   8B0485 3C93D26F  MOV EAX,DWORD PTR DS:[EAX*4+6FD2933C]  ; Lookup in jump table
6FC90238   85C0             TEST EAX,EAX               ; Make sure an address is returned
6FC9023A   74 0E            JE SHORT D2Game.6FC9024A
6FC9023C   8B5424 18        MOV EDX,DWORD PTR SS:[ESP+18]
6FC90240   8B4C24 10        MOV ECX,DWORD PTR SS:[ESP+10]
6FC90244   FFD0             CALL EAX                   ; Jump to portal code.
Now change to our custom jump table. I had to binary edit this value and let windows fixup the code when I exited since this code can get relocated and the values are generated at run time.

Code: Select all

6FC90224   84C0             TEST AL,AL
6FC90226   74 22            JE SHORT D2Game.6FC9024A
[color=red]6FC90228   3C 04            CMP AL,4[/color]
6FC9022A   73 1E            JNB SHORT D2Game.6FC9024A
6FC9022C   25 FF000000      AND EAX,0FF
[color=red]6FC90231   8B0485 20BCD26F  MOV EAX,DWORD PTR DS:[EAX*4+6FD2BC20]
[/color]
Now we need to add our custom portal code for the Pandemonium Finale Portal at 0x6FD26AA0. This is modified Cow Portal code with irrelevant checks and quest status removed.

Edit: Updated this routine for Pandemonium Finale Portal. Removed Cow Portal Specific Items.

Code: Select all

6FD26AA0   83EC 0C          SUB ESP,0C                               ; Pandemonium Finale Code
6FD26AA3   53               PUSH EBX
6FD26AA4   56               PUSH ESI
6FD26AA5   57               PUSH EDI
6FD26AA6   8BF2             MOV ESI,EDX
6FD26AA8   8BF9             MOV EDI,ECX
6FD26AAA   56               PUSH ESI
6FD26AAB   E8 464BFFFF      CALL <JMP.&D2Common.#10424>
6FD26AB0   33C9             XOR ECX,ECX
6FD26AB2   8B4F 70          MOV ECX,DWORD PTR DS:[EDI+70]            ; Game Version?
6FD26AB5   85C9             TEST ECX,ECX                             ; See if 0 which is Classic D2
6FD26AB7   0F84 9A000000    JE D2Game.6FD26B57                       ; If Classic D2 then exit
6FD26ABD   33C9             XOR ECX,ECX
6FD26ABF   8A4F 6D          MOV CL,BYTE PTR DS:[EDI+6D]              ; Difficulty Level
6FD26AC2   80F9 02          CMP CL,2                                 ; See if Hell
6FD26AC5   0F85 8C000000    JNZ D2Game.6FD26B57                      ; If not Hell then exit
6FD26ACB   6A 00            PUSH 0                                   ; Reward Granted / Quest Complete
6FD26ACD   6A 28            PUSH 28                                  ; Eve of Destruction Quest
6FD26ACF   8B4488 10        MOV EAX,DWORD PTR DS:[EAX+ECX*4+10]
6FD26AD3   50               PUSH EAX                                 ; Quest History ptr (Quest and difficulty based)
6FD26AD4   E8 3B4EFFFF      CALL <JMP.&D2Common.#11107>              ; Check Quest State
6FD26AD9   85C0             TEST EAX,EAX
6FD26ADB   74 7A            JE SHORT D2Game.6FD26B57                 ; If not complete then exit
6FD26ADD   56               PUSH ESI                                 ; ptUnit
6FD26ADE   E8 C74BFFFF      CALL <JMP.&D2Common.#10342>              ; Get hRoom from ptUnit
6FD26AE3   8BD8             MOV EBX,EAX
6FD26AE5   85DB             TEST EBX,EBX                             ; Check if 0. (NULL pointer?)
6FD26AE7   74 6E            JE SHORT D2Game.6FD26B57                 ; If 0 then exit
6FD26AE9   53               PUSH EBX                                 ; hRoom
6FD26AEA   E8 6D4BFFFF      CALL <JMP.&D2Common.#10057>              ; Get level from hRoom
6FD26AEF   83F8 6D          CMP EAX,6D
6FD26AF2   75 63            JNZ SHORT D2Game.6FD26B57                ; If not Act V - Harrogath then exit
6FD26AF4   8D5424 10        LEA EDX,DWORD PTR SS:[ESP+10]
6FD26AF8   C74424 0C 00000000 MOV DWORD PTR SS:[ESP+C],0
6FD26B00   52               PUSH EDX
6FD26B01   56               PUSH ESI
6FD26B02   E8 034FFFFF      CALL <JMP.&D2Common.#10332>
6FD26B07   8D4424 0C        LEA EAX,DWORD PTR SS:[ESP+C]
6FD26B0B   8D5424 10        LEA EDX,DWORD PTR SS:[ESP+10]
6FD26B0F   6A 04            PUSH 4
6FD26B11   50               PUSH EAX
6FD26B12   68 00040000      PUSH 400
6FD26B17   6A 03            PUSH 3
6FD26B19   8BCB             MOV ECX,EBX
6FD26B1B   E8 20FEF6FF      CALL D2Game.6FC96940
6FD26B20   8B4424 0C        MOV EAX,DWORD PTR SS:[ESP+C]
6FD26B24   85C0             TEST EAX,EAX
6FD26B26   74 2F            JE SHORT D2Game.6FD26B57
6FD26B28   8B4C24 14        MOV ECX,DWORD PTR SS:[ESP+14]
6FD26B2C   8B5424 10        MOV EDX,DWORD PTR SS:[ESP+10]
6FD26B30   6A 00            PUSH 0
6FD26B32   6A 3C            PUSH 3C
6FD26B34   6A 00            PUSH 0
6FD26B36   68 88000000      PUSH 88
6FD26B3B   51               PUSH ECX
6FD26B3C   52               PUSH EDX
6FD26B3D   50               PUSH EAX
6FD26B3E   8BD6             MOV EDX,ESI
6FD26B40   8BCF             MOV ECX,EDI
6FD26B42   E8 A9D2FEFF      CALL D2Game.6FD13DF0                     ; Create Portal Object
6FD26B47   85C0             TEST EAX,EAX                             ; Check if portal failed
6FD26B49   74 0C            JE SHORT D2Game.6FD26B57                 ; If so, then exit
6FD26B4B   B8 01000000      MOV EAX,1                                ; Return 1
6FD26B50   5F               POP EDI
6FD26B51   5E               POP ESI
6FD26B52   5B               POP EBX
6FD26B53   83C4 0C          ADD ESP,0C
6FD26B56   C3               RETN
6FD26B57   56               PUSH ESI                                 ; Exit without success code.
6FD26B58   BA 14000000      MOV EDX,14
6FD26B5D   8BCE             MOV ECX,ESI
6FD26B5F   E8 1C59F9FF      CALL D2Game.6FCBC480
6FD26B64   33C0             XOR EAX,EAX                              ; Return 0
6FD26B66   5F               POP EDI
6FD26B67   5E               POP ESI
6FD26B68   5B               POP EBX
6FD26B69   83C4 0C          ADD ESP,0C
6FD26B6C   C3               RETN
Now we need to add our custom Pandemonium Portal code at 0x6FD26B9A. This is the Pandemonium Finale code with the addition of status byte which we are storing at 0x6FD4FFF0. I am using the caller address from the stack to derive this address to avoid any relocation issues. I did not need to use the quest status functions since the status is reset every game. I also added Nefarius’ random number code to generate the random portals.

Edit: This code has been added. Before both recipes pointed to the same portal generation code.

Code: Select all

6FD26B9A   83EC 0C          SUB ESP,0C                               ; Pandemonium Portal Code
6FD26B9D   53               PUSH EBX
6FD26B9E   56               PUSH ESI
6FD26B9F   57               PUSH EDI
6FD26BA0   8BF2             MOV ESI,EDX
6FD26BA2   8BF9             MOV EDI,ECX
6FD26BA4   56               PUSH ESI                                 ; ptUnit
6FD26BA5   E8 4C4AFFFF      CALL <JMP.&D2Common.#10424>              ; Get ptPlayerInfo from ptUnit
6FD26BAA   33C9             XOR ECX,ECX
6FD26BAC   8B4F 70          MOV ECX,DWORD PTR DS:[EDI+70]            ; Game Version?
6FD26BAF   85C9             TEST ECX,ECX                             ; See if 0 which is Classic D2
6FD26BB1   0F84 37010000    JE D2Game.6FD26CEE                       ; Exit if Classic D2
6FD26BB7   33C9             XOR ECX,ECX
6FD26BB9   8A4F 6D          MOV CL,BYTE PTR DS:[EDI+6D]              ; Difficulty Level
6FD26BBC   80F9 02          CMP CL,2
6FD26BBF   0F85 29010000    JNZ D2Game.6FD26CEE                      ; If not Hell then Exit
6FD26BC5   6A 00            PUSH 0                                   ; Reward Granted / Quest Complete
6FD26BC7   6A 28            PUSH 28                                  ; Eve of Destruction Quest
6FD26BC9   8B4488 10        MOV EAX,DWORD PTR DS:[EAX+ECX*4+10]
6FD26BCD   50               PUSH EAX                                 ; Quest History ptr (Quest and Difficulty based)
6FD26BCE   E8 414DFFFF      CALL <JMP.&D2Common.#11107>              ; Check Quest State
6FD26BD3   85C0             TEST EAX,EAX
6FD26BD5   0F84 13010000    JE D2Game.6FD26CEE                       ; If not Complete then Exit
6FD26BDB   56               PUSH ESI                                 ; ptUnit
6FD26BDC   E8 C94AFFFF      CALL <JMP.&D2Common.#10342>              ; Get hRoom from ptUnit
6FD26BE1   8BD8             MOV EBX,EAX
6FD26BE3   85DB             TEST EBX,EBX                             ; Check if 0 (NULL pointer?)
6FD26BE5   0F84 03010000    JE D2Game.6FD26CEE                       ; If so then exit
6FD26BEB   53               PUSH EBX                                 ; hRoom
6FD26BEC   E8 6B4AFFFF      CALL <JMP.&D2Common.#10057>              ; Get level from hRoom
6FD26BF1   83F8 6D          CMP EAX,6D                               ; See if Act V - Harrogath
6FD26BF4   0F85 F4000000    JNZ D2Game.6FD26CEE                      ; If not then exit
6FD26BFA   8B5424 18        MOV EDX,DWORD PTR SS:[ESP+18]            ; Pull caller address from stack
6FD26BFE   8B8A AAFD0B00    MOV ECX,DWORD PTR DS:[EDX+BFDAA]         ; Add offset to point to our status location
6FD26C04   83F9 07          CMP ECX,7                                ; See if all 3 portals open
6FD26C07   0F84 E1000000    JE D2Game.6FD26CEE                       ; If so exit
6FD26C0D   33C0             XOR EAX,EAX                              ; Clear number of unopened portals counter
6FD26C0F   0FBAE1 00        BT ECX,0                                 ; Test first portal
6FD26C13   72 06            JB SHORT D2Game.6FD26C1B                 ; If set the goto next test
6FD26C15   40               INC EAX                                  ; Increment number of unopened portals
6FD26C16   68 85010000      PUSH 185                                 ; Destination + status bitmask
6FD26C1B   0FBAE1 01        BT ECX,1                                 ; Test Second Portal
6FD26C1F   72 06            JB SHORT D2Game.6FD26C27                 ; If set the goto next test
6FD26C21   40               INC EAX                                  ; Increment number of unopened portals
6FD26C22   68 86020000      PUSH 286                                 ; Destination + status bitmask
6FD26C27   0FBAE1 02        BT ECX,2                                 ; Test Third Portal
6FD26C2B   72 06            JB SHORT D2Game.6FD26C33                 ; If set the goto next test
6FD26C2D   40               INC EAX                                  ; Increment number of unopened portals
6FD26C2E   68 87040000      PUSH 487                                 ; Destination + status bitmask
6FD26C33   85C0             TEST EAX,EAX                             ; Make sure there is at least 1 portal to open
6FD26C35   0F84 B3000000    JE D2Game.6FD26CEE                       ; If not then exit
6FD26C3B   83F8 01          CMP EAX,1                                ; See if there is only 1 portal to open
6FD26C3E   75 12            JNZ SHORT D2Game.6FD26C52                ; If not goto random number code
6FD26C40   58               POP EAX                                  ; Get parameter from stack
6FD26C41   33C9             XOR ECX,ECX
6FD26C43   8ACC             MOV CL,AH                                ; Set status mask
6FD26C45   25 FF000000      AND EAX,0FF                              ; Clear status mask from destination
6FD26C4A   098A AAFD0B00    OR DWORD PTR DS:[EDX+BFDAA],ECX          ; Update status
6FD26C50   EB 3B            JMP SHORT D2Game.6FD26C8D                ; Proceed to opening portal
6FD26C52   55               PUSH EBP                                 ; Start of random number code
6FD26C53   53               PUSH EBX                                 ; From Nefarius
6FD26C54   52               PUSH EDX
6FD26C55   50               PUSH EAX
6FD26C56   8BC8             MOV ECX,EAX                              ; Number of portals on stack
6FD26C58   8B46 24          MOV EAX,DWORD PTR DS:[ESI+24]            ; get ptUnit+24 (highseed) -> EAX
6FD26C5B   BA C590C66A      MOV EDX,6AC690C5                         ; set static seed for rand
6FD26C60   33ED             XOR EBP,EBP                              ; flush EBP
6FD26C62   F7E2             MUL EDX                                  ; 64bit multiplication of EDX (EDX&EAX)
6FD26C64   8B5E 20          MOV EBX,DWORD PTR DS:[ESI+20]            ; get ptUnit+20 (lowseed) -> EBX
6FD26C67   03C3             ADD EAX,EBX                              ; EAX+EBX
6FD26C69   11EA             ADC EDX,EBP                              ; EDX+0+overflow (i.e. carryover)
6FD26C6B   33D2             XOR EDX,EDX                              ; flush EDX
6FD26C6D   F7F1             DIV ECX                                  ; 64bit division of ECX (EDX&EAX, but EDX is 0, so it divides by 0.EAX!)
6FD26C6F   83C2 04          ADD EDX,4                                ; EDX contains the main result - Add offset for push statements above
6FD26C72   8B0494           MOV EAX,DWORD PTR SS:[ESP+EDX*4]         ; Pull Parameter from stack
6FD26C75   59               POP ECX                                  ; Restore saved registers
6FD26C76   5A               POP EDX
6FD26C77   5B               POP EBX
6FD26C78   5D               POP EBP
6FD26C79   C1E1 02          SHL ECX,2                                ; 4 * number of portals on stack
6FD26C7C   03E1             ADD ESP,ECX                              ; Clean up stack
6FD26C7E   33C9             XOR ECX,ECX
6FD26C80   8ACC             MOV CL,AH                                ; Get status mask
6FD26C82   098A AAFD0B00    OR DWORD PTR DS:[EDX+BFDAA],ECX          ; Update Status
6FD26C88   25 FF000000      AND EAX,0FF                              ; Clear status mask from destination
6FD26C8D   50               PUSH EAX                                 ; Save destination
6FD26C8E   8D5424 14        LEA EDX,DWORD PTR SS:[ESP+14]
6FD26C92   C74424 10 000000>MOV DWORD PTR SS:[ESP+10],0
6FD26C9A   52               PUSH EDX                                 ; ptGame?
6FD26C9B   56               PUSH ESI                                 ; ptUnit
6FD26C9C   E8 694DFFFF      CALL <JMP.&D2Common.#10332>              ; Get hPath from ptUnit?
6FD26CA1   8D4424 10        LEA EAX,DWORD PTR SS:[ESP+10]
6FD26CA5   8D5424 14        LEA EDX,DWORD PTR SS:[ESP+14]            ; Arg2 - X Position from hPath
6FD26CA9   6A 04            PUSH 4                                   ; Arg6 - 4
6FD26CAB   50               PUSH EAX                                 ; Arg5 - Y position?
6FD26CAC   68 00040000      PUSH 400                                 ; Arg4 - 400
6FD26CB1   6A 03            PUSH 3                                   ; Arg3 - 3
6FD26CB3   8BCB             MOV ECX,EBX                              ; Arg1 -
6FD26CB5   E8 86FCF6FF      CALL D2Game.6FC96940
6FD26CBA   8B4424 10        MOV EAX,DWORD PTR SS:[ESP+10]
6FD26CBE   85C0             TEST EAX,EAX
6FD26CC0   74 2C            JE SHORT D2Game.6FD26CEE
6FD26CC2   8B4C24 18        MOV ECX,DWORD PTR SS:[ESP+18]            ; Y Position from hPath?
6FD26CC6   5A               POP EDX                                  ; Get Destination
6FD26CC7   6A 00            PUSH 0                                   ; Arg9 - 0 = non permanent
6FD26CC9   6A 3C            PUSH 3C                                  ; Arg8 - Object ID / 3C = Red Portal
6FD26CCB   6A 00            PUSH 0                                   ; Arg7 - unit / source pointer
6FD26CCD   52               PUSH EDX                                 ; Arg6 - Level ID
6FD26CCE   8B5424 20        MOV EDX,DWORD PTR SS:[ESP+20]            ; X Position from hPath?
6FD26CD2   51               PUSH ECX                                 ; Arg5 - Y position from hPath
6FD26CD3   52               PUSH EDX                                 ; Arg4 - X position from hPath
6FD26CD4   50               PUSH EAX                                 ; Arg3 - ptRoom
6FD26CD5   8BD6             MOV EDX,ESI                              ; Arg2 - ptPlayer
6FD26CD7   8BCF             MOV ECX,EDI                              ; Arg1 - ptGame
6FD26CD9   E8 12D1FEFF      CALL D2Game.6FD13DF0                     ; Create Object
6FD26CDE   85C0             TEST EAX,EAX
6FD26CE0   74 0C            JE SHORT D2Game.6FD26CEE                 ; If not successful then exit
6FD26CE2   B8 01000000      MOV EAX,1                                ; Return success
6FD26CE7   5F               POP EDI
6FD26CE8   5E               POP ESI
6FD26CE9   5B               POP EBX
6FD26CEA   83C4 0C          ADD ESP,0C
6FD26CED   C3               RETN
6FD26CEE   56               PUSH ESI                                 ; Begin non-success here
6FD26CEF   BA 14000000      MOV EDX,14
6FD26CF4   8BCE             MOV ECX,ESI
6FD26CF6   E8 8557F9FF      CALL D2Game.6FCBC480                     ; "I can't" message?
6FD26CFB   33C0             XOR EAX,EAX                              ; Return failure
6FD26CFD   5F               POP EDI
6FD26CFE   5E               POP ESI
6FD26CFF   5B               POP EBX
6FD26D00   83C4 0C          ADD ESP,0C
6FD26D03   C3               RETN
6FD26D04   90               NOP
Now we will need to modify the object creation function since it looks specifically for the moo moo farm destination and will not create the portal if the destination is different. The original code is like this:

Code: Select all

6FD13E3B   85C0             TEST EAX,EAX
6FD13E3D   74 27            JE SHORT D2Game.6FD13E66
6FD13E3F   837C24 30 27     CMP DWORD PTR SS:[ESP+30],27  ; Moo moo farm
6FD13E44   75 07            JNZ SHORT D2Game.6FD13E4D
6FD13E46   837C24 38 3C     CMP DWORD PTR SS:[ESP+38],3C  ; Red Portal
6FD13E4B   74 19            JE SHORT D2Game.6FD13E66
I just add a jump to my custom code to check for additional destinations.

Code: Select all

6FD13E3B   85C0             TEST EAX,EAX
6FD13E3D   74 27            JE SHORT D2Game.6FD13E66
[color=red]6FD13E3F   E9 0C2C0100      JMP D2Game.6FD26B6E[/color]
6FD13E44   75 07            JNZ SHORT D2Game.6FD13E4D
6FD13E46   837C24 38 3C     CMP DWORD PTR SS:[ESP+38],3C
6FD13E4B   74 19            JE SHORT D2Game.6FD13E66
The custom code is below. This now checks for the moo moo farm since it was replaced by the jump along with the 4 additional levels for the pandemonium areas.
Edit: Moved this code to fit with additional portal creation functions.

Code: Select all

6FD26B6E   50               PUSH EAX
6FD26B6F   8B4424 34        MOV EAX,DWORD PTR SS:[ESP+34]
6FD26B73   83F8 27          CMP EAX,27
6FD26B76   74 0E            JE SHORT D2Game.6FD26B86
6FD26B78   3D 85000000      CMP EAX,85
6FD26B7D   7C 14            JL SHORT D2Game.6FD26B93
6FD26B7F   3D 88000000      CMP EAX,88
6FD26B84   77 0D            JA SHORT D2Game.6FD26B93
6FD26B86   837C24 3C 3C     CMP DWORD PTR SS:[ESP+3C],3C
6FD26B8B   75 06            JNZ SHORT D2Game.6FD26B93
6FD26B8D   58               POP EAX
6FD26B8E  ^E9 D3D2FEFF      JMP D2Game.6FD13E66
6FD26B93   58               POP EAX
6FD26B94  ^E9 B4D2FEFF      JMP D2Game.6FD13E4D
Okay, now the portal will open with cube recipe, but if you mouse over the portal the destination will be incorrect since this appears to be hard coded by location. I added Kingpins modification from his portal tutorial to fix this.

Code: Select all

6FD13FD0   8BF0             MOV ESI,EAX
6FD13FD2   85F6             TEST ESI,ESI
6FD13FD4   75 0A            JNZ SHORT 6FD13FE0
6FD13FD6   5F               POP EDI
Change it to jump to the custom code.

Code: Select all

[color=red]6FD13FD0   E9 B12A0100      JMP D2Game.6FD26A86
6FD13FD5   90               NOP
[/color]
Kingpin’s custom code to display the correct destination on the red portal:

Code: Select all

6FD26A86   50               PUSH EAX
6FD26A87   51               PUSH ECX
6FD26A88   8B4E 14          MOV ECX,DWORD PTR DS:[ESI+14]
6FD26A8B   8B4424 38        MOV EAX,DWORD PTR SS:[ESP+38]
6FD26A8F   8841 04          MOV BYTE PTR DS:[ECX+4],AL
6FD26A92   59               POP ECX
6FD26A93   58               POP EAX
6FD26A94  ^E9 47D5FEFF      JMP D2Game.6FD13FE0
Now you can open a red portal with a cube recipe to a custom location and it will correctly display the destination location.
To emulate V1.11 functionality one still needs to create the maps for the new levels since the areas in the txt file do not work as is. However, this should demonstrate how one could make a mod with cube recipes that open portals to custom areas.

Taking it a step further
After I originally posted this, Joel made a excellent suggestion to soft code the red portal and add a cube output code of lvl= so that a mod maker can specify a destination for the red portal without resorting to code editing.
The first step is to add another output type to the cube parser. I will assign it to type 4 and store the destination level in the item quality field of the cube output structure. I will store the Act in the item type field of the structure.

Code: Select all

[color=black]6F68D57C   814424 FC 6CE60000    ADD DWORD PTR SS:[ESP-4],0E66C                  ; Get Cow Portal String location from stack
6F68D584   83EC 04               SUB ESP,4                                       ; And add offset to our strings 0xE66C and emulate push.
6F68D587   55                    PUSH EBP                                        ; String to compare to
6F68D588   E8 B372FFFF           CALL D2Common.6F684840                          ; Case insensitive string compare
6F68D58D   83C4 08               ADD ESP,8                                       ; Clean up Stack
6F68D590   85C0                  TEST EAX,EAX                                    ; See if we have a match
6F68D592   75 0E                 JNZ SHORT D2Common.6F68D5A2                     ; If not goto next test
6F68D594   C646 08 02            MOV BYTE PTR DS:[ESI+8],2                       ; Set Output to type 2 (Pandemonium Portal)
6F68D598   A3 F0FFD46F           MOV DWORD PTR DS:[6FD4FFF0],EAX                 ; Clear Pandemonium Quest Status
6F68D59D  ^E9 D653F8FF           JMP D2Common.6F612978                           ; Jump back to original code for more processing
6F68D5A2   834424 FC 13          ADD DWORD PTR SS:[ESP-4],13                     ; Add offset for next custom string (pand finale)
6F68D5A7   83EC 04               SUB ESP,4                                       ; Emulate push of string location
6F68D5AA   55                    PUSH EBP                                        ; String to compare to
6F68D5AB   E8 9072FFFF           CALL D2Common.6F684840                          ; Case insensitive string compare
6F68D5B0   83C4 08               ADD ESP,8                                       ; Clean up stack
6F68D5B3   85C0                  TEST EAX,EAX                                    ; See if we have a match
[/color][color=red]6F68D5B5   75 0E                 JNZ SHORT D2Common.6F68D5C5                     ; If not then goto next test[/color]
[color=black]6F68D5B7   C646 08 03            MOV BYTE PTR DS:[ESI+8],3                       ; Set output to type 3 (Pand Finale Portal)
6F68D5BB   A3 F0FFD46F           MOV DWORD PTR DS:[6FD4FFF0],EAX                 ; Clear Pandemonium Quest Status
6F68D5C0  ^E9 B353F8FF           JMP D2Common.6F612978                           ; Jump back to original code for more processing
[/color][color=green]6F68D5C5   8B7C24 FC             MOV EDI,DWORD PTR SS:[ESP-4]
6F68D5C9   83C7 1A               ADD EDI,1A                                      ; Add offset to next custom string (lvl=)
6F68D5CC   56                    PUSH ESI                                        ; Save ESI
6F68D5CD   8BF5                  MOV ESI,EBP                                     ; EBP - String to compare to
6F68D5CF   B9 04000000           MOV ECX,4                                       ; Only check 4 characters
6F68D5D4   F3:A6                 REPE CMPS BYTE PTR ES:[EDI],BYTE PTR DS:[ESI]   ; Compare strings
6F68D5D6   5E                    POP ESI                                         ; Restore ESI
6F68D5D7  ^0F85 D953F8FF         JNZ D2Common.6F6129B6                           ; If we do not match goto next test in original code
6F68D5DD   3E:807D 04 00         CMP BYTE PTR DS:[EBP+4],0                       ; Make sure we have another parameter
6F68D5E2  ^0F84 CE53F8FF         JE D2Common.6F6129B6                            ; If we do not match goto next test in original code
6F68D5E8   83C5 03               ADD EBP,3                                       ; Skip over already checked chars.  Used 3 for loop to work
6F68D5EB   B9 05000000           MOV ECX,5                                       ; Only check 4 characters
6F68D5F0   8BC5                  MOV EAX,EBP
6F68D5F2   40                    INC EAX                                         ; Point to next character
6F68D5F3   8038 41               CMP BYTE PTR DS:[EAX],41                        ; See if we found Capital A
6F68D5F6  ^E0 FA                 LOOPDNE SHORT D2Common.6F68D5F2                 ; Loop until we find it or CX reaches 0
6F68D5F8   85C9                  TEST ECX,ECX                                    ; See if we dropped out of loop
6F68D5FA  ^0F84 B653F8FF         JE D2Common.6F6129B6                            ; If we did not find (A) then go back to original code
6F68D600   8BD8                  MOV EBX,EAX                                     ; Save pointer to A
6F68D602   C603 00               MOV BYTE PTR DS:[EBX],0                         ; Clear out A to separate parameters
6F68D605   807B 01 00            CMP BYTE PTR DS:[EBX+1],0                       ; See if another parameter follows A
6F68D609  ^0F84 A753F8FF         JE D2Common.6F6129B6                            ; If not then go back to original code
6F68D60F   45                    INC EBP
6F68D610   55                    PUSH EBP
6F68D611   E8 8573FFFF           CALL D2Common.6F68499B                          ; Convert ASCII to numeric
6F68D616   83C4 04               ADD ESP,4                                       ; Clean up stack
6F68D619   C646 08 04            MOV BYTE PTR DS:[ESI+8],4                       ; Set to type 4 (Custom Portal)
6F68D61D   66:8946 06            MOV WORD PTR DS:[ESI+6],AX                      ; Save Destination ID in Quality field
6F68D621   43                    INC EBX                                         ; Adjust pointer to point to Act parameter
6F68D622   53                    PUSH EBX
6F68D623   E8 7373FFFF           CALL D2Common.6F68499B                          ; Convert ASCII to numeric
6F68D628   83C4 04               ADD ESP,4                                       ; Clean up stack
6F68D62B   66:8946 02            MOV WORD PTR DS:[ESI+2],AX                      ; Save Act in item type field.
6F68D62F   33DB                  XOR EBX,EBX                                     ; Set remaining parameters to 0
6F68D631  ^E9 4253F8FF           JMP D2Common.6F612978                           ; Go back to original code for more checks[/color]
Now we need to add the new recipe code string at 0x000A6D1E:

Code: Select all

[color=black]
000A6CF0  50 61 6E 64 65 6D 6F 6E 69 75 6D 20 50 6F 72 74  Pandemonium Port
000A6D00  61 6C 00 50 61 6E 64 65 6D 6F 6E 69 75 6D 20 46  al.Pandemonium F
000A6D10  69 6E 61 6C 65 20 50 6F 72 74 61 6C 00 [color=green]6C 76 6C[/color]  inale Portal.[color=green]lvl[/color]
000A6D20  [color=green]3D[/color] 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  [color=green]=[/color]...............
[/color]
After that is in place, then the game will need to recognize the new output type. We need to add another location of 0x6FC90AD4 to the jump table.

Code: Select all

[color=black]
000FBC20  00 00 00 00 00 00 C9 6F 05 00 C9 6F 0A 00 C9 6F  ......Éo .Éo..Éo
000FBC30  [color=green]D4 0A C9 6F[/color] 00 00 00 00 00 00 00 00 00 00 00 00  [color=green]Ô.Éo[/color]............
[/color]
I like to add another jump to the actual code at 0x6FC90AD4 so I have the option to easily move the code without changing the jump table. Like this:

Code: Select all

[color=red]
6FC90AD4   E9 2D620900      JMP D2Game.6FD26D06
[/color]
We need to let the type 4 output code pass as a valid cube recipe so we change the comparison code at 0x6FC90228 below:

Code: Select all

[color=red]
6FC90228   3C 05            CMP AL,5
[/color]
Edit: The following section is changed to put the destination level checking back in.

Instead of replacing the object creation code destination level check at 0x6FD13E3F with NOPs, we will just expand the range and remove the specific check for the Cow Portal. Here is the code that we modified above which will remain unchanged:

Code: Select all

6FD13E3F   E9 2A2D0100  JMP D2Game.6FD26B6E
6FD13E44   75 07             JNZ SHORT D2Game.6FD13E4D
This code now checks for the destination level between 1 and 999. Even though the parser in D2Common should catch anything out of range, it does not hurt to retain some boundary checking here during the creation. Below is my original destination code at 0x6FD26B6E with the range checking modified:

Code: Select all

6FD26B6E   50               PUSH EAX
6FD26B6F   8B4424 34        MOV EAX,DWORD PTR SS:[ESP+34]
[color=red]6FD26B73   90               NOP
6FD26B74   90               NOP
6FD26B75   90               NOP
6FD26B76   90               NOP
6FD26B77   90               NOP
6FD26B78   83F8 01          CMP EAX,1
6FD26B7B   90               NOP
6FD26B7C   90               NOP[/color]
6FD26B7D   7C 14            JL SHORT D2Game.6FD26B93
[color=red]6FD26B7F   3D E7030000      CMP EAX,3E7[/color]
6FD26B84   77 0D            JA SHORT D2Game.6FD26B93
6FD26B86   837C24 3C 3C     CMP DWORD PTR SS:[ESP+3C],3C
6FD26B8B   75 06            JNZ SHORT D2Game.6FD26B93
6FD26B8D   58               POP EAX
6FD26B8E  ^E9 D3D2FEFF      JMP D2Game.6FD13E66
6FD26B93   58               POP EAX
6FD26B94  ^E9 B4D2FEFF      JMP D2Game.6FD13E4D
Now we add the actual portal code at 0x6FD26D06.

Code: Select all

[color=green]
6FD26D06   > 83EC 0C              SUB ESP,0C
6FD26D09   . 53                   PUSH EBX
6FD26D0A   . 56                   PUSH ESI                              ;  ESI - Pointer to cube output structure
6FD26D0B   . 57                   PUSH EDI
6FD26D0C   . 8BF2                 MOV ESI,EDX                           ;  EDX - ptUnit
6FD26D0E   . 8BF9                 MOV EDI,ECX
6FD26D10   . 56                   PUSH ESI                              ;  ptUnit
6FD26D11   . E8 9449FFFF          CALL <JMP.&D2Common.#10342>           ;  Get hRoom from ptUnit
6FD26D16   . 8BD8                 MOV EBX,EAX                           ;  Save hRoom
6FD26D18   . 85DB                 TEST EBX,EBX                          ;  See if 0
6FD26D1A   . 0F84 89000000        JE D2Game.6FD26DA9                    ;  If so then exit
6FD26D20   . 53                   PUSH EBX                              ;  hRoom
6FD26D21   . E8 3649FFFF          CALL <JMP.&D2Common.#10057>           ;  Get Level from hRoom
6FD26D26   . 50                   PUSH EAX                              ;  Level
6FD26D27   . E8 384AFFFF          CALL <JMP.&D2Common.#10001>           ;  Get Act from level
6FD26D2C   . 8B4C24 04            MOV ECX,DWORD PTR SS:[ESP+4]          ;  Get cube output structure pointer
6FD26D30   . 33D2                 XOR EDX,EDX
6FD26D32   . 66:36:8B51 02        MOV DX,WORD PTR SS:[ECX+2]            ;  Get Act from cube recipe
6FD26D37   . 3BC2                 CMP EAX,EDX                           ;  See if current Act matches cube recipe
6FD26D39   . 75 6E                JNZ SHORT D2Game.6FD26DA9             ;  If not then exit
6FD26D3B   . 8D5424 10            LEA EDX,DWORD PTR SS:[ESP+10]
6FD26D3F   . C74424 0C 00000000   MOV DWORD PTR SS:[ESP+C],0
6FD26D47   . 52                   PUSH EDX                              ;  ptGame?
6FD26D48   . 56                   PUSH ESI                              ;  ptUnit
6FD26D49   . E8 BC4CFFFF          CALL <JMP.&D2Common.#10332>           ;  Get hPath from ptUnit
6FD26D4E   . 8D4424 0C            LEA EAX,DWORD PTR SS:[ESP+C]
6FD26D52   . 8D5424 10            LEA EDX,DWORD PTR SS:[ESP+10]
6FD26D56   . 6A 04                PUSH 4                                ; /Arg4 = 00000004
6FD26D58   . 50                   PUSH EAX                              ; |Arg3
6FD26D59   . 68 00040000          PUSH 400                              ; |Arg2 = 00000400
6FD26D5E   . 6A 03                PUSH 3                                ; |Arg1 = 00000003
6FD26D60   . 8BCB                 MOV ECX,EBX                           ; |
6FD26D62   . E8 D9FBF6FF          CALL D2Game.6FC96940                  ; \D2Game.6FC96940
6FD26D67   . 8B4C24 04            MOV ECX,DWORD PTR SS:[ESP+4]          ;  Get cube output pointer
6FD26D6B   . 33D2                 XOR EDX,EDX
6FD26D6D   . 66:36:8B51 06        MOV DX,WORD PTR SS:[ECX+6]            ;  Get destination from cube recipe
6FD26D72   . 85D2                 TEST EDX,EDX                          ;  See if 0
6FD26D74   . 74 33                JE SHORT D2Game.6FD26DA9              ;  If so then exit
6FD26D76   . 8B4424 0C            MOV EAX,DWORD PTR SS:[ESP+C]          ;  Get ptRoom
6FD26D7A   . 85C0                 TEST EAX,EAX                          ;  See if 0
6FD26D7C   . 74 2B                JE SHORT D2Game.6FD26DA9              ;  If so exit.
6FD26D7E   . 8B4C24 14            MOV ECX,DWORD PTR SS:[ESP+14]         ;  Y Position from hPath
6FD26D82   . 6A 00                PUSH 0                                ;  Non Permanant portal
6FD26D84   . 6A 3C                PUSH 3C                               ;  Red Portal
6FD26D86   . 6A 00                PUSH 0                                ;  Unit / Source ptr
6FD26D88   . 52                   PUSH EDX                              ;  Destination ID
6FD26D89   . 8B5424 20            MOV EDX,DWORD PTR SS:[ESP+20]         ;  X Position from hPath
6FD26D8D   . 51                   PUSH ECX                              ;  Y position
6FD26D8E   . 52                   PUSH EDX                              ;  X Position
6FD26D8F   . 50                   PUSH EAX                              ;  ptRoom
6FD26D90   . 8BD6                 MOV EDX,ESI                           ;  ptPlayer
6FD26D92   . 8BCF                 MOV ECX,EDI                           ;  ptGame
6FD26D94   . E8 57D0FEFF          CALL D2Game.6FD13DF0                  ;  Create portal Object
6FD26D99   . 85C0                 TEST EAX,EAX                          ;  See if successful
6FD26D9B   . 74 0C                JE SHORT D2Game.6FD26DA9              ;  If not then exit
6FD26D9D   . B8 01000000          MOV EAX,1                             ;  Success
6FD26DA2   . 5F                   POP EDI
6FD26DA3   . 5E                   POP ESI
6FD26DA4   . 5B                   POP EBX
6FD26DA5   . 83C4 0C              ADD ESP,0C
6FD26DA8   . C3                   RETN
6FD26DA9   > 56                   PUSH ESI
6FD26DAA   . BA 14000000          MOV EDX,14                            ;  Start of failure code
6FD26DAF   . 8BCE                 MOV ECX,ESI
6FD26DB1   . E8 CA56F9FF          CALL D2Game.6FCBC480                  ;  "I can't" statement
6FD26DB6   . 33C0                 XOR EAX,EAX                           ;  Failure
6FD26DB8   . 5F                   POP EDI
6FD26DB9   . 5E                   POP ESI
6FD26DBA   . 5B                   POP EBX
6FD26DBB   . 83C4 0C              ADD ESP,0C
6FD26DBE   . C3                   RETN
[/color]
Now you will be able to soft code a portal with a cube recipe. I added an additional parameter to tell the code which Act you level belongs to. I use this parameter to reject the transmutation if you are in the wrong Act. The syntax would be like this for output1 of cubemain file:
lvl=133A4
lvl= tells the parser that it is a custom portal.
133 is the destination level ID
A is the Act parameter marker
4 is the act for the destination level. This is 0 based so this would be Act 5.

The level ID is limited to 3 characters so your max level is 999. This can be changed to 4 characters by changing the line at 0x6FDCD5E1 in the parsing code and the upper limit at 0x6FD26B7F in the object creation code. You can also limit portal activation by difficulty level using the min diff field in the recipe. Also note that the syntax is exactly like I have stated above and the A marker must be capitalized or the recipe will not work. I tried putting enough checks in so that syntax errors in the output will not be catastrophic, but one cannot catch everything.
That’s about it to cubing red portals. Thanks to Kingpin for his really helpful tutorial, Nefarius for the random number code and everyone who contributes to the Phrozen Keep for their invaluable knowledge.

The files for this tutoral are here in the file center. Note that this post contains the most up to date information.

The VillageIdiot

Edit: Added note about relocations of strings for portal names in cube recipe. Also added version to title.

Edit: Cleaned up Pandemonium Finale function. Added Pandemonium Portal function. Relocated some code to fit with Extended Levels Plug-In.

Edit: Add 'soft' portal code so that destination can be specified in cube formula instead of hard coded.

Edit: Fixed a jmp that needed to be NOP'd out.

Edit: Fixed use of term relocate to be more proper.

Edit: Added destination level check back to soft portal section. Fixed some formatting. Added note about using D2Client and D2Common pre-edited with extended levels plug-in.

Edit: Added link to file center for tutorial files. I also have discovered a minor bug. The portal status does not reset when a character is reloaded and the whole game needs a restart to reset the portal status.

Edit: Forgot to add the search string for the custom portals. Added recommendation to use rebased D2Common.

Edit: Changed D2Common addresses to use rebased locations. Added code to reset the Pandemonium Quest status when parsing the Pandemonium recipes. This fixes the issue where you would need to close the game once you open the portals to get them to reopen.
Last edited by VillageIdiot on Sat Apr 22, 2006 6:22 pm, edited 11 times in total.

User avatar
Joel
Retired staff
Dominion
Posts: 6921
Joined: Mon May 27, 2002 7:19 am
Location: Orsay
Contact:

Hand-picked

Post by Joel » Thu Sep 22, 2005 1:17 pm

can it be easier to well add a cube modifier called lvl so we can use recipes like :

hpot + mpot -> lvl=127

Opening a portal to level of ID 127 ?

This will be even more customizable than hardcoded string ?
"How much suffering, mortal, does it take before you lose your grace?"
Shadow Empire (coming soon) | forum

User avatar
VillageIdiot
Posts: 44
Joined: Thu Sep 22, 2005 5:46 am
Location: Bay Area, CA

Re: Custom Portals with Cube Recipes (long)

Post by VillageIdiot » Fri Sep 23, 2005 3:42 am

I really did not think of it that way as I looking more at the v1.11 approach to it.

You could something like that where you could make a custom cube output code like lvl where it takes the destination ID as the parameter. Modifying the parser to recognize the string lvl and extracting the parameter would be fairly simple. Where we store this information for later use is a little more difficult. Unless my math is incorrect and it could very well be, the type value moves from offset 8 to offset 4 in the output1 section of the cube recipe structure. I have not traced through the character loadup code to verify this 100%. Anyway you can make the code lvl be a type 2 output and then possibly store the destination at offset 2 (item code) or offset 6 (item quality) of the output1 portion of the cube recipe structure.

You would also have to modify object creation code to allow more destination level IDs other than what I have listed.

In addition, you would have to make sure there are checks in place so that the cube recipe will not work in acts that are not valid for the destination ID.

User avatar
Ulmo
Forum Regular
Angel
Posts: 860
Joined: Fri Jun 04, 2004 5:04 pm

Post by Ulmo » Fri Sep 23, 2005 12:51 pm

Nice first post :)
I also have some additional notes on the cube input/output structs (recognize and itemtype, a set/unique particular item, ...). Should I exhume them ?

User avatar
HashCasper
Junior Member
Champion of the Light
Posts: 257
Joined: Mon Apr 28, 2003 6:46 am
Location: Baton Rouge, LA
Contact:

Post by HashCasper » Thu Oct 13, 2005 2:15 am

I had been looking for something like this. I have insane amounts of dungeons in my mod, at least the one i was working on before it was lost in a HD crash.

This proves very valuable to me. Although I wanted something more like what joel said.

In my mod i have dungeons going 20+ levels deep, after which you get to the next area, etc.

By having one unique monster in each level of the dungeon, which upon bieng killed drops a recepie scroll, containg a recepie for when cubed with a town portal scroll, opens a level back to THAT level ID. This way, if you have to stop and save your game, you can at least get back to the level you were in last.

I am also only a beginner when it comes to messing with comipled code. A little more work on this would be awesome. spawning portals to custom lvl ids.
"We have a point to work to, to make our nation huge; make our home stretch around the world, increase our army too. We must take over the world, make it our own, the Earth we must control, so you can't F#CK it anymore." --Mathew Chalk--

User avatar
VillageIdiot
Posts: 44
Joined: Thu Sep 22, 2005 5:46 am
Location: Bay Area, CA

Re: Custom Portals with Cube Recipes (long) V1.10

Post by VillageIdiot » Fri Oct 21, 2005 9:16 pm

I have been really busy the last few weeks, but I do intend to try to implement Joel's suggestion. I have run into an issue with text string relocations which I have added a note about in the original post. If I manually edit the relocation table, the string pointers relocate correctly but then D2Client crashes on loading.

Once I figure this out, it should not be too hard to implement Joel's more general approach to opening portals with the cube along with pre-modified DLL set.

User avatar
HashCasper
Junior Member
Champion of the Light
Posts: 257
Joined: Mon Apr 28, 2003 6:46 am
Location: Baton Rouge, LA
Contact:

Post by HashCasper » Mon Oct 24, 2005 2:34 am

word man, when ever you get the chance, its all good. Thanks again for this method/tutorial.
"We have a point to work to, to make our nation huge; make our home stretch around the world, increase our army too. We must take over the world, make it our own, the Earth we must control, so you can't F#CK it anymore." --Mathew Chalk--

User avatar
VillageIdiot
Posts: 44
Joined: Thu Sep 22, 2005 5:46 am
Location: Bay Area, CA

Re: Custom Portals with Cube Recipes (long) V1.10

Post by VillageIdiot » Wed Jan 25, 2006 4:06 am

I hate to resurrect an old post, but I could not leave it with broken code. I assume nobody really tried to do this since the code was broken and I do not see many complaints, though I finally got around to fixing the relocation problem with D2Common. See the edit in the original post for the update.

I will also continue to update as I add functionality.

This may seem like a total noob question, but how do I upload?
I cannot find any area in File Planet and I have this 'tutorial' in a .rtf file along with the edited DLL files that I can provide for people who would like to try any of this.

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: Custom Portals with Cube Recipes (long) V1.10

Post by Nefarius » Wed Jan 25, 2006 9:28 am

@VilllageIdiot - look at your PMs
''(...) The game can basically be considered unhackable. '' - Blizzard Entertainment (30th May 2000)
Black Omen Productions | MetalStorm: Progress Report | Screenshots

User avatar
zysus
Posts: 94
Joined: Sat Apr 17, 2004 1:48 pm
Spain

Re: Custom Portals with Cube Recipes (long) V1.10

Post by zysus » Mon Jan 30, 2006 6:50 pm

Pardon to follow a Post so old, I to lower pack Cubing_Red_Portals_Tutorial.zip that to have in dowloas in filePlanet, my problem to be when to insert the Dlls and txt, to only open in cube the Pandemonium Finale Porta l, the rest not to open itself, monsters does not even leave in the pandemonium? pardon but now I am a little lost
pardon by my badly language, Hi have to translate with dictionary

User avatar
VillageIdiot
Posts: 44
Joined: Thu Sep 22, 2005 5:46 am
Location: Bay Area, CA

Re: Custom Portals with Cube Recipes (long) V1.10

Post by VillageIdiot » Tue Jan 31, 2006 3:48 am

The files in the tutorial will only open the portal to Uber Tristram. The area also has not been populated with the Uber Prime Evils so what you are seeing is correct if I understand your post correctly.

The files in the download were only meant to show what was done in the tutorial on opening a red portal and it is not a full implementation of the v1.11 Uber Quest. To get a full Uber Quest implementation there is alot more code which needs to be added and is not in the tutorial.

Edit: Updated post above adding full Uber Quest portal functionality. The levels still need to be built, but now the portals will open mirroring V1.11b functionality.

Edit: Updated code for 'soft' portals where the destination can be specified in the cube formula instead of hard coding.
Last edited by VillageIdiot on Sun Feb 12, 2006 6:44 am, edited 2 times in total.

User avatar
Joel
Retired staff
Dominion
Posts: 6921
Joined: Mon May 27, 2002 7:19 am
Location: Orsay
Contact:

Hand-picked

Post by Joel » Thu Feb 16, 2006 9:27 am

Great stuff :)
One question tough.
To implement the soft portals, shoudl I go trough every step or onyl the latest ?
"How much suffering, mortal, does it take before you lose your grace?"
Shadow Empire (coming soon) | forum

User avatar
talonrage
Retired staff
Arch-Angel
Posts: 1511
Joined: Sat Jul 20, 2002 11:05 pm

Re: [1.10] Custom Portals with Cube Recipes

Post by talonrage » Thu Feb 16, 2006 1:49 pm

I added the actual text strings to the data section of the dll at offset 0x0A6CF0 which relocates to 0x6FDE6CF0.
My stupid question of the year :oops: . How exactly do you "relocate" offset 0x0A6CF0 to 0x6FDE6CF0. (IE: How do you change the format/structure from 0x0A6CF0 to display as 0x6FDE6CF0).

I'd like to implament this into my Mod , but I'm having problems with this "minor" step.

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: [1.10] Custom Portals with Cube Recipes

Post by Nefarius » Thu Feb 16, 2006 2:55 pm

You add the base address at which the module resides in memory to the offset.

So, for D2Game.dll in 1.10, you'd do this.

Offset + 6FC30000 = Address
''(...) The game can basically be considered unhackable. '' - Blizzard Entertainment (30th May 2000)
Black Omen Productions | MetalStorm: Progress Report | Screenshots

User avatar
VillageIdiot
Posts: 44
Joined: Thu Sep 22, 2005 5:46 am
Location: Bay Area, CA

Re: [1.10] Custom Portals with Cube Recipes

Post by VillageIdiot » Fri Feb 17, 2006 1:29 am

Joel,

The tutorial is meant to go through linearly so to get the "soft" portals to work you need to do the whole thing. I have a sent a set of pre-modified DLL files for upload so hopefully these will posted within the next couple of days.

Talonrage,

The offset is where the code would be if you chose to edit the file in Ollydebug. The relocation is where the code would be if the DLL were loaded into memory which is as Nefarius put it at Base + Offset.
Last edited by VillageIdiot on Fri Feb 17, 2006 1:33 am, edited 1 time in total.

User avatar
Myhrginoc
Retired Admin
Cherub
Posts: 12062
Joined: Sat May 25, 2002 7:28 am
Location: Percussion U

Hand-picked

Re: [1.10] Custom Portals with Cube Recipes

Post by Myhrginoc » Fri Feb 17, 2006 3:00 am

Actually Ollydbg will get you to both address forms. The first form is if you use View | File to open the dll from the file. The second form is if you use File | Attach to hook onto a running game and look at the dll in memory. File | Attach is more useful for determining program behavior, because you can set breakpoints and see how dynamic assignments actually play out.

It may seem like nitpicky semantics, but I would hesitate to use "relocation" in this context. Relocation of a dll means something very specific in Windows, the operating system has assigned an arbitrary loading address to the dll because the preferred loading address doesn't point to a block of available memory large enough to contain the dll. Ever since the first 1.10 beta, at least one Blizzard dll gets shoved around like this (which is why I uploaded rebased versions of key dlls). I call the first form "file offset" and the second form "memory address" myself.
Do the right thing. It will gratify some people and astonish the rest.
~ Mark Twain
Run Diablo II in any version for mods: tutorial
The Terms of Service!! Know them, abide by them, and enjoy the forums at peace.
The Beginner's Guide v1.4: (MS Word | PDF) || Mod Running Scripts || TFW: Awakening

User avatar
VillageIdiot
Posts: 44
Joined: Thu Sep 22, 2005 5:46 am
Location: Bay Area, CA

Re: [1.10] Custom Portals with Cube Recipes

Post by VillageIdiot » Fri Feb 17, 2006 6:57 am

It's not really nitpicky as we should use the correct terms when referring to technical items. Relocation is definitely not the right term in this case especially when I am referring to addresses based on the preferred load address. I should have said offset which refers to its location in the file and resident location which is where it resides in memory which usually is the preferred load address + offset. Relocation is what happens the DLL does not load at the preferred address and then absolute pointers need to be "fixed up" by the OS to make everything work again. This new address is what I generally mean when I mention relocation in the tutorial.

In Ollydebug, 90% of the time I use the DLLs memory resident so that I can track progress and manipulate the program. I do find it easier to edit the .data and .rdata sections using the offset instead of the memory resident locations and that is why I specify both locations.

I also changed a misuse of the relocation term in the tutorial above. Relocation now only used when the DLL addresses are not based on the preferred address.

I would also like to hear from anyone who tries this since Zysus is having a hard time getting the portals to work.
Last edited by VillageIdiot on Fri Feb 17, 2006 7:06 am, edited 2 times in total.

User avatar
talonrage
Retired staff
Arch-Angel
Posts: 1511
Joined: Sat Jul 20, 2002 11:05 pm

Re: [1.10] Custom Portals with Cube Recipes

Post by talonrage » Fri Feb 17, 2006 11:18 am

I'm no "coder" as I've stated , so I'll just leave the tech stuff to the real knowledgable individuals. The best I can do with "code" is follow exact instructions. I'll just wait for the updated version and sing praises (or yell insults like a back-seat driver :twisted: ). This will be a great Birthday gift if its released today :D .

User avatar
zysus
Posts: 94
Joined: Sat Apr 17, 2004 1:48 pm
Spain

Re: [1.10] Custom Portals with Cube Recipes

Post by zysus » Fri Feb 17, 2006 1:50 pm

VillageIdiot";p="259213" wrote:I would also like to hear from anyone who tries this since Zysus is having a hard time getting the portals to work.
if, until now I have been able to VillageIdiot and to work perfectly pandemoniun Portals, the problem to be when to try to open to other Red_portal in others lvls different that not to be pandemonium.

to disappear [ vis ] of the town lvls to execute localizacion error, in the map, [ vis ] in the maps to become crazy and not to work, however, the pandemonium portal perfectly works, I to think that to lack small pieces of code in some of dlls, "posible idea in d2win.dll", but I am not possibly safe, since to be very good in code editing and I am not very Newie in CE
pardon by my badly language, Hi have to translate with dictionary

User avatar
VillageIdiot
Posts: 44
Joined: Thu Sep 22, 2005 5:46 am
Location: Bay Area, CA

Re: [1.10] Custom Portals with Cube Recipes

Post by VillageIdiot » Sat Feb 18, 2006 1:23 am

I have a pretty good feeling that the way I have created the Pandemonium Portals with preset levels in the tutorial will work. I would like to hear of anyone trying the soft portals. So far, I have only tested it with opening the Matron's Den (V1.11 version) in Act 1, Matron's Den (preset version) in Act 5 and the Mausoleum in Act 1. All of these work.

I do know that you need to be careful with the areas that you access via the red portal. Presets seem to work well. Mazes seem to work as long as you keep in mind that the vis entries need to be correct. (i.e. the Crypt should have a vis0 which links to something on the current act.) I also have not tried outdoor areas. Also know that I am not a very experienced map modder myself and am only learning about it now so there are probably other factors to your areas not opening.

User avatar
Joel
Retired staff
Dominion
Posts: 6921
Joined: Mon May 27, 2002 7:19 am
Location: Orsay
Contact:

Hand-picked

Re: [1.10] Custom Portals with Cube Recipes

Post by Joel » Sat Feb 18, 2006 10:08 am

VillageIdiot";p="259169" wrote:Joel,

The tutorial is meant to go through linearly so to get the "soft" portals to work you need to do the whole thing. I have a sent a set of pre-modified DLL files for upload so hopefully these will posted within the next couple of days.
Ok, premodifed DLL is no use for me as I use D2Mod system anyway. SO I'll have the dirty task to implement that myself ;)
"How much suffering, mortal, does it take before you lose your grace?"
Shadow Empire (coming soon) | forum

User avatar
Myhrginoc
Retired Admin
Cherub
Posts: 12062
Joined: Sat May 25, 2002 7:28 am
Location: Percussion U

Hand-picked

Re: [1.10] Custom Portals with Cube Recipes

Post by Myhrginoc » Sat Feb 18, 2006 4:50 pm

Well shoot, Joel, you might as well make a d2mod version! :mrgreen: I am working on levels for TFW: Awakening and need this technique myself, but I am a few weeks away from looking at the code so it appears you would beat me to it!
Do the right thing. It will gratify some people and astonish the rest.
~ Mark Twain
Run Diablo II in any version for mods: tutorial
The Terms of Service!! Know them, abide by them, and enjoy the forums at peace.
The Beginner's Guide v1.4: (MS Word | PDF) || Mod Running Scripts || TFW: Awakening

User avatar
talonrage
Retired staff
Arch-Angel
Posts: 1511
Joined: Sat Jul 20, 2002 11:05 pm

Re: [1.10] Custom Portals with Cube Recipes

Post by talonrage » Sun Feb 19, 2006 5:57 am

Ok , I've downloaded and added the Red Portal Plugin with the edited Dll's and added them to my mod. The Plugin works fine , I can open the portal in any "Preset Map" without problems. (I havent tested it on random maps).

But a problem appears with Activating the Portal to Tristrim via the normal Quest. This is when you get the scroll , go to Akara , and then activate the stones. When you finish activating them and the lightning drops , at the moment the portal opens an assertion Error :

20:28:27.609 -------- FILE: D2Client\ENGINE\Gfx.cpp LINE: 1243 --------

Halt
Location : D2Client\ENGINE\Gfx.cpp, line #1243
Expression : [GFX.CPP] Mode (1) is not used by object (0)
20:28:40.687 Stack bytes:

If I exit the game right after the Quest Update activates but before the Red Portal appears and crashes the game , I can reenter the game and the portal is there and usable with no problems.

I am using the D2Mod plugin , but I havent edited any of the original DLL's and I dont see this as a problem. I have also disabled the Extended Levels Plugin for D2mod and the same thing happens.

However , all the other "red Portals" in the game work fine. I have tested the ones spawned from :

Act 1 - Cow Level Portal
Act 2 - Arcane sancuary after killing Summoner.
Act 3 - Durance of Hate 3 after killing Meph.
Act 4 - Town from Tyrial to Harrogoth after killing Diablo.
Act 5 - Anya after freeing her and she opens the Portal to Nilithak.
Act 5 - Portal to End Game spawned from tyrial after Baal dies.

Currently I have made a "workaround" by removing the Cairn Stones in Stoney Field and making a Red portal to tristrim using a cubing formula that uses the Scroll of Inifuss with a scroll of Town Portal. I added a waypoint to tristrim (using the waypoint d2mod plugin) , and this wethod works like a charm.

When setting up the Mod , I ONLY used your the Edited DLL's. I didn't add any of the txt files to my Mod (as I dont spawn the other portals) , but I did add the currect lines to CubeMain.txt.

I didnt edit lvltypes.txt , becasue I didnt see what exactly you changed for these (and I use custom edited ones) , so this could very well be the problem.

As I said , everything works fine except for the Cairn Stones Portal to Tristrim when first activated.

I am using the files you E-Mailed me.
Last edited by talonrage on Sun Feb 19, 2006 6:06 am, edited 1 time in total.

User avatar
VillageIdiot
Posts: 44
Joined: Thu Sep 22, 2005 5:46 am
Location: Bay Area, CA

Re: [1.10] Custom Portals with Cube Recipes

Post by VillageIdiot » Sun Feb 19, 2006 7:45 am

I tried this and I am seeing the same thing. It's very strange since I do not think that I should have affected this code. It looks like it is crashing betweem the custom missile code and the Tristram Portal creation code. I guess I have some more work to do and I will update when I know more.

User avatar
Joel
Retired staff
Dominion
Posts: 6921
Joined: Mon May 27, 2002 7:19 am
Location: Orsay
Contact:

Hand-picked

Re: [1.10] Custom Portals with Cube Recipes

Post by Joel » Sun Feb 19, 2006 1:14 pm

Myhrginoc";p="259428" wrote:Well shoot, Joel, you might as well make a d2mod version! :mrgreen: I am working on levels for TFW: Awakening and need this technique myself, but I am a few weeks away from looking at the code so it appears you would beat me to it!
Well ^^ I'll try to get it done but i dunno when :p
My main problem is how does it interacts with portal.dll, extlvl.dll and maybe other plugin :s
"How much suffering, mortal, does it take before you lose your grace?"
Shadow Empire (coming soon) | forum

Post Reply

Return to “Code Editing”