rosalina inputredir: Use ir patch from @Nanquitas ; also refactor the code
Fixes #1428, #1438 (I think)
This commit is contained in:
parent
9ca52054cf
commit
b17eb66d55
@ -168,6 +168,246 @@ void inputRedirectionThreadMain(void)
|
||||
void hidCodePatchFunc(void);
|
||||
void irCodePatchFunc(void);
|
||||
|
||||
static Result InputRedirection_DoUndoIrPatches(Handle processHandle, bool doPatch)
|
||||
{
|
||||
static u32* hookLoc = NULL;
|
||||
static u32* syncLoc = NULL;
|
||||
static u32* cppFlagLoc = NULL;
|
||||
static u32 origIrSync = 0;
|
||||
static u32 origCppFlag = 0;
|
||||
|
||||
static bool patchPrepared = false;
|
||||
|
||||
static u32 irOrigReadingCode[5] = {
|
||||
0xE5940000, // ldr r0, [r4]
|
||||
0xE1A01005, // mov r1, r5
|
||||
0xE3A03005, // mov r3, #5
|
||||
0xE3A02011, // mov r2, #17
|
||||
0x00000000 // (bl i2c_read_raw goes here)
|
||||
};
|
||||
|
||||
static u32 irHook[] = {
|
||||
0xE5940000, // ldr r0, [r4]
|
||||
0xE1A01005, // mov r1, r5
|
||||
0xE59FC000, // ldr r12, [pc] (actually +8)
|
||||
0xE12FFF3C, // blx r12
|
||||
0x00000000 // irCodePhys goes here
|
||||
};
|
||||
|
||||
static u32 syncHookCode[] = {
|
||||
0xE5900000, // ldr r0, [r0]
|
||||
0xEF000024, // svc 0x24
|
||||
0xE3A00000, // mov r0, #0
|
||||
0xE51FF004, // ldr pc, [pc, #-4]
|
||||
0x00000000, // (return address goes here)
|
||||
};
|
||||
|
||||
// Find offsets for required patches
|
||||
s64 startAddress, textTotalRoundedSize, rodataTotalRoundedSize, dataTotalRoundedSize;
|
||||
u32 totalSize;
|
||||
Result res;
|
||||
|
||||
svcGetProcessInfo(&textTotalRoundedSize, processHandle, 0x10002); // only patch .text + .data
|
||||
svcGetProcessInfo(&rodataTotalRoundedSize, processHandle, 0x10003);
|
||||
svcGetProcessInfo(&dataTotalRoundedSize, processHandle, 0x10004);
|
||||
|
||||
totalSize = (u32)(textTotalRoundedSize + rodataTotalRoundedSize + dataTotalRoundedSize);
|
||||
|
||||
svcGetProcessInfo(&startAddress, processHandle, 0x10005);
|
||||
res = svcMapProcessMemoryEx(processHandle, 0x00100000, (u32) startAddress, totalSize);
|
||||
|
||||
if(R_SUCCEEDED(res) && !patchPrepared)
|
||||
{
|
||||
static const u32 irOrigWaitSyncCode[] = {
|
||||
0xEF000024, // svc 0x24 (WaitSynchronization)
|
||||
0xE1B01FA0, // movs r1, r0, lsr#31
|
||||
0xE1A0A000, // mov r10, r0
|
||||
}, irOrigWaitSyncCodeOld[] = {
|
||||
0xE0AC6000, // adc r6, r12, r0
|
||||
0xE5D70000, // ldrb r0, [r7]
|
||||
}; // pattern for 8.1
|
||||
|
||||
static const u32 irOrigCppFlagCode[] = {
|
||||
0xE3550000, // cmp r5, #0
|
||||
0xE3A0B080, // mov r11, #0x80
|
||||
};
|
||||
|
||||
u32 irDataPhys = (u32)PA_FROM_VA_PTR(irData);
|
||||
u32 irCodePhys = (u32)PA_FROM_VA_PTR(&irCodePatchFunc);
|
||||
|
||||
u32 *off = (u32 *)memsearch((u8 *)0x00100000, &irOrigReadingCode, totalSize, sizeof(irOrigReadingCode) - 4);
|
||||
if(off == NULL)
|
||||
{
|
||||
svcUnmapProcessMemoryEx(processHandle, 0x00100000, totalSize);
|
||||
return -1;
|
||||
}
|
||||
|
||||
u32 *off2 = (u32 *)memsearch((u8 *)0x00100000, &irOrigWaitSyncCode, totalSize, sizeof(irOrigWaitSyncCode));
|
||||
if(off2 == NULL)
|
||||
{
|
||||
off2 = (u32 *)memsearch((u8 *)0x00100000, &irOrigWaitSyncCodeOld, totalSize, sizeof(irOrigWaitSyncCodeOld));
|
||||
if(off2 == NULL)
|
||||
{
|
||||
svcUnmapProcessMemoryEx(processHandle, 0x00100000, totalSize);
|
||||
return -2;
|
||||
}
|
||||
}
|
||||
|
||||
u32 *off3 = (u32 *)memsearch((u8 *)0x00100000, &irOrigCppFlagCode, totalSize, sizeof(irOrigCppFlagCode));
|
||||
if(off3 == NULL)
|
||||
{
|
||||
svcUnmapProcessMemoryEx(processHandle, 0x00100000, totalSize);
|
||||
return -3;
|
||||
}
|
||||
|
||||
origIrSync = *off2;
|
||||
origCppFlag = *off3;
|
||||
|
||||
*(void **)(irCodePhys + 8) = decodeArmBranch(off + 4);
|
||||
*(void **)(irCodePhys + 12) = (void*)irDataPhys;
|
||||
|
||||
irHook[4] = irCodePhys;
|
||||
irOrigReadingCode[4] = off[4]; // Copy the branch.
|
||||
syncHookCode[4] = (u32)off2 + 4; // Hook return address
|
||||
|
||||
hookLoc = PA_FROM_VA_PTR(off);
|
||||
syncLoc = PA_FROM_VA_PTR(off2);
|
||||
cppFlagLoc = PA_FROM_VA_PTR(off3);
|
||||
|
||||
patchPrepared = true;
|
||||
}
|
||||
|
||||
if (R_SUCCEEDED(res))
|
||||
{
|
||||
if (doPatch)
|
||||
{
|
||||
memcpy(hookLoc, &irHook, sizeof(irHook));
|
||||
|
||||
// We keep the WaitSynchronization1 to avoid general slowdown because of the high cpu load
|
||||
if (*syncLoc == 0xEF000024) // svc 0x24 (WaitSynchronization)
|
||||
{
|
||||
syncLoc[-1] = 0xE51FF004;
|
||||
syncLoc[0] = (u32)PA_FROM_VA_PTR(&syncHookCode);
|
||||
}
|
||||
else
|
||||
{
|
||||
// This "NOP"s out a WaitSynchronisation1 (on the event bound to the 'IR' interrupt) or the check of a previous one
|
||||
*syncLoc = 0xE3A00000; // mov r0, #0
|
||||
}
|
||||
|
||||
// This NOPs out a flag check in ir:user's CPP emulation
|
||||
*cppFlagLoc = 0xE3150000; // tst r5, #0
|
||||
}
|
||||
else
|
||||
{
|
||||
memcpy(hookLoc, irOrigReadingCode, sizeof(irOrigReadingCode));
|
||||
|
||||
if (*syncLoc == 0xE3A00000)
|
||||
*syncLoc = origIrSync;
|
||||
else
|
||||
{
|
||||
syncLoc[-1] = 0xE5900000; // ldr r0, [r0]
|
||||
syncLoc[0] = 0xEF000024; // svc 0x24
|
||||
}
|
||||
|
||||
*cppFlagLoc = origCppFlag;
|
||||
}
|
||||
}
|
||||
|
||||
svcInvalidateEntireInstructionCache();
|
||||
svcUnmapProcessMemoryEx(processHandle, 0x00100000, totalSize);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static Result InputRedirection_DoUndoHidPatches(Handle processHandle, bool doPatches)
|
||||
{
|
||||
static const u32 hidOrigRegisterAndValue[] = { 0x1EC46000, 0x4001 };
|
||||
static const u32 hidOrigCode[] = {
|
||||
0xE92D4070, // push {r4-r6, lr}
|
||||
0xE1A05001, // mov r5, r1
|
||||
0xEE1D4F70, // mrc p15, 0, r4, c13, c0, 3
|
||||
0xE3A01801, // mov r1, #0x10000
|
||||
0xE5A41080, // str r1, [r4,#0x80]!
|
||||
};
|
||||
|
||||
static bool patchPrepared = false;
|
||||
static u32 *hidRegPatchOffsets[2];
|
||||
static u32 *hidPatchJumpLoc;
|
||||
|
||||
// Find offsets for required patches
|
||||
s64 startAddress, textTotalRoundedSize, rodataTotalRoundedSize, dataTotalRoundedSize;
|
||||
u32 totalSize;
|
||||
Result res;
|
||||
|
||||
svcGetProcessInfo(&textTotalRoundedSize, processHandle, 0x10002); // only patch .text + .data
|
||||
svcGetProcessInfo(&rodataTotalRoundedSize, processHandle, 0x10003);
|
||||
svcGetProcessInfo(&dataTotalRoundedSize, processHandle, 0x10004);
|
||||
|
||||
totalSize = (u32)(textTotalRoundedSize + rodataTotalRoundedSize + dataTotalRoundedSize);
|
||||
|
||||
svcGetProcessInfo(&startAddress, processHandle, 0x10005);
|
||||
res = svcMapProcessMemoryEx(processHandle, 0x00100000, (u32) startAddress, totalSize);
|
||||
|
||||
if (R_SUCCEEDED(res) && !patchPrepared)
|
||||
{
|
||||
u32 *off = (u32 *)memsearch((u8 *)0x00100000, &hidOrigRegisterAndValue, totalSize, sizeof(hidOrigRegisterAndValue));
|
||||
if(off == NULL)
|
||||
{
|
||||
svcUnmapProcessMemoryEx(processHandle, 0x00100000, totalSize);
|
||||
return -1;
|
||||
}
|
||||
|
||||
u32 *off2 = (u32 *)memsearch((u8 *)off + sizeof(hidOrigRegisterAndValue), &hidOrigRegisterAndValue, totalSize - ((u32)off - 0x00100000), sizeof(hidOrigRegisterAndValue));
|
||||
if(off2 == NULL)
|
||||
{
|
||||
svcUnmapProcessMemoryEx(processHandle, 0x00100000, totalSize);
|
||||
return -2;
|
||||
}
|
||||
|
||||
u32 *off3 = (u32 *)memsearch((u8 *)0x00100000, &hidOrigCode, totalSize, sizeof(hidOrigCode));
|
||||
if(off3 == NULL)
|
||||
{
|
||||
svcUnmapProcessMemoryEx(processHandle, 0x00100000, totalSize);
|
||||
return -3;
|
||||
}
|
||||
|
||||
hidRegPatchOffsets[0] = off;
|
||||
hidRegPatchOffsets[1] = off2;
|
||||
hidPatchJumpLoc = off3;
|
||||
|
||||
patchPrepared = true;
|
||||
}
|
||||
|
||||
if(R_SUCCEEDED(res))
|
||||
{
|
||||
if (doPatches)
|
||||
{
|
||||
u32 hidDataPhys = (u32)PA_FROM_VA_PTR(hidData);
|
||||
u32 hidCodePhys = (u32)PA_FROM_VA_PTR(&hidCodePatchFunc);
|
||||
u32 hidHook[] = {
|
||||
0xE59F3004, // ldr r3, [pc, #4]
|
||||
0xE59FC004, // ldr r12, [pc, #4]
|
||||
0xE12FFF1C, // bx r12
|
||||
hidDataPhys,
|
||||
hidCodePhys,
|
||||
};
|
||||
|
||||
*hidRegPatchOffsets[0] = *hidRegPatchOffsets[1] = hidDataPhys;
|
||||
memcpy(hidPatchJumpLoc, &hidHook, sizeof(hidHook));
|
||||
}
|
||||
else
|
||||
{
|
||||
memcpy(hidRegPatchOffsets[0], &hidOrigRegisterAndValue, sizeof(hidOrigRegisterAndValue));
|
||||
memcpy(hidRegPatchOffsets[1], &hidOrigRegisterAndValue, sizeof(hidOrigRegisterAndValue));
|
||||
memcpy(hidPatchJumpLoc, &hidOrigCode, sizeof(hidOrigCode));
|
||||
}
|
||||
}
|
||||
|
||||
svcUnmapProcessMemoryEx(processHandle, 0x00100000, totalSize);
|
||||
return res;
|
||||
}
|
||||
|
||||
Result InputRedirection_Disable(s64 timeout)
|
||||
{
|
||||
if(!inputRedirectionEnabled)
|
||||
@ -186,209 +426,46 @@ Result InputRedirection_Disable(s64 timeout)
|
||||
|
||||
Result InputRedirection_DoOrUndoPatches(void)
|
||||
{
|
||||
s64 startAddress, textTotalRoundedSize, rodataTotalRoundedSize, dataTotalRoundedSize;
|
||||
u32 totalSize;
|
||||
Handle processHandle;
|
||||
|
||||
Result res = OpenProcessByName("hid", &processHandle);
|
||||
static bool hidPatched = false;
|
||||
static bool irPatched = false;
|
||||
|
||||
Handle hidProcHandle = 0, irProcHandle = 0;
|
||||
|
||||
// Prevent hid and ir from running, in any case
|
||||
|
||||
svcKernelSetState(0x10000, 4);
|
||||
|
||||
Result res = OpenProcessByName("hid", &hidProcHandle);
|
||||
if (R_FAILED(res))
|
||||
goto cleanup;
|
||||
|
||||
res = OpenProcessByName("ir", &irProcHandle);
|
||||
if (R_FAILED(res))
|
||||
goto cleanup;
|
||||
|
||||
if(R_SUCCEEDED(res))
|
||||
{
|
||||
svcGetProcessInfo(&textTotalRoundedSize, processHandle, 0x10002); // only patch .text + .data
|
||||
svcGetProcessInfo(&rodataTotalRoundedSize, processHandle, 0x10003);
|
||||
svcGetProcessInfo(&dataTotalRoundedSize, processHandle, 0x10004);
|
||||
|
||||
totalSize = (u32)(textTotalRoundedSize + rodataTotalRoundedSize + dataTotalRoundedSize);
|
||||
|
||||
svcGetProcessInfo(&startAddress, processHandle, 0x10005);
|
||||
res = svcMapProcessMemoryEx(processHandle, 0x00100000, (u32) startAddress, totalSize);
|
||||
|
||||
if(R_SUCCEEDED(res))
|
||||
{
|
||||
static const u32 hidOrigRegisterAndValue[] = { 0x1EC46000, 0x4001 };
|
||||
static const u32 hidOrigCode[] = {
|
||||
0xE92D4070, // push {r4-r6, lr}
|
||||
0xE1A05001, // mov r5, r1
|
||||
0xEE1D4F70, // mrc p15, 0, r4, c13, c0, 3
|
||||
0xE3A01801, // mov r1, #0x10000
|
||||
0xE5A41080, // str r1, [r4,#0x80]!
|
||||
};
|
||||
|
||||
static u32 *hidRegPatchOffsets[2];
|
||||
static u32 *hidPatchJumpLoc;
|
||||
|
||||
if(hidPatched)
|
||||
{
|
||||
memcpy(hidRegPatchOffsets[0], &hidOrigRegisterAndValue, sizeof(hidOrigRegisterAndValue));
|
||||
memcpy(hidRegPatchOffsets[1], &hidOrigRegisterAndValue, sizeof(hidOrigRegisterAndValue));
|
||||
memcpy(hidPatchJumpLoc, &hidOrigCode, sizeof(hidOrigCode));
|
||||
hidPatched = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
u32 hidDataPhys = (u32)PA_FROM_VA_PTR(hidData);
|
||||
u32 hidCodePhys = (u32)PA_FROM_VA_PTR(&hidCodePatchFunc);
|
||||
u32 hidHook[] = {
|
||||
0xE59F3004, // ldr r3, [pc, #4]
|
||||
0xE59FC004, // ldr r12, [pc, #4]
|
||||
0xE12FFF1C, // bx r12
|
||||
hidDataPhys,
|
||||
hidCodePhys,
|
||||
};
|
||||
|
||||
u32 *off = (u32 *)memsearch((u8 *)0x00100000, &hidOrigRegisterAndValue, totalSize, sizeof(hidOrigRegisterAndValue));
|
||||
if(off == NULL)
|
||||
{
|
||||
svcUnmapProcessMemoryEx(processHandle, 0x00100000, totalSize);
|
||||
return -1;
|
||||
}
|
||||
|
||||
u32 *off2 = (u32 *)memsearch((u8 *)off + sizeof(hidOrigRegisterAndValue), &hidOrigRegisterAndValue, totalSize - ((u32)off - 0x00100000), sizeof(hidOrigRegisterAndValue));
|
||||
if(off2 == NULL)
|
||||
{
|
||||
svcUnmapProcessMemoryEx(processHandle, 0x00100000, totalSize);
|
||||
return -2;
|
||||
}
|
||||
|
||||
u32 *off3 = (u32 *)memsearch((u8 *)0x00100000, &hidOrigCode, totalSize, sizeof(hidOrigCode));
|
||||
if(off3 == NULL)
|
||||
{
|
||||
svcUnmapProcessMemoryEx(processHandle, 0x00100000, totalSize);
|
||||
return -3;
|
||||
}
|
||||
|
||||
hidRegPatchOffsets[0] = off;
|
||||
hidRegPatchOffsets[1] = off2;
|
||||
hidPatchJumpLoc = off3;
|
||||
|
||||
*off = *off2 = hidDataPhys;
|
||||
memcpy(off3, &hidHook, sizeof(hidHook));
|
||||
hidPatched = true;
|
||||
}
|
||||
}
|
||||
|
||||
res = svcUnmapProcessMemoryEx(processHandle, 0x00100000, totalSize);
|
||||
res = InputRedirection_DoUndoHidPatches(hidProcHandle, !hidPatched);
|
||||
if (R_SUCCEEDED(res))
|
||||
hidPatched = !hidPatched;
|
||||
}
|
||||
svcCloseHandle(processHandle);
|
||||
|
||||
res = OpenProcessByName("ir", &processHandle);
|
||||
if(R_SUCCEEDED(res) && GET_VERSION_MINOR(osGetKernelVersion()) >= 44)
|
||||
{
|
||||
svcGetProcessInfo(&textTotalRoundedSize, processHandle, 0x10002); // only patch .text + .data
|
||||
svcGetProcessInfo(&rodataTotalRoundedSize, processHandle, 0x10003);
|
||||
svcGetProcessInfo(&dataTotalRoundedSize, processHandle, 0x10004);
|
||||
|
||||
totalSize = (u32)(textTotalRoundedSize + rodataTotalRoundedSize + dataTotalRoundedSize);
|
||||
|
||||
svcGetProcessInfo(&startAddress, processHandle, 0x10005);
|
||||
res = svcMapProcessMemoryEx(processHandle, 0x00100000, (u32) startAddress, totalSize);
|
||||
|
||||
if(R_SUCCEEDED(res))
|
||||
res = InputRedirection_DoUndoIrPatches(irProcHandle, !irPatched);
|
||||
if (R_SUCCEEDED(res))
|
||||
irPatched = !irPatched;
|
||||
else if (!irPatched)
|
||||
{
|
||||
static bool useOldSyncCode;
|
||||
static u32 irOrigReadingCode[5] = {
|
||||
0xE5940000, // ldr r0, [r4]
|
||||
0xE1A01005, // mov r1, r5
|
||||
0xE3A03005, // mov r3, #5
|
||||
0xE3A02011, // mov r2, #17
|
||||
0x00000000 // (bl i2c_read_raw goes here)
|
||||
};
|
||||
|
||||
static const u32 irOrigWaitSyncCode[] = {
|
||||
0xEF000024, // svc 0x24 (WaitSynchronization)
|
||||
0xE1B01FA0, // movs r1, r0, lsr#31
|
||||
0xE1A0A000, // mov r10, r0
|
||||
}, irOrigWaitSyncCodeOld[] = {
|
||||
0xE0AC6000, // adc r6, r12, r0
|
||||
0xE5D70000, // ldrb r0, [r7]
|
||||
}; // pattern for 8.1
|
||||
|
||||
static const u32 irOrigCppFlagCode[] = {
|
||||
0xE3550000, // cmp r5, #0
|
||||
0xE3A0B080, // mov r11, #0x80
|
||||
};
|
||||
|
||||
static u32 *irHookLoc, *irWaitSyncLoc, *irCppFlagLoc;
|
||||
|
||||
if(irPatched)
|
||||
{
|
||||
memcpy(irHookLoc, &irOrigReadingCode, sizeof(irOrigReadingCode));
|
||||
if(useOldSyncCode)
|
||||
memcpy(irWaitSyncLoc, &irOrigWaitSyncCodeOld, sizeof(irOrigWaitSyncCodeOld));
|
||||
else
|
||||
memcpy(irWaitSyncLoc, &irOrigWaitSyncCode, sizeof(irOrigWaitSyncCode));
|
||||
memcpy(irCppFlagLoc, &irOrigCppFlagCode, sizeof(irOrigCppFlagCode));
|
||||
|
||||
irPatched = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
u32 irDataPhys = (u32)PA_FROM_VA_PTR(irData);
|
||||
u32 irCodePhys = (u32)PA_FROM_VA_PTR(&irCodePatchFunc);
|
||||
|
||||
u32 irHook[] = {
|
||||
0xE5940000, // ldr r0, [r4]
|
||||
0xE1A01005, // mov r1, r5
|
||||
0xE59FC000, // ldr r12, [pc] (actually +8)
|
||||
0xE12FFF3C, // blx r12
|
||||
irCodePhys,
|
||||
};
|
||||
|
||||
u32 *off = (u32 *)memsearch((u8 *)0x00100000, &irOrigReadingCode, totalSize, sizeof(irOrigReadingCode) - 4);
|
||||
if(off == NULL)
|
||||
{
|
||||
svcUnmapProcessMemoryEx(processHandle, 0x00100000, totalSize);
|
||||
return -4;
|
||||
}
|
||||
|
||||
u32 *off2 = (u32 *)memsearch((u8 *)0x00100000, &irOrigWaitSyncCode, totalSize, sizeof(irOrigWaitSyncCode));
|
||||
if(off2 == NULL)
|
||||
{
|
||||
off2 = (u32 *)memsearch((u8 *)0x00100000, &irOrigWaitSyncCodeOld, totalSize, sizeof(irOrigWaitSyncCodeOld));
|
||||
if(off2 == NULL)
|
||||
{
|
||||
svcUnmapProcessMemoryEx(processHandle, 0x00100000, totalSize);
|
||||
return -5;
|
||||
}
|
||||
|
||||
useOldSyncCode = true;
|
||||
}
|
||||
else
|
||||
useOldSyncCode = false;
|
||||
|
||||
u32 *off3 = (u32 *)memsearch((u8 *)0x00100000, &irOrigCppFlagCode, totalSize, sizeof(irOrigCppFlagCode));
|
||||
if(off3 == NULL)
|
||||
{
|
||||
svcUnmapProcessMemoryEx(processHandle, 0x00100000, totalSize);
|
||||
return -6;
|
||||
}
|
||||
|
||||
*(void **)(irCodePhys + 8) = decodeArmBranch(off + 4);
|
||||
*(void **)(irCodePhys + 12) = (void*)irDataPhys;
|
||||
|
||||
irHookLoc = off;
|
||||
irWaitSyncLoc = off2;
|
||||
irCppFlagLoc = off3;
|
||||
|
||||
irOrigReadingCode[4] = off[4]; // Copy the branch.
|
||||
|
||||
memcpy(irHookLoc, &irHook, sizeof(irHook));
|
||||
|
||||
// This "NOP"s out a WaitSynchronisation1 (on the event bound to the 'IR' interrupt) or the check of a previous one
|
||||
*irWaitSyncLoc = 0xE3A00000; // mov r0, #0
|
||||
|
||||
// This NOPs out a flag check in ir:user's CPP emulation
|
||||
*irCppFlagLoc = 0xE3150000; // tst r5, #0
|
||||
|
||||
irPatched = true;
|
||||
}
|
||||
InputRedirection_DoUndoHidPatches(hidProcHandle, false);
|
||||
hidPatched = false;
|
||||
}
|
||||
|
||||
res = svcUnmapProcessMemoryEx(processHandle, 0x00100000, totalSize);
|
||||
}
|
||||
svcCloseHandle(processHandle);
|
||||
|
||||
cleanup:
|
||||
svcKernelSetState(0x10000, 4);
|
||||
|
||||
svcCloseHandle(hidProcHandle);
|
||||
svcCloseHandle(irProcHandle);
|
||||
return res;
|
||||
}
|
||||
|
Reference in New Issue
Block a user