[Урок] Мини-уроки

Kailo

Участник
Сообщения
168
Реакции
755
Уроки не заслуживающие отдельных тем под них.

Содержание:
1. Порядок байтов
2. Нарушение размеров массива для неявной модификации данных
3. Присвоение char массиву начального значения
4. Преобразование типа массивов на примере не обычного получения адреса из hostip
5. Структура массивов SourcePawn
6. Блок кода
7. Условие с инструкциями
8. Конкатенация строк
9. Опасность проверки на интервал (двойное сравнение)
10. Именованная передача параметров

1. Порядок байтов
Память разбита на байтовые ячейки. В памяти данные переменных размером более одного байта записываются в порядке определенным языком программирования. У C++ и SourcePawn этот порядок от младшего к старшему (англ. little-endian). Пару примеров для лучшего понимания:
Код:
332967832 // Число в десятичной системе счисления (DEC)
13 D8 AF 98 // Оно же в шестнадцетиричной системе (HEX), каждому байту будет соответствовать по 2 символа, поэтому сразу разделим число
98 AF D8 13 // Но если считать участок памяти побайтового, где хранится число, то в HEX получим
Рассмотрим еще один случай с числом где старших разрядов нет
512 // DEC
200 // HEX
00 00 02 00 // Дополняем HEX до 8 чисел нулями
00 02 00 00 // Вид в памяти
Если хотите знать больше или не поняли, читайте Википедия:Порядок байтов

2. Нарушение размеров массива для неявной модификации данных
Все мы знаем, что запрещено превышать размеры массивов, иначе получим ошибку "out of bounds" (дословно, за пределами). Ну а сейчас я расскажу, как за них выбраться, не получая ошибку и что это дает.

Во-первых надо знать как происходит передача параметров при вызове функции. А конкретно как передаются массивы.
Массивы передаются только по адресу образуя ссылку (Если вы этого не понимаете, можете дальше особо не читать этот урок, позже, если скажут что нужно, напишу отдельный урок).
А так как ссылка это по сути всего лишь адрес без размера, то проверки на размер не существует.
Пример:
PHP:
int g_array[3];
int g_var = 4;

public void OnPluginStart()
{
    func(g_array);
}

void func(int[] array)
{
    PrintToSever("%i", array[3]); // Сработает, выведет значение g_var
    PrintToSever("%i", g_array[3]); // Выдаст ошибку
}
Что же это нам позволяет делать, спросите вы. Учитывая тот факт, что переменные расположены последовательно в памяти, согласно их объявлению, мы можем менять значения переменных "не явно". Т.е. присваивания значения переменной в программе вы не найдете. Вот вам пример:
PHP:
int g_state[MAXPALYERS + 1];
bool g_cankill = false;
const int g_worktype = 3;

public void OnPluginStart()
{
    g_cankill = !g_cankill;
    RegConsoleCmd("sm_cmd", Cmd);
    func(g_state);
}

void func(int[] array)
{
    array[g_worktype + 64] = 2;
}

public Action Cmd(int client, int args)
{
    if (g_worktype != 2) return Plugin_Handled;

    // Do something...

    return Plugin_Handled;
}
Как видите здесь не явно изменяется значение константы :blink:
Это лишь один из многих способов использовать данную возможность. Вы можете придумать своё применение этому. Но вы должны понимать, что вы делаете и зачем, иначе работать не будет.

P.S. Именно поэтому вы передаете размер строки (символьного массива) при вызове функций, которые при модификации содержимого могут увеличивать кол-во символов.

