Extend fkNetcode with settings and better error handling.

This commit is contained in:
Ray Koopa 2020-07-12 17:40:11 +02:00
parent 71c8f33ea7
commit 180a61a147
16 changed files with 378 additions and 182 deletions

View File

@ -1,4 +1,3 @@
#define WIN32_LEAN_AND_MEAN
#include <vector> #include <vector>
#include <Windows.h> #include <Windows.h>
@ -28,7 +27,7 @@ void fkAttach()
} }
else else
{ {
sprintf_s<MAX_PATH>(buffer, "Could not load module %s.", findFileData.cFileName); sprintf_s(buffer, "Could not load module %s.", findFileData.cFileName);
MessageBox(NULL, buffer, "FrontendKit", MB_ICONWARNING); MessageBox(NULL, buffer, "FrontendKit", MB_ICONWARNING);
} }
} while (FindNextFile(hFindFile, &findFileData)); } while (FindNextFile(hFindFile, &findFileData));

View File

@ -1,5 +1,4 @@
#pragma once #pragma once
#define WIN32_LEAN_AND_MEAN
#include <Windows.h> #include <Windows.h>
typedef struct PEInfo typedef struct PEInfo

View File

@ -0,0 +1,38 @@
#include "fkConfig.h"
#include <stdio.h>
namespace fk
{
Config::Config(LPCSTR fileName)
{
DWORD length = GetModuleFileName(NULL, _filePath, MAX_PATH);
strcpy_s(strrchr(_filePath, '\\') + 1, MAX_PATH, fileName);
}
void Config::get(LPCSTR category, LPCSTR key, BOOL& result, UINT fallback) const
{
result = GetPrivateProfileInt(category, key, fallback, _filePath);
}
void Config::get(LPCSTR category, LPCSTR key, UINT& result, UINT fallback) const
{
result = GetPrivateProfileInt(category, key, fallback, _filePath);
}
void Config::get(LPCSTR category, LPCSTR key, LPSTR result, INT resultLength, LPCSTR fallback) const
{
GetPrivateProfileString(category, key, fallback, result, resultLength, _filePath);
}
void Config::set(LPCSTR category, LPCSTR key, UINT value) const
{
CHAR buffer[32];
sprintf_s(buffer, "%d", value);
WritePrivateProfileString(category, key, buffer, _filePath);
}
void Config::set(LPCSTR category, LPCSTR key, LPCSTR value) const
{
WritePrivateProfileString(category, key, value, _filePath);
}
}

View File

@ -0,0 +1,20 @@
#pragma once
#include <Windows.h>
namespace fk
{
class Config
{
public:
Config(LPCSTR fileName);
void get(LPCSTR category, LPCSTR key, BOOL& result, UINT fallback) const;
void get(LPCSTR category, LPCSTR key, UINT& result, UINT fallback) const;
void get(LPCSTR category, LPCSTR key, LPSTR result, INT resultLength, LPCSTR fallback = NULL) const;
void set(LPCSTR category, LPCSTR key, UINT value) const;
void set(LPCSTR category, LPCSTR key, LPCSTR value) const;
private:
CHAR _filePath[MAX_PATH];
};
}

View File

@ -87,14 +87,23 @@
</Link> </Link>
</ItemDefinitionGroup> </ItemDefinitionGroup>
<ItemGroup> <ItemGroup>
<ClCompile Include="fkConfig.cpp" />
<ClCompile Include="fkPatch.cpp" />
<ClCompile Include="fkUtils.cpp" />
<ClCompile Include="fkWinHandle.cpp" />
<ClCompile Include="main.cpp" /> <ClCompile Include="main.cpp" />
<ClCompile Include="misc_tools.cpp" />
<ClCompile Include="PEInfo.cpp" /> <ClCompile Include="PEInfo.cpp" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClInclude Include="misc_tools.h" /> <ClInclude Include="fkConfig.h" />
<ClInclude Include="fkWinHandle.h" />
<ClInclude Include="fkPatch.h" />
<ClInclude Include="fkUtils.h" />
<ClInclude Include="PEInfo.h" /> <ClInclude Include="PEInfo.h" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<None Include="fkPatch.inl" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets"> <ImportGroup Label="ExtensionTargets">
</ImportGroup> </ImportGroup>

View File

@ -0,0 +1,28 @@
#include "fkPatch.h"
#include <stdexcept>
namespace fk
{
Patch::Patch(LPVOID lpAddress, SIZE_T dwSize)
: _lpAddress(static_cast<LPBYTE>(lpAddress))
, _dwSize(dwSize)
, dwPosition(0)
{
if (!_lpAddress || !_dwSize)
throw std::invalid_argument("Address and size must not be 0.");
if (!VirtualProtect(_lpAddress, _dwSize, PAGE_EXECUTE_READWRITE, &_flOldProtect))
throw std::exception("VirtualProtect failed, call GetLastError for more info.");
}
Patch::~Patch()
{
close();
}
void Patch::close() const
{
DWORD oldProtect;
if (!VirtualProtect(_lpAddress, _dwSize, _flOldProtect, &oldProtect))
throw std::exception("VirtualProtect failed, call GetLastError for more info.");
}
};

View File

@ -0,0 +1,24 @@
#pragma once
#include <Windows.h>
namespace fk
{
struct Patch
{
public:
DWORD dwPosition;
Patch(LPVOID lpAddress, SIZE_T dwSize);
~Patch();
void close() const;
template <class T> void write(const T& value);
private:
LPBYTE _lpAddress;
SIZE_T _dwSize;
DWORD _flOldProtect;
};
}
#include "fkPatch.inl"

View File

@ -0,0 +1,10 @@
namespace fk
{
template <class T>
void Patch::write(const T& value)
{
memcpy_s(_lpAddress + dwPosition, sizeof(T), &value, sizeof(T));
dwPosition += sizeof(T);
}
}

View File

@ -0,0 +1,89 @@
#include "fkUtils.h"
#include "fkPatch.h"
namespace fk
{
int getGameVersion(DWORD timeDateStamp)
{
switch (timeDateStamp)
{
case 0x3528DAFA: return GAME_VERSION_BR;
case 0x3528DCB1: return GAME_VERSION_EN;
case 0x3528DB52: return GAME_VERSION_GE;
case 0x3528DA98: return GAME_VERSION_NA;
case 0x3528DBDA: return GAME_VERSION_SA;
case 0x3587BE19: return GAME_VERSION_TRY;
}
return GAME_VERSION_NONE;
}
void throwLastError()
{
DWORD error = GetLastError();
if (error == ERROR_SUCCESS)
return;
LPTSTR buffer = NULL;
const DWORD cchMsg = FormatMessageA(
FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_ALLOCATE_BUFFER, NULL,
error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), reinterpret_cast<LPTSTR>(&buffer), 0, NULL);
if (cchMsg > 0)
{
std::string message(buffer);
LocalFree(buffer);
throw std::exception(message.c_str());
}
else
{
CHAR buffer[32];
sprintf_s(buffer, "Error code 0x%08X.", error);
throw std::exception(buffer);
}
}
void patchNops(ULONG dwAddr, SIZE_T dwPatchSize)
{
fk::Patch patch(reinterpret_cast<void*>(dwAddr), dwPatchSize);
while (dwPatchSize--)
patch.write<BYTE>(0x90);
}
void patchJump(PVOID pDest, SIZE_T dwPatchSize, PVOID pCallee, DWORD dwJumpType)
{
fk::Patch patch(pDest, dwPatchSize);
if (dwPatchSize >= 5 && pDest)
{
BYTE OpSize, OpCode;
switch (dwJumpType)
{
case IJ_PUSHRET: OpSize = 6; OpCode = 0x68; break;
case IJ_FARJUMP: OpSize = 7; OpCode = 0xEA; break;
case IJ_FARCALL: OpSize = 7; OpCode = 0x9A; break;
case IJ_CALL: OpSize = 5; OpCode = 0xE8; break;
default: OpSize = 5; OpCode = 0xE9; break;
}
if (dwPatchSize < OpSize)
throw std::exception("Not enough space to patch opcode.");
patch.write(OpCode);
switch (OpSize)
{
case 7:
patch.write((ULONG)pCallee);
patch.write<WORD>(0x23);
break;
case 6:
patch.write((ULONG)pCallee);
patch.write<BYTE>(0xC3);
break;
default:
patch.write((ULONG)pCallee - (ULONG)pDest - 5);
break;
}
for (DWORD i = OpSize; i < dwPatchSize; i++)
patch.write<BYTE>(0x90);
}
}
}

View File

@ -0,0 +1,32 @@
#pragma once
#include <stdexcept>
#include <Windows.h>
namespace fk
{
enum GameVersion
{
GAME_VERSION_NONE = -1,
GAME_VERSION_BR, // 1.05 Br
GAME_VERSION_EN, // 1.05 Du, En, Fr, It, Po, Sp, Sw
GAME_VERSION_GE, // 1.05
GAME_VERSION_NA, // 1.05
GAME_VERSION_SA, // 1.05
GAME_VERSION_TRY // 1.07 Trymedia
};
enum InsertJump
{
IJ_JUMP, // Insert a jump (0xE9) with patchJump
IJ_CALL, // Insert a call (0xE8) with patchJump
IJ_FARJUMP, // Insert a farjump (0xEA) with patchJump
IJ_FARCALL, // Insert a farcall (0x9A) with patchJump
IJ_PUSHRET, // Insert a pushret with patchJump
};
int getGameVersion(DWORD timeDateStamp);
void throwLastError();
void patchNops(ULONG dwAddr, SIZE_T dwPatchSize);
void patchJump(PVOID pDest, SIZE_T dwPatchSize, PVOID pCallee, DWORD dwJumpType = IJ_JUMP);
}

View File

@ -0,0 +1,20 @@
#include "fkWinHandle.h"
#include "fkUtils.h"
#include <stdexcept>
namespace fk
{
WinHandle::WinHandle(HANDLE handle, BOOL(__stdcall* closeProc)(HANDLE))
: _handle(handle)
, _closeProc(closeProc)
{
if (!_handle || _handle == INVALID_HANDLE_VALUE)
fk::throwLastError();
}
WinHandle::~WinHandle()
{
if (_handle)
_closeProc(_handle);
}
}

View File

@ -0,0 +1,19 @@
#pragma once
#include <Windows.h>
namespace fk
{
struct WinHandle
{
public:
WinHandle(HANDLE handle, BOOL(__stdcall* closeProc)(HANDLE));
~WinHandle();
operator bool() { return _handle; }
operator HANDLE() { return _handle; }
private:
HANDLE _handle;
BOOL(__stdcall* _closeProc)(HANDLE);
};
}

View File

@ -1,77 +1,101 @@
#define WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN
#include <iostream>
#include <stdlib.h>
#include <Windows.h> #include <Windows.h>
#include <WinInet.h> #include <WinInet.h>
#include "fkConfig.h"
#include "fkUtils.h"
#include "fkWinHandle.h"
#include "PEInfo.h" #include "PEInfo.h"
#include "misc_tools.h"
CHAR cfgFallbackIP[16];
CHAR cfgServiceUrl[MAX_PATH];
BOOL cfgShowErrors;
PEInfo pe; PEInfo pe;
CHAR szFoundIP[16] = {}; CHAR resolvedIP[16] = {};
enum GameRegion void configure()
{ {
GAME_REGION_NONE = -1, fk::Config config("fkNetcode.ini");
GAME_REGION_BR,
GAME_REGION_EN, // Du, En, Fr, It, Po, Sp, Sw have the same executable
GAME_REGION_GE,
GAME_REGION_NA,
GAME_REGION_SA
};
int getRegion() // Load INI settings.
config.get("AddressResolval", "FallbackIP", cfgFallbackIP, 16);
config.get("AddressResolval", "ServiceUrl", cfgServiceUrl, MAX_PATH, "http://ip.syroot.com");
config.get("AddressResolval", "ShowErrors", cfgShowErrors, TRUE);
// Ensure INI file has been created with default setting.
config.set("AddressResolval", "FallbackIP", cfgFallbackIP);
config.set("AddressResolval", "ServiceUrl", cfgServiceUrl);
config.set("AddressResolval", "ShowErrors", cfgShowErrors);
// Validate fallback IP.
BYTE b;
if (*cfgFallbackIP && sscanf_s(cfgFallbackIP, "%hhu.%hhu.%hhu.%hhu", &b, &b, &b, &b) != 4)
{ {
switch (pe.FH->TimeDateStamp) *cfgFallbackIP = NULL;
{ MessageBox(NULL, "Invalid fallback IP setting in fkNetcode.ini has been ignored.", "fkNetcode", MB_ICONWARNING);
case 0x3528DAFA: return GAME_REGION_BR;
case 0x3528DCB1: return GAME_REGION_EN;
case 0x3528DB52: return GAME_REGION_GE;
case 0x3528DA98: return GAME_REGION_NA;
case 0x3528DBDA: return GAME_REGION_SA;
} }
return GAME_REGION_NONE;
} }
bool __stdcall getLabelIP(LPSTR buffer, int bufferLength) void resolveIP(LPTSTR buffer)
{ {
if (!szFoundIP[0]) if (!*resolvedIP)
{ {
// Initially resolve the external IP through a web service. // Initially resolve the external IP through a web service.
HINTERNET hInternet = 0, hFile = 0; fk::WinHandle hInternet(InternetOpen(NULL, INTERNET_OPEN_TYPE_DIRECT, NULL, NULL, 0), InternetCloseHandle);
DWORD bytesRead = 0; fk::WinHandle hFile(InternetOpenUrl(hInternet, cfgServiceUrl, NULL, 0,
CHAR buffer[16]; INTERNET_FLAG_NO_COOKIES | INTERNET_FLAG_NO_CACHE_WRITE | INTERNET_FLAG_RELOAD, NULL), InternetCloseHandle);
if ((hInternet = InternetOpen(NULL, INTERNET_OPEN_TYPE_DIRECT, NULL, NULL, 0)) DWORD responseLength = 0;
&& (hFile = InternetOpenUrl(hInternet, "http://ip.syroot.com", NULL, 0, CHAR response[16];
INTERNET_FLAG_NO_COOKIES | INTERNET_FLAG_NO_CACHE_WRITE | INTERNET_FLAG_RELOAD, NULL)) if (!InternetReadFile(hFile, response, 16, &responseLength))
&& InternetReadFile(hFile, buffer, 16, &bytesRead) && bytesRead >= 8) fk::throwLastError();
if (responseLength < 8)
throw std::exception("Response was too short.");
response[responseLength] = '\0';
BYTE temp;
if (sscanf_s(response, "%hhu.%hhu.%hhu.%hhu", &temp, &temp, &temp, &temp) != 4)
throw std::exception("Response could not be parsed.");
lstrcpy(resolvedIP, response);
}
lstrcpy(buffer, resolvedIP);
}
bool __stdcall patchResolveIP(LPSTR buffer, int bufferLength)
{ {
buffer[bytesRead] = '\0'; try
uint8_t temp;
if (sscanf_s(buffer, "%hhu.%hhu.%hhu.%hhu", &temp, &temp, &temp, &temp) == 4)
lstrcpy(szFoundIP, buffer);
}
if (hFile) InternetCloseHandle(hFile);
if (hInternet) InternetCloseHandle(hInternet);
}
// Return the detected IP.
lstrcpy(buffer, szFoundIP);
return buffer[0];
}
void applyPatches()
{ {
// Replace NAT IP detection with web service. resolveIP(buffer);
InsertJump((PVOID)pe.Offset(0x00001799), 5, &getLabelIP, IJ_JUMP); }
catch (std::exception& ex)
{
lstrcpy(buffer, *cfgFallbackIP ? cfgFallbackIP : "0.0.0.0");
if (cfgShowErrors)
{
CHAR message[512];
sprintf_s(message, "Could not resolve your IP address. %s", ex.what());
MessageBox(NULL, message, "fkNetcode", MB_ICONWARNING);
}
}
return true; // not used
}
// Prevent overriding the resolved IP when connecting to server. void patch(int gameVersion)
PatchMemNop(pe.Offset(0x00053E96), 5); // writes user name {
PatchMemNop(pe.Offset(0x00054993), 5); // writes NAT IP fk::patchJump((PVOID)pe.Offset(0x00001799), 5, &patchResolveIP, fk::IJ_JUMP); // replace IP resolve with web service
// Remove useless sleeps. if (gameVersion == fk::GAME_VERSION_TRY)
PatchMemNop(pe.Offset(0x00054935), 11); {
fk::patchNops(pe.Offset(0x00053B96), 5); // prevent overriding IP with user name
fk::patchNops(pe.Offset(0x00054693), 5); // prevent overriding IP with NAT IP
fk::patchNops(pe.Offset(0x00054635), 11); // useless sleep when connecting to server
}
else
{
fk::patchNops(pe.Offset(0x00053E96), 5); // prevent overriding IP with user name
fk::patchNops(pe.Offset(0x00054935), 11); // useless sleep when connecting to server
fk::patchNops(pe.Offset(0x00054993), 5); // prevent overriding IP with NAT IP
}
} }
BOOL WINAPI DllMain(HMODULE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) BOOL WINAPI DllMain(HMODULE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
@ -79,15 +103,19 @@ BOOL WINAPI DllMain(HMODULE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
switch (fdwReason) switch (fdwReason)
{ {
case DLL_PROCESS_ATTACH: case DLL_PROCESS_ATTACH:
if (getRegion() == GAME_REGION_NONE)
{ {
MessageBox(NULL, "fkNetcode is incompatible with your game version or region and will not be able to " int version = fk::getGameVersion(pe.FH->TimeDateStamp);
"resolve your external IP address. Please install the Worms 2 1.05 patch or delete this module to " if (version == fk::GAME_VERSION_NONE)
"remove this warning.", "fkNetcode", MB_ICONWARNING); {
MessageBox(NULL, "fkNetcode is incompatible with your game version. Please run the 1.05 patch or "
"1.07 release of Worms 2. Otherwise, you can delete the module to remove this warning.",
"fkNetcode", MB_ICONWARNING);
} }
else else
{ {
applyPatches(); configure();
patch(version);
}
} }
break; break;

View File

@ -1,92 +0,0 @@
#include <Windows.h>
#include <stdio.h>
#include "misc_tools.h"
BOOL WritePrivateProfileIntA(LPCSTR lpAppName, LPCSTR lpKeyName, int nInteger, LPCSTR lpFileName)
{
CHAR lpString[32];
sprintf_s(lpString, "%d", nInteger);
return WritePrivateProfileStringA(lpAppName, lpKeyName, lpString, lpFileName);
}
LPSTR GetPathUnderModuleA(HMODULE hModule, LPSTR OutBuf, LPCSTR FileName)
{
GetModuleFileNameA(hModule, OutBuf, MAX_PATH);
CHAR* dirend = strrchr(OutBuf, '\\') + 1;
strcpy_s(dirend, MAX_PATH, FileName);
return OutBuf;
}
BOOL __stdcall PatchMemData(ULONG dwAddr, ULONG dwBufLen, PVOID pNewData, ULONG dwDataLen)
{
if (!dwDataLen || !pNewData || !dwAddr)
{
SetLastError(ERROR_INVALID_PARAMETER);
return 0;
}
DWORD dwLastProtection;
if (!VirtualProtect((void*)dwAddr, dwDataLen, PAGE_EXECUTE_READWRITE, &dwLastProtection))
return 0;
memcpy_s((PVOID)dwAddr, dwBufLen, pNewData, dwDataLen);
return VirtualProtect((void*)dwAddr, dwDataLen, dwLastProtection, &dwLastProtection);
}
BOOL __stdcall PatchMemQword(ULONG dwAddr, QWORD qNewValue) { return PatchMemData(dwAddr, 8, &qNewValue, 8); }
BOOL __stdcall PatchMemDword(ULONG dwAddr, DWORD dwNewValue) { return PatchMemData(dwAddr, 4, &dwNewValue, 4); }
BOOL __stdcall PatchMemWord(ULONG dwAddr, WORD wNewValue) { return PatchMemData(dwAddr, 2, &wNewValue, 2); }
BOOL __stdcall PatchMemByte(ULONG dwAddr, BYTE bNewValue) { return PatchMemData(dwAddr, 1, &bNewValue, 1); }
BOOL __stdcall PatchMemFloat(ULONG dwAddr, FLOAT fNewValue)
{
DWORD dwLastProtection;
if (!VirtualProtect((void*)dwAddr, sizeof(FLOAT), PAGE_EXECUTE_READWRITE, &dwLastProtection))
return 0;
*(FLOAT*)dwAddr = fNewValue;
return VirtualProtect((void*)dwAddr, sizeof(FLOAT), dwLastProtection, &dwLastProtection);
}
BOOL __stdcall PatchMemNop(ULONG dwAddr, DWORD dwPatchSize)
{
while (dwPatchSize-- && PatchMemByte(dwAddr++, 0x90));
return dwPatchSize == 0;
}
BOOL __stdcall InsertJump(PVOID pDest, DWORD dwPatchSize, PVOID pCallee, DWORD dwJumpType)
{
if (dwPatchSize >= 5 && pDest)
{
DWORD OpSize, OpCode;
switch (dwJumpType)
{
case IJ_PUSHRET: OpSize = 6; OpCode = 0x68; break;
case IJ_FARJUMP: OpSize = 7; OpCode = 0xEA; break;
case IJ_FARCALL: OpSize = 7; OpCode = 0x9A; break;
case IJ_CALL: OpSize = 5; OpCode = 0xE8; break;
default: OpSize = 5; OpCode = 0xE9; break;
}
if (dwPatchSize < OpSize)
return 0;
PatchMemByte((ULONG)pDest, (BYTE)OpCode);
switch (OpSize)
{
case 7:
PatchMemDword((ULONG)pDest + 1, (ULONG)pCallee);
PatchMemWord((ULONG)pDest + 5, 0x23);
break;
case 6:
PatchMemDword((ULONG)pDest + 1, (ULONG)pCallee);
PatchMemByte((ULONG)pDest + 5, 0xC3);
break;
default:
PatchMemDword((ULONG)pDest + 1, (ULONG)pCallee - (ULONG)pDest - 5);
break;
}
for (ULONG i = OpSize; i < dwPatchSize; i++)
PatchMemByte((ULONG)pDest + i, 0x90);
}
return 0;
}

View File

@ -1,27 +0,0 @@
#pragma once
typedef unsigned long long QWORD;
typedef QWORD *PQWORD, *LPQWORD;
extern "C" IMAGE_DOS_HEADER __ImageBase;
#define HINST_THISCOMPONENT ((HINSTANCE)&__ImageBase)
#define IJ_JUMP 0 //Insert a jump (0xE9) with InsertJump
#define IJ_CALL 1 //Insert a call (0xE8) with InsertJump
#define IJ_FARJUMP 2 //Insert a farjump (0xEA) with InsertJump
#define IJ_FARCALL 3 //Insert a farcall (0x9A) with InsertJump
#define IJ_PUSHRET 4 //Insert a pushret with InsertJump
BOOL WritePrivateProfileIntA(LPCSTR lpAppName, LPCSTR lpKeyName, int nInteger, LPCSTR lpFileName);
LPSTR GetPathUnderModuleA(HMODULE hModule, LPSTR OutBuf, LPCSTR FileName);
BOOL __stdcall PatchMemData(ULONG dwAddr, ULONG dwBufLen, PVOID pNewData, ULONG dwDataLen);
BOOL __stdcall PatchMemQword(ULONG dwAddr, QWORD qNewValue);
BOOL __stdcall PatchMemDword(ULONG dwAddr, DWORD dwNewValue);
BOOL __stdcall PatchMemWord(ULONG dwAddr, WORD wNewValue);
BOOL __stdcall PatchMemByte(ULONG dwAddr, BYTE bNewValue);
BOOL __stdcall PatchMemFloat(ULONG dwAddr, FLOAT fNewValue);
BOOL __stdcall PatchMemNop(ULONG dwAddr, DWORD dwPatchSize);
BOOL __stdcall InsertJump(PVOID pDest, DWORD dwPatchSize, PVOID pCallee, DWORD dwJumpType = IJ_JUMP);