Jump to content

jmerry's Tweak Collection


Recommended Posts

In another bit of weirdness, the "Death Ward against Aec'Letec" component just tripped on Detect Alignment:

ERROR: illegal 2-byte read from offset 2402 of 2402-byte file SPWI202.SPL
ERROR: [SPWI202.SPL] -> [override/SPWI202.SPL] Patching Failed (COPY) (Failure("SPWI202.SPL: read out of bounds"))

I suspect aTweaks' "Make alignment detection spells more accurate" might he the source of the problem, that's on it. But uh I gotta ask why would a mod for Death Ward have anything to do with Detect Alignment? Wouldn't that be in the complete opposite side of what the mod should look up?

Link to comment

It wouldn't have anything to do with Detect Alignment, but it's a global scan through all spells and items so it can trip on anything malformed. The only thing doing any reads is the built-in CLONE_EFFECT function, which obscures everything behind a black box of how that works.

Right, Detect Alignment ... actually, SPWI202 is Detect Evil in the base game. And I have no idea what aTweaks does to it, because that appears to be an SHS-hosted mod and I don't see it on the SHS github.

Thinking on what might be happening here... first, 2402 bytes? Vanilla Detect Evil is only 298. Whatever aTweaks is doing here greatly expands the file. Anywhay, CLONE_EFFECT has to scan through stuff, and in my use it's looking for matches in opcode (2 bytes, beginning of effect substructure) and parameter 2 (4 bytes, some way in). My guess here is a mis-indexed file; it says there's at least one more effect than there actually is, so the numbering points to an effect that starts at the end of the file, the function tries to read that, and splat.

Wait, I found links.

Old version: https://github.com/FredrikLindgren/aTweaks

Newer fork: https://github.com/TotoR115/aTweaks

Here's the code for what the component does (old version):

Spoiler

 

// Detect Evil

ACTION_FOR_EACH ~file~ IN                                                          // for each of the following files
              ~SPPR104~                                                            // Detect Evil (Divine version)
              ~SPWI202~                                                            // Detect Evil (Arcane version)
              ~SPCL212~                                                            // Detect Evil (Paladin innate)
              ~SPIN696~                                                            // Moon Dog Sight (Moon Dog figurine)
             ~CDDETEVL~                                                            // G2 BG2 Fixpack Detect Evil (shell spell)
