spell_known_add

Discussion in 'General Modification' started by SCO, Jan 1, 2012.

Remove all ads!
  1. SCO

    SCO Member

    Joined:
    Jan 2, 2011
    Messages:
    58
    Likes Received:
    0
    There are mechanical problems with the levelup interface if you cheated.

    It happens most often in the spells phase if you used
    cheats.add_spells_by_class() . Since you have all of them, you can't levelup because you haven "chosen" one.

    But i think it can also happen with very high charisma/intelligence on lower levels (get all the spells of a level, get a free slot for that level)

    Btw, what are the 3 arguments to
    game.party[X].spell_known_add ?
    I tried to debug it with olly (Py_Initialize) but it locks up ubuntu running toee in wine tut-tut.

    edit: ok if i am not wrong, it seems that
    CPU Disasm
    Address Hex dump Command Comments
    101DFFD0 /$ 53 PUSH EBX

    is the line where the function that pushes commands from the cmd line ingame to python begins.

    I couldn't make tails or heads of the python interpreter so i'm not sure where is the actual function. I'm sure it takes 3 arguments, probably int (by introspecting the error messages that toee old python interperter doesn't display)

    Oh, you need to run commands like this:
    game.party[X].spell_known_add(2,3,4);

    To avoid triggering the construction of a spurious error that i'm not sure affects anything.
    Could you help?
     
    Last edited: Jan 1, 2012
  2. SCO

    SCO Member

    Joined:
    Jan 2, 2011
    Messages:
    58
    Likes Received:
    0
    Re: Improving ToEE's User Interface

    edit question self answered.
     
    Last edited: Jan 1, 2012
  3. Sitra Achara

    Sitra Achara Senior Member

    Joined:
    Sep 1, 2003
    Messages:
    3,622
    Likes Received:
    538
    Re: Improving ToEE's User Interface

    SCO,

    AFAIK you're the first to seriously examine spell_known_add.
    I don't have much to add, except that spell enumeration can be found at data\rules\spell_enum.mes.
    Good luck and let us know what you find out.
     
  4. SCO

    SCO Member

    Joined:
    Jan 2, 2011
    Messages:
    58
    Likes Received:
    0
    Re: Improving ToEE's User Interface

    Do any of you debugger specialists know how to make ollydbg stop on first access to another dll from another?

    I want to stop on that function that pushes console commands, and then "run until..." it enters the temple.dll to find out what the function actually is when executed by the function pointer without having to deal with lots of python.dll and msvcr71.dll junk.

    At a guess, the 3 int arguments might be:
    a spell id
    a class id (though i guess, since the npc is a implicit self argument, maybe not).
    a level id

    order unknown, ranges unknown. I tried simple variations but never could get it to actually create a spell though (things like 1,1,1).

    The first call (not intermodular) in that function is comparing that call to a internal list of allowed functions...


    Funny enough the function is testing:
    all_spells
    all_spells_known
    bling
    kos
    die
    (among a few others)

    that i'm not sure were all known.

    all_spells() and all_spells_known() appear to be global namespace alias of cheats.all_spells_by_class
    bling() gives large amounts of money.

    After this first checking function there is a call guarded by a short jump testing for equality to zero i guess
    (TEST EAX EAX)
    It seems these commands (and a few others) might not go into the python interpreter at all.

    spell_known_add is not of those tested, but it doesn't bode well
     
    Last edited: Jan 2, 2012
  5. SCO

    SCO Member

    Joined:
    Jan 2, 2011
    Messages:
    58
    Likes Received:
    0
    Re: Improving ToEE's User Interface

    Progress of a sort:
    I set a breakpoint on the python code called from that console push function, selected to restrict the run trace to the temple executable module (not toee, that is just a launcher i think) and run a function i know exists
    game.party[0].stat_base_set(stat_strength, 30);

    hit the breakpoint on the python dll, activate the trace range,
    Hit run trace, wait a lot until it hits back temple code, pause

    The first hit i get on temple dll is
    CPU Disasm
    Address Hex dump Command Comments
    1010CBF0 /. 8B4424 08 .MOV EAX,DWORD PTR SS:[ARG.2]


    That appears to be a large function (with no entry points eh, function pointer confirmed), not only dedicated to this particular command from the memory values of some static string vars there.
    spell_known_add also hits it, so it seems they have all functions that get in the python layer actually hitting that.
    Maybe...


    First hit is dedicated to a "party" argument...
    seems familiar no?
    game.party[0].stat_base_set(stat_strength, 30);

    It seems to be wrapping that party object in a CPython tuple, probably to return to the interpreter to go elsewhere.

    BTW, to actually run toee in olly you need the -window parameter and to "ignore kernel32 access violations" and to "pass all service exceptions" in the options menu.
     
    Last edited: Jan 2, 2012
  6. SCO

    SCO Member

    Joined:
    Jan 2, 2011
    Messages:
    58
    Likes Received:
    0
    Re: Improving ToEE's User Interface

    For some strange reason,
    Py_FindMethod
    http://docs.python.org/release/2.0/api/common-structs.html
    is called even on the console calls (strange because if it was C code driving python calling Py_FindMethod would be useless, because it's arguments are already from C api (structs with function pointers etc), name of the function.

    It seems to be a attempt at dynamic binding or something because it's called even if you input a non existing function on a party member....

    Now, if the struct it uses as argument is well defined.
    It might be possible to just look at it's values to find the indexes of all functions.
     
  7. Gaear

    Gaear Bastard Maestro Administrator

    Joined:
    Apr 27, 2004
    Messages:
    11,038
    Likes Received:
    42
    I split these posts off from UI to a new thread ...

    I have a feeling that there's really no active debugger specialists here atm.
     
  8. SCO

    SCO Member

    Joined:
    Jan 2, 2011
    Messages:
    58
    Likes Received:
    0
    Strange. I set a break point on all callers of Py_FindMethod on temple.dll.

    It is called with a array of PyMethodDef structs whoose first field is always PyObjHandle, on most commands you can write to console.

    Remember that the first field of PyMethodDef is
    ml_name char * name of the method

    So there is probably a method called PyObjHandle that handles most of the command line commands.

    So i continue from the caller (which is in temple.dll) till the return... that is on python.dll again!

    console->python->intermediate temple.dll function->pythonfetch of PyObjHandle function->python execute of function in object (inside function:
    1E0502F0 pyToEE22.PyCFunction_Call(guessed Arg1,Arg2,Arg3))
    -> return to temple dll, our function.

    So if you want to get a function offset that is callable from the console just set a breakpoint on that console function at 101DFFD0; type out and enter the function, and in the debugger activate a breakpoint on PyCFunction_Call at 1E0502F0. Run and it hits that new breakpoint, then Step it until you get into a call (on that same function, otherwise something went wrong) to temple.dll again.

    There it is. Examples:
    100B3740
    (game.party[0].spell_known_add(1,2,3); )

    100B0070
    (game.party[0].stat_base_set(stat_strength,30); )


    BTW, PyCFunction_Call will be called again if you run again, but that seems a internal bookkeeping python call (in call inside the function will call a python method).


    I still don't know what arguments are valid.
     
    Last edited: Jan 3, 2012
  9. SCO

    SCO Member

    Joined:
    Jan 2, 2011
    Messages:
    58
    Likes Received:
    0
    Seems like it is failing (unsurprisingly) on one of the arguments, or on a additional method precondition.

    And indeed, argument 1 seems to be the spell name because on the call
    spell_known_add there was a string fetched somewhere that pointed to "Aid" that is "1" in the spell.mes, and i changed it to "2" and then it was "Air Walk".

    BTW; spell_known_add is being called in many places, probably the UI/spell learning for mages/cheats.

    Maybe that is a next step, try to see the valid arguments on those cases.

    Edit: hmmm, sorceror level up or wizard, or wizard copy scroll don't trigger that code. Dang.
    Then again, maybe it's the subcall.

    EDIT: yes it is!

    The subcall has 7 arguments:
    I learned shocking grasp on a wizard with a spell scroll.
    Argument values:
    1: 26421A (prob npc reference pointer)
    2: 1BA (dunno)
    3: 1AF ( == 431 in hex, it's argument 1 of the console call, the spell ref)
    4: 91 (dunno)
    5: 1 (dunno)
    6: 1 (dunno)
    7: 0 (dunno)

    while if i try the same with the console cmd the subcall args (keeping only arg 1 the same and the others set to -1 and -2):
    1: 26421A
    2: 1BA (equal... good)
    3: 1AF (same spell)
    4: 0FF (uh)
    5: -2 (it's the third argument unmodified)
    6: 1
    7: 0

    So likely culprit at this point appears to be the second argument. Trying with 91 (as decimal in the console 145) as second argument and 1 as third:
    1: 26421A
    2: 1BA
    3: 1AF
    4: 91 (second argument!)
    5: 1 (third argument)
    6: 1
    7: 0

    And our wizard has shocking grasp!!!!

    Now to see for the others. The second argument must be some kind of class value maybe.

    Edit: 90 (= 144 in the console) seems to be the sorceror constant.
    So:
    game.party[X].spell_known_add(SPELL_MES_REF, CLASS_ID, LEVEL);

    Class id's:
    (145) is wizard.
    (144) is sorceror.
    (138) is druid.
    (136) is bard.
    (142) is ranger.
    (137) is cleric.
    (141) is paladin.


    third argument: controls spell level the spell appears at. 0-9 likely at hex too, but doesn't need translation due to hex going from 0 to 9 too. Dunno about domains (probably another level).

    I haven't been able to get a spell on a non-spellcasting class(booo) yet. Maybe i just need the right values of the second argument.

    Curiously, you can get spells from other classes with this (just use the right spell ref). And they work too (probably with wrong caster level). Tested with aid (cleric only right?) on a sorceror. However, when you levelup a few dummy submenus are created (it doesn't matter because the original remain).

    Argument 7 of the subfunction appears to be the feat enhancement bitmask (silent, quick, etc). You can still do those from the spell menu.

    Dunno about 2 or 6 (of the subfunction, that appears inaccessible from python code anyway).
     
    Last edited: Jan 7, 2012
  10. SCO

    SCO Member

    Joined:
    Jan 2, 2011
    Messages:
    58
    Likes Received:
    0
    What are the other spell_* or spell* functions?

    I wanted to cheat a fighter with sorceror spells without taking a level as a sorceror, but it seems there might be other factors.

    I think the "spell_caster_level" vars might be part of it, but they can't be changed with either stat_base_set or the (missing) stat_level_set or (obviously) by stat_base_get.

    And all the others. I know there is spell_memorized_add (that would seem to do the obvious but haven't tested).

    But what controls the number of memorizable spells per spell level? Might the subfunction adding spells allow the creation of the spell menu? (there are other arguments there inaccessibly by spell_known_add).

    Then again, it's probably impossible without editing the dll. Just getting spells from other classes is pretty good. If i can increase the spell slots it would be even better.
     
    Last edited: Jan 3, 2012
  11. krokyk

    krokyk Member

    Joined:
    Feb 12, 2006
    Messages:
    11
    Likes Received:
    0
    Great job in finding how this works!

    Confirming that game.party[X].spell_known_add(49, 145, 2) added a spell Cat's Grace (49) to my wizard's (145) spell list under Level 2 (2) spells.

    1. Dunno if there are any implications if one specifies incorrect level - for example, I tried to add this spell as a 3rd lvl and it worked - appeared under 3rd lvl spells.
    2. Is there anything wrong in adding extra spell? Will the wizard be able to choose another 2 spells when he levels up?

    With that in mind - is there function that actually REMOVES a spell from the known spells list? I tried to search for 'spell_known_' but to no avail, only 'spell_known_add' was found.

    EDIT: Answer to point 2: Yes, wizard is able to choose 2 new spells at level up.
     
    Last edited: Jan 7, 2012
  12. Sitra Achara

    Sitra Achara Senior Member

    Joined:
    Sep 1, 2003
    Messages:
    3,622
    Likes Received:
    538
    Attached are Spellslinger's notes on DLL mods he made - some pertaining to spell slots and such. Hope it helps in some way.
     

    Attached Files:

  13. krokyk

    krokyk Member

    Joined:
    Feb 12, 2006
    Messages:
    11
    Likes Received:
    0
    Uff... Quite a few files to read... Thanks, I'll take look into that. :)
     
  14. SCO

    SCO Member

    Joined:
    Jan 2, 2011
    Messages:
    58
    Likes Received:
    0
    There is a consequence: on levelup the spell will be "duplicated" on the radial menu on the appropriate class/level combo (edit: not on the menu) when you levelup. It seems not to matter (but i've been using bards, that allow completing levelups without choosing spells if the slots are full).
     
    Last edited: Jan 7, 2012
  15. SCO

    SCO Member

    Joined:
    Jan 2, 2011
    Messages:
    58
    Likes Received:
    0
    Beware of something i noticed: the offsets listed are debugger ones. The temple.dll "entry" offset must be subtracted.

    Fortunatly in toee case is really easy. The offset is 10000000 so just ignore the first 1 if you're hex editing the file directly using the offsets in those files.

    Also; found a error in "NPC_usepotion_AoO_fix.txt":
    new
    10098DE7 C705 00AC8611 44000000 MOV DWORD PTR DS:[1186AC00],44
    old
    10098DE7 C705 00AC8611 44000000 MOV DWORD PTR DS:[1186AC00],1B


    Spot the difference.

    Should probably be:
    old
    10098DE7 C705 00AC8611 1B000000 MOV DWORD PTR DS:[1186AC00],1B

    The installed file itself has the new version (44) so no problems there.
     
    Last edited: Jan 7, 2012
Our Host!