Перейти к содержанию

Sergey99

Пользователи
  • Постов

    97
  • Зарегистрирован

  • Посещение

  • Победитель дней

    2

Сообщения, опубликованные Sergey99

  1. 8 часов назад, Xipho сказал:

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

    Теперь ясно, по такой логике скажем так для определенного патча игры можно не пытаться даже найти указатель. Достаточно находить адрес структуры, чтобы если например машин такси на карте 5 то и адресов CE найдет 5, можно даже сделать такой классный счётчик всех типов машин на карте.

    P.S. уже проверял да это работает

  2. В 26.09.2022 в 19:59, KRYPTOPUNK сказал:

    Так в программе разные типы памяти.
    RAW DATA( или глобальные данные), стек и куча. 
    вот твой указатель и есть глобальные данные, которые хранятся непосредственно в exe-шнике. 

    То есть не важно DMA или нет если есть данные которые в exe файле то они по любому будут по статическим адресам расположены?

  3. Небольшое дополнение к вопросу:

    Выше я простенько описал последовательность того как расположены адреса памяти по СИшному, вот нормальный пример:

    *Car taxi;
    taxi->xCoordinate = X;
    taxi->yCoordinate = Y;
    taxi->ZCoordinate = Z;

    Из этого понятно, что Car - это та самая базовая структура, а такси это уже указатель или адрес начала машины такси грубо говоря. Так вот Car хранится в статике.

  4. Всем привет, решил поламать игрушку GTA 3 и в процессе возник вопрос. Пускай игра и использует DMA, НО к примеру я нашёл X координату (которая естественно лежит по динамическому адресу) и с помощью отладчика по смещением перешёл к базовому адресу структуры игрока скажем так, которая тоже лежит в динамических адресах, то первый адрес этой структуры указывает уже на статику, тоже самое с машинами и вертолётами. Я посидел и немножко подумал, а что если базовый адрес структуры будто игрока или машины указывает на скажем так базовую структуру, которая уже определяет что это за объект в игровом мире (игрок, машина, вертолёт и тд.)

    Опишу простенько по СИшному:

    car->taxi->xCoordinate;
    helicopter->policeHelicopter->xCoordinate;

    И соотвественно при поиске задом наперёд скажем так можно прийти к этой базовой структуре, но вот в чём дилема, игра то у нас DMA, а адрес статический.

    Вопрос, почему в играх с DMA всё же есть статические адреса?

    P.S. при поиске указателей мы тоже в конце получаем статику, что собственно изначально но мысль о вопросе и навело.

  5. В 22.06.2021 в 17:07, Xipho сказал:

    Это я, увы, без исследования игры, сказать не смогу

    Разобрался как работают функции рисования примитивов в этих библиотечках. Всё очень просто, оказывается, что так как функция вызывается очень много раз, практически каждый кадр в игре, то и соответственно рисует она каждый кадр и каждый кадр количество примитивов, которые необходимо нарисовать может отличаться. К примеру в первый кадр рисуется мир, а во второй рисуется картинка с камеры игрока, через которую мы и видим мир игры и штука вся в том, что количество примитивов для рисования картинки с камеры нужно меньше чем для рисования мира (мир - от 100 примитивов, камера от 2 до ~10 примитивов).

    • Понравилось 1
  6. 1 час назад, Xipho сказал:

    Это я, увы, без исследования игры, сказать не смогу

    Такая же штучка и в D3D работает к примеру в 9 версии, я пробовал, проверяя значение параметра primCount метода DrawIndexedPrimitive, похоже что функция не рендерит всё сразу а как то по очереди обрабатывает каждый примитив.

  7. 3 часа назад, Xipho сказал:

    Извини, я не совсем понимаю твой вопрос

    Вот в функции перехвата я проверяю параметр count, который пришёл в функцию glDrawElements, он хранит количество примитивов для рендера и вот по идее рисование линиями должно работать для всех примитивов так как условие выполняется.

     

    К примеру если count равен 100 (то есть в функцию пришёл аргумент count со значением 100 то функция не будет рисовать линиями так как условие count > 260, а вот если в функцию придёт 261 то по идее он должен всё нарисовать линиями а получается только один примитив так как 261 > 260. Это то что нужно но сам алгоритм не понятен. Так как по логике условие сработало и выполняется glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); (по идее для всех примитивов)

  8. В 21.06.2021 в 13:15, Xipho сказал:

    Очень похоже на стандартную обертку над GDI функцией SwapBuffers.

    Решил проблемку, как оказалось перехватывать нужно было не glBegin а функцию именно для рисования как в том же Direct3D. Функция называется glDrawElements и рисует примитивы из массива, который приходит четвёртым аргументом.

     

    Конечно если почитать справочки от Microsoft и от Khronos, то из описания параметром понятно что где лежит, а вот если почитать справочку [здесь] по этой функции, то немножко запутаться можно в описании четвёртого параметра ну да ладно.

     

    Всё рисуется всё работает, проблема была в том, что как оказалось сам экран или камера через которую мы смотрим на мир игры тоже является примитивом и если мы отключаем рисование для него то он соответственно не будет обновляться и будет якобы висеть с одним изображением. Проблема решена через проверку сколько примитивов рисует функция (второй параметр):

    Спойлер
    
    //Hooked function that will be executed instead of original function in the process
    void WINAPI hkglDrawElements(GLenum mode, GLsizei count, GLenum type, const GLvoid* indices)
    {
    	//Restores bytes of original function
    	BYTE* codeDest = (BYTE*)oglDrawElements;
    	codeDest[0] = codeFragment_glDrawElements[0];
    	*((DWORD*)(codeDest + 1)) = *((DWORD*)(codeFragment_glDrawElements + 1));
    
    	if (isEnabled)
    	{
    		if (count > 260) 
    		{
    			glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
    		}
    		else
    		{
    			glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
    		}
    	}
    	else
    	{
    		glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
    	}
    
    	oglDrawElements(mode, count, type, indices);
    
    	//Restores bytes of hooked function so process of the game will execute hooked function instead of original function 
    	codeDest[0] = jump_glDrawElements[0];
    	*((DWORD*)(codeDest + 1)) = *((DWORD*)(jump_glDrawElements + 1));
    }

     

    При таком раскладе функция будет рисовать линиями только если число примитивов больше 260, а если нет, то рисовать сплошной заливкой. Вопрос теперь в том как сама функция это понимает? Ведь это хук и выполняется также в потоке или в цикле в игре. Эта функция вызывается и ей приходят аргументы и вот пришли ей на пример 261 примитив, то она рисует только один примитив линиями, хотя по логике должна все ведь условие стоит на это. Как такое происходит? Если посмотреть справочку то можно понять что второй параметр это число примитивов для рисования (то есть они все рисуются в этой функции), а условие будто выполняется для конкретного диапазона от 260 как так?

     

    Вот результат стараний, темновато но думаю видно:

    CrZPdoN.png

    8GeeAqH.png

    • Плюс 1
  9. 6 минут назад, Garik66 сказал:

    Функция wglSwapBuffers принимает один параметр HDC контекст устройства, а wglSwapLayerBuffers два параметра, контекст HDC и собственно для чего выполнить смену буферов будто плоскости или overlay. Сами функции разные хотя бы потому что принимают разное число аргументов и разные имена имеют.

  10. 3 часа назад, Xipho сказал:

    Кстати, сейчас только вспомнил (давно не ковырял огл). glBegin и glEnd - это же, если я правильно помню, что-то вроде "операторских скобок", отделяющих некоторые блоки отрисовки. То есть, совершенно не обязательно на кадр будет строго один glBegin и строго один glEnd. В отличие от DirectX, упомянутых функций в рамках построения одного кадра может быть несколько. Возможно, у тебя хук срабатывает на тех вызовах, на которых не должен срабатывать, от того и результат такой "замороженный". Попробуй под отладкой найти именно тот, который тебе нужен, чтобы отрисовать объекты сеткой.

    Да действительно между glBegin и glEnd обычно указывают вершины, по типу такого:

    glBegin(GL_QUADS);
    glVertex3f(x, y, z);
    glVertex3f(x, y, z);  
    glVertex3f(x, y, z);
    glVertex3f(x, y, z); 
    glEnd();

    Тут указываются вершины, так как в аргументах glBegin пришёл GL_QUADS то и вершин соответственно 4

     

    По поводу перехватов, я пытался перехватить wglSwapBuffers из той же библиотеки opengl32.dll, читал на зарубежных форумах, там её используют но для рисования всяких там квадратиков и менюшек на эране. Но результат практически тот же что и при перехвате glBegin. К тому же очень странно но справочки по wglSwapBuffers я не нашёл однако она как бы работает и в opengl32.dll как бы есть.

     

    Вы предлагаете искать через отладку а в каком смысле? Я же DLL инжектирую, а VS не позволяет отлаживать так как DLL это не исполняемый файл. И разумеется функция GetProcAddress не найдёт больше адресов функции glBegin так как в библиотеке opengl32.dll вроде как не может быть больше таких функций, она же как бы одна и зачем там больше.

    glBeginAddress = (DWORD)GetProcAddress(Moduleandle, "glBegin");
  11. 3 минуты назад, Xipho сказал:

    Скорее всего, у тебя не отрабатывает оригинальная функция glBegin. Смотри под пошаговой отладкой.

    Если я пытаюсь очистить буфер экрана и буфер глубины к примеру зелёным цветом без рисования линиями, то оно как бы работает, но вот так вот:

     

    Код:

    OpenGL Microsoft docs - glColorMask, glClearColor, glClear

    glColorMask(GL_FALSE, GL_TRUE, GL_FALSE, GL_TRUE);
    glClearColor(0.0, 1.0, 0.0, 1.0);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

     

    И пример после очистки зелёным цветом:

    rqVytkh.png

     

    Примечательно то что что если я буду рисовать линиями GL_LINE то экран будет весь зелёный будто весь очищается цветом.

     

    Также переменная glBeginAddress хранит адрес функции которую собственно я и перехватываю, вот тут даже jmp на инъекцию есть так как DLL уже загружена:

    yWr6Tn9.png

  12. Всем привет, решил я написать простенький хук для OpengGL. Игрушка, которую я хочу ломать использует как раз OpenGL, это можно понять если присоединиться через Cheat Engine к процессу игры и вызвать функцию для перечисления всех DLL загруженных в процесс (Enumerate DLL's). Игрушка использует opengl32.dll соответственно и ломать я буду OpengGL.

     

    И так вот небольшой кусок кода для функции GetOpenGLFunctions, весь код написан на C++, он естественно закоментирован на английском языке так как английский это круто и по хорошему код нужно коментить:

     

    Спойлер
    
    //Gets address of function from OpenGL library
    void GetOpenGLFunctions()
    {
    	HMODULE Moduleandle = GetModuleHandle(L"opengl32.dll");
    	if (Moduleandle != nullptr)
    	{
    		glBeginAddress = (DWORD)GetProcAddress(Moduleandle, "glBegin");
    	}
    	else
    	{
    		MessageBox(0, L"Module handle not found", L"Message", MB_OK);
    	}
    }

     

    Эта функция получает адрес функции glBegin из модуля opengl32.dll, который подргужен самой игрой и сохраняет его в переменную glBeginAddress.

     

    После этого вызывается функция HookOpenGLFunctions:

    Спойлер
    
    //Performs code injection
    void HookOpenGLFunctions()
    {
    	oglBegin = (tglBegin)glBeginAddress; //Gets address of original function
    
    	jump_glBegin[0] = 0xE9;
    	DWORD addr_glBegin = (DWORD)hkglBegin - (DWORD)oglBegin - 5; //Gets address of injection (where to jump)
    	memcpy(jump_glBegin + 1, &addr_glBegin, sizeof(DWORD)); //Adds address after 'jmp' opcode to get assembler instruction jmp [address]
    	memcpy(codeFragment_glBegin, oglBegin, 5); //Saves address of original function
    	VirtualProtect(oglBegin, 8, PAGE_EXECUTE_READWRITE, &savedProtection_glBegin);
    	memcpy(oglBegin, jump_glBegin, 5); //Replaces address of original function with assembler instruction jmp [address] (jump to injection where hook is)
    }

     

    По сути это инъекция кода то есть jmp [address] вместо оригинальной функции glBegin. Я не использую Detours от Microsoft потому что вижу в них смысл если требуется перехват функции в x64 играх, так как там способ перехвата и инъекции кода немножко отличается от x86.

     

    И вот собственно сам код инъекции:

    Спойлер
    
    //Hooked function that will be executed instead of original function in the process
    void WINAPI hkglBegin(GLenum mode)
    {
    	//Restores bytes of original function
    	BYTE* codeDest = (BYTE*)oglBegin;
    	codeDest[0] = codeFragment_glBegin[0];
    	*((DWORD*)(codeDest + 1)) = *((DWORD*)(codeFragment_glBegin + 1));
    
    	if (isEnabled)
    	{
    		glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
    	}
    	else
    	{
    		glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
    	}
    
    	oglBegin(mode);
    
    	//Restores bytes of hooked function so process of the game will execute hooked function instead of original function 
    	codeDest[0] = jump_glBegin[0];
    	*((DWORD*)(codeDest + 1)) = *((DWORD*)(jump_glBegin + 1));
    }

     

    Тут происходит восстановление байт оригинальной функции, чтобы она смогла выполнится в конце хука, потом идёт проверка включён ли чит и вызов функции glPolygonMode, котоая устанавливает режим отрисовки полигонов грубо говоря. Соответственно GL_FRONT_AND_BACK - полигоны которые рисуются спереди и сзади, GL_LINE - отрисовка линиями и GL_FILL - полная заливка полигонов текстурами. Ну а дальше вызов оригинальной функции glBegin и восстановление байт для инъекции и всё это крутится в цикле.

     

    Вот тут ссылки на документацию по функциям только не с сайта Microsoft а уже с сайта OpenGL документации: glBeginglPolygonMode

    Уж не знаю кому как удобнее ну вот сайтик с документацией от разработчика: Khronos

     

    В чём может быть проблема?

    Всё работает только вот при отрисовке линиями игра почему отображает мне предыдущий кадр в цикле, как будто изображение заморожено, вот пример до:

    uPDlN8F.png

     

    И вот как это выглядит после хука:

    vuFuE2C.png

     

    Тут видно наверное плохо но изображение как бы замараживается и не обновляется (даже сохраняется и рисуется предыдущая позиция курсора), но в момент выключения на долю секунды можно заметить отрисовку только линиями как здесь (пример из d3d9 игры):

    LTR2b8R.png

  13. 4 часа назад, Antonshka сказал:

    @Sergey99 как успехи с поиском матрицы?

     

    Всё нашёл, я создал топик на иностранном форуме там мы вместе решили вопрос, так как я понимаю принцип работы матрицы и  знаю технический английски :). Я готовлю детальный разбор по этой теме для этого топика или выведу в отдельный гайдик, не знаю в общем.

  14. 3 часа назад, Xipho сказал:

    @Sergey99 не нужно устраивать чат из форума. Отвечать нужно в одном сообщение, а не в нескольких последовательных. И цитировать только ту часть, на которую отвечаешь. Это первое и последнее устное предупреждение. Далее будут выдаваться преды.

    Понял, принял

  15. 10 часов назад, roma912 сказал:

    Не обязательно находить матрицу из этой функции. Можно просто использовать саму функцию, вызывать ее для перевода.

    И так тоже, но разве это не через инъекции кода делается, например, все хаки для Phasmophobia, которые существуют они все поголовно являются .dll файлами, которые инжектятся внутрь. А моя задача состоит в том чтобы получить координаты на экране, зная координаты в 3D и видовую матрицу (чтобы не использовать инъекции когда всегда).

     

    И да, я знаю что через Mono тоже можно инжектиться.

     

    10 часов назад, Antonshka сказал:

    Я пока не знаю тоже. Планирую на днях заняться темой преобразования 3D в 2D. Пока занимаюсь переустановкой Windows.

    Хех ну да, Windows бывает капризная :)

     

    10 часов назад, roma912 сказал:

    Не обязательно находить матрицу из этой функции. Можно просто использовать саму функцию, вызывать ее для перевода.

    Или же ты имеешь ввиду вызывать через движк Lua в Cheat Engine или Auto Assembler? Просто конкретно как вызывать методы через Auto Assembler не знаю, а вот документацию по CE Lua успел почитать.

  16. 8 часов назад, pachela сказал:

    3d координаты и актера и точки есть. Я просто не вижу смысла использовать Z координаты.

    Положение актера у нас будет всегда в центре компаса.

    Как перенести игровые координаты точки в координаты на плоскости радара, я не знаю. В этом и вопрос.

    Компас должен быть в отдельном от игры окне, что бы использовать на 2 мониторе, да и это будет часть уже готовой программы.

     

    Получи координаты центра твоего второго окна путём деления ширины и высоты на 2. К примеру для Windows Form .NET C#:

    System.Int32 CenterX = this.Width / 2;
    System.Int32 CenterY = this.Height / 2;
    //Где "this" - указатель на текущий экземляр класса, описывающий твою форму
    //System.Int32 - структура описывающая тип данных integer

    Найди свои координаты, потом найди расстояния от своих до координат другого объекта путём вычитания значений каждой оси и эту разность прибавляй к переменным CenterX, CenterY, таким образом центр радара - это "ты" грубо говоря.

    System.Int32 PosX = CenterX + Convert.ToInt32(/*разность координат по оси X*/);
    System.Int32 PosY = CenterY + Convert.ToInt32(/*разность координат по оси Y*/);
    //Convert.ToInt32 - потому что на Windows Form позиция X, Y является целым числом особенно если использовать структуру Point
    //Далее можно спользовать PosX и PosY для рисования объекта на "радаре"

     

  17. В общем спустя много времени я нашёл структуру очень похожу на видовую матрицу, однако есть одно отличие от тех, скриншоты которых были выше по теме:

     

    2.png - Синий прямоугольник (предположительно видовая матрица), зелёный - координаты (меняются когда я передвигаюсь, мышь и соответственно камера не двигается). В общем всё как для Cod:MW2 и Amnesia.

     

    А вот есть ещё оранжевый прямоугольник (поверх зелёного следовательно затрагивает зелёный). Значения в нём изменяются когда я хожу только влево и вправо (клавиши A, D). Это очень сильно похоже на поведение когда я прыгаю в Amnesia и CoD:MW2, так как там меняется лишь одна координата Z, а тут видимо эту особенность переняла другая координата, ведь если я с клавишами A, D буду ещё и камеру двигать то остальные координаты тоже начнут меняться.

     

    Похоже здесь каждый элемент матрицы разбит на два значения, тогда вопрос:

    такое разве возможно?

    И как тогда мне конвертировать координаты объекта в экранные координаты с такой видовой матрицей?

     

    Если каждый элемента матрицы действительно разбит на 2 значения, как тогда одномерный массив (32 элемента для такой матрицы 4x8 = 32) использовать в моей функции WorldToScreen()? Нужно складывать эти два значения или как-то по другому?

     

    Решил через Mono попробовать, как раз нашёл метод класса Camera -> Camera.WorldToScreenPoint, который как раз таки должен использовать видовую матрицу для преобразования координат из 3D в 2D, вот только как найти эту самую матрицу, которую использует эта функция. Нужно регистры проверять или адреса (если использовать средства Cheat Engine)? Потому что, по моему мнению, метод того же класса Camera.GetStereoViewMatrix, не совсем подходит хотя, возможно, я ошибаюсь.

  18. 8 часов назад, Antonshka сказал:

    Ты используешь матрицу 4x4 для вычисления X, Y координат. А если она в памяти будет в виде другой размерности, или может быть просто в виде Yaw, Pitch, Roll, X, Y, Z?

     

    Я тоже хочу попробовать такое в одной гоночной игре, ради интереса. В ней есть матрица 1х4, есть 3х3, но нет 4х4. Вот я и думаю, как мне произвести вычисления X, Y координат по твоему примеру если в игре не используется 4х4 матрица.

    Я тоже думаю что матрица представлена в памяти как-то "не по обычному". Но вряд ли думаю что байты будут представлены так как ты описал выше (6 значений). Так как в любой игре всё это должно храниться в виде матрицы (как я читал) иначе от куда функция WorlsToScreen() будет брать все три вектора + 4 вектор (позиция), ведь каждый вектор это 4 байта => матрица 4x4 = 16 байт.

  19. 16 часов назад, ChestGlaring сказал:

    А что мешает взять dnSpy и просто найти, в каком методе используется видовая  матрица? Затем взять CE Mono и перейти по методу найденному в dnSpy.
    Поиск можно начать с Camera, тем более, в зависимости от игры камер может быть несколько и у каждой будет своя матрица.

    Хотя вот качнул Cheat Engine 7.2 -> Mono есть.

  20. 14 часов назад, ChestGlaring сказал:

    А что мешает взять dnSpy и просто найти, в каком методе используется видовая  матрица? Затем взять CE Mono и перейти по методу найденному в dnSpy.
    Поиск можно начать с Camera, тем более, в зависимости от игры камер может быть несколько и у каждой будет своя матрица.

    И да, Cheat Engine даже 7.0 не предлагает Dissest Mono, а отладчик OllyDbg не может присоединиться к процессу даже. Учитывая что в Cheat Engine если нашёл адрес с координатой и потом ищешь инструкцию, который пишет туда, то из Memory Viewer'а на ту же инструкцию тыкаешь правой кнопкой и хочешь найти к чему получает доступ эта инструкция, то он тебе выдает либо ничего либо какой-то левый адрес. Похоже это игра не до конца закончена оно и понятно, ранний доступ всё-таки, но всё равно даже так она как-то не так работает (это я про проблему описанную выше).

  21. 3 минуты назад, KRYPTOPUNK сказал:

    еще видовые матрицы делятся на два типа: Column-major и Row-major.
    Если матрица в игре Row-major, то значения в ней так и идут: x11,x12,x13,x14 и так далее. Но могут быть и Column-major, значения в ней пойдут уже так: x11,x21,x31,x41 и так далее. Нужно это так же учитывать.
    Для ознакомления: https://en.wikipedia.org/wiki/Row-_and_column-major_order

    Да, как раз на C# уже написал алгоритм для записи в одномерный массив column-major матрицы, таким же образом чтобы в обработку WorldToScreen() мой массив приходил таким же упорядоченным как и в других играх. Надеюсь что для column-major матрицы функцию WorldToScreen() не придётся переписывать.

  22. 19 минут назад, Antonshka сказал:

    По моему опыту матрица почти всегда расположена рядом с координатами.

    Почему у тебя на видео надпись двигается рывками?

     

    Потому что я её рисую и перерисовываю каждый кадр + Bandicam пишет параллельно, потом упрощу немного задачу.

  23. 17 минут назад, ChestGlaring сказал:

     

    Но ведь и ты не спрашиваешь про общие принципы нахождения видовой матрицы. Какой вопрос - такой ответ. А в той игре, которая указана в названии темы, 3 видовых матрицы, для игрока, камеры и еще какой-то штуки.

    Ищу я видовую матрицу для игрока, вот как он смотрит на 3D мир (вид от первого лица, никакого геймплея через "камеру" нет, просто грубо говоря глаза игрока), а вообще выше по теме было целых два видео где я её использую так что думаю очевидно что я ищу в этой игре.

  24. 45 минут назад, roma912 сказал:

    Ну если игра не на юнити, то тогда и нужно искать видовую матрицу
    Для юнити довольно просто все писать. Берешь саму dll, которая Assembly-csharp из папки manage.
    Это считай твоя "основная библиотека классов" для модификаций чего угодно. После написания инжектируешь как mono библиотеку

    А может быть такое что байты видовой матрица перевёрнуты? то есть порядок столбцов тот же, а вот строки снизу вверх?

  25. 6 часов назад, ChestGlaring сказал:

    А что мешает взять dnSpy и просто найти, в каком методе используется видовая  матрица? Затем взять CE Mono и перейти по методу найденному в dnSpy.
    Поиск можно начать с Camera, тем более, в зависимости от игры камер может быть несколько и у каждой будет своя матрица.

    Да, идея хорошая, учитывая то что игра на Unity с использование скриптинга на C#, но не каждая же игра написана на таких общедоступных движках, к примеру CoD:MW2 была написана на C++ и "IW Engine" (движок разработчиков - они сами его написали и сами же модернизировали).

×
×
  • Создать...

Важная информация

Находясь на нашем сайте, Вы автоматически соглашаетесь соблюдать наши Условия использования.