Форум программистов, компьютерный форум, киберфорум
С++ для начинающих
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск Заказать работу  
 
Рейтинг 4.72/29: Рейтинг темы: голосов - 29, средняя оценка - 4.72
 Аватар для Misterkat
7 / 7 / 0
Регистрация: 16.11.2012
Сообщений: 84

RAM, чтение и запись в RAM, Pointer ы

24.07.2013, 06:37. Показов 6153. Ответов 10
Метки нет (Все метки)

Студворк — интернет-сервис помощи студентам
Так! Начну сразу с проблемы.
Допустим есть приложение "какая-то хрень.exe", а в нем есть переменная допустим "32" с типом integer(int), а проблема заключается в том, что переменная находится каждый раз в разном участке памяти! . Ну ладно, нашел я что такое Pointer - Некий адрес+смещение = нужный адрес! Вроде бы все нишьяГ, но находим снова проблему:
поинтер начинается с адреса, который должен найтись по СУПЕРПУПЕРСЛОЖНОЙФОРМУЛЕ: "какая-то хрень.exe+нулевое смещение = некий адрес". И так, кто еще не понял, вопрос заключается в следующем: как определить начальный адрес приложения? Имею ввиду:
0x00000 - начало озу
... - всякие программы
0x001d0 - начало выделенной озу под "какая-то хрень.exe" - То, что нужно узнать.
...
0xFFFFFF
ЗЫ:На всякий случай кину сорс:
main.cpp:
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
#include <windows.h>
#include <TlHelp32.h>
#include "MemoryManager.h"
#include <gdiplus.h>
 
#pragma comment(lib, "gdiplus.lib")
 
#define ID_BUTTON1 3000
#define ID_BUTTON2 3001
#define ID_BUTTON3 3002
#define ID_EDIT1 2000
 
 
HINSTANCE hInst;
char* szClassName = "ClassOFTheWindow";
char* szWindowName = "Sniper Elite Multiplayer Trainer";
 
WCHAR Health[32];
WCHAR Ammo[32];
WCHAR Grenades[32];
 
 
void GdiPaint(HDC hdc)
{
    Gdiplus::Graphics graphics(hdc);
    Gdiplus::SolidBrush brush(Gdiplus::Color::Chocolate);
    Gdiplus::Pen BrushPen(Gdiplus::Color::Aqua);
    Gdiplus::FontFamily fontFamily(L"Times New Roman");
    Gdiplus::Font font(&fontFamily, 24, Gdiplus::FontStyleRegular, Gdiplus::UnitPixel);
    Gdiplus::PointF HealtTextPos(10.0f, 20.0f);
    Gdiplus::PointF AmmoTextPos(10.0f, 75.0f);
 
    graphics.DrawString(Health, -1, &font, HealtTextPos, &brush);
    graphics.DrawString(Grenades, -1, &font, AmmoTextPos, &brush);
}
 
int RegClass(WNDPROC Proc, LPCTSTR szName)
{
WNDCLASS WindowClass; 
WindowClass.style = CS_HREDRAW|CS_VREDRAW|CS_DBLCLKS; 
WindowClass.cbClsExtra = WindowClass.cbWndExtra = 0; 
WindowClass.lpfnWndProc = Proc; 
WindowClass.hInstance = hInst; 
WindowClass.hIcon = LoadIcon(NULL, IDI_WINLOGO); 
WindowClass.hCursor = LoadCursor(NULL, IDC_ARROW); 
WindowClass.hbrBackground = (HBRUSH)GetStockObject(GRAY_BRUSH); 
WindowClass.lpszMenuName = (LPCSTR)NULL; 
WindowClass.lpszClassName = szName; 
return RegisterClass(&WindowClass);
}
 
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam){
static short cx; 
static short cy; 
static short left; 
static short top;
 
HWND hEdit;
switch(msg)
{
 case WM_CREATE: { 
      
      return 0;
      }
 case WM_PAINT: {
      PAINTSTRUCT ps;
      HDC hdc = BeginPaint(hwnd, &ps);
      GdiPaint(hdc);
      EndPaint(hwnd, &ps);
      
      return 0;
      }
 case WM_MOVE: { 
      left = LOWORD(lParam);
      top = HIWORD(lParam);
      return 0;
      }
 case WM_SIZE: { 
      cx = LOWORD(lParam);
      cy = HIWORD(lParam);
      return 0;
      }
 case WM_COMMAND: { 
      switch(LOWORD(wParam))
      {   
        case ID_BUTTON1: {
             MemoryManager* MM = new MemoryManager("sniperelite.exe");
             DWORD offsets[3] = {0x438, 0x30, 0x328};
             DWORD offsetsAmmo[3] = {0x4c, 0x16c, 0x18};
             DWORD offsetsGrenades[3] = {0x594, 0x174, 0x20};
             
             wsprintfW(Health, L"Proccess ID = %d \nЖизни: %d(0x%X)         ", MM->GetPID(),
                (DWORD)MM->ReadPointer(0x1dc0e28, offsets, 3).toFloat(), MM->ReadPointer(0x1dc0e28, offsets, 3, true).toDword());
             
             wsprintfW(Ammo, L"\nПатроны: %d(0x%X)", MM->ReadPointer(0x1088F4F8, offsetsAmmo, 3).toDword(), MM->ReadPointer(0x1088F4F8, offsetsAmmo, 3, true).toDword());
             InvalidateRect(hwnd, NULL, true);
 
             wsprintfW(Grenades, L"\nЛимонки: %d(0x%X)", MM->ReadPointer(0x100f5830, offsetsGrenades, 3).toDword(), MM->ReadPointer(0x100f5830, offsetsAmmo, 3, true).toDword());
             InvalidateRect(hwnd, NULL, true);
             return 0; 
             }
      }
      return 0;
      }
 case WM_DESTROY: {
      PostQuitMessage(0);
      return 0;
      }
 }
 return DefWindowProc(hwnd, msg, wParam, lParam);
}
 
int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow)
{
 MSG msg;
 HWND hwnd;
 HWND hButton1;
 Gdiplus::GdiplusStartupInput gdiplusStartupInput;
 ULONG_PTR gdiplusToken;
 if(!RegClass(WndProc, szClassName))
                       return FALSE;
 GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
 hwnd = CreateWindow(szClassName, szWindowName, WS_OVERLAPPEDWINDOW, 0, 0, 400, 200, NULL, NULL, hInst, NULL);
 if(!hwnd)
          return FALSE;
 
 hButton1 = CreateWindow("button", "Обновить", WS_CHILD|WS_VISIBLE|BS_DEFPUSHBUTTON, 100, 125, 100, 25, hwnd, (HMENU)ID_BUTTON1, hInst, NULL);
 
 ShowWindow(hwnd, SW_SHOW);
 UpdateWindow(hwnd);
 
 while(GetMessage(&msg, NULL, 0, 0))
 {
  TranslateMessage(&msg);
  DispatchMessage(&msg);
 }
 Gdiplus::GdiplusShutdown(gdiplusToken);
 return msg.wParam;
}
MemoryManager.h:
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
#define MAXBUFFER_SIZE 255
class MemoryManager
{
public: 
        MemoryManager(char* pName)
        {
         this->pName = pName;  
         UpdatePID();                  
        }
        ~MemoryManager();
        
        void UpdatePID()
{
      HANDLE SnapShot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
      PROCESSENTRY32 pInfo;
      pInfo.dwSize = sizeof(PROCESSENTRY32);
      
      if(Process32First(SnapShot, &pInfo))
      {
           while(Process32Next(SnapShot, &pInfo))
                   {
                    if(_stricmp(pName, pInfo.szExeFile) == 0)
                    {
                     pID = pInfo.th32ProcessID;
                     CloseHandle(SnapShot);
                     break;
                    }
                   }
      }
 
}
 
MemoryManager& ReadMemory(DWORD adress, DWORD size)
{
      memset(buffer, 0, MAXBUFFER_SIZE);
      DWORD OLDPROTECT = 0;
      
      HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, false, pID);
      VirtualProtectEx(hProcess, (void*)adress, size, PAGE_EXECUTE_READWRITE, &OLDPROTECT);
      ReadProcessMemory(hProcess, (void*)adress, buffer, size, NULL);
      VirtualProtectEx(hProcess, (void*)adress, size, OLDPROTECT, &OLDPROTECT);
      CloseHandle(hProcess);
      return *this;
}
 
void WriteMemory(DWORD adress, void* content, DWORD size)
{
      DWORD OLDPROTECT = 0;
      
      HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, false, pID);
      VirtualProtectEx(hProcess, (void*)adress, size, PAGE_EXECUTE_READWRITE, &OLDPROTECT);
      WriteProcessMemory(hProcess, (void*)adress, content, size, NULL);
      VirtualProtectEx(hProcess, (void*)adress, size, OLDPROTECT, &OLDPROTECT);
      CloseHandle(hProcess);
}
 
DWORD toDword()
{
      DWORD buf = 0;
      memcpy(&buf, buffer, sizeof(DWORD));
      return buf;
}
 
MemoryManager& ReadPointer(DWORD adress, DWORD* offset, DWORD nOffsets, bool getAddr = false)
{
        for(int i = 0; i < nOffsets; i++)
       {
       if(i == nOffsets - 1)
            {
             if(getAddr == true)
                {
                 DWORD buff = adress + offset[i];
                 memcpy(buffer, &buff, sizeof(DWORD));
                 return *this;
                }
            }
        adress = ReadMemory(adress + offset[i], sizeof(DWORD)).toDword();
       }
       return *this;
}
 
 
 
char* toStringA()
{
      DWORD len = strlen((char*)buffer);
      static char buff[MAXBUFFER_SIZE];
      memset(buff, 0, MAXBUFFER_SIZE);
      strncpy(buff, (char*)buffer, len);
      return buff;
}
float toFloat()
{
      float buf = 0.f;
      memcpy(&buf, buffer, sizeof(float));
      return buf;
}
DWORD GetPID(){return pID;}
private:
        char* pName;
        DWORD pID;
        byte buffer[MAXBUFFER_SIZE];
};
Добавлено через 6 часов 53 минуты

Не по теме:

Да ну.... Ребята, ну ответьте, пожалуйста. Тема уходит в ж*пу ведь.

0
cpp_developer
Эксперт
20123 / 5690 / 1417
Регистрация: 09.04.2010
Сообщений: 22,546
Блог
24.07.2013, 06:37
Ответы с готовыми решениями:

I don't understand RAM. What RAM do I get?
Hello everyone,, First time builder here and all I have to say in the shopping process is... RAM??? Why in the world is there no simple...

Lenovo 110-15IBR / WDC WD5000LPCX-24VHAT0 / RAM 2Gb: заменять на SSD или увеличить RAM, или разом это всё?
Что первое заменить на ноуте - 1) HDD заменить на SSD 2) RAM купить планку на 4 Gb ? Хочется чтоб быстрее работал....

Asus P5PQL-am без RAM пищит, с RAM тоже пищит
Asus P5PQL-am без оперативка пишит (это понятно что нет оперативка) а с оперативка тоже пишит.... Все напряжение оперативки присутствует,...

10
106 / 87 / 13
Регистрация: 29.08.2012
Сообщений: 538
24.07.2013, 06:55
Цитата Сообщение от Misterkat Посмотреть сообщение
И так, кто еще не понял, вопрос заключается в следующем: как определить начальный адрес приложения? Имею ввиду:
0x00000 - начало озу
... - всякие программы
0x001d0 - начало выделенной озу под "какая-то хрень.exe" - То, что нужно узнать.
может это глупый вопрос: а так вообще в принципе можно сделать?
вроде как ядро делит память динмически под каждое приложения и те адреса памяти, на которые у вас указывают ваши указатели ВНУТРИ программы на самом деле совсем другие в реальной ОЗУ. и этим занимается ядро. мож ошибаюсь конечно... у интеловских процов вроде даже появилась какая-то технология, которая позволяет без лишних тактов из динамических (замаскированных системой) адресов доставать именно реальные в ОЗУ. но это мое мнения в коем я не совсем уверен.
1
 Аватар для Misterkat
7 / 7 / 0
Регистрация: 16.11.2012
Сообщений: 84
24.07.2013, 07:39  [ТС]
Цитата Сообщение от Kukurudza Посмотреть сообщение
а так вообще в принципе можно сделать?
Ну ведь трейнеры же пишут люди.
Цитата Сообщение от Kukurudza Посмотреть сообщение
вроде как ядро делит память динмически под каждое приложения
Похоже, что вы не поняли моей просьбы. Я имею ввиду, что мне нужно узнать как получить начальный адрес выделенной под приложение памяти. То есть вот например у приложения есть некий диапазон памяти, допустим от 0x04B2C7B8 до 0x0C0E00E4. Так ведь? Если так, то мне нужно узнать(при помощи С++) 0x04B2C7B8.
Если вы мне пытаетесь доказать, что 0x04B2C7B8 будет после перезапуска приложения уже не 0x04B2C7B8, то тут напрасно, ибо я и сам это понимаю. И мне нужно неким чудодейственным методом вычислить это значение, после перезапуска приложения(желательно на С++).
0
106 / 87 / 13
Регистрация: 29.08.2012
Сообщений: 538
24.07.2013, 07:50
Не буду я с вами спорить ибо сам в этой теме плаваю.
Цитата Сообщение от Misterkat Посмотреть сообщение
допустим от 0x04B2C7B8 до 0x0C0E00E4. Так ведь?
вот в том то и дело что вроде как не так. а если я в какой то момент решил внутри приложения выделить еще памяти, а реально за моим приложением уже давно кто-то хорошенько разместился? тогда этого "кого-то" смещать вперед на столько, сколько я хочу выделить? внутри вашего приложения вы видите память от 0x00000000 до 0xffffffff в 32х разрядной системе, а уж как спроецирует ядро эти ячейки на реальную память, надо спрашивать у ядра. у вас в машине вообще может и не быть 4х гигабайт а только 128 мегабайт, все остальное будет свапаться на ХДД. я это к тому что например у вас есть переменная, которая записана по адресу (внутри вашей программы) 0x00000001, а в реальности она записана по адресу 0x57291837 (в ОЗУ), есть вторая переменная, которая записана по адресу 0x00000002 (внутри программы), и нифига это не значит что в реальности она записана по адресу 0x57291838 (в ОЗУ). не?
1
 Аватар для Misterkat
7 / 7 / 0
Регистрация: 16.11.2012
Сообщений: 84
24.07.2013, 09:32  [ТС]
Цитата Сообщение от Kukurudza Посмотреть сообщение
вот в том то и дело что вроде как не так. а если я в какой то момент решил внутри приложения выделить еще памяти, а реально за моим приложением уже давно кто-то хорошенько разместился? тогда этого "кого-то" смещать вперед на столько, сколько я хочу выделить? внутри вашего приложения вы видите память от 0x00000000 до 0xffffffff в 32х разрядной системе, а уж как спроецирует ядро эти ячейки на реальную память, надо спрашивать у ядра. у вас в машине вообще может и не быть 4х гигабайт а только 128 мегабайт, все остальное будет свапаться на ХДД. я это к тому что например у вас есть переменная, которая записана по адресу (внутри вашей программы) 0x00000001, а в реальности она записана по адресу 0x57291837 (в ОЗУ), есть вторая переменная, которая записана по адресу 0x00000002 (внутри программы), и нифига это не значит что в реальности она записана по адресу 0x57291838 (в ОЗУ). не?
Возможно, но мне [censure] не нужны реальные значения, мне конечным результатом нужно отследить переменную, если вы знаете как это сделать - прошу ответ в студию.

Добавлено через 1 час 35 минут

Не по теме:

Цитата Сообщение от Kukurudza Посмотреть сообщение
у вас в машине вообще может и не быть 4х гигабайт а только 128 мегабайт
У меня физической 4(Hunyx 2gb + Corsair 2gb)

0
106 / 87 / 13
Регистрация: 29.08.2012
Сообщений: 538
24.07.2013, 09:51
Цитата Сообщение от Misterkat Посмотреть сообщение
мне конечным результатом нужно отследить переменную
видимо я не понял чего вы хотите.
Цитата Сообщение от Misterkat Посмотреть сообщение
У меня физической 4(Hunyx 2gb + Corsair 2gb)
да это пофиг вообще. у вас есть как минимум система, которая хавает 1+ гиг.
0
Эксперт С++
2924 / 1274 / 114
Регистрация: 27.05.2008
Сообщений: 3,465
24.07.2013, 10:20
Для начала уточни, о какой ОС идет речь?

В Win каждый процесс выполняется в своем собственном отдельном виртуальном адресном пространстве, никак не связанном со страницами физической памяти (точнее, таблица соответствия есть, но управляет ей исключительно ядро операционной системы). Для подавляющего большинства процессов базовый адрес загрузки есть 0x00400000. Узнать его можно очень просто:
C++
1
2
3
4
5
6
7
8
9
#include <iostream>
#include <windows.h>
using namespace std;
 
int main()
{
    void* p = (void*)GetModuleHandle(NULL);
    cout << p << endl;
}
2
Ушел с форума
Эксперт С++
 Аватар для Убежденный
16481 / 7444 / 1187
Регистрация: 02.05.2013
Сообщений: 11,616
Записей в блоге: 1
24.07.2013, 10:21
Цитата Сообщение от Misterkat Посмотреть сообщение
И так, кто еще не понял, вопрос заключается в следующем: как определить начальный адрес приложения? Имею ввиду:
0x00000 - начало озу
... - всякие программы
0x001d0 - начало выделенной озу под "какая-то хрень.exe" - То, что нужно узнать.
Про адреса в ОЗУ и "карту памяти" можете забыть.
Современные ОС давно уже работают с виртуальной памятью, где каждому процессу выделяется
свое адресное пространство размером от 4 Гигабайт и выше. "Начальный адрес приложения" -
это, грубо говоря, 0x00000000 (для 64-битных систем 0x00000000 00000000), более точно
верхнюю и нижнюю границы доступных адресов можно узнать с помощью GetSystemInfo.
Если и искать что-то, то в этой памяти, а не в физических адресах ОЗУ, куда, кстати
говоря, у пользовательских приложений вообще нет доступа.

Короче, открываете нужный процесс (OpenProcess) с правами PROCESS_QUERY_INFORMATION и
PROCESS_VM_READ, получаете информацию о доступных страницах памяти (VirtualQueryEx) и
читаете их содержимое (ReadProcessMemory). Вот и весь рецепт.
2
106 / 87 / 13
Регистрация: 29.08.2012
Сообщений: 538
24.07.2013, 10:21
Цитата Сообщение от CheshireCat Посмотреть сообщение
В Win каждый процесс выполняется в своем собственном отдельном виртуальном адресном пространстве, никак не связанном со страницами физической памяти (точнее, таблица соответствия есть, но управляет ей исключительно ядро операционной системы).
да, вот я как раз это имел ввиду.
1
 Аватар для Misterkat
7 / 7 / 0
Регистрация: 16.11.2012
Сообщений: 84
24.07.2013, 11:13  [ТС]
Цитата Сообщение от Убежденный Посмотреть сообщение
Короче, открываете нужный процесс (OpenProcess) с правами PROCESS_QUERY_INFORMATION и
PROCESS_VM_READ, получаете информацию о доступных страницах памяти (VirtualQueryEx) и
читаете их содержимое (ReadProcessMemory). Вот и весь рецепт.
Спасибо большое за азимут поиска, но все же, можно ли ткнуть носом.
0
Эксперт С++
2924 / 1274 / 114
Регистрация: 27.05.2008
Сообщений: 3,465
24.07.2013, 11:17
Ткнуть носом можно: сюда - http://msdn.microsoft.com/en-US/

Например, вот первая функция: http://msdn.microsoft.com/en-u... 85%29.aspx
1
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
raxper
Эксперт
30234 / 6612 / 1498
Регистрация: 28.12.2010
Сообщений: 21,154
Блог
24.07.2013, 11:17
Помогаю со студенческими работами здесь

Перевел ПК в "сон" -> ПК не выходит из "сна" -> не включается, нет POST-BIOS -> вынул RAM, вставил RAM -> все работает
Доброго времени суток! Буду краток (с). Стационарный ПК, собран в 2011 году. i7 второго поколения, 2 планки RAM, интегрированная...

Запись данных в RAM
Здравствуйте Подскажите пожалуйста с кодом.двух портовый RAM записываю данные так что на адресах 0 16 всегда дата 128.на всех остальных...

RAM 4 GB
всем добрый день, возникла такая проблема ... подарили новый ноут , решил просмотреть какой он мощности ... в системе показывает что...

RAM
А как получить общее кол-во RAM-а, занятое в общем, свободное в общем и занятое самой программой? Есть что нибудь встроенное?

2 RAM
1.Поставил два ОЗУ, один 128 МВ и 64 МВ. Но комп показывает что, сушестиует 194 МВ ОЗУ... Как это может быт? 2. Как правиьно поставить...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
11
Ответ Создать тему
Новые блоги и статьи
Настройка записи справочника по регистру сведений
Maks 29.03.2026
Решение ниже реализовано на примере нетипового справочника "ТарифыМобильнойСвязи" разработанного в конфигурации КА2, с целью учета корпоративной мобильной связи в коммерческом предприятии. . . .
Автозаполнение реквизита при выборе элемента справочника
Maks 27.03.2026
Программный код из решения ниже на примере нетипового документа "ЗаявкаНаРемонтСпецтехники" разработанного в конфигурации КА2. При выборе "Спецтехники" (Тип Справочник. Спецтехника), заполняется. . .
Сумматор с применением элементов трёх состояний.
Hrethgir 26.03.2026
Тут. https:/ / fips. ru/ EGD/ ab3c85c8-836d-4866-871b-c2f0c5d77fbc Первый документ красиво выглядит, но без схемы. Это конечно не даёт никаких плюсов автору, но тем не менее. . . всё может быть. . .
Автозаполнение реквизитов при создании документа
Maks 26.03.2026
Программный код из решения ниже размещается в модуле объекта документа, в процедуре "ПриСозданииНаСервере". Алгоритм проверки заполнения реализован для исключения перезаписи значения реквизита,. . .
Команды формы и диалоговое окно
Maks 26.03.2026
1. Команда формы "ЗаполнитьЗапчасти". Программный код из решения ниже на примере нетипового документа "ЗаявкаНаРемонтСпецтехники" разработанного в конфигурации КА2. В качестве источника данных. . .
Кому нужен AOT?
DevAlt 26.03.2026
Решил сделать простой ланчер Написал заготовку: dotnet new console --aot -o UrlHandler var items = args. Split(":"); var tag = items; var id = items; var executable = args;. . .
Отправка уведомления на почту при создании или изменении элементов справочника
Maks 24.03.2026
Программная отправка письма электронной почты на примере типового справочника "Склады" в конфигурации БП3. Перед реализацией необходимо выполнить настройку системной учетной записи электронной. . .
модель ЗдравоСохранения 5. Меньше увольнений- больше дохода!
anaschu 24.03.2026
Теперь система здравосохранения уменьшает количество увольнений. 9TO2GP2bpX4 a42b81fb172ffc12ca589c7898261ccb/ https:/ / rutube. ru/ video/ a42b81fb172ffc12ca589c7898261ccb/ Слева синяя линия -. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2026, CyberForum.ru