First, some basics. This is a typical header for a mod's .tp2 installer. This text file is called something like setup-mymod.tp2 or just mymod.tp2. It is executed by a copy of WeiDU.exe renamed to the .tp2 name (setup-mymod.exe).
BACKUP ~adjports/backup~ //Where the mod backs up files AUTHOR ~www.shsforums.net/forum/584-portrait-mods/~ //Where to submit error reports VERSION ~v1~ README ~adjports/adjports.html~BACKUP and AUTHOR are mandatory. VERSION is optional but recommended. It prints the version number in the WeiDU.log file that keeps track of all mods installed. README will ask the user whether to view the readme.
There is also a subheader for each component. Some mods will have only one component; others will have many. In simple form, a component for changing an NPC's colours would look something like this:
BEGIN ~My Portrait Mod~ DESIGNATED 100 ACTION_FOR_EACH npc IN ~aerbod01~ ~aerie12~ ~cwsaer~ BEGIN ACTION_IF FILE_EXISTS_IN_GAME ~%npc%.cre~ BEGIN COPY_EXISTING ~%npc%.cre~ ~override~ //Aerie WRITE_BYTE 0x2c 67 //Metal color (shiny gold, was 27 gray) WRITE_BYTE 0x2d 47 //Minor color (pure dark red, was 41 dark brown) WRITE_BYTE 0x2e 2 //Major color (dark gold, was 37 dark dirty yellow) WRITE_BYTE 0x2f 12 //Skin color (light carnation pink) WRITE_BYTE 0x30 16 //Leather color (silver gold, was 93 dark cement gray) WRITE_BYTE 0x31 30 //Armor color (light iron gray) WRITE_BYTE 0x32 115 //Hair color (sunkissed, was 3 light gold) BUT_ONLY_IF_IT_CHANGES END ENDBEGIN specifies the component name when installing; it is mandatory. DESIGNATED is optional but highly recommended - it hardcodes a component number as appears in the WeiDU.log. It also allows other mods to detect the component and automated programs (such as the BiG World Project) to install the component. If you don't use DESIGNATED, your component numbers will run consecutively from zero. Thus, they will change as you add and remove new components, which isn't a good thing for automation.
The main block is a loop consisting of ACTION_FOR_EACH, ACTION_IF FILE_EXISTS_IN_GAME and COPY_EXISTING. In layman's terms, this feeds a list of filenames to WeiDU (ACTION_FOR_EACH) and says: if each file exists in the game (ACTION_IF ...) then copy it to the override folder (COPY_EXISTING) and make some changes. As for those changes, we are making a series of one-byte changes to the file (WRITE_BYTE). We use an editor (either Near Infinity or DLTCEP) to look up the existing values if we want, the IESDP to find the offsets and the desired colour gradients and their numeric values. Once that is done, we simply plug all that into our patch.
BUT_ONLY_IF_IT_CHANGES (BUT_ONLY for short) says only to copy the file if it is modified by the patches. If, for example, it already has the colour values specified, it will simply be skipped. Two ENDs close out our ACTION loops. That is all you need to do to change an NPC's colours in its simplest form. Like other mods, this will not change any NPC who has already joined the party, as they are stored in the save game.
Here is a slightly more complex example. This component gives two choices for changing Aerie's garb, thus uses SUBCOMPONENT.
BEGIN ~Aerdrie's Garb~ SUBCOMPONENT ~Aerie as Priestess of Aerdrie/Baervan~ DESIGNATED 50 REQUIRE_PREDICATE ((NOT FILE_EXISTS_IN_GAME ~_plat06.itm~) AND ((FILE_EXISTS_IN_GAME ~aerie12.cre~) OR (FILE_EXISTS_IN_GAME ~cwsaer.cre~))) ~Aerie not available~In this case, SUBCOMPONENT actually indicates the component name, and BEGIN indicates one of several choices the user has. Other choices will follow with their own BEGIN/SUBCOMPONENT sections.
REQUIRE_PREDICATE is a good way to ensure the NPC is present. If the conditions aren't fulfilled, the installer will skip the component and go to the next one. In this case, we want to skip the component if Tutu. Aerie is present but not joinable on Tutu (all BG2 files are). Therefore, we give it a Tutu item _plat06.itm and NOT FILE_EXISTS_IN_GAME for the condition. With the AND we also make sure at least version of Aerie is present (either aerie12 or cwsaer) and pair those with an OR. The whole condition looks like this in simpler terms: (NOT Tutu) AND (aerie12 OR cwsaer). This is followed by a text message (~Aerie not available~) that will display when the installer skips the component.
COPY ~adjports/bmp/t-aeri1l.bmp~ ~override~ ~adjports/bmp/t-aeri1m.bmp~ ~override~ ~adjports/bmp/t-aeri1s.bmp~ ~override~The section above copies the portraits for this component. Most people don't do it this way - they overwrite the existing portraits. This demonstrates a way of avoiding that to keep the previous portraits available for other purposes.
ACTION_FOR_EACH tda IN ~aeriend1~ ~aeriend2~ ~aerifnd1~ BEGIN ACTION_IF FILE_EXISTS_IN_GAME ~%tda%.2da~ BEGIN COPY_EXISTING ~%tda%.2da~ ~override~ //ToB epilogue tables SET_2DA_ENTRY 1 0 1 ~*T-AERI1L~ BUT_ONLY END ENDThat section replaces Aerie's Throne of Bhaal epilogue portrait in the relevant tables. It is only relevant for ToB NPCs, and then only if you don't overwrite the existing portrait.
COPY_EXISTING ~spin745.spl~ ~override~ //Aerie Portrait Spell PATCH_IF SOURCE_SIZE > 0x71 BEGIN READ_LONG 0x64 hf //Extended header offset READ_SHORT 0x68 hc //Extended header count READ_LONG 0x6a fb //Feature block offset FOR (i1 = 0; i1 < hc; i1 += 1) BEGIN //Cycle through extended headers READ_SHORT (0x28 * i1 + hf + 0x1e) fc //Feature count READ_SHORT (0x28 * i1 + hf + 0x20) fs //Feature offset FOR (i2 = fs; i2 < (fs + fc); i2 += 1) BEGIN //Cycle through ability effects READ_SHORT (i2 * 0x30 + fs) opcode READ_LONG (i2 * 0x30 + fs + <img src='http://www.shsforums.net/public/style_emoticons/<#EMO_DIR#>/cool.png' class='bbc_emoticon' alt='8)' /> param2 PATCH_IF (opcode = 107) AND (param2 = 0) BEGIN //If small portrait WRITE_ASCII (i2 * 0x30 + fs + 0x14) ~t-aeri1s~ #8 END PATCH_IF (opcode = 107) AND (param2 = 1) BEGIN //If large portrait WRITE_ASCII (i2 * 0x30 + fs + 0x14) ~t-aeri1m~ #8 END END END END BUT_ONLYThe above section is relevant only to Aerie, as there is a spell effect that morphs her back into avariel form after being an ogre. It specifies her small and medium portraits. I won't detail the code, but it basically loops through all effects attached to the spell and changes those where it finds portraits.
ACTION_FOR_EACH npc IN ~aerbod01~ ~aerie12~ ~cwsaer~ BEGIN ACTION_IF FILE_EXISTS_IN_GAME ~%npc%.cre~ BEGIN COPY_EXISTING ~%npc%.cre~ ~override~ //Aerie PATCH_IF SOURCE_SIZE > 0x2d3 BEGIN //Ensures valid .cre file size WRITE_BYTE 0x2c 67 //Metal color (shiny gold, was 27 gray) WRITE_BYTE 0x2d 47 //Minor color (pure dark red, was 41 dark brown) WRITE_BYTE 0x2e 2 //Major color (dark gold, was 37 dark dirty yellow) WRITE_BYTE 0x2f 12 //Skin color (light carnation pink) WRITE_BYTE 0x30 16 //Leather color (silver gold, was 93 dark cement gray) WRITE_BYTE 0x31 30 //Armor color (light iron gray) WRITE_BYTE 0x32 115 //Hair color (sunkissed, was 3 light gold) END BUT_ONLY_IF_IT_CHANGES END ENDFinally, the code above changes her colours as in the first example. For the second subcomponent, we'd copy and paste the same code above and just change the BEGIN name (e.g. to ~Baervan's Garb~), increase the DESIGNATED component number (e.g. to 60), change all the portraits we're copying (e.g. from t-aeri1 to t-aeri2) and finally, change the colours in the second set of code to match her second portrait, following the techique above (i.e. look them up in the IESDP).
Here's the full code for a slightly simpler example. It provides two different examples for Branwen (based on portraits with either brown or purple robes).
There's no ToB Branwen nor morphing spell for her, so we just need to copy the relevant portraits and assign them and the appropriate colours on her .cre files. We do, however, have to check via REQUIRE_PREDICATE that Branwen is present, so we look for Tutu (_branwe5.cre), BGT (branwe5.cre) and at least 4 different mod versions. Apparently, Branwen is a popular gal - you could in theory have a party with you and 5 other Branwens! Ah, the joys of redundant mods, er, I mean user choices.
BEGIN ~Brown Robe~ SUBCOMPONENT ~Branwen as Priestess of Tempus~ DESIGNATED 100 REQUIRE_PREDICATE ((FILE_EXISTS_IN_GAME ~_branwe5.cre~) OR (FILE_EXISTS_IN_GAME ~branwe5.cre~) OR (FILE_EXISTS_IN_GAME ~cb3513bw.cre~) OR (FILE_EXISTS_IN_GAME ~dl#bwn.cre~) OR (FILE_EXISTS_IN_GAME ~ttbran.cre~) OR (FILE_EXISTS_IN_GAME ~wlbran0.cre~)) ~Branwen not available~ COPY ~adjports/bmp/t-bran1l.bmp~ ~override~ ~adjports/bmp/t-bran1m.bmp~ ~override~ ~adjports/bmp/t-bran1s.bmp~ ~override~ ACTION_FOR_EACH npc IN ~_branwe~ ~_branwe5~ ~branwe~ ~branwe5~ ~cb3513bw~ ~dl#bwn~ ~ttbran~ ~wlbran0~ BEGIN ACTION_IF FILE_EXISTS_IN_GAME ~%npc%.cre~ BEGIN COPY_EXISTING ~%npc%.cre~ ~override~ //Branwen PATCH_IF SOURCE_SIZE > 0x2d3 BEGINWeiDU documentation WRITE_BYTE 0x2c 67 //Metal color (shiny gold, was 27 gray) WRITE_BYTE 0x2d 47 //Minor color (pure dark red, was 41 dark brown) WRITE_BYTE 0x2e 2 //Major color (dark gold, was 37 dark dirty yellow) WRITE_BYTE 0x2f 12 //Skin color (light carnation pink) WRITE_BYTE 0x30 16 //Leather color (silver gold, was 93 dark cement gray) WRITE_BYTE 0x31 30 //Armor color (light iron gray) WRITE_BYTE 0x32 115 //Hair color (sunkissed, was 3 light gold) WRITE_ASCII 0x34 ~t-bran1s~ #8 //Small portrait WRITE_ASCII 0x3c ~t-bran1m~ #8 //Large portrait END BUT_ONLY_IF_IT_CHANGES END END BEGIN ~Purple Robe~ SUBCOMPONENT ~Branwen as Priestess of Tempus~ DESIGNATED 105 REQUIRE_PREDICATE ((FILE_EXISTS_IN_GAME ~_plat06.itm~) OR (FILE_EXISTS_IN_GAME ~plat06.itm~)) ~Branwen not found~ COPY ~adjports/bmp/t-bran2l.bmp~ ~override~ ~adjports/bmp/t-bran2m.bmp~ ~override~ ~adjports/bmp/t-bran2s.bmp~ ~override~ ACTION_FOR_EACH npc IN ~_branwe~ ~_branwe5~ ~branwe~ ~branwe5~ ~cb3513bw~ ~dl#bwn~ ~ttbran~ ~wlbran0~ BEGIN ACTION_IF FILE_EXISTS_IN_GAME ~%npc%.cre~ BEGIN COPY_EXISTING ~%npc%.cre~ ~override~ //Branwen PATCH_IF SOURCE_SIZE > 0x2d3 BEGIN WRITE_BYTE 0x2c 67 //Metal color (shiny gold, was 27 gray) WRITE_BYTE 0x2d 47 //Minor color (pure dark red, was 41 dark brown) WRITE_BYTE 0x2e 60 //Major color (dark purple, was 37 dark dirty yellow) WRITE_BYTE 0x2f 12 //Skin color (light carnation pink) WRITE_BYTE 0x30 16 //Leather color (silver gold, was 93 dark cement gray) WRITE_BYTE 0x31 104 //Armor color (dark chrome purple) WRITE_BYTE 0x32 115 //Hair color (sunkissed, was 3 light gold) WRITE_ASCII 0x34 ~t-bran2s~ #8 //Small portrait WRITE_ASCII 0x3c ~t-bran2m~ #8 //Large portrait END BUT_ONLY_IF_IT_CHANGES END ENDAnd that's it for a simple component with two different portrait options.
Folks have also asked me how to change the colours of items. Say you've changed the hue of an NPC's special armour in your portrait and you want the item to follow suit. I won't get into how to change the icons. BAM Batcher and your favourite graphics program can do that easily enough, and there are probably other tutorials for that. This changes the colours on the paperdoll and animation or avatar when equipping the item.
BEGIN ~Ankheg Plate matches its icon~ DESIGNATED 25 ACTION_FOR_EACH ank IN ~_plat06~ ~plat06~ BEGIN ACTION_IF FILE_EXISTS_IN_GAME ~%ank%.itm~ BEGIN COPY_EXISTING ~%ank%.itm~ ~override~ //Ankheg Plate PATCH_IF SOURCE_SIZE > 0x71 BEGIN //Ensures valid .itm file size READ_LONG 0x6a eq_off //Equipped effects offset READ_SHORT 0x70 eq_cnt //Equipped effects count FOR (i1 = 0; i1 < eq_cnt; i1 += 1) BEGIN READ_SHORT (i2 * 0x30 + eq_off) opcode READ_LONG (i2 * 0x30 + eq_off + <img src='http://www.shsforums.net/public/style_emoticons/<#EMO_DIR#>/cool.png' class='bbc_emoticon' alt='8)' /> param2 PATCH_IF (opcode = 7) AND (param2 = 5) BEGIN //If armor color WRITE_SHORT (i2 * 0x30 + eq_off + 4) 7 //Dark metallic green END PATCH_IF (opcode = 7) AND (param2 = 4) BEGIN //If strap color WRITE_SHORT (i2 * 0x30 + eq_off + 4) 1 //Light pure gold END PATCH_IF (opcode = 7) AND (param2 = 0) BEGIN //If belt color WRITE_SHORT (i2 * 0x30 + eq_off + 4) 36 //Light dirty yellow END END END BUT_ONLY END ENDFairly straightforward example for the Ankheg Plate. Following the ACTION... logic described above, the code loops through each version of the item, looks for equipped effects matching certain conditions (opcode 7 for colour effects) and sets them to the relevant colours when it finds the right effects (for armour, strap and belt in this case).
Edited by Miloch, 29 January 2012 - 08:11 AM.