Гайд Укрощение случайности: Анализ нового no spread.

Разработчик
Разработчик
Статус
Оффлайн
Регистрация
1 Сен 2018
Сообщения
1,663
Реакции
908
Вступление
Привет всем! Сегодня я хочу поделиться с вами информацией о том, почему снова появился no-spread и как он работает. Это действительно то, что изменит правила игры для всех

Изменения которые привели к его появлению
В ночь 9 ноября успешная компания Valve выпустила новое обновление для Counter-Strike 2. В этом обновлении они решили исправить проблемы с системой sub-tick. Одно из решений, к которому они пришли, было синхронизировать клиентское и серверный сид случайностей.
Как мы знаем, предыдущий no-spread появился из-за того, что сервер доверял клиентскому сиду. Решение этой проблемы было в генерации сида на сервере, что привело к десинхронизации до 9 ноября (в редких случаях она все еще существует)
Изменения на клиенте
В этой части я применю реверс-инжиниринг @Soufiw в знак уважения к исходному источнику.
После этого обновления, сид синхронизируется между клиентом и сервером с помощью хэширования углов обзора и текущего тика отрисовки.

C++:
Expand Collapse Copy
uint32_t GetRandomSeed( const QAngle& angles, int player_render_tick )

{

#ifdef SERVER_DLL

    // sv_usercmd_custom_random_seed = false by default now.

    // m_nPredictionRandomSeed = cmd->random_seed;

    // m_nPredictionRandomSeedServer = sv_usercmd_custom_random_seed ? GetRngSeed() : cmd->random_seed;

    if ( m_nPredictionRandomSeedServer != m_nPredictionRandomSeed )

        return m_nPredictionRandomSeedServer;

#endif

    struct HashBuffer

    {

        float pitch;

        float yaw;

        int tick;

    };

    HashBuffer buffer;

    float pitch = AngleNormalize(angles.pitch);

    pitch += pitch;

    buffer.pitch = RoundFloat(pitch) * 0.5f;

    float yaw = AngleNormalize(angles.yaw);

    yaw += yaw;

    buffer.yaw = RoundFloat(yaw) * 0.5f;

    buffer.tick = player_render_tick;

    CSHA1 sha1;

    sha1.Reset(  );

    sha1.Update( &buffer, sizeof(buffer) );

    sha1.Final();

    return *reinterpret_cast<uint32_t*>(&sha1.m_digest[0]);

}

В итоге, создание no-spread кажется действительно простым: всего лишь нужно получить текущий сид и компенсировать его, как в 2015 году! Но, к сожалению, это не работает так. При изменении угла обзора, сид также меняется, что логично. Чтобы обойти это ограничение, я пришел к более простому и оригинальному решению.

Использование хаоса как основу для порядка
Если вы сейчас стоите, то вам лучше присесть, потому что это самое простое решение, которое можно придумать.

Предположим, у нас есть угол обзора и текущий тик отрисовки. Для компенсации разброса мы можем перебирать случайные углы, вычислять для них сид и вычислять финальный угол с учетом разброса.

В итоге, наш принцип действия будет выглядеть так:
  1. Генерируем случайный угол.
  2. Вычисляем сид для этого случайного угла и текущего тика.
  3. Используем полученный сид для моделирования выстрела с разбросом из текущего угла.
  4. Получаем финальный угол выстрела с учетом разброса.
  5. Если финальный угол достаточно близок к исходному (в пределах заданного порога), сохраняем найденный сид.
  6. Если нет, генерируем новый случайный угол и повторяем шаги с 3 по 6.
  7. Как только находим подходящий угол, используем его вместо реального.
  8. Для нового угла обзора повторяем поиск подходящего угла сначала.

