ksteel Опубликовано Суббота в 15:28 Автор Жалоба Поделиться Опубликовано Суббота в 15:28 (изменено) и т.к. в Angel.ini будет прям очень много информации, соответственно скорее всего сам файл Angel.ini будет разделён на разные версии Angel_eng.ini Angel_rus.ini Angel_fr.ini и так далее. В каждом из инишников будет описание как на родном для локали языке, так и на английском. Где "особые" уникальные фиксы будут описываться подробнее (пример: почему его нет в другой локали, а в этой он есть). И сама структура инишников будет переделана - сначала будут идти общие фиксы, которые необходимы всем локалям, а потом будут идти уже узконаправленные фиксы для определённой локали. Изменено Суббота в 15:29 пользователем ksteel Ссылка на комментарий Поделиться на другие сайты Поделиться
ksteel Опубликовано Суббота в 15:41 Автор Жалоба Поделиться Опубликовано Суббота в 15:41 (изменено) 1 час назад, olol сказал: Насколько я помню, для плаванья аргонцев движок игры проверяет не ID расы, а ее название, которое отличается для других языков: EN: Argonian DE: Argonier ES: Argoniano FR: Argonien HU: Argóniai IT: Argoniano PL: Argonian PT: Argonianos RU: Аргонианин А у китайского шрифт Win32!!! Фиксить нужно проверку не на название, а на ID расы, которая одинакова для всех языков. Соответственно это исправление должно быть в MCP. хоть я уже и перенёс фикс по "названию расы в игре", ваша мысль просто бриллиантовая! я переделаю этот фикс, сделаю его изначально "универсальным" для всех языков, которые НЕ английские! тем более у нас есть уже явный пример от Angel Death в файле items_info.cpp. Он использует сравнение по ID типа предмета (0x4f4d4d41 = 'AMMO'), а не по строковому имени! Это именно тот подход, который нужен для аргониан! Изменено Суббота в 15:54 пользователем ksteel Ссылка на комментарий Поделиться на другие сайты Поделиться
ksteel Опубликовано Суббота в 15:58 Автор Жалоба Поделиться Опубликовано Суббота в 15:58 (изменено) ещё забавно, но факт что в стиме при скачивании En, Fr или De версий нет констракшн сета 😃 совсем безолаберность upd Construction Set и всякие источники показывают только строковое значение ID для расы = Argonian. Совершенно бесполезно. Надо искать числовое значение внутри движка и вновь погружаюсь в декомпиляторы) upd из строчек: 007af2a4 Unable to find race id "%s" in script %s. "Unable to find race id \"%s\" in script %s." и 004fde03 PUSH s_Unable_to_find_race_id_"%s"_in_s_007af2a4 DATA прихожу к выводу, что Race id в "скриптах" передаётся как строка ("%s") (что грустно), а не число, Construction Set говорит, что id аргониан = Argonian, которую нещадно переводит наша (да и вообще любая) локализация. Надежда на то, что Движок анимаций использует числа, а не строку, продолжаю поиски. Можно конечно задать параметр, чтобы пеередавалось не как строка.....но хз чем это будет грозить в рамках всего движка (тотальным ахтунгом, потому что все функции ждут строку, а не число int) upd (далее пойдёт немного хаотичное повествование, т.к. автор поста выпил вина 😄 ) что было выявлено посредством шаманства: 0x004C2B40 - 0x004C3090 - это ОДНА ФУНКЦИЯ выбора анимации MOV EAX - загрузка пути анимации в регистр PUSH EAX - передача пути в функцию загрузки base_animKnA.nif vs argonian_swimKnA.nif - два варианта анимации! Вывод в функции происходит ветвление: Если аргонианин → argonian_swimKnA.nif Если другая раса → base_animKnA.nif А ещё я нашёл ЧИСЛОВУЮ (ура) проверку: dec eax ; jz short loc_4C2B47 ; (Если EAX == 2 → зелёная ветка (аргониане)) Вывод EAX содержит числовой race_id. Теперь смотрим на код в декомпиляторе: Спойлер int __thiscall sub_4C27C0(_DWORD *this) { int j; // esi const char *v3; // eax int v4; // ebx const char *v5; // eax char *v6; // eax char *v7; // eax int v8; // eax int k; // eax char *v10; // eax int v11; // eax int v12; // ecx int v13; // edi bool v14; // zf char *v15; // eax int v16; // edi char **v17; // ebx char *v18; // eax char *v19; // eax int v20; // ebx const char *v21; // eax int *v22; // edi const char *v23; // eax char *v24; // eax char *v25; // ecx char *v26; // eax int v27; // eax int n; // eax char *v29; // eax int v30; // eax int v31; // ecx int v32; // esi int v33; // ebx char *v34; // eax char **v35; // edi char *v36; // esi _DWORD *v37; // ebx int result; // eax int i; // [esp+10h] [ebp-14h] int *v40; // [esp+14h] [ebp-10h] _BYTE *v41; // [esp+14h] [ebp-10h] int m; // [esp+14h] [ebp-10h] char *String1; // [esp+18h] [ebp-Ch] int v44; // [esp+1Ch] [ebp-8h] int *v45; // [esp+20h] [ebp-4h] v44 = sub_4D51B0(&unk_7CB478); String1 = (char *)sub_4D51E0(&unk_7CB478); for ( i = 0; i < 2; ++i ) { for ( j = 0; j < 2; ++j ) { v45 = this + 2 * i + j + 10699; if ( *v45 ) { v3 = (const char *)sub_4C2720(i, j); nullsub_12("Clearing 3D for baseanim '%s'.", v3); sub_4EE5B0(*(this + 2 * i + j + 10695)); sub_471B00(0); } v4 = j + 2 * i; memset(this + 150 * v4 + 10095, 0, 0x258u); v40 = this + v4 + 10695; if ( *v40 ) { v5 = (const char *)sub_4C2720(i, j); nullsub_12("Clearing 3D for baseanim '%s'.", v5); sub_4EE5B0(*v40); sub_471B00(0); } if ( i ) { if ( i == 1 ) { if ( j == 1 ) v6 = aBaseAnimFemale; else v6 = aBaseAnimFemale_0; } else { v6 = 0; } } else { v6 = aBaseAnim1stNif; if ( j != 1 ) v6 = aBaseAnimNif; } if ( (unsigned __int8)sub_47D640(v6) ) { sprintf(v44, aSBaseAnim, off_792674[i]); if ( i ) { if ( i == 1 ) { if ( j == 1 ) v7 = aBaseAnimFemale; else v7 = aBaseAnimFemale_0; } else { v7 = 0; } } else { v7 = aBaseAnim1stNif; if ( j != 1 ) v7 = aBaseAnimNif; } v8 = sub_4EE200(v7, v44); *v45 = v8; if ( v8 ) { for ( k = *(_DWORD *)(v8 + 16); k; k = *(_DWORD *)(k + 32) ) *(this + 150 * v4 + *(unsigned __int8 *)(k + 16) + 10095) = k; } if ( i ) { if ( i == 1 ) { if ( j == 1 ) v10 = aBaseAnimFemale; else v10 = aBaseAnimFemale_0; } else { v10 = 0; } } else { v10 = aBaseAnim1stNif; if ( j != 1 ) v10 = aBaseAnimNif; } sub_47D870(v10, (int)&off_789D88, (int)String1, 0); v11 = sub_4EE0A0(String1); v12 = *v40; v13 = v11; if ( *v40 != v11 ) { if ( v12 ) { v14 = (*(_DWORD *)(v12 + 4))-- == 1; if ( v14 ) (**(void (__thiscall ***)(int, int))v12)(v12, 1); } *v40 = v13; if ( v13 ) ++*(_DWORD *)(v13 + 4); } if ( *v40 ) { if ( i ) { if ( i == 1 ) { if ( j == 1 ) v15 = aBaseAnimFemale; else v15 = aBaseAnimFemale_0; } else { v15 = 0; } } else { v15 = aBaseAnim1stNif; if ( j != 1 ) v15 = aBaseAnimNif; } sub_6EA1A0(v15); sub_472B70(*v40, 1); sub_6EB000(0, 0, 1); } } if ( !i ) { v16 = 0; v17 = &off_78A970; v41 = (char *)&unk_7A6008 + j; do { if ( *v41 != 1 || *(this + 150 * j + v16 + 10095) ) { if ( *v41 == 0xFF && *(this + 150 * j + v16 + 10095) ) { v19 = aBaseAnim1stNif; if ( j != 1 ) v19 = aBaseAnimNif; sub_477400("Please remove anim group (%s) in \"%s\".\r\n", *v17, v19); } } else { v18 = aBaseAnim1stNif; if ( j != 1 ) v18 = aBaseAnimNif; sub_477400("Missing required anim group (%s) in \"%s\".\r\n", *v17, v18); } ++v17; ++v16; v41 += 2; } while ( (int)v17 < (int)off_78ABC8 ); } } } v20 = 0; for ( m = 0; ; v20 = m ) { if ( *(this + v20 + 11156) ) { if ( v20 ) { if ( v20 == 1 ) { v21 = aBaseAnimkna1st; } else if ( v20 == 2 ) { v21 = aArgonianSwimkn; } else { v21 = 0; } } else { v21 = aBaseAnimknaNif; } nullsub_12("Clearing 3D for baseanim '%s'.", v21); sub_4EE5B0(*(this + v20 + 11153)); sub_471B00(0); } memset(this + 150 * v20 + 10703, 0, 0x258u); v22 = this + v20 + 11153; if ( *v22 ) { if ( v20 ) { if ( v20 == 1 ) { v23 = aBaseAnimkna1st; } else if ( v20 == 2 ) { v23 = aArgonianSwimkn; } else { v23 = 0; } } else { v23 = aBaseAnimknaNif; } nullsub_12("Clearing 3D for baseanim '%s'.", v23); sub_4EE5B0(*v22); sub_471B00(0); } if ( v20 ) { if ( v20 == 1 ) { v24 = aBaseAnimkna1st; } else if ( v20 == 2 ) { v24 = aArgonianSwimkn; } else { v24 = 0; } } else { v24 = aBaseAnimknaNif; } if ( (unsigned __int8)sub_47D640(v24) ) { if ( v20 ) { if ( v20 == 1 ) { v25 = aBaseAnimkna1st; } else if ( v20 == 2 ) { v25 = aArgonianSwimkn; } else { v25 = 0; } } else { v25 = aBaseAnimknaNif; } if ( v20 ) { if ( v20 == 1 ) { v26 = aBaseAnimkna1st; } else if ( v20 == 2 ) { v26 = aArgonianSwimkn; } else { v26 = 0; } } else { v26 = aBaseAnimknaNif; } v27 = sub_4EE200(v26, (int)v25); *(this + v20 + 11156) = v27; if ( v27 ) { for ( n = *(_DWORD *)(v27 + 16); n; n = *(_DWORD *)(n + 32) ) *(this + 150 * v20 + *(unsigned __int8 *)(n + 16) + 10703) = n; } if ( v20 ) { if ( v20 == 1 ) { v29 = aBaseAnimkna1st; } else if ( v20 == 2 ) { v29 = aArgonianSwimkn; } else { v29 = 0; } } else { v29 = aBaseAnimknaNif; } sub_47D870(v29, (int)&off_789D88, (int)String1, 0); v30 = sub_4EE0A0(String1); v31 = *v22; v32 = v30; if ( *v22 != v30 ) { if ( v31 ) { v14 = (*(_DWORD *)(v31 + 4))-- == 1; if ( v14 ) (**(void (__thiscall ***)(int, int))v31)(v31, 1); } *v22 = v32; if ( v32 ) ++*(_DWORD *)(v32 + 4); } if ( *v22 ) { if ( v20 ) { v33 = v20 - 1; if ( v33 ) { if ( v33 == 1 ) v34 = aArgonianSwimkn; else v34 = 0; } else { v34 = aBaseAnimkna1st; } } else { v34 = aBaseAnimknaNif; } sub_6EA1A0(v34); sub_472B70(*v22, 1); sub_6EB000(0, 0, 1); } } v35 = &off_78A970; v36 = (char *)&unk_7A6008; v37 = this + 10703; do { if ( *v36 != 1 || *v37 ) { if ( *v36 == -1 ) { if ( *v37 ) sub_477400("Please remove anim group (%s) in \"%s\".\r\n", *v35, aBaseAnimknaNif); } } else { sub_477400("Missing required anim group (%s) in \"%s\".\r\n", *v35, aBaseAnimknaNif); } v36 += 2; ++v37; ++v35; } while ( (int)v36 < (int)aHandlerCollisi ); result = ++m; if ( m >= 3 ) break; } return result; } из которого делаю выводы: if ( v20 ) { if ( v20 == 1 ) { v24 = aBaseAnimkna1st;} else if ( v20 == 2 ){ v24 = aArgonianSwimkn;} else { v24 = 0; } } v24 = aBaseAnimkna1st; // "base_animkna1st.nif" v24 = aArgonianSwimkn; // "argonian_swimKnA.nif" - аргониане получается переменная v20 v20 = 0 → base_animkna.nif (стандартная анимация) v20 = 1 → base_animkna1st.nif (анимация от первого лица) v20 = 2 → argonian_swimKnA.nif (специальная анимация аргониан) НО где же определяется эта переменная? Смотрим выше: v20 = 0; for ( m = 0; ; v20 = m ) {... огромный блок кода ... result = ++m; if ( m >= 3 ) break; } Вывод v20 (m) проходит значения 0, 1, 2, v20 = 2 соответствует аргонианам, Значит race_id аргониан = 2. Нам надо перехватить в момент проверки когда v20 = 2 и обеспечить загрузку правильной анимации. Изменено Суббота в 16:50 пользователем ksteel Ссылка на комментарий Поделиться на другие сайты Поделиться
ksteel Опубликовано 22 часа назад Автор Жалоба Поделиться Опубликовано 22 часа назад (изменено) пока сделал заметку. по поводу фикса аргониан (имею в виду починку самой функции и замену "название расы" на "ID расы"), начал разбираться что да как, чуть не заблудился 😄 А сейчас вот к этому молчаливому патчу: 22 часа назад, ksteel сказал: Спойлер Я обнаружил "молчаливый фикс" в MCP для русской локализации, о котором в принципе не пишется в логе, хотя этот фикс ОЧЕНЬ важен. Было: Стало: Фикс из категории БУФЕРОВ И БЕЗОПАСНОСТИ. Предположение: Фикс переполнения буферов в системе обработки ошибок звуковой подсистемы Morrowind. Я фиксил уже расширение консоли по переполнению буфера. Этот же фикс прорабатывает систему буферов по звукам. Проблема: Слишком длинные сообщения об ошибках в английской версии В русской локализации эти строки становятся еще длиннее из-за кириллицы Приводит к переполнению буферов и возможным сбоям Что делает MCP? Он укорачивает строки ошибок, чтобы избежать переполнения., заменяет list на l. 0x00 00 80 3F - скорее всего, padding или служебные данные. Это довольно важный фикс безопасности, предотвращающий потенциальные сбои при работе со звуковой системой в русской локализации. НО переносить его в таком виде - кощунство. Я буду "забивать гвозди микроскопом". Надо использовать text.dll в полной мере. Мы увеличим буфер для обработки такого типа ошибок (как я сделал фикс по переполнению буфера расширения консоли). Что удалось выяснить: конкретно в FUN_0048b000 есть конкретная функция логирования: sub_477400 Спойлер ; char sub_477400(int, ...) sub_477400 proc near var_568= byte ptr -568h var_554= dword ptr -554h var_550= dword ptr -550h var_54C= dword ptr -54Ch var_548= dword ptr -548h var_544= dword ptr -544h NumberOfBytesWritten= dword ptr -540h Msg= tagMSG ptr -53Ch Buffer= byte ptr -520h var_C= dword ptr -0Ch var_4= dword ptr -4 arg_0= dword ptr 4 arg_4= byte ptr 8 ; FUNCTION CHUNK AT .text:0072AFD0 SIZE 0000002B BYTES ; __unwind { // SEH_477400 mov eax, large fs:0 mov ecx, [esp+arg_0] push 0FFFFFFFFh push offset SEH_477400 push eax mov large fs:0, esp sub esp, 548h push ebx push ebp push esi push edi lea eax, [esp+564h+arg_4] push eax push ecx lea edx, [esp+56Ch+Buffer] push edx call ds:vsprintf or ebp, 0FFFFFFFFh xor eax, eax add esp, 0Ch lea edi, [esp+564h+Buffer] mov ecx, ebp repne scasb not ecx dec ecx lea eax, [esp+ecx+564h+Buffer] cmp byte ptr [eax-2], 0Dh jnz short loc_47745A где Первый параметр — строка формата (наши сообщения об ошибках) Второй параметр — дополнительная информация Смотрим подробно структуру данного инструмента логирования: Спойлер CHAR Buffer[1300]; // [esp+6Ah] [ebp-520h] BYREF Что собственно происходит? vsprintf(Buffer, a1, va); - форматирование строки в буфер 1300 байт. Находим этот "буфер": 00477419 FUN_00477400 SUB ESP,0x548 Немного математики: 0x548 в hex = 1352 байта в десятичной Текущий буфер: 1300 байт (как видно в коде) Разница: 52 байта - это (скорее всего) служебные данные/выравнивание Увы, это не расширение консоли, где я могу увидеть весь исходный код и впаять запросто динамический буфер (хотя я попробую). Потому Моё решение: меняем SUB ESP,0x548 (1352 байта) на SUB ESP,0x800 (2048 байт) итого +696 байт доп пространства, чего должно хватить для кириллицы (да и вообще любого языка). А т.к. я трогаю ТОЛЬКО размер буфера — нулевой риск, не затрагивающий основную логику поведения сообщений по звуковым ошибкам. Данный фикс должен покрывать все 5 найденных строк ошибок звуковой системы. К сожалению (как я уже неоднократно говорил) я не плагиностроитель или мододел, потому воспроизвести случай появления этих ошибок я не смогу (для проведения тестов). У кого есть время, прошу провести тесты. Прикладываю файлы собсно для теста. Условия для тестов - чистый MTB без MCP/MGE/MWSE патча - это очень важно. Мой файл по статичному увеличению буфера: Спойлер angel.iniText.dll Изменено 21 час назад пользователем ksteel Ссылка на комментарий Поделиться на другие сайты Поделиться
ksteel Опубликовано 5 часов назад Автор Жалоба Поделиться Опубликовано 5 часов назад (изменено) В 07.11.2025 в 11:07 PM, Pirate443 сказал: а вот чего MCP хочет от text.dll непонятно. Уважаемые форумчане!!! Я выяснил что делает MCP с text.dll! Что я таки нашёл после патча text.dll от версии GFM 5.7 через MCP: Оригинальный файл: Спойлер Пропатченный через MCP: Спойлер Различие в байтах на лицо, итого имеем: Спойлер В Hex Workshop побаловались, идём в декомпилятор/дизассемблер и ищем: в оригинальном text.dll последовательность CC CC CC CC 8B 44 24 04 56 8B 74 24 10 83 FE 08 F7 D0 0F 8C D4 00 в пропатченном text.dll последовательность CC CC CC CC B8 AF 88 56 38 C3 74 24 10 83 FE 08 F7 D0 0F 8C D4 00 По обоим-двум последовательностям В РАЗНЫХ text.dll мы находим: text:1000188C align 10h НО align 10h - это паддинг, а не код!!! Подозрительно так? Заменять столько информации, на паддинг? Прыгаем в адрес 1000188C в обеих dll и смотрим код вокруг него: оригинальный Text.dll: Спойлер пропатченный через MCP text.dll: Спойлер Ничего подозрительного не находите? В детстве играли в "найди различия на картинке"? Так вот они! (это не всё, я не буду сюда скидывать весь дизассемблер) Спойлер На лицо явная замена функции. Что за функции? Смотрим вот в это место оригинала: sub_10001890 proc near mov eax, [esp+arg_0] push esi mov esi, [esp+4+arg_8] cmp esi, 8 not eax jl loc_10001978 А теперь в то же место патча: sub_10001890 proc near mov eax, 385688AFh retn всё. ТОЧКА. MCP заменил всю сложную функцию CRC32 на простой возврат хардкодного значения. Но вот как он это сделал...после значения retn идут случайные байты dw 2474h и повреждённый код. Грубо конечно, но для такого метода очень эффективно, хотя и не в полной мере. А теперь немного по шаманим с плагинами декомпилятора и сравним полностью эти два файлика, их код и глянем разницу, сначала в HEX, а затем и в дизассемблере: Первая вкладка: Спойлер Вторая вкладка: Спойлер Начнём со второй вкладки - HEX Разница: Спойлер Знакомая в принципе картина, уже видели её в Hex Workshop. А теперь самое интересное - дизассемблер: Спойлер Тадам БЫЛО: Сложный CRC32 алгоритм (50+ строк кода) СТАЛО: Всего 2 инструкции! Таким образом получается mov eax, 385688AFh + retn - это магическое число, которое подсовывает MCP вместо реальных вычислений. Выходит что: MCP обходит проверки целостности Возвращает фиксированный хеш вместо реального Это объясняет, почему пропатченный text.dll работает с модифицированным Morrowind.exe А теперь давайте посмотрим и найдём похожие функции, связанные с проверкой по crc ключу между text.dll и morrowind.exe...(кто следит за этой темой с самого начала и внимательно читал, наверное уже настальгирует сидит 😃 ) Спойлер А вот и она, долго искать не пришлось. Да-да, эта та самая проверка, про фикс которой я писал ещё в самом первом своём посте в этой теме 😄 не дословная цитата из того поста: "После патча через MCP у Morrowind.exe менялся цифровой отпечаток и старенький text.dll не понимал что это за Морровинд такой. Ошибка Unknown Morrowind.exe" Вот так вот, работаешь-работаешь и случайно вспоминаешь о прошлых деяниях 😃 . настальгия: MCP ломает систему определения версии Morrowind в самом text.dll и САМ определяет её, вычисляя хэш Morrowind.exe! Всё оказалось просто: MCP эту функцию WORD crc = CalcExeCRC(); подменяет на пустышку и все дальнейшие проверки внутри text.dll уже бессмысленны. Вот почему MCP спокойно патчит Morrowind.exe и text.dll не ругается на изменение хэша исполняемого файла. Итог расследования: Я уже сам лично купировал эту crc проверку (как указано в шапке темы), мой text.dll универсально работает с ЛЮБЫМ Morrowind.exe, которому нужен ЛЮБОЙ другой text.dll!!! Всем спасибо за внимание!!! Изменено 5 часов назад пользователем ksteel Ссылка на комментарий Поделиться на другие сайты Поделиться
Рекомендуемые сообщения
Для публикации сообщений создайте учётную запись или авторизуйтесь
Вы должны быть пользователем, чтобы оставить комментарий
Создать учетную запись
Зарегистрируйте новую учётную запись в нашем сообществе. Это очень просто!
Регистрация нового пользователяВойти
Уже есть аккаунт? Войти в систему.
Войти