3. Присвоение char массиву начального значения
Этот урок, ответ на вопрос, как задать начальное значение массива типа char конструкцией вида
Код:
char a[] = { ... };
Объяснение: (если вам не интересно, то сразу смотрите в конец урока)
Что бы понять как это сделать и не получать ошибок, надо знать несколько аспектов программирования на SourcePawn.
1) Конструкция типа { ... } возвращает объект типа int.
2) Константа типа 'f', имеет тип int, а не char как вы могли подумать.
3) Каждая ячейка конструкции { ... } имеет размер 4 байта и тип соответствующий типу введённых данных
4) Нужно понимать 1й урок этой темы о порядке байтов данных в памяти.
5) Система ячеек (англ. cell), подразумевает что данные должны быть записаны блоками по 4 байта (Поэтому char переменные кратны 4 (Могли видеть при декомпиляции, что размеры "строк" увеличиваются до кратного 4), и всё по этой же причине переменная типа bool занимает 4 байта, а не 1 бит).
6) Пустым ячейкам автоматически дается нулевое значение
Часто, если мы делаем так, ...:
Код:
char a[] = {'5', '3', '\0'};
('\0' это, если кто-то не знает, эквивалент нулевого символа, соответственно равное \x00 или 0).
... многие ожидают что получат строку вида "53", или же, если расписывать байтовые ячейки памяти, то:
Код:
'5', '3', '\0', '\0' (4 символа по причине 5го пункта)
Но если вы все уже поняли, то вам ясно, что согласно пункту 3 и 6, память выглядит следующим образом:
Код:
'5', '\0', '\0', '\0', '3', '\0', '\0', '\0', '\0', '\0', '\0', '\0'
А согласно пунктам 1, 2 и 3 о типах данных, получим предупреждения о том что массиву типа char присваиваются данные типа int, и еще 3 ошибки (на каждую ячейку) о несовместимости типов (ожидался char, а дан int).
Получается, надо слепить 4 байта в одну ячейку, но вспоминая о порядке байтов, их следует перевернуть:
Т.е. для строки "53", что есть '5', '3', '\0', '0', а если перевернуть '\0', '\0', '3', '5'. Переводим буквы в hex по таблице ANSI/UTF-8 и получаем 00 00 33 35, сливаем в единое число 3335 и либо пишем его в hex как 0x3335 или переводим в DEC 13109
Получим:
Код:
char a[] = {0x3335};
или char a[] = {13109};
Далее исправляем типы данных и получаем:
Код:
char a[] = view_as<char>({view_as<char>(13109)});
Это абсолютно правильный код, который не вызывает ошибок, можете сами проверить, попытавшить вывести строку в чат или консоль, и получите "53".
Но этот процесс немного утомительный и сложный, поэтому я облегчил задачу используя макросы:
Код:
#define charblock(%1,%2,%3,%4) view_as<char>(%4 << 24 | %3 << 16 | %2 << 8 | %1)
Все математические операции будут произведены во время компиляции, и в итоге превратится в конечное число.
Теперь, мы можем делать следующие:
Код:
char a[] = view_as<char>({charblock('5', '3', '\0', '\0')});
Итог:
Код:
#define charblock(%1,%2,%3,%4) view_as<char>(%4 << 24 | %3 << 16 | %2 << 8 | %1)
// Запишем строку "Hello, world!"
char a[] = view_as<char>({charblock('H', 'e', 'l', 'l'), charblock('o', ',', ' ', 'w'), charblock('o', 'r', 'l', 'd'), charblock('\0', '\0', '\0', '\0')});
Что же это нам даёт:
1) Расширяет инструментарий языка;
2) Позволяет записывать нулевой символ в любом месте строки или строку без нулевого символа;
3) Следуя из 2го, позволяет обманывать декомпиляторы, т.к. тот считывает строку до первого нулевого символа. Запишем строку где первый символ '\0', а остальные нормальные, и потом или обращаемся к строке через a[1] или изменяем 1й символ a[0] = 'a', а декомпилятор, либо выдаст ошибку, либо подумает что строка пустая.
P.S. Мне кажется, что такого даже на AlliedModers форуме нет — эксклюзив.

4. Преобразование типа массивов на примере не обычного получения адреса из hostip
В основе лежит возможность поменять тип массива при передачи его как параметра, с помощью конструкции view_as<>().
Квар hostip хранит адрес сервера. Так как, адрес это 4 восьмибитных числа (0-255), его записали как 32 бита int числа. Наша цель представить 4 байта числа, как 4х байтовый массив.
Сделать это есть несколько способов, но после нескольких попыток, я решил, что нашел самый оптимальный способ для конкретной задачи по получению адреса (см. пример 1).
В функцию передается один и тот же массив два раза, но с разными типами (для упрощения я сделал макрос; иначе придется добавлять дополнительную функцию, см. пример 2).
Присваиваю первым 4м битам значение равное адресу.
Далее не забывая о порядке байтов, формирую строку адреса; плюс ноль нужен для получения значения по указанному адресу, иначе он передает адрес char массива со смещением. Идеальный способ был бы разыменовать, но у SourcePawn нет такой операции.