Таким образом, мы перебираем случайные варианты, пока не найдем сид, которое компенсирует разброс для текущего угла.
Улучшения реализации: В нашем реализации мы брали угол обзора и моделировали разброс с сидом от 0 до 255. Это позволило оптимизировать подбор случайного угла.
Как же посчитать разброс?
Давайте сразу перейдем к коду, который отвечает за моделирование разброса. Я не буду углубляться в подробные объяснения, чтобы быстрее показать реализацию.

C++:
Expand Collapse Copy
bool bForceMaxInaccuracy = weapon_debug_max_inaccuracy.GetBool();

bool bForceInaccuracyDirection = weapon_debug_inaccuracy_only_up.GetBool();

bool bWeaponAccuracyShotgunSpreadPatterns = weapon_accuracy_shotgun_spread_patterns.GetBool();


Math::RandomSeed(nRandomSeed + 1);


float flRadiusCurveDensity = Math::RandomFloat(0.0f, 1.0f);


if (nItemIndex == 64 && nMode == nSecondaryMode) {

  // R8 Revolver secondary fire

  flRadiusCurveDensity = 1.0f - flRadiusCurveDensity * flRadiusCurveDensity;

}

else if (nItemIndex == 28 && flRecoilIndex < 3) {

  // Negev Wild Beast

  for (int nIndex = 3; nIndex > flRecoilIndex; --nIndex) {

    flRadiusCurveDensity *= flRadiusCurveDensity;

  }

  flRadiusCurveDensity = 1.0f - flRadiusCurveDensity;

}


if (bForceMaxInaccuracy) {

  flRadiusCurveDensity = 1.0f;

}


float flTheta0 = Math::RandomFloat(0.0f, 2.0f * M_PI);

if (bForceInaccuracyDirection) {

  flTheta0 = M_PI * 0.5f;

}


if (bWeaponAccuracyShotgunSpreadPatterns)

{

    // Missing.

}


float flRadius0 = flRadiusCurveDensity * flInaccuracy;

float flX0 = flRadius0 * cosf(flTheta0);

float flY0 = flRadius0 * sinf(flTheta0);


float flSpreadCurveDensity = Math::RandomFloat(0.0f, 1.0f);


if (nItemIndex == 64 && nMode == nSecondaryMode) {

  // R8 Revolver secondary fire

  flSpreadCurveDensity = 1.0f - flSpreadCurveDensity * flSpreadCurveDensity;

}

else if (nItemIndex == 28 && flRecoilIndex < 3) {

  // Negev Wild Beast

  for (int nIndex = 3; nIndex > flRecoilIndex; --nIndex) {

    flSpreadCurveDensity *= flSpreadCurveDensity;

  }

  flSpreadCurveDensity = 1.0f - flSpreadCurveDensity;

}


if (bForceMaxInaccuracy) {

  flSpreadCurveDensity = 1.0f;

}


float flTheta1 = Math::RandomFloat(0.0f, 2.0f * M_PI);

if (bForceInaccuracyDirection) {

  flTheta1 = M_PI * 0.5f;

}


float flRadius1 = flSpreadCurveDensity * flSpread;

float flX1 = flRadius1 * cosf(flTheta1);

float flY1 = flRadius1 * sinf(flTheta1);


double flYResult = (double)(flY1 * flRadius0) +

               (double)(flY0 * flRadius1);


double flXResult = (double)(flX1 * flRadius0) +

               (double)(flX0 * flRadius1);

Итог
Сложив всю информацию, вы могли прийти к тому же результату, что и я. Возврат компенсации отдачи повлечёт серьёзные изменения в культуре HvH и читеров в целом. Сейчас я хочу продемонстрировать, что это действительно работает.
В этом видео, записанном моим другом porches, мы тестируем работу компенсации отдачи. Для создания лабораторных условий отдачи установлено на 0.25 с помощью команды weapon_accuracy_forcespread.

Давайте посмотрим, как это работает на практике:

Всем пока, еще встретимся!

Credits:
SapDragon - Theoretical research
Porches - Practical implementation
 
не умеешь абузить ролл - не берись за брутфорс сида
 
Да это ж чисто PoC того, что но сприд снова существует, и очень просто делается
это больше похоже на улучшенный просприд триггербот, который брутфорсит сид через углы. ноусприд это когда твой выстрел регает в чела....
 
это больше похоже на улучшенный просприд триггербот, который брутфорсит сид через углы. ноусприд это когда твой выстрел регает в чела....
просьба хоть ознакомиться с постом и видео прежде чем это писать
 
просьба хоть ознакомиться с постом и видео прежде чем это писать
Анализ нового no spread.
Анализ нового no spread.
Анализ нового no spread.
Анализ нового no spread.
Анализ нового no spread.
Анализ нового no spread.
Анализ нового no spread.
Анализ нового no spread.
Анализ нового no spread.
Анализ нового no spread.
 
Анализ нового no spread.
Анализ нового no spread.
Анализ нового no spread.
Анализ нового no spread.
Анализ нового no spread.
Анализ нового no spread.
Анализ нового no spread.
Анализ нового no spread.
Анализ нового no spread.
Анализ нового no spread.
4 слова прочитал, а дальше??
 
Привет! У меня два вопроса:
  1. Как ты узнаёшь, какой сид выберет сервер, когда просчитываешь возможные сиды для каждого отдельного угла. Получается, что тебе известен алгоритм, по которому он просчитывается?
  2. Судя по тому, что при просчётах ты берёшь разные углы, после успешного совпадения на конкретном угле & тике тебе нужно вовремя установить правильный угол. Поэтому выходит ли так, что модель игрока будет фликать?
Если что — извиняюсь, я далёк от читкодинга, поэтому могу писать бред, но мне интересно, как это работает.
 
Привет! У меня два вопроса:
  1. Как ты узнаёшь, какой сид выберет сервер, когда просчитываешь возможные сиды для каждого отдельного угла. Получается, что тебе известен алгоритм, по которому он просчитывается?
  2. Судя по тому, что при просчётах ты берёшь разные углы, после успешного совпадения на конкретном угле & тике тебе нужно вовремя установить правильный угол. Поэтому выходит ли так, что модель игрока будет фликать?
Если что — извиняюсь, я далёк от читкодинга, поэтому могу писать бред, но мне интересно, как это работает.
1. Ну да, он же и лежит в посте.
2. Нет
 
какой сид выберет сервер, когда просчитываешь возможные сиды для каждого отдельного угла. Получается, что тебе известен алгоритм, по которому он просчитывается
Алгоритм генерации сида теперь общий между клиентом и сервером.
Обе стороны знают углы и номер тика, из них они вычисляют хэш, который и станет общим сидом (упрощенно).
Клиент знает эти данные потому что он сам их генерирует, а сервер знает потому что они пришли от клиента.
 
как хорошо, что лично сам сапдрагон расписал про носприд, благослови тебя тим энтериал
 
Вступление
Привет всем! Сегодня я хочу поделиться с вами информацией о том, почему снова появился no-spread и как он работает. Это действительно то, что изменит правила игры для всех

Изменения которые привели к его появлению
В ночь 9 ноября успешная компания Valve выпустила новое обновление для Counter-Strike 2. В этом обновлении они решили исправить проблемы с системой sub-tick. Одно из решений, к которому они пришли, было синхронизировать клиентское и серверный сид случайностей.

Изменения на клиенте
В этой части я применю реверс-инжиниринг @Soufiw в знак уважения к исходному источнику.
После этого обновления, сид синхронизируется между клиентом и сервером с помощью хэширования углов обзора и текущего тика отрисовки.

C++:
Expand Collapse Copy
uint32_t GetRandomSeed( const QAngle& angles, int player_render_tick )

{

#ifdef SERVER_DLL

    // sv_usercmd_custom_random_seed = false by default now.

    // m_nPredictionRandomSeed = cmd->random_seed;

    // m_nPredictionRandomSeedServer = sv_usercmd_custom_random_seed ? GetRngSeed() : cmd->random_seed;

    if ( m_nPredictionRandomSeedServer != m_nPredictionRandomSeed )

        return m_nPredictionRandomSeedServer;

#endif

    struct HashBuffer

    {

        float pitch;

        float yaw;

        int tick;

    };

    HashBuffer buffer;

    float pitch = AngleNormalize(angles.pitch);

    pitch += pitch;

    buffer.pitch = RoundFloat(pitch) * 0.5f;

    float yaw = AngleNormalize(angles.yaw);

    yaw += yaw;

    buffer.yaw = RoundFloat(yaw) * 0.5f;

    buffer.tick = player_render_tick;

    CSHA1 sha1;

    sha1.Reset(  );

    sha1.Update( &buffer, sizeof(buffer) );

    sha1.Final();

    return *reinterpret_cast<uint32_t*>(&sha1.m_digest[0]);

}

В итоге, создание no-spread кажется действительно простым: всего лишь нужно получить текущий сид и компенсировать его, как в 2015 году! Но, к сожалению, это не работает так. При изменении угла обзора, сид также меняется, что логично. Чтобы обойти это ограничение, я пришел к более простому и оригинальному решению.

Использование хаоса как основу для порядка
Если вы сейчас стоите, то вам лучше присесть, потому что это самое простое решение, которое можно придумать.

Предположим, у нас есть угол обзора и текущий тик отрисовки. Для компенсации разброса мы можем перебирать случайные углы, вычислять для них сид и вычислять финальный угол с учетом разброса.

В итоге, наш принцип действия будет выглядеть так:
  1. Генерируем случайный угол.
  2. Вычисляем сид для этого случайного угла и текущего тика.
  3. Используем полученный сид для моделирования выстрела с разбросом из текущего угла.
  4. Получаем финальный угол выстрела с учетом разброса.
  5. Если финальный угол достаточно близок к исходному (в пределах заданного порога), сохраняем найденный сид.
  6. Если нет, генерируем новый случайный угол и повторяем шаги с 3 по 6.
  7. Как только находим подходящий угол, используем его вместо реального.
  8. Для нового угла обзора повторяем поиск подходящего угла сначала.

Таким образом, мы перебираем случайные варианты, пока не найдем сид, которое компенсирует разброс для текущего угла.

Как же посчитать разброс?
Давайте сразу перейдем к коду, который отвечает за моделирование разброса. Я не буду углубляться в подробные объяснения, чтобы быстрее показать реализацию.

C++:
Expand Collapse Copy
bool bForceMaxInaccuracy = weapon_debug_max_inaccuracy.GetBool();

bool bForceInaccuracyDirection = weapon_debug_inaccuracy_only_up.GetBool();

bool bWeaponAccuracyShotgunSpreadPatterns = weapon_accuracy_shotgun_spread_patterns.GetBool();


Math::RandomSeed(nRandomSeed + 1);


float flRadiusCurveDensity = Math::RandomFloat(0.0f, 1.0f);


if (nItemIndex == 64 && nMode == nSecondaryMode) {

  // R8 Revolver secondary fire

  flRadiusCurveDensity = 1.0f - flRadiusCurveDensity * flRadiusCurveDensity;

}

else if (nItemIndex == 28 && flRecoilIndex < 3) {

  // Negev Wild Beast

  for (int nIndex = 3; nIndex > flRecoilIndex; --nIndex) {

    flRadiusCurveDensity *= flRadiusCurveDensity;

  }

  flRadiusCurveDensity = 1.0f - flRadiusCurveDensity;

}


if (bForceMaxInaccuracy) {

  flRadiusCurveDensity = 1.0f;

}


float flTheta0 = Math::RandomFloat(0.0f, 2.0f * M_PI);

if (bForceInaccuracyDirection) {

  flTheta0 = M_PI * 0.5f;

}


if (bWeaponAccuracyShotgunSpreadPatterns)

{

    // Missing.

}


float flRadius0 = flRadiusCurveDensity * flInaccuracy;

float flX0 = flRadius0 * cosf(flTheta0);

float flY0 = flRadius0 * sinf(flTheta0);


float flSpreadCurveDensity = Math::RandomFloat(0.0f, 1.0f);


if (nItemIndex == 64 && nMode == nSecondaryMode) {

  // R8 Revolver secondary fire

  flSpreadCurveDensity = 1.0f - flSpreadCurveDensity * flSpreadCurveDensity;

}

else if (nItemIndex == 28 && flRecoilIndex < 3) {

  // Negev Wild Beast

  for (int nIndex = 3; nIndex > flRecoilIndex; --nIndex) {

    flSpreadCurveDensity *= flSpreadCurveDensity;

  }

  flSpreadCurveDensity = 1.0f - flSpreadCurveDensity;

}


if (bForceMaxInaccuracy) {

  flSpreadCurveDensity = 1.0f;

}


float flTheta1 = Math::RandomFloat(0.0f, 2.0f * M_PI);

if (bForceInaccuracyDirection) {

  flTheta1 = M_PI * 0.5f;

}


float flRadius1 = flSpreadCurveDensity * flSpread;

float flX1 = flRadius1 * cosf(flTheta1);

float flY1 = flRadius1 * sinf(flTheta1);


double flYResult = (double)(flY1 * flRadius0) +

               (double)(flY0 * flRadius1);


double flXResult = (double)(flX1 * flRadius0) +

               (double)(flX0 * flRadius1);

Итог
Сложив всю информацию, вы могли прийти к тому же результату, что и я. Возврат компенсации отдачи повлечёт серьёзные изменения в культуре HvH и читеров в целом. Сейчас я хочу продемонстрировать, что это действительно работает.


Давайте посмотрим, как это работает на практике:

Всем пока, еще встретимся!

Credits:
SapDragon - Theoretical research
Porches - Practical implementation
попадал в похожую ситуацию где с клиента на сервер передавался rdtsc от чего на сервере устанавливался common-seed на выстрел.
при этом
Код:
Expand Collapse Copy
if (current_seed <= prev_seed) {
    // detect
}
при этом от seed высчитывались ~6+ значений, т.е сиды должны были давать последовательность 6+ даблов с минимальной погрешностью от 0.
результатом стало предварительное высчитывание огромного баффера сидов(100к раз(ну уж за игровую сессию 100к раз выстрелить ещё надо умудриться)).
делалось все на GPU шейдером из-за очевидной низкой производительности CPU.

ну а насчет новой эры...я так не думаю.вернут старый рассинхрон сидов клиент-сервера и лишитесь вы ноусприда.(почему это вообще допустилось - я хуй его знает..хотя
1700314252974.png

)
 
Последнее редактирование:
это больше похоже на улучшенный просприд триггербот, который брутфорсит сид через углы. ноусприд это когда твой выстрел регает в чела....
это так но по сути похуй если всегда есть сид при котором сприд минимальный
вернут старый рассинхрон сидов клиент-сервера и лишитесь вы ноусприда.(почему это вообще допустилось - я хуй его знает..хотя
они скорее ченить другое придумают нежели вернут рассинхрон сидов, один хуй детектить носпреад не так уж и трудно........
 
что за половина числа пи откуда ты это взял?
 
что за половина числа пи откуда ты это взял?
Это из кода самой игры, при weapon_debug_inaccuracy_only_up направление сприда всегда будет идти вверх, PI = 180 градусов, половина будет +90 градусов, т.е. вверх.
 
Это из кода самой игры, при weapon_debug_inaccuracy_only_up направление сприда всегда будет идти вверх, PI = 180 градусов, половина будет +90 градусов, т.е. вверх.
спасибо, буду знать топ
 
Ждём добавления носприд триггера в абсолютно каждую пасту
 
Назад
Сверху Снизу