BEGIN                                                                              // execute the following
ACTION_IF FILE_EXISTS_IN_GAME ~%file%.spl~ BEGIN                                   // if the designated file with a SPL extension exists
COPY_EXISTING ~%file%.spl~ ~override~
PATCH_IF (%SOURCE_SIZE% > 0x71) THEN BEGIN                                         // file size check
// =============================================================================== // the actual work starts from here
  READ_LONG  0x64 "abil_off" ELSE 0
  READ_SHORT 0x68 "abil_num" ELSE 0
  READ_LONG  0x6a "fx_off"   ELSE 0
  SET "delta" = 0
  FOR (index = 0 ; index < abil_num ; index = index + 1) BEGIN
    READ_SHORT ("%abil_off%" + 0x1e + (0x28 * "%index%")) "abil_fx_num"
    READ_SHORT ("%abil_off%" + 0x20 + (0x28 * "%index%")) "abil_fx_idx"
    SET "abil_fx_idx" = ("%abil_fx_idx%" + "%delta%")
    WRITE_SHORT ("%abil_off%" + 0x20 + (0x28 * "%index%")) "%abil_fx_idx%"
    FOR (index2 = 0 ; index2 < abil_fx_num ; index2 = index2 + 1) BEGIN
     READ_SHORT ("%fx_off%" +        (0x30 * ("%abil_fx_idx%" + "%index2%"))) "opcode"
      PATCH_IF ("%opcode%" = 115) BEGIN // clone effect #115: Detect Alignment
        READ_ASCII ("%fx_off%" +        (("%abil_fx_idx%" + "%index2%") * 0x30)) "clone_fx" (0x30)
        SET "index2"= "%abil_fx_num%" // kills loop
        INSERT_BYTES            ("%fx_off%" +        ("%abil_fx_idx%" * 0x30)) 0x30             // new effect
          WRITE_EVALUATED_ASCII ("%fx_off%" +        ("%abil_fx_idx%" * 0x30)) "%clone_fx%"     // cloned effect
          WRITE_SHORT           ("%fx_off%" +        ("%abil_fx_idx%" * 0x30)) "177"            // effect #177: use EFF File
          WRITE_LONG            ("%fx_off%" + 0x04 + ("%abil_fx_idx%" * 0x30)) "3"              // param1: 3 (IDS Entry - MASK_EVIL)
          WRITE_LONG            ("%fx_off%" + 0x08 + ("%abil_fx_idx%" * 0x30)) "8"              // param2: 8 (IDS File - ALIGN.IDS)
          WRITE_BYTE            ("%fx_off%" + 0x0c + ("%abil_fx_idx%" * 0x30)) "0"              // timing: 0 (Duration)
          WRITE_LONG            ("%fx_off%" + 0x0e + ("%abil_fx_idx%" * 0x30)) "1"              // duration: 1
          WRITE_ASCII           ("%fx_off%" + 0x14 + ("%abil_fx_idx%" * 0x30)) ~RR#DAGLR~ #8    // resref: RR#DAGLR.EFF (evil alignments glow red)
        INSERT_BYTES            ("%fx_off%" +        ("%abil_fx_idx%" * 0x30)) 0x30             // new effect
          WRITE_EVALUATED_ASCII ("%fx_off%" +        ("%abil_fx_idx%" * 0x30)) "%clone_fx%"     // cloned effect
          WRITE_SHORT           ("%fx_off%" +        ("%abil_fx_idx%" * 0x30)) "177"            // effect #177: use EFF File
          WRITE_LONG            ("%fx_off%" + 0x04 + ("%abil_fx_idx%" * 0x30)) "19"             // param1: 19 (IDS Entry - LAWFUL_EVIL)
          WRITE_LONG            ("%fx_off%" + 0x08 + ("%abil_fx_idx%" * 0x30)) "8"              // param2: 8 (IDS File - ALIGN.IDS)
          WRITE_BYTE            ("%fx_off%" + 0x0c + ("%abil_fx_idx%" * 0x30)) "1"              // timing: 1 (Permanent)
          WRITE_LONG            ("%fx_off%" + 0x0e + ("%abil_fx_idx%" * 0x30)) "0"              // duration: 0
          WRITE_ASCII           ("%fx_off%" + 0x14 + ("%abil_fx_idx%" * 0x30)) ~RR#DASLE~ #8    // resref: RR#DASLE.EFF (display string - Lawful Evil)
        INSERT_BYTES            ("%fx_off%" +        ("%abil_fx_idx%" * 0x30)) 0x30             // new effect
          WRITE_EVALUATED_ASCII ("%fx_off%" +        ("%abil_fx_idx%" * 0x30)) "%clone_fx%"     // cloned effect
          WRITE_SHORT           ("%fx_off%" +        ("%abil_fx_idx%" * 0x30)) "177"            // effect #177: use EFF File
          WRITE_LONG            ("%fx_off%" + 0x04 + ("%abil_fx_idx%" * 0x30)) "35"             // param1: 35 (IDS Entry - NEUTRAL_EVIL)
          WRITE_LONG            ("%fx_off%" + 0x08 + ("%abil_fx_idx%" * 0x30)) "8"              // param2: 8 (IDS File - ALIGN.IDS)
          WRITE_BYTE            ("%fx_off%" + 0x0c + ("%abil_fx_idx%" * 0x30)) "1"              // timing: 1 (Permanent)
          WRITE_LONG            ("%fx_off%" + 0x0e + ("%abil_fx_idx%" * 0x30)) "0"              // duration: 0
          WRITE_ASCII           ("%fx_off%" + 0x14 + ("%abil_fx_idx%" * 0x30)) ~RR#DASNE~ #8    // resref: RR#DASNE.EFF (display string - Neutral Evil)
        INSERT_BYTES            ("%fx_off%" +        ("%abil_fx_idx%" * 0x30)) 0x30             // new effect
          WRITE_EVALUATED_ASCII ("%fx_off%" +        ("%abil_fx_idx%" * 0x30)) "%clone_fx%"     // cloned effect
          WRITE_SHORT           ("%fx_off%" +        ("%abil_fx_idx%" * 0x30)) "177"            // effect #177: use EFF File
          WRITE_LONG            ("%fx_off%" + 0x04 + ("%abil_fx_idx%" * 0x30)) "51"             // param1: 51 (IDS Entry - CHAOTIC_EVIL)
          WRITE_LONG            ("%fx_off%" + 0x08 + ("%abil_fx_idx%" * 0x30)) "8"              // param2: 8 (IDS File - ALIGN.IDS)
          WRITE_BYTE            ("%fx_off%" + 0x0c + ("%abil_fx_idx%" * 0x30)) "1"              // timing: 1 (Permanent)
          WRITE_LONG            ("%fx_off%" + 0x0e + ("%abil_fx_idx%" * 0x30)) "0"              // duration: 0
          WRITE_ASCII           ("%fx_off%" + 0x14 + ("%abil_fx_idx%" * 0x30)) ~RR#DASCE~ #8    // resref: RR#DASCE.EFF (display string - Chaotic Evil)
        SET "delta" = "%delta%" + 4
        WRITE_SHORT ("%abil_off%" + 0x1e + (0x28 * "%index%")) ("%abil_fx_num%" + 4)
      END
    END
  END
SET opcode_to_delete = "115"                                                       // mark effect #115 (Detect Alignment) for deletion
SET header = "-1"                                                                  // mark all extended headers for deletion
LAUNCH_PATCH_MACRO ~DELETE_SPELL_EFFECT~                                           // delete the designated opcodes from all of the spell's extended headers
// =============================================================================== // the actual work ends here
END                                                                                // ends file size check
BUT_ONLY_IF_IT_CHANGES
END                                                                                // ends ACTION_IF FILE_EXISTS_IN_GAME block
END                                                                                // ends ACTION_FOR_EACH block

 

It goes low-level all the way. So if it makes a mistake, no guards against that going wrong. And checking the other version ... no obvious differences in this part.

So what does that code do? Basically, it's a hand-done CLONE_EFFECT. And I don't see anything wrong with it. But the component has more code ... wait.

Do you have Spell Revisions installed as well? Because there's a "compatibility block" for SR, and I think that one can break the indexing. The code above (for all versions of the spell) inserts four new effects for an instance of opcode 115, adds 4 to the number of effects in this header, increments the effect indexing for later headers by 4, and then runs DELETE_EFFECT on opcode 115 which would catch any mis-indexing. All good.

The SR "compatibility block" inserts nine new effects for an instance of opcode 177, adds 9 to the number of effects in this header, increments the effect indexing for later headers by 12, and then doesn't run any sort of macro that would catch mis-indexing. And it's its own loop, so ... yeah.

But wait - even this won't actually break, because SPWI202 only has one header, even in the SR version. Unless some other mod came around in the interim and expanded the spell with additional headers. Which, given the size - a one-header spell with 16 effects is only about 900 bytes - is looking distinctly likely.

All right, just how many mods do you have that mess with this spell?

Link to comment

So, probably the new header. Looks like SR -> T&B -> aTweaks can break this spell, because of the shoddy re-indexing in aTweaks. And it still might cast fine, unless you did whatever it is that uses the header - using that memorization slot for a spontaneous spell in your specialty? But anything that tries to parse the spell fully is liable to error out with a bad read.

Link to comment

I do have SR as well as T&B. That said, @subtledoctor the Spell Tweaks components "Revised Invisibility and True Seeing" and "Change SR Expeditious Retreat into Chameleon" also seem to touch this spell. The last one in fact seems to increase the file size from 298 to 346 bytes, before T&B's Revised Specialists lifts it to 1.45 kb (the header I guess?). Jmerry is convincing me that atweaks is the bigger troublemaker, but could be worth a look into those two?

Link to comment

Yeah Expeditious Retreat-> Chameleon adds an op321 effect canceling chameleon. Revised Invisibility… I don’t remember, it might do something similar. 

But those just use a basic LPF ADD_SPELL_EFFECT function, it is fairly bulletproof AFAIK and should not corrupt the file structure. He suspects aTweaks because it is doing low-level patching, like inserting bytes directly into the file, which can throw everything off if not done right. 

EDIT - @jmerry is there anything I can do in TnB to add that new header more cleanly? Or will aTweaks choke on it regardless it it has an extra header. 

Edited by subtledoctor
Link to comment

The mere presence of any extra header with at least one effect will do it. As long as that header comes after the one with the normal spell effects, anyway. The aTweaks component will proceed to mis-index that header so that it points to effects that aren't in the file. So as long as you're implementing your system with a "level 51" header, there's not really anything you can do about it on your end.

(What happens if you put spell headers in the "wrong" order? Probably not good things; my guess is that the game ends up using the last header that the condition is met for, so a header that comes before one with a lower level minimum will never be used.)

For aTweaks, it's a one-line fix; replace a "12" with a "9". Or recode that whole component to use the built-in functions instead of reinventing them.

Link to comment

Yes, it is quite old stuff. Anyway, here's the specific code block that has the problem. Somewhat abridged, with extra comments by me.

Spoiler
// Special Spell Revisions compatibility block. Somewhat abridged, with additional comments by jmerry

ACTION_IF MOD_IS_INSTALLED SPELL_REV.TP2 0 THEN BEGIN                              // SR's Arcane version of Detect Evil needs special treatment
COPY_EXISTING ~SPWI202.SPL~  ~override~                                            // Detect Evil (Arcane version)
  READ_LONG  0x64 "abil_off" ELSE 0
  READ_SHORT 0x68 "abil_num" ELSE 0
  READ_LONG  0x6a "fx_off"   ELSE 0
  SET "delta" = 0
  FOR (index = 0 ; index < abil_num ; index = index + 1) BEGIN
    READ_SHORT ("%abil_off%" + 0x1e + (0x28 * "%index%")) "abil_fx_num"
    READ_SHORT ("%abil_off%" + 0x20 + (0x28 * "%index%")) "abil_fx_idx"            // This field is the index of the header's first effect.
    SET "abil_fx_idx" = ("%abil_fx_idx%" + "%delta%")                              // So we increment it by delta. Zero for the first header.
    WRITE_SHORT ("%abil_off%" + 0x20 + (0x28 * "%index%")) "%abil_fx_idx%"         // And the number of effects we added for later headers.
    FOR (index2 = 0 ; index2 < abil_fx_num ; index2 = index2 + 1) BEGIN
     READ_SHORT ("%fx_off%" +        (0x30 * ("%abil_fx_idx%" + "%index2%"))) "opcode"
      PATCH_IF ("%opcode%" = 177) BEGIN // clone effect #177: Use EFF file
        READ_ASCII ("%fx_off%" +        (("%abil_fx_idx%" + "%index2%") * 0x30)) "clone_fx" (0x30)
        SET "index2"= "%abil_fx_num%" // kills loop
        INSERT_BYTES            ("%fx_off%" +        ("%abil_fx_idx%" * 0x30)) 0x30             // new effect #1
        // Abridged; write data for inserted effect here
        INSERT_BYTES            ("%fx_off%" +        ("%abil_fx_idx%" * 0x30)) 0x30             // new effect #2
        // Abridged; write data for inserted effect here
        INSERT_BYTES            ("%fx_off%" +        ("%abil_fx_idx%" * 0x30)) 0x30             // new effect #3
        // Abridged; write data for inserted effect here
        INSERT_BYTES            ("%fx_off%" +        ("%abil_fx_idx%" * 0x30)) 0x30             // new effect #4
        // Abridged; write data for inserted effect here
        INSERT_BYTES            ("%fx_off%" +        ("%abil_fx_idx%" * 0x30)) 0x30             // new effect #5
        // Abridged; write data for inserted effect here
        INSERT_BYTES            ("%fx_off%" +        ("%abil_fx_idx%" * 0x30)) 0x30             // new effect #6
        // Abridged; write data for inserted effect here
        INSERT_BYTES            ("%fx_off%" +        ("%abil_fx_idx%" * 0x30)) 0x30             // new effect #7
        // Abridged; write data for inserted effect here
        INSERT_BYTES            ("%fx_off%" +        ("%abil_fx_idx%" * 0x30)) 0x30             // new effect #8
        // Abridged; write data for inserted effect here
        INSERT_BYTES            ("%fx_off%" +        ("%abil_fx_idx%" * 0x30)) 0x30             // new effect #9
        // Abridged; write data for inserted effect here
        SET "delta" = "%delta%" + 12                  // 9 more effects in this header, so increase effect indexes in later headers by 12? Broken.
        WRITE_SHORT ("%abil_off%" + 0x1e + (0x28 * "%index%")) ("%abil_fx_num%" + 9)    // 9 more effects in this header, so increment that field.
      END
    END
  END
COPY ~atweaks/spl/elementals/rr#ekal2.spl~ ~override/rr#ekal.spl~                  // Know Alignment (aTweaks Elemental Prince version)
END                                                                                // ends SR compatibility block

 

This code block is present in both versions of the project that I linked to. The fork, at least, looks to still be actively maintained.

Link to comment

Received a warning when re-installing the Shapeshift Correction component of jTweaks v3.1.  Otherwise, everything else was installed smoothly, (mostly.)

 

[setup-jtweaks.exe] WeiDU version 24900
{setup-atweaks.exe} Queried (pid = 408) version = 24900
{setup-dragonspear_ui++.exe} Queried (pid = 436) version = 24900
Using Language [English]

Using .\lang\en_us\dialog.tlk

.
.
.

Installing [Shapeshift corrections -> All transformations] [3.1]
Copying and patching 1 file ...

WARNING: no effects altered on SPCL612.SPL
Copying and patching 1 file ...
Copying and patching 1 file ...
Copying and patching 2 files ...
Copying and patching 2 files ...
Copying and patching 2 files ...
Copying and patching 1 file ...
Copying and patching 1 file ...
Copying and patching 1 file ...
Copying and patching 1 file ...
Copying and patching 1 file ...
Copying and patching 1 file ...
Copying and patching 1 file ...
Copying and patching 13 files ...


[.\lang\en_us\dialog.tlk] created, 91583 string entries

INSTALLED WITH WARNINGS     Shapeshift corrections -> All transformations

.
.
.

 

WeiDU.log is attached.  jTweaks are the last components in the log.

WeiDU.log

Link to comment

The warning is harmless. However, you should uninstall that component; it's meant to fix minor issues with the vanilla shapeshifts, and you have a much more thorough overhaul already installed (SCS #2040 for wizard shapeshifts, SCS #4030 for druid shapeshifts) which fundamentally changes what those spells and abilities do. With my component installed over that, you'll have inaccurate descriptions and likely worse.

... You know, I think I'll add a "forbid" condition and enforce the incompatibility for the next version.

Link to comment
On 1/21/2022 at 5:12 PM, jmerry said:

48. Variable drow magic resistance
- Choose base drow MR (required). Three options: Low (35), Medium (50), High (65).
- Choose MR progression rate (required). Three options: none, slow (1/level to 30), fast (2/level to 20)
- Choose sunlight penalties (required). Two options: none, Dazzled (-2 THAC0, -2 missile AC)

The components are broken.  

I installed the medium option (50), fast progression (2/level to 20), and dazzled.

It set Viconia's base MR to 0.  

MR did NOT improve when she leveled up several times.  It stayed at 0.

She was dazzled ALL the time, even during nighttime. 

I tried to use EE Keeper to edit her character and give her 50 MR... which did NOT work.  When I loaded the game, her MR was reset to 0. 

I tested with another character (main character) and edited his MR to 50.  Loaded the game, and the 50 MR was there.  Viconia was in the party in the same game, (I had also edited her MR to 50,) and her MR showed up as 0. She was dazzled while indoor at nighttime.  I saved the game, and Viconia's MR was reset to 0. 

It seemed like the dazzle somehow overrode and interfered with her MR.

Baeloth's MR was 67 at level 6 with Robe of Evil Archmagi (MR +5).   He was NOT dazzled at all, not even during daytime.  That is why I suspected the problem was dazzled: he was not affected by dazzled, which was why his MR was not overridden. 

BTW, the +2/level calculation was off.  Baeloth's base MR should be: 50 at level 1, 52 at level 2, 54 at level 3, 56 at level 4, 58 at level 5, and 60 at level 6.  The Archmagi robe gave him another 5 MR.  So his total MR at level 6, with the Archmagi Robe, should be 60 + 5 = 65.   His base MR should start at 50 at level 1.  The first +2 progression should start at level 2, not level 1.

Edited by ktchong
Link to comment

Just uninstalled the components, (i.e., all three sub-components,) and rolled back to just before saving/recruiting Viconia.  Her MR was 50 when recruited, and stayed at 50 after saving/reloading.

So the "dazzled" component DID break Viconia and her MR.

Rolled back to before the components: Baeloth's MR is 55, (50 base + 5 from Archmagi Robe.)

Edited by ktchong
Link to comment

Join the conversation

You are posting as a guest. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...