Пример 1
PHP:
#define GetServerIp(%1,%2) GetServerIpFunc(view_as<int>(%1), %1, %2)

// Размер буфера >= 16
void GetServerIpFunc(int[] array, char[] buffer, int maxlength)
{
    array[0] = cvar_hostip.IntValue;
    FormatEx(buffer, maxlength, "%i.%i.%i.%i", buffer[3] + 0, buffer[2] + 0, buffer[1] + 0, buffer[0] + 0);
}
Пример 2
PHP:
// Размер буфера >= 16
void GetServerIp(char[] buffer, int maxlength)
{
    GetServerIpHelper(view_as<int>(buffer));
    FormatEx(buffer, maxlength, "%i.%i.%i.%i", buffer[3] + 0, buffer[2] + 0, buffer[1] + 0, buffer[0] + 0);
}

void GetServerIpHelper(int[] array)
{
    array[0] = cvar_hostip.IntValue;
}
Остальная часть тестовой программы
PHP:
// Тут располагается функция

ConVar cvar_hostip;

public void OnPluginStart()
{
    RegConsoleCmd("sm_ip", Cmd_IP);
    cvar_hostip = FindConVar("hostip");
}

public Action Cmd_IP(int client, int args)
{
    char ipaddress[16];
    GetServerIp(ipaddress, sizeof(ipaddress));
    ReplyToCommand(client, "%s", ipaddress);

    return Plugin_Handled;
}
5. Структура массивов SourcePawn
Одно размерный массив устроен по обычному. А вот о формате много размерных я и собираюсь рассказать.
К примеру скажем вы захотели массив myarray[3][4]. Какой будет его суммарный размер? Правильно! 15! (Что? Не угадали?)
Ну 12 байтов понятно, на сами данные. Куда же еще 3 и какую информацию они хранят?
Для того чтобы пример был нагляднее постановим, что
Код:
myarray[3][4] = {
    {50, 100, 150, 200},
    {-1, -2, -3, -4},
    {1001, 1002, 1003, 1004}
};
Если представить, что по тому же адресу что и myarray хранится одно размерный массив, его содержание было бы
Код:
mybro[15] = {12, 24, 36, 50, 100, 150, 200, -1, -2, -3, -4, 1001, 1002, 1003, 1004};
Первые три числа обозначают смещение в байтах от своего адреса, до начала соответствующего "подмассива" (нижней размерности)!

В памяти массив хранится адресом. Обозначим адресс нашего массива за n.Когда мы делаем myarray[2] или myarray[var], где var - переменная (предположим тоже 2 равна), происходит вычисление адреса "подмассива" по следующем алгоритму:
  • Если это переменная, а массив имеет фиксированное значение, то сначала проверяется, не превышает ли он размеры массива. Потом значение индекса преобразовывается в смещение адреса, путем умножения на 4 (размер ячейки).
    b = n + var * 4;
    Если это константа, то она уже была проверена при компиляции и преобразована в смещение.
    b = n + const;
  • Далее происходит получение значения по адресу и прибавление к текущему адресу b.
    Получаем адрес i-го "подмасива"
    c = load(b) + b;
    где load это получение значения по адресу b (так же можно записать для любителей C++ как c = *b + b)
  • Далее происходит проверка следующей размерности, если переменная. И либо прибавления смещения (если тип массива char, то просто прибавляется значение без домножения на размер ячейки) к c, если нужен адрес или еще последующая загрузка значения.
    val = load(c + var2 * 4);
Краткий математический вариант этого будет такой
Код:
var = 1;
var2 = 2;
val = myarray[var][var2]; // Получим -3
// Эквивалентно
val = mybro[var + (mybro[var] / 4) + var2]; // Получим -3
P.S. С увеличением размерности матрешка разрастается.

6. Блок кода
Представляет собой набор инструкций объединенных фигурными скобками.
PHP:
public void OnPluginStart()
{
    {
        // Блок кода
    }
}
Все переменные объявленные внутри блока перестают существовать после конца блока.
Применяется редко и скорее всего в целях оптимизации.
Т.е. в случае когда переменная используется не на протяжении всей функции, а только её части. С целью уменьшить кол-во одновременно занимаемого кол-ва памяти, в конце блока переменная прекратит существовать.
Пример объявления одноименных переменных в разных блоках.
PHP:
public void OnPluginStart()
{
    {
        int a = 3;
        PrintToServer("%i", a);
    }
    {
        int a = 5;
        PrintToServer("%i", a);
    }
    int a = 7;
    PrintToServer("%i", a);
}
Так же, используя старый синтаксис можно сделать неявное присваивание.
PHP:
public OnPluginStart()
{
    {
        decl String:HS[10];
        strcopy(HS, sizeof(HS), "Trick me!");
    }
    decl String:str[10];
    PrintToServer(str); // Выведет "Trick me!", т.к. str занимает тот же участок памяти что ранее HS
}
И так же это можно использовать для обмана декомпиляторов, т.к. они редко поддерживают отслеживание блоков. К примеру такой вот код. Декомпилятор не поставит скобки блока, и компилятор выдаст ошибки описанные в комментариях.
PHP:
{
    int buffer = 5;
    PrintToServer("Good Day Sir!", buffer);
}
char buffer[16]; // Ругаемся что переменная с таким именем уже объявлена
GetCmdArgString(buffer, sizeof(buffer)) // Ругаемся на не совпадение типов, т.к. думаем что buffer int. Ну и не можем выполнить sizeof не для массива.

7. Условие с инструкциями
В SourcePawn разрешено выполнять инструкции внутри if условия, отделяя их запятыми от самого условия
PHP:
if (инструкция, условие)
{
}
Инструкций может быть сколько угодно много.
Данная конструкция полезна когда требуется выполнить промежуточные вычисления для следующего условия.
К примеру мы ищем объекты класса prop_dynamic:
PHP:
char classname[32];
for (int entity = 65, max = GetMaxEntities(); entity < max; entity++)
    if (IsValidEntity(entity)) {
        GetEntityClassname(entity, classname, sizeof(classname));
        if (StrEqual(classname, "prop_dynamic"))
            PrintToServer("prop_dynamic(%i)", entity);
    }
В свою очередь можно записать как
PHP:
char classname[32];
for (int entity = 65, max = GetMaxEntities(); entity < max; entity++)
    if (IsValidEntity(entity) && (GetEntityClassname(entity, classname, sizeof(classname)), StrEqual(classname, "prop_dynamic")))
        PrintToServer("prop_dynamic(%i)", entity);
Пример для уроков 6 и 7 из личной практики.
Команда в которой происходит передача steamid и нам надо проверить его корректность.
PHP:
public Action Cmd_MyCmd(int client, int args)
{
    char auth[24];
    // Getting auth and validate
    {
        int reason;
        if ((reason = 1, GetCmdArg(1, auth, sizeof(auth)) < 11)
            || (reason = 2, strncmp(auth, "STEAM_1:", 8))
            || (reason = 3, auth[8] != '0' && auth[8] != '1')
            || (reason = 4, auth[9] != ':')
            || (reason = 5, !IsStringNumeric(auth[10])))
        {
            ReplyToCommand(client, "Not valid steamid (%i). Need: STEAM_1:X:Y", reason);
            return Plugin_Handled;
        }
    }

    // Do stuff

    return Plugin_Handled;
}
8. Конкатенация строк
У компилятора SourcePawn есть оператор конкатенации для объединения константных строк.
В процессе компиляции строки будут объединены как единая новая строка. В роли оператора выступает троеточие.
PHP:
"строка1" ... "строка2" // после компиляции на этом месте будет "строка1строка2"
Данный синтаксис будет удобным в случае выноса каких-либо частей в define.
К примеру версия плагина или имя таблицы.
PHP:
#define PLUGIN_VERSION

public Plugin myinfo = {
    author = PLUGIN_VERSION,
};

// Где-то в коде, сообщение при входе игрока на сервер
    PrintToChat(client, "Сервер работает под управлением Мой_Плагин версии " ... PLUGIN_VERSION ... ".");
PHP:
#define DBTABLE "sm_myvipplugin"

// Где-то в коде, запрос к БД
    g_DB.Query(SQLQueryCallback, "SELECT steamid, group, time FROM " ... DBTABLE);
9. Опасность проверки на интервал (двойное сравнение)
Я говорю о коде вида
PHP:
if (0 < var < 10)
Такое часто используется для проверки индекса игрока
PHP:
if (0 < client <= MaxClients)
Но на самом деле это преобразуется в (могли видеть, если использовали Lysis)
PHP:
if (MaxClients >= client & 0 < client)
Теперь поговорим что может быть в этом плохого
1) Обработка результатов сравнений совместно (Если из увиденного кода вам не понятно, читайте спойлер)
Рассмотрим пример
PHP:
if (IsClientInGame(client) && GetClientTeam(client) > CS_TEAM_SPECTATOR)
Как вы знаете GetClientTeam выкинет ошибку, если игрок не в игре, но данный код не кидает ошибку, потому что, если первая часть логического И false, то он и не станет проверять вторую, а сразу перейдет к коду после конца if блока. Это распространяется так же и на логическое ИЛИ (||), если оператор вернет true, то программа не станет проверять/выполнять последующие условия, а сразу перейдет к коду внутри блока if.
В случае же проверки на интервал, бинарное И. Программа сначала посчитает оба условия и только потом будет проверять его в if.
Т.е. если у нас client > MaxClients программа все равно проверит если client > 0.
2) У кого-то может сложится мнение, что он сравнивает полученное значение, получая его только 1 раз (у тех кто еще не читал пункт 1). Но как вы можете видеть, значение получается дважды. А в случае функции, будет два вызова функции.
PHP:
if (0 < MyFunc() < 10)
// на самом деле будет
if (10 > MyFunc() & 0 < MyFunc())
// И более лучше будет сделать
int value = MyFunc();
if (value > 0 && value < 10)
10. Именованная передача параметров
Еще один редкий приём синтаксиса, унаследованный от Pawn.
Сначала напомню еще одну истину, вдруг кто не знает.
В SourcePawn доступны параметры вызова по-умолчанию. Это значит что при вызове вы можете их не указывать, и они буду установлены на значения по умолчанию, которые указаны в описании функци. К примеру:
PHP:
// Имея функцию
void MyFuncAlpha(int param1 = 1, int param2 = 3, int param3 = 3, int param4 = 7);

// Мы можем вызвать её как
MyFuncAlpha();
// и тогда параметры будут переданы с значениями 1, 3, 3 и 7.
При этом есть специальный символ "_" (нижнее подчеркивание), чтобы пропустить параметр при передаче и оставить его со значением по-умолчанию.
К примеру мы хотим указать param3 как 9 и не трогать при этом другие параметры, мы можем сделать:
PHP:
MyFuncAlpha(_, _, 9);
Значения параметров будут 1, 3, 9, 7.
При малом кол-ве параметров это не проблема пропустить 3, 4 параметра, но вот вам к примеру функция EmitSoundToAll:
PHP:
/**
* Wrapper to emit sound to all clients.
*
* @param sample        Sound file name relative to the "sounds" folder.
* @param entity        Entity to emit from.
* @param channel        Channel to emit with.
* @param level            Sound level.
* @param flags            Sound flags.
* @param volume        Sound volume.
* @param pitch            Sound pitch.
* @param speakerentity    Unknown.
* @param origin        Sound origin.
* @param dir            Sound direction.
* @param updatePos        Unknown (updates positions?)
* @param soundtime        Alternate time to play sound for.
* @error                Invalid client index.
*/
stock void EmitSoundToAll(const char[] sample,
                int entity = SOUND_FROM_PLAYER,
                int channel = SNDCHAN_AUTO,
                int level = SNDLEVEL_NORMAL,
                int flags = SND_NOFLAGS,
                float volume = SNDVOL_NORMAL,
                int pitch = SNDPITCH_NORMAL,
                int speakerentity = -1,
                const float origin[3] = NULL_VECTOR,
                const float dir[3] = NULL_VECTOR,
                bool updatePos = true,
                float soundtime = 0.0);
А нам как назло понадобилось изменить soundtime на 5.0...
PHP:
EmitSoundToAll("sample.wav", _, _, _, _, _, _, _, _, _, _, 5.0); // НЕ БРО!
Но нас есть решение, вместо того что-бы указывать 10 пропусков, мы можем указать значение какого именно параметра мы передаем по его имени:
PHP:
EmitSoundToAll("sample.wav", .soundtime = 5.0); // БРО!
Думаю синтаксис понятен: точка, имя параметра, знак равенство и само значение для передачи.
На их использование действуют следующие ограничения:
Все не именованные параметры должны передаваться только до именованных.
Соответственно и нельзя передавать не именованные параметры после именованных.
Порядок передачи именованных не важен и не как не зависит от порядка объявления, т.е. следующий код будет вполне корректным:
PHP:
EmitSoundToAll(.soundtime = 5.0, .updatePos = false, .sample = g_AlarmSound);
 
Последнее редактирование:

Vit_ amin

Участник
Сообщения
1,281
Реакции
471
Очень интересные уроки, спасибо тебе!
 

Kailo

Участник
Сообщения
168
Реакции
755
Добавил новый урок, см. в 1-м посте "4. Преобразование типа массивов на примере не обычного получения адреса из hostip".
 
Последнее редактирование:

Kailo

Участник
Сообщения
168
Реакции
755
Добавил новый урок, см. в 1-м посте "5. Структура массивов SourcePawn".

P.S. Так же я не забросил тему "[Урок] Структура smx", сегодня добавил информацию о ".publics", ".pubvars" и ".dbg.symbols" секциях.
Надеюсь в ближайшее время доделаю.
 

Kailo

Участник
Сообщения
168
Реакции
755
Добавил пару новых уроков, связанных с редко используемым синтаксисом, см. в 1-м посте:
"6. Блок кода"
"7. Условие с инструкциями"
А там же еще один пример к ним из личной практики.
 

Kruzya

Социопат
Команда форума
Сообщения
9,148
Реакции
7,433
Не могу не высказаться:
PHP:
public OnPluginStart()
{
    {
        decl String:HS[8];
        strcopy(HS, sizeof(HS), "Trick me!");
    }
    decl String:str[8];
    PrintToServer(str); // Выведет "Trick me!", т.к. str занимает тот же участок памяти что ранее HS
}[
Это ведь немного не так сработает.
Размер строки - 8 байт, а записывается 9. Вызываемый натив SM будет вынужден обрезать строку, и вместо "Trick me!" мы получим "Trick m" (7 байт - сам текст + 1 байт под NULL).
 

Kruzya

Социопат
Команда форума
Сообщения
9,148
Реакции
7,433
Приём с именованной передачей параметров очень прикольный. Впервые я его увидел 4 года назад в коде Чдаты здесь: Chdata/Versus-Saxton-Hale
Я о нём пару раз говорил в Дискорде Рико.
 

Саша Шеин

Кому костылей?
Сообщения
1,619
Реакции
473
Как видите здесь не явно изменяется значение константы :blink:
Это лишь один из многих способов использовать данную возможность. Вы можете придумать своё применение этому. Но вы должны понимать, что вы делаете и зачем, иначе работать не будет.
Решил попробовать.
В итоге:
Код:
PHP:
int g_state[MAXPLAYERS + 1];
bool g_cankill = false;
const int g_worktype = 3;

public void OnPluginStart() { RegConsoleCmd("sm_cmd", Cmd); func(g_state); }

void func(int[] array) {
    LogMessage("func(): g_worktype = \"%i\"", array[g_worktype + 64]);
    array[g_worktype + 64] = 2;
}
public Action Cmd(int client, int args)
{
    LogMessage("g_worktype: \"%i\"", g_worktype);
    if (g_worktype != 2) { return Plugin_Handled; }
   
    // Do something...
   
    return Plugin_Handled;
}
--- Добавлено позже ---
Для интереса, от скуки написал такой код:
PHP:
#define _String_ "String____________________________________________________________________________________"

char g_sArray[1] =  { "123" };
int g_iVar, g_iMinIndex, g_iMaxIndex, g_iIndexSameString;
const g_iConstValue = 1999;

public void OnPluginStart() {
    CreateTimer(0.1, Timer_ArrayGetMinIndex);
    CreateTimer(0.1, Timer_ArrayGetMaxIndex);
    CreateTimer(0.1, Timer_DumpStringList);
    CreateTimer(0.1, Timer_DumpAllList);
    RegConsoleCmd("sm_test_strings", Command_TestStrings);
    RegConsoleCmd("sm_dump_strings", Command_DumpStrings);
}
public Action Timer_ArrayGetMinIndex(Handle timer) { ArrayGetMinIndex(g_sArray); return Plugin_Stop; }
public Action Timer_ArrayGetMaxIndex(Handle timer) { ArrayGetMaxIndex(g_sArray); return Plugin_Stop; }
public Action Timer_DumpStringList(Handle timer) { DumpStringList(g_sArray); return Plugin_Stop; }
public Action Timer_DumpAllList(Handle timer) { DumpAllList(g_sArray); return Plugin_Stop; }
void ArrayGetMinIndex(char[] Array) { for (; g_iMinIndex < 1; g_iMinIndex--) { if (Array[g_iMinIndex]) {  } } }
void ArrayGetMaxIndex(char[] Array) { for (; g_iMaxIndex > -1; g_iMaxIndex++) { if (Array[g_iMaxIndex]) {  } } }
void DumpStringList(char[] Array) {
    int iSize = 0;
    for (int i = g_iMinIndex + 1; i < g_iMaxIndex; i++) {
        if (Array[i] != '\0') {
            iSize = strlen(Array[i]);
            LogToFileEx("addons/sourcemod/plugins/DumpStrings.log", "Index: \"%i\" Value: \"%s\"", i, Array[i]);
            if (strcmp(Array[i], _String_) == 0) {
                g_iIndexSameString = i;
            }
            i += iSize;
        }
    }
}
void DumpAllList(char[] Array) {
    for (int i = g_iMinIndex + 1; i < g_iMaxIndex; i++) {
        LogToFileEx("addons/sourcemod/plugins/DumpAll.log", "Index: \"%i\" Value: \"%i\"", i, Array[i]);
    }
}

public Action Command_TestStrings(int iClient, int iArgs) {
    LogMessage(_String_);
    ChangeString(g_sArray);
    return Plugin_Handled;
}
char g_sStringsArray[][] =  { "First str", "String №2", "Hello World!" };
void ChangeString(char[] Array) {
    static iIndex;
    if (iIndex >= sizeof(g_sStringsArray)) { iIndex = 0; }
    strcopy(Array[g_iIndexSameString], 94, g_sStringsArray[iIndex]);
    iIndex++;
}

public Action Command_DumpStrings(int iClient, int iArgs) {
    DumpStringList(g_sArray);
    return Plugin_Handled;
}
Как итог:
1) Я понял, что дальше памяти выделенной плагину не выйти, ошибка: Invalid memory access
2) Константу в области, которая доступна плагину, я так и не нашел
3) Строку смог заменить:
Файлы DumpAll.log и DumpStrings.log прикрепил, кому интересно.
 

Вложения

Последнее редактирование:
  • Мне нравится
Реакции: All

Kailo

Участник
Сообщения
168
Реакции
755
Решил попробовать.
Код:
PHP:
int g_state[MAXPLAYERS + 1];
bool g_cankill = false;
const int g_worktype = 3;

public void OnPluginStart() { RegConsoleCmd("sm_cmd", Cmd); func(g_state); }

void func(int[] array) {
    LogMessage("func(): g_worktype = \"%i\"", array[g_worktype + 64]);
    array[g_worktype + 64] = 2;
}
public Action Cmd(int client, int args)
{
    LogMessage("g_worktype: \"%i\"", g_worktype);
    if (g_worktype != 2) { return Plugin_Handled; }
  
    // Do something...
  
    return Plugin_Handled;
}
К сожалению ты не упомянул о очень важном факте - предупреждение при компиляции " warning 203: symbol is never used: "g_cankill" ". При этом он "вырезает" переменную из программы, а следовательно ломает нам всю адресацию. Есть два варианта:
1) Как-либо задействовать g_cankill, что бы она не считалась не использованной, я сделал "g_cankill = !g_cankill;" исправив пример, данный в статье.
2) Убрать переменную, но при этом надо понимать что адресация изменилась, и теперь должно быть "array[g_worktype + 63] = 2;" в func функции.

Еще нетривиальной механикой работы является передача массивов. Как в случае, когда ты пытался вывести g_worktype через "array[g_worktype + 64]". Здесь 25709 очень хороший пример непредсказуемого поведения, т.к. без тщательного анализа не определить почему он именно это число получил и откуда он его взял. И кстати, если хочешь выводить значение функцией с форматированием строки, из массива, используй трюк показанный в уроке 4. Сделай математическое преобразование не меняющие результат, как "+ 0" или "* 1".
 
Сверху