Introduction
I go by the handle 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?  Be warned this mod is not for the meek as there is a lot of code editing involved.
Adding your text strings to the parsing code
You should start with the extended levels plug-in modified D2Common and D2Client files.  You should also 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.
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:
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.
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.
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 92          JNZ SHORT D2Common.6F612906
 
In addition, add the long jump to the custom code.  The original code is like this:
6F612906   90             NOP
Change it to long jump to our custom code at 0x6F68D57C:
6F612906   E9 71AC0700    JMP D2Common.6F68D57C
Now we add our custom code at 0x6F68D57C to make room for the extended levels plug-in.  The parser will now use type 2 and type 3 in the cube recipe structure to signify our custom portal codes.  The code is similar to the Cow Portal parsing code except for pushing the compare string location.  Since these values are not relative and not fixed up when the code relocates, this code will fail on relocation.  To get around this, I use the Cow Portal location which is still on the stack and add an offset to it.  I then pass this value to string comparison code by adjusting ESP.
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.
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:
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.
6FC90000   E9 ABC50000      JMP D2Game.6FC9C5B0  ; Original Cow Portal Code
6FC90005   E9 966A0900      JMP D2Game.6FD26B9A
6FC9000A   E9 916A0900      JMP D2Game.6FD26AA0
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:
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.
6FC90224   84C0             TEST AL,AL
6FC90226   74 22            JE SHORT D2Game.6FC9024A
6FC90228   3C 04            CMP AL,4
6FC9022A   73 1E            JNB SHORT D2Game.6FC9024A
6FC9022C   25 FF000000      AND EAX,0FF
6FC90231   8B0485 20BCD26F  MOV EAX,DWORD PTR DS:[EAX*4+6FD2BC20]
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.
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.
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:
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.
6FD13E3B   85C0             TEST EAX,EAX
6FD13E3D   74 27            JE SHORT D2Game.6FD13E66
6FD13E3F   E9 2A2D0100      JMP D2Game.6FD26B6E
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.
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.
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.
6FD13FD0   E9 B12A0100      JMP D2Game.6FD26A86
6FD13FD5   90               NOP
Kingpin’s custom code to display the correct destination on the red portal:
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.
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   75 0E                 JNZ SHORT D2Common.6F68D5C5                     ; If not then goto next test
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
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
Now we need to add the new recipe code string at 0x000A6D1E: 
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 6C 76 6C  inale Portal.lvl
000A6D20  3D 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  =...............
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.
000FBC20  00 00 00 00 00 00 C9 6F 05 00 C9 6F 0A 00 C9 6F  ......Éo.Éo..Éo
000FBC30  D4 0A C9 6F 00 00 00 00 00 00 00 00 00 00 00 00  Ô.Éo............
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:
6FC90AD4   E9 2D620900      JMP D2Game.6FD26D06
We need to let the type 4 output code pass as a valid cube recipe so we change the comparison code at 0x6FC90228 below:
6FC90228   3C 05            CMP AL,5
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:
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:
6FD26B6E   50               PUSH EAX
6FD26B6F   8B4424 34        MOV EAX,DWORD PTR SS:[ESP+34]
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
6FD26B7D   7C 14            JL SHORT D2Game.6FD26B93
6FD26B7F   3D E7030000      CMP EAX,3E7
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.
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
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.