Вверх Вниз



Страница 1 из 11
Модератор форума: Kris†a™ 
Форум » TES V: Skyrim » Мастерская » Твик на отравление оружием (Нужна помощь в редактировании плагина dll SKSE)
Твик на отравление оружием
kelamor  Offline Сообщение №1 написано: 12 Февраля 2017 в 18:21


Странник


18
Добрый день!
Товарищи, есть на Нексусе такой плагин "Infinite Weapon Poison", который через SKSE делает три вещи:
1. Убирает ограничение на удары отравленным оружием и делает их бесконечными.
2. Позволяет менять яд на оружии.
3. Позволяет убрать яд с оружия.

Автор мода meh321 выложил исходники и я два дня мучаюсь, чтобы оставить только 2 и 3 твики, так как сделал небольшой мод, который добавляет перк, ставящий
количество ударов отравленным оружием в зависимость от прокачки Алхимии (от 33 до 100% от навыка алхимии) и 1-ый твик плагина "Infinite Weapon Poison" убивает
всю конструкцию.

В принципе там один скрипт main.cpp со следующим текстом:


По всякому редактировал (методом научного тыка, так как в этой теме не очень), добился только того, что если бесконечны удары убираю, то нарушаются и смена яда, и очистка яда.

Может кто-нибудь сможет помочь советом или отредактируют (исходники на странице мода)?
Автора мода запрашивал - молчит, партизан, хотя прочитал сообщение )

Добавлено (12 Февраля 2017, 18:21)
---------------------------------------------
Что-то код в спойлере криво записался, а отредактировать не даёт сообщение.

Код
typedef unsigned char  UInt8;  //!< An unsigned 8-bit integer value
typedef unsigned short  UInt16;  //!< An unsigned 16-bit integer value
typedef unsigned long  UInt32;  //!< An unsigned 32-bit integer value
typedef unsigned long long UInt64;  //!< An unsigned 64-bit integer value
typedef signed char   SInt8;  //!< A signed 8-bit integer value
typedef signed short  SInt16;  //!< A signed 16-bit integer value
typedef signed long   SInt32;  //!< A signed 32-bit integer value
typedef signed long long SInt64;  //!< A signed 64-bit integer value
typedef float    Float32; //!< A 32-bit floating point value
typedef double    Float64; //!< A 64-bit floating point value
#include "Windows.h"
#include "skse/PluginAPI.h"
#include "skse/skse_version.h"
int allowOverwrite1 = 0x7462CD;
int allowOverwrite2 = 0x7462D4;
int isPoison1 = 0x4757B0;
int reducePoison1 = 0x475810;
int reduceCharge1 = 0x410711;
int reduceCharge2 = 0x410719;
int doRemove = 0;
extern "C"
{
 bool SKSEPlugin_Query(const SKSEInterface * skse, PluginInfo * info)
 {
  // populate info structure
  info->infoVersion = PluginInfo::kInfoVersion;
  info->name = "item poison plugin";
  info->version = 1;
  if (skse->isEditor || skse->runtimeVersion != RUNTIME_VERSION_1_9_32_0)
   return false;
  return true;
 }
 bool SKSEPlugin_Load(const SKSEInterface * skse)
 {
  void * jumpAddress1 = NULL;
  void * jumpAddress2 = NULL;
  _asm
  {
   mov jumpAddress1, offset IPCodeStart
   jmp IPCodeEnd
  IPCodeStart:
   mov ecx, esi
   mov doRemove, 1
   call reducePoison1
   mov ecx, esi
   call isPoison1
   test eax, eax
   jmp allowOverwrite2
  IPCodeEnd:
  }
  _asm
  {
   mov jumpAddress2, offset RPCodeStart
   jmp RPCodeEnd
  RPCodeStart:
   test ecx, ecx
   jbe RPBack
   cmp doRemove, 0
   je RPBack
   mov doRemove, 0
   mov ecx, 0
   mov [eax+8], ecx
  RPBack:
   jmp reduceCharge2
  RPCodeEnd:
  }
  DWORD oldProtect = 0;
  int len2 = allowOverwrite2 - allowOverwrite1;
  if (VirtualProtect((void *)allowOverwrite1, len2, PAGE_EXECUTE_READWRITE, &oldProtect))
  {
   *((unsigned char *)allowOverwrite1) = (unsigned char)0xE9;
   *((int *)(allowOverwrite1 + 1)) = (int)jumpAddress1 - allowOverwrite1 - 5;
   for (int i = 5; i < len2; i++)
    *((unsigned char *)(i + allowOverwrite1)) = (unsigned char)0x90;
   VirtualProtect((void *)allowOverwrite1, len2, oldProtect, &oldProtect);
  }
  int len1 = reduceCharge2 - reduceCharge1;
  if (VirtualProtect((void *)reduceCharge1, len1, PAGE_EXECUTE_READWRITE, &oldProtect))
  {
   *((unsigned char *)reduceCharge1) = (unsigned char)0xE9; //0xE9
   *((int *)(reduceCharge1 + 1)) = (int)jumpAddress2 - reduceCharge1 - 5;  //   *((int *)(reduceCharge1 + 1)) = (int)jumpAddress2 - reduceCharge1 - 5;
   for (int i = 5; i < len1; i++)
    *((unsigned char *)(i + reduceCharge1)) = (unsigned char)0x90; //0x90
   VirtualProtect((void *)reduceCharge1, len1, oldProtect, &oldProtect);
  }
  return true;
 }
};



Dsion  Offline Сообщение №2 написано: 12 Февраля 2017 в 20:12



1203
Лично я помочь вряд ли смогу. Хотел только проверить, понял ли ты то, что main.cpp - это не скрипт, а исходник на С++, который еще надо во что-то компилировать (скорее всего, в dll).

kelamor  Offline Сообщение №3 написано: 13 Февраля 2017 в 02:10


Странник


18
Да, конечно. Visual Studio. Проект нормально компилируется.

Dsion  Offline Сообщение №4 написано: 13 Февраля 2017 в 14:20 | Отредактировано: Dsion - Понедельник, 13 Февраля 2017, 14:21



1203
Тогда извиняй. Просто проверяю.
Сейчас гляну немного, что он там наворотил.

Добавлено (13 Февраля 2017, 14:10)
---------------------------------------------
Ну, в общем, не много я понял. Тем более, что сам мод не устанавливал и как он работает не знаю. И С++ тут почти нет, а, в основном, реверс-инженерия и asm. С моим нубским уровнем разбираться пришлось бы часа 3.
Вот то, что понял... В коде Скайрима (x86 команды) есть два небольших блока. Автор мода назвал их allowOverwrite и reduceCharge. Что именно они делают я полностью не понял. Предположительно, первый выполняется при попытке переналожить яд, а второй - когда игра понижает число зарядов на яде. Хотя и не факт.
Этот мод заменяет код этих блоков на свой собственный. Команд там (и оригинальных и замененных) кот наплакал, но все-равно прямо сходу не поймешь, что они делают. Блоки - это даже не отдельные процедуры, а куски других процедур. Так что код и близко не самодостаточный, а вырван из контекста. В первом (после изменения), возможно, (при каких-то условиях) снимается яд. А во втором - вообще без понятия, что делается. Но, при этом, блоки еще и не независимы, а связаны через переменную doRemove. Первый блок (при каких-то условиях?) её устанавливает в 1, а второй - как-то использует и снимает...
В общем, лениво разбираться в этом всём :(


Код
#include "Windows.h"
#include "skse/PluginAPI.h"
#include "skse/skse_version.h"

int allowOverwrite1 = 0x7462CD; //начало какого-то блока кода Скайрима
int allowOverwrite2 = 0x7462D4; //конец блока

int isPoison1 = 0x4757B0; //начало оригинальной процедуры проверки ядовости?
int reducePoison1 = 0x475810; //начало какой-то оригинальной процедуры. что делает? снимает яд?

int reduceCharge1 = 0x410711; //еще какой-то блок кода
int reduceCharge2 = 0x410719; //его конец

int doRemove = 0;

//--------------------------------------------------------------------------------

//Это всё хрень
extern "C" bool SKSEPlugin_Query(const SKSEInterface * skse, PluginInfo * info)
{
    // populate info structure
    info->infoVersion = PluginInfo::kInfoVersion;
    info->name = "item poison plugin";
    info->version = 1;

    if (skse->isEditor || skse->runtimeVersion != RUNTIME_VERSION_1_9_32_0)
    {
        return false;
    }

    return true;
}

//--------------------------------------------------------------------------------

extern "C" bool SKSEPlugin_Load(const SKSEInterface * skse)
{
    void *jumpAddress1 = NULL;
    void *jumpAddress2 = NULL;

    //Создает код в памяти процесса. Этот код (начиная с IPCodeStart) заменит собой блок allowOverwrite
    _asm
    {
        mov jumpAddress1, offset IPCodeStart
        jmp IPCodeEnd
        IPCodeStart:
        mov ecx, esi
        mov doRemove, 1
        call reducePoison1
        mov ecx, esi
        call isPoison1
        test eax, eax
        jmp allowOverwrite2 //прыжок к концу оригинального блока (продолжается оригинальный код)
        IPCodeEnd:
    }

    //То же самое, но заменит reduceCharge
    _asm
    {
        mov jumpAddress2, offset RPCodeStart
        jmp RPCodeEnd
        RPCodeStart:
        test ecx, ecx
        jbe RPBack
        cmp doRemove, 0
        je RPBack
        mov doRemove, 0
        mov ecx, 0
        mov [eax+8], ecx
        RPBack:
        jmp reduceCharge2
        RPCodeEnd:
    }

//--------------------------------------------------------------------------------

    DWORD oldProtect = 0;

//--------------------------------------------------------------------------------

    int len2 = allowOverwrite2 - allowOverwrite1; //Вычисляет размер блока allowOverwrite, который хрен-знает-что делает

    if (!VirtualProtect((void *)allowOverwrite1, len2, PAGE_EXECUTE_READWRITE, &oldProtect)){return false;} //снимает защиту памяти c блока

    *((unsigned char *)allowOverwrite1) = (unsigned char)0xE9; //заменяет первый байт на jump with a 32bit relative offset
    *((int *)(allowOverwrite1 + 1)) = (int)jumpAddress1 - allowOverwrite1 - 5; //прыжок к новому коду

    for (int i = 5; i < len2; i++)
    {
        *((unsigned char *)(i + allowOverwrite1)) = (unsigned char)0x90; //забивает остальное NOPами?
    }

    VirtualProtect((void *)allowOverwrite1, len2, oldProtect, &oldProtect); //возвращает защиту памяти

//--------------------------------------------------------------------------------

    //Та же хрень, но заменяет блок reduceCharge1

    int len1 = reduceCharge2 - reduceCharge1;

    if (!VirtualProtect((void *)reduceCharge1, len1, PAGE_EXECUTE_READWRITE, &oldProtect)) {return false;}

    *((unsigned char *)reduceCharge1) = (unsigned char)0xE9; //0xE9
    *((int *)(reduceCharge1 + 1)) = (int)jumpAddress2 - reduceCharge1 - 5;  //   *((int *)(reduceCharge1 + 1)) = (int)jumpAddress2 - reduceCharge1 - 5;

    for (int i = 5; i < len1; i++)
    {
        *((unsigned char *)(i + reduceCharge1)) = (unsigned char)0x90; //0x90
    }

    VirtualProtect((void *)reduceCharge1, len1, oldProtect, &oldProtect);

//--------------------------------------------------------------------------------

    return true;
}

//--------------------------------------------------------------------------------

}


Добавлено (13 Февраля 2017, 14:20)
---------------------------------------------
Хм... Или второй блок - часть процедуры reducePoison1? Первый блок устанавливает doRemove в 1 и вызывает процедуру, в которой где-то мелькает и второй блок... Тогда, возможно, снятие яда происходит именно во втором блоке. Короче, без OllyDbg не разобраться.

kelamor  Offline Сообщение №5 написано: 13 Февраля 2017 в 14:21


Странник


18
Ну как минимум, направление для рытья траншеи ты дал. Хоть какое-то понимание появилось. Спасибо, покопаюсь )))

Dsion  Offline Сообщение №6 написано: 13 Февраля 2017 в 14:32



1203
Аж два направления на выбор: либо забить, либо OllyDbg :(

Добавлено (13 Февраля 2017, 14:32)
---------------------------------------------
Допустим, первый блок при каких-то условиях устанавливает переменную doRemove (снять яд) в 1.
А второй блок, допустим, должен бы срабатывать при каждом ударе и уменьшать заряды яда. И количество зарядов, допустим, содержится в регистре ecx и в eax+8. Тогда измененную версию второго блока можно понимать вот так:

Если doRemove не установлена (нет команды снять яд), то не делать вообще ничего (в том числе, не понижать заряды яда).
А если doRemove установлена в 1, то сбросить её обратно в ноль и установить количество зарядов яда тоже в ноль. Возможно, именно в этом месте яд и снимается.


kelamor  Offline Сообщение №7 написано: 13 Февраля 2017 в 18:44 | Отредактировано: kelamor - Понедельник, 13 Февраля 2017, 18:44


Странник


18
Я правильно понимаю, что вот этот кусок кода:
int reduceCharge1 = 0x410711; //еще какой-то блок кода
int reduceCharge2 = 0x410719; //его конец


в Ollydbg выглядит вот так, по адресу же нахожу?

CPU Disasm
Address   Hex dump          Command                                  Comments
00410711  |.  85C9          TEST ECX,ECX
00410713  |.  76 04         JBE SHORT 00410719
00410715  |.  49            DEC ECX
00410716  |.  8948 08       MOV DWORD PTR DS:[EAX+8],ECX
00410719  |>  8378 08 00    CMP DWORD PTR DS:[EAX+8],0

Dsion  Offline Сообщение №8 написано: 13 Февраля 2017 в 19:28



1203
Ну да, как-то так...
Видишь вон там "DEC ECX"? Это, вроде, уменьшение значения в регистре ECX на единицу. А дальше значение регистра ECX копируется куда-то в память по адресу [EAX+8]. Предположим, что это и есть тот код, который уменьшает заряды яда. А автор заменил этот кусок. В его измененной версии есть два варианта. Если doRemove не установлена (равняется нулю), то вообще ничего не делается: не уменьшается ECX и не уменьшается число в памяти [EAX+8]. А если doRemove установлена в 1, то ECX и [EAX+8] сразу устанавливаются в ноль. Число зарядов сразу устанавливается в ноль? Не уверен, но вероятно...

Добавлено (13 Февраля 2017, 19:28)
---------------------------------------------
Если что, ECX - это один из регистров процессора. Регистры - это, типа, сверхбыстрая оперативная память. Но в них информация (1-8 байт) хранится временно. То есть, основное место хранения количества зарядов - это оперативная память по адресу [EAX+8]. А в ECX это число попадает только иногда и на короткое время. Ну это при условии, что я правильно угадал смысл операций...

В OllyDbg есть очень полезная штука - брейкпоинты. Ставишь такой брейкпоинт на любой адрес кода и, если он будет выполняться, программа поставится на паузу. И пока она на паузе, можно посмотреть содержимое регистров и оперативной памяти. Так что можно занефиг делать проверить, действительно ли в ЕСХ попадает число зарядов яда. И при каких условиях вообще вызываются эти два блока.


kelamor  Offline Сообщение №9 написано: 13 Февраля 2017 в 19:31


Странник


18
Вот что выкопал из OllyDbg

Код
int allowOverwrite1 = 0x7462CD;  //начало какого-то блока кода Скайрима
int allowOverwrite2 = 0x7462D4;  //конец блока
//CPU Disasm
//Address   Hex dump          Command                    Comments
//007462CD  |.  A1 A898B101   MOV EAX,DWORD PTR DS:[1B198A8]
//007462D2  |.  EB 5C         JMP SHORT 00746330
//007462D4  |>  899F EC050000 MOV DWORD PTR DS:[EDI+5EC],EBX

int isPoison1 = 0x4757B0;   //начало оригинальной процедуры проверки ядовости?
//CPU Disasm
//Address   Hex dump          Command                    Comments
//004757B0  /$  8B41 04       MOV EAX,DWORD PTR DS:[ECX+4]             ; TESV.004757B0(guessed void)
//004757B3  |.  57            PUSH EDI
//004757B4  |.  33FF          XOR EDI,EDI
//004757B6  |.  85C0          TEST EAX,EAX
//004757B8  |.  74 1C         JZ SHORT 004757D6
//004757BA  |.  56            PUSH ESI
//004757BB  |.  8B30          MOV ESI,DWORD PTR DS:
[EAX]//004757BD  |.  85F6          TEST ESI,ESI
//004757BF  |.  74 14         JZ SHORT 004757D5
//004757C1  |.  8BCE          MOV ECX,ESI
//004757C3  |.  E8 389FF9FF   CALL 0040F700
//004757C8  |.  85C0          TEST EAX,EAX
//004757CA  |.  74 09         JZ SHORT 004757D5
//004757CC  |.  8BCE          MOV ECX,ESI
//004757CE  |.  5E            POP ESI
//004757CF  |.  5F            POP EDI
//004757D0  |.^ E9 2B9FF9FF   JMP 0040F700
//004757D5  |>  5E            POP ESI
//004757D6  |>  8BC7          MOV EAX,EDI
//004757D8  |.  5F            POP EDI
//004757D9  \.  C3            RETN

int reducePoison1 = 0x475810;  //начало какой-то оригинальной процедуры. что делает? снимает яд?
//CPU Disasm
//Address   Hex dump          Command                    Comments
//00475810  /$  8B41 04       MOV EAX,DWORD PTR DS:[ECX+4]
//00475813  |.  85C0          TEST EAX,EAX
//00475815  |.  74 1B         JZ SHORT 00475832
//00475817  |.  56            PUSH ESI
//00475818  |.  8B30          MOV ESI,DWORD PTR DS:
[EAX]//0047581A  |.  85F6          TEST ESI,ESI
//0047581C  |.  74 13         JZ SHORT 00475831
//0047581E  |.  8BCE          MOV ECX,ESI
//00475820  |.  E8 DB9EF9FF   CALL 0040F700
//00475825  |.  85C0          TEST EAX,EAX
//00475827  |.  74 08         JZ SHORT 00475831
//00475829  |.  8BCE          MOV ECX,ESI
//0047582B  |.  5E            POP ESI
//0047582C  |.^ E9 CFAEF9FF   JMP 00410700
//00475831  |>  5E            POP ESI
//00475832  \>  C3            RETN

int reduceCharge1 = 0x410711;  //еще какой-то блок кода
int reduceCharge2 = 0x410719;  //его конец
//CPU Disasm
//Address   Hex dump          Command                    Comments
//00410711  |.  85C9          TEST ECX,ECX
//00410713  |.  76 04         JBE SHORT 00410719
//00410715  |.  49            DEC ECX
//00410716  |.  8948 08       MOV DWORD PTR DS:[EAX+8],ECX
//00410719  |>  8378 08 00    CMP DWORD PTR DS:[EAX+8],0



Добавлено (13 Февраля 2017, 19:31)
---------------------------------------------

Цитата Dsion ()
Число зарядов сразу устанавливается в ноль? Не уверен, но вероятно...

Я так понял, что ноль - это и есть бесконечное число ударов.

Dsion  Offline Сообщение №10 написано: 13 Февраля 2017 в 19:40



1203
Мне кажется, что установка в ноль приводит к снятию яда. Вот оно тебе надо, добрый человек?( Это даже не нормальное программирование, а хакерство какое-то...
Разве в Скайриме нельзя заменять яд на оружии? А если нет, то разве нельзя это по-нормальному сделать,?

kelamor  Offline Сообщение №11 написано: 13 Февраля 2017 в 19:51


Странник


18
Цитата Dsion ()
В OllyDbg есть очень полезная штука - брейкпоинты. Ставишь такой брейкпоинт на любой адрес кода и, если он будет выполняться, программа поставится на паузу. И пока она на паузе, можно посмотреть содержимое регистров и оперативной памяти. Так что можно занефиг делать проверить, действительно ли в ЕСХ попадает число зарядов яда. И при каких условиях вообще вызываются эти два блока.

Ставлю через контекстное меню "Breakpoint -> Toggle" - программа не останавливается.

А этот кусок "CMP P DWORD PTR DS:[EAX+8],0 " что делает?
То есть получается, что "MOV DWORD PTR DS:[EAX+8],ECX" копирует в [EAX+8] значение регистра ECX, а CMP? Не на ноль же значение [EAX+8] заменяет?

Добавлено (13 Февраля 2017, 19:49)
---------------------------------------------

Цитата Dsion ()
Мне кажется, что установка в ноль приводит к снятию яда. Вот оно тебе надо, добрый человек?( Это даже не нормальное программирование, а хакерство какое-то...Разве в Скайриме нельзя заменять яд на оружии? А если нет, то разве нельзя это по-нормальному сделать,?

В том-то и дело, что нельзя, только сделать выстрел/удар.
В Скайриме яд наносится на один удар, после прокачки Алхимии - на 2 удара.
При нанесении нового яда игра сообщает, что оружие уже отравлено и всё.

Но это же бред (((
Сделал перк с увеличением ударов ядом до 10, всё работает через стандартные инструменты, но сменить-то яд невозможно... Тоже хрень получается.
Играю с модом Статик и возможность нормально работать с ядами была бы большим подспорьем, но никак не читом )))

Добавлено (13 Февраля 2017, 19:51)
---------------------------------------------

Цитата Dsion ()
А если нет, то разве нельзя это по-нормальному сделать,?
Средствами Папируса, СКСЕ, Creation Kit - не нашёл, весь интернет облазил - не нашёл.

Dsion  Offline Сообщение №12 написано: 13 Февраля 2017 в 20:05



1203
Брейкпоинты я, по-моему, через F2 ставил...
CMP - это, вроде, сравнение. А потом разветвление в зависимости от результата.
Эй, я тебе не бог ассемблера какой-то :(

kelamor  Offline Сообщение №13 написано: 13 Февраля 2017 в 20:14


Странник


18
Да, почитал. В самом деле - сравнение, там дальше разветвление идёт.

CPU Disasm
Address   Hex dump          Command                                  Comments
00410711  |.  85C9          TEST ECX,ECX
00410713  |.  76 04         JBE SHORT 00410719
00410715  |.  49            DEC ECX
00410716  |.  8948 08       MOV DWORD PTR DS:[EAX+8],ECX
00410719  |>  8378 08 00    CMP DWORD PTR DS:[EAX+8],0
0041071D  |.  75 09         JNE SHORT 00410728
0041071F  |.  6A 3E         PUSH 3E                                  ; /Arg1 = 3E
00410721  |.  8BCE          MOV ECX,ESI                              ; |
00410723  |.  E8 E8B4FFFF   CALL 0040BC10                            ; \TESV.0040BC10
00410728  |>  5E            POP ESI
00410729  \.  C3            RETN

Добавлено (13 Февраля 2017, 20:11)
---------------------------------------------
Не бог ассемблера :D 

Спасибо за помощь, я хоть начал что-то понимать ))

Добавлено (13 Февраля 2017, 20:14)
---------------------------------------------
В Ассембелере я ещё не ковырялся никогда, мне уже интересно )
Раньше максимум где я что-то понимал - 1С, MS Access и DA Script в Dragon Age Origin.


Dsion  Offline Сообщение №14 написано: 13 Февраля 2017 в 20:43



1203
Ну и хорошо. А то я немного болею и думать очень лень.

kelamor  Offline Сообщение №15 написано: 17 Февраля 2017 в 20:00 | Отредактировано: kelamor - Пятница, 17 Февраля 2017, 19:59


Странник


18
Цитата Dsion ()
Ну и хорошо. А то я немного болею и думать очень лень.

Я сделал это )))

С OlleDbg замучился, потом выяснил опытным путём, что в Visual Studio можно отлаживать не компилированный проект, присоединяешься к процессы Скайрима и skse исходник
может перехватывать события Скайрима (при условии, что скомпилированный dll находится в SKSE\Plugins.

Как всегда, оказалось всё просто )))
Во втором только блоке asm внёс правки и в первый добавил переменную, которая мне говорит, что был использован яд.

  
Код
_asm
  {
   mov jumpAddress2, offset RPCodeStart
   jmp RPCodeEnd
  
  RPCodeStart:
   test ecx, ecx    //ecx - счётчик яда
   jbe RPBack     
   cmp kPoisonUse, 1   //проверяем, был ли использован яд, если 1, то был, значит переходим к RPPoisonUse
   je RPPoisonUse    //если был выстрел/удар, то продолжаем RPWeaponUse
  
  RPWeaponUse:
   dec ecx      //снимаем один удар
   mov [eax+8], ecx   //устанавливает регистр [eax+8] в новое количество ударов
   jbe RPBack     //так как использовано оружие, то пропускаем обнуление яда (смену яда)
  
  RPPoisonUse:     //смена/снятие яда
   cmp doRemove, 0    
   je RPBack     
   mov doRemove, 0    
   mov kPoisonUse, 0
   mov ecx, 0     
   mov [eax+8], ecx    
  RPBack:
   jmp reduceCharge2   
  
  RPCodeEnd:
  }

Спасибо тебе, Dsion, за подсказки good

Добавлено (17 Февраля 2017, 20:00)
---------------------------------------------
Теперь бы ещё понять, как из dll skse сообщение показывать в уголке экрана с информацией, сколько осталось ударов с ядом )


Dsion  Offline Сообщение №16 написано: 17 Февраля 2017 в 20:31



1203
Ничего не ясно, но раз работает, то круто :)

Форум » TES V: Skyrim » Мастерская » Твик на отравление оружием (Нужна помощь в редактировании плагина dll SKSE)
Страница 1 из 11
Поиск: