Compare commits

...

11 Commits

Author SHA1 Message Date
Summate 1bfca4ff6a
Merge 6e95cb463b into 560c49ba2d 2024-03-21 17:52:12 +10:30
François Berder 560c49ba2d
Core: Fix N64 disk IPL load address check (#2401)
The IPL load address check always evaluated to false due
to a wrong operator.

Signed-off-by: Francois Berder <fberder@outlook.fr>
2024-03-21 17:52:09 +10:30
zilmar 45fb2ad965 Core: In X86RecompilerOps::CompileCheckFPUResult64 make sure RegPointer is protected 2024-03-21 17:44:53 +10:30
zilmar 2811b63ff0 Core: Update CX86RecompilerOps::COP1_D_CVT_S and CX86RecompilerOps::COP1_D_CVT_W 2024-03-21 17:41:29 +10:30
zilmar 33d2722841 Core: fix up CX86RecompilerOps::COP1_D_FLOOR_W 2024-03-21 17:40:14 +10:30
zilmar 9a9c2e5439 Core: Update CX86RecompilerOps::COP1_D_CEIL_W 2024-03-21 17:32:12 +10:30
zilmar 401efae0d9 Core: fix up CX86RecompilerOps::COP1_D_ROUND_W 2024-03-21 17:28:16 +10:30
zilmar 772a20f07d Core: Update CX86RecompilerOps::COP1_D_SQRT 2024-03-21 17:15:10 +10:30
zilmar 87c732b65d Core: update CX86RecompilerOps::COP1_D_NEG 2024-03-21 17:14:00 +10:30
zilmar ece5e30a80 Core: create a function to handle .d recompiler opcodes that use fd and fs 2024-03-21 17:13:16 +10:30
Summate 6e95cb463b Allowing a paste into a number field to be trimmed automatically
The specific issue I experienced is that Excel/LibreOffice Calc add a newline when you copy the contents of a single cell. This is bad behavior and they should provide a copy option that does not do that, but alas, it's much harder to get that into those applications. This behavior made it impossible to paste an otherwise-valid hex address into the Project64 fields without first putting it into Notepad, deleting the newline, recopying, and then doing the paste from there. If the field was simply text, you can go into the field edit and shift + home to select all and then do a copy, but that does not work for a formula. When you edit the file, it shows the formula instead. Therefore, you have absolutely no way of working around this except pasting it somewhere else and removing the newline manually.

In principle, there's no reason why you wouldn't trim the ends at least. Whitespace on either end is useless to you. However, content being after the newline should be rejected as it was before.

There were two secondary issues in the pasting code that are fixed here: One is that it only sort of collapsed single spaces. So if you had more than one space, spaces still would have ended up in the result. Actually I think the semantics were slightly more insidious, <space><number> would have turned into <number><same number> effectively. The only thing it did was remove the space by duplicating the number. If you had two spaces, then it would have ended up with e.g. <space><number><same number>. The only case where this wouldn't have happened is a space at the end which would have been preserved in the paste.

Secondly, it mutated the clipboard data directly. This would have lead to confusing results where multiple pastes would have had clipboard data in the clipboard itself move from, for example, two spaces to a single space to no spaces at all. The better solution is to preprocess to figure out how big we ultimately want our space-less result to be and stamp out the copy ourselves skipping anything we don't want. Leave the clipboard alone.

If it's desired to preserve single spaces only in the middle, the code will need to be modified a bit.
2024-03-10 19:12:17 -05:00
6 changed files with 218 additions and 96 deletions

View File

@ -191,7 +191,7 @@ bool CN64Disk::IsValidDiskImage(uint8_t Test[0x20])
// IPL load address
uint32_t ipl_load_addr = (Test[0x1C] << 24) | (Test[0x1D] << 16) | (Test[0x1E] << 8) | Test[0x1F];
if (ipl_load_addr < 0x80000000 && ipl_load_addr >= 0x80800000) return false;
if (ipl_load_addr < 0x80000000 || ipl_load_addr >= 0x80800000) return false;
// Country code
if (*((uint32_t *)&Test[0]) == 0x16D348E8)

View File

@ -8294,9 +8294,6 @@ void CX86RecompilerOps::COP1_S_CMP()
// COP1: D functions
void CX86RecompilerOps::COP1_D_ADD()
{
uint32_t Reg1 = m_Opcode.ft == m_Opcode.fd ? m_Opcode.ft : m_Opcode.fs;
uint32_t Reg2 = m_Opcode.ft == m_Opcode.fd ? m_Opcode.fs : m_Opcode.ft;
if (FpuExceptionInRecompiler())
{
COP1_D_Opcode(&CX86Ops::Fadd);
@ -8305,6 +8302,9 @@ void CX86RecompilerOps::COP1_D_ADD()
{
CompileCop1Test();
uint32_t Reg1 = m_Opcode.ft == m_Opcode.fd ? m_Opcode.ft : m_Opcode.fs;
uint32_t Reg2 = m_Opcode.ft == m_Opcode.fd ? m_Opcode.fs : m_Opcode.ft;
m_RegWorkingSet.Load_FPR_ToTop(m_Opcode.fd, Reg1, CRegInfo::FPU_Double);
if (m_RegWorkingSet.RegInStack(Reg2, CRegInfo::FPU_Double))
{
@ -8323,9 +8323,6 @@ void CX86RecompilerOps::COP1_D_ADD()
void CX86RecompilerOps::COP1_D_SUB()
{
uint32_t Reg1 = m_Opcode.ft == m_Opcode.fd ? m_Opcode.ft : m_Opcode.fs;
uint32_t Reg2 = m_Opcode.ft == m_Opcode.fd ? m_Opcode.fs : m_Opcode.ft;
if (FpuExceptionInRecompiler())
{
COP1_D_Opcode(&CX86Ops::Fsub);
@ -8334,6 +8331,9 @@ void CX86RecompilerOps::COP1_D_SUB()
{
CompileCop1Test();
uint32_t Reg1 = m_Opcode.ft == m_Opcode.fd ? m_Opcode.ft : m_Opcode.fs;
uint32_t Reg2 = m_Opcode.ft == m_Opcode.fd ? m_Opcode.fs : m_Opcode.ft;
if (m_Opcode.fd == m_Opcode.ft)
{
m_RegWorkingSet.UnMap_FPR(m_Opcode.fd, true);
@ -8364,15 +8364,15 @@ void CX86RecompilerOps::COP1_D_SUB()
void CX86RecompilerOps::COP1_D_MUL()
{
uint32_t Reg1 = m_Opcode.ft == m_Opcode.fd ? m_Opcode.ft : m_Opcode.fs;
uint32_t Reg2 = m_Opcode.ft == m_Opcode.fd ? m_Opcode.fs : m_Opcode.ft;
if (FpuExceptionInRecompiler())
{
COP1_D_Opcode(&CX86Ops::Fmul);
}
else
{
uint32_t Reg1 = m_Opcode.ft == m_Opcode.fd ? m_Opcode.ft : m_Opcode.fs;
uint32_t Reg2 = m_Opcode.ft == m_Opcode.fd ? m_Opcode.fs : m_Opcode.ft;
CompileCop1Test();
m_RegWorkingSet.FixRoundModel(CRegInfo::RoundDefault);
@ -8394,15 +8394,15 @@ void CX86RecompilerOps::COP1_D_MUL()
void CX86RecompilerOps::COP1_D_DIV()
{
uint32_t Reg1 = m_Opcode.ft == m_Opcode.fd ? m_Opcode.ft : m_Opcode.fs;
uint32_t Reg2 = m_Opcode.ft == m_Opcode.fd ? m_Opcode.fs : m_Opcode.ft;
if (FpuExceptionInRecompiler())
{
COP1_D_Opcode(&CX86Ops::Fdiv);
}
else
{
uint32_t Reg1 = m_Opcode.ft == m_Opcode.fd ? m_Opcode.ft : m_Opcode.fs;
uint32_t Reg2 = m_Opcode.ft == m_Opcode.fd ? m_Opcode.fs : m_Opcode.ft;
CompileCop1Test();
if (m_Opcode.fd == m_Opcode.ft)
@ -8436,21 +8436,7 @@ void CX86RecompilerOps::COP1_D_ABS()
{
if (FpuExceptionInRecompiler())
{
CompileInitFpuOperation(CRegInfo::RoundDefault);
if (m_RegWorkingSet.RegInStack(m_Opcode.fs, CRegInfo::FPU_Any) ||
m_RegWorkingSet.RegInStack(m_Opcode.fd, CRegInfo::FPU_Any))
{
g_Notify->BreakPoint(__FILE__, __LINE__);
return;
}
asmjit::x86::Gp TempReg = m_RegWorkingSet.FPRValuePointer(m_Opcode.fs, CRegInfo::FPU_Double);
CompileCheckFPUInput(TempReg, FpuOpSize_64bit);
m_RegWorkingSet.PrepareFPTopToBe(m_Opcode.fd, m_Opcode.fs, CRegInfo::FPU_Double);
m_Assembler.fabs();
m_Assembler.mov(TempReg, (uint64_t)&m_TempValue64);
m_Assembler.fpuStoreQwordFromX86Reg(m_RegWorkingSet.StackTopPos(), TempReg, false);
CompileCheckFPUResult64(TempReg);
m_RegWorkingSet.SetFPTopAs(m_Opcode.fd);
COP1_D_Opcode(&CX86Ops::Fabs);
}
else
{
@ -8461,31 +8447,24 @@ void CX86RecompilerOps::COP1_D_ABS()
}
void CX86RecompilerOps::COP1_D_NEG()
{
if (FpuExceptionInRecompiler())
{
COP1_D_Opcode(&CX86Ops::Fchs);
}
else
{
CompileCop1Test();
m_RegWorkingSet.Load_FPR_ToTop(m_Opcode.fd, m_Opcode.fs, CRegInfo::FPU_Double);
m_Assembler.fchs();
}
}
void CX86RecompilerOps::COP1_D_SQRT()
{
if (FpuExceptionInRecompiler())
{
CompileInitFpuOperation(CRegInfo::RoundDefault);
if (m_RegWorkingSet.RegInStack(m_Opcode.fs, CRegInfo::FPU_Any) ||
m_RegWorkingSet.RegInStack(m_Opcode.fd, CRegInfo::FPU_Any))
{
g_Notify->BreakPoint(__FILE__, __LINE__);
return;
}
asmjit::x86::Gp TempReg = m_RegWorkingSet.FPRValuePointer(m_Opcode.fs, CRegInfo::FPU_Double);
CompileCheckFPUInput(TempReg, FpuOpSize_64bit);
m_RegWorkingSet.PrepareFPTopToBe(m_Opcode.fd, m_Opcode.fs, CRegInfo::FPU_Double);
m_Assembler.fsqrt();
m_Assembler.mov(TempReg, (uint64_t)&m_TempValue64);
m_Assembler.fpuStoreQwordFromX86Reg(m_RegWorkingSet.StackTopPos(), TempReg, false);
CompileCheckFPUResult64(TempReg);
m_RegWorkingSet.SetFPTopAs(m_Opcode.fd);
COP1_D_Opcode(&CX86Ops::Fsqrt);
}
else
{
@ -8499,6 +8478,7 @@ void CX86RecompilerOps::COP1_D_MOV()
{
if (FpuExceptionInRecompiler())
{
CompileCop1Test();
if (m_RegWorkingSet.RegInStack(m_Opcode.fs, CRegInfo::FPU_Any) || m_RegWorkingSet.RegInStack(m_Opcode.fd, CRegInfo::FPU_Any))
{
g_Notify->BreakPoint(__FILE__, __LINE__);
@ -8577,6 +8557,12 @@ void CX86RecompilerOps::COP1_D_FLOOR_L()
}
void CX86RecompilerOps::COP1_D_ROUND_W()
{
if (FpuExceptionInRecompiler())
{
COP1_S_CVT(CRegInfo::RoundNearest, CRegInfo::FPU_Double, CRegInfo::FPU_Dword);
}
else
{
CompileCop1Test();
if (m_RegWorkingSet.RegInStack(m_Opcode.fs, CRegInfo::FPU_Double) || m_RegWorkingSet.RegInStack(m_Opcode.fs, CRegInfo::FPU_Qword))
@ -8589,6 +8575,7 @@ void CX86RecompilerOps::COP1_D_ROUND_W()
}
m_RegWorkingSet.ChangeFPURegFormat(m_Opcode.fd, CRegInfo::FPU_Double, CRegInfo::FPU_Dword, CRegInfo::RoundNearest);
}
}
void CX86RecompilerOps::COP1_D_TRUNC_W()
{
@ -8612,6 +8599,12 @@ void CX86RecompilerOps::COP1_D_TRUNC_W()
}
void CX86RecompilerOps::COP1_D_CEIL_W()
{
if (FpuExceptionInRecompiler())
{
COP1_S_CVT(CRegInfo::RoundUp, CRegInfo::FPU_Double, CRegInfo::FPU_Dword);
}
else
{
CompileCop1Test();
if (m_RegWorkingSet.RegInStack(m_Opcode.fs, CRegInfo::FPU_Double) || m_RegWorkingSet.RegInStack(m_Opcode.fs, CRegInfo::FPU_Qword))
@ -8624,8 +8617,15 @@ void CX86RecompilerOps::COP1_D_CEIL_W()
}
m_RegWorkingSet.ChangeFPURegFormat(m_Opcode.fd, CRegInfo::FPU_Double, CRegInfo::FPU_Dword, CRegInfo::RoundUp);
}
}
void CX86RecompilerOps::COP1_D_FLOOR_W()
{
if (FpuExceptionInRecompiler())
{
COP1_S_CVT(CRegInfo::RoundDown, CRegInfo::FPU_Double, CRegInfo::FPU_Dword);
}
else
{
CompileCop1Test();
if (m_RegWorkingSet.RegInStack(m_Opcode.fs, CRegInfo::FPU_Double) || m_RegWorkingSet.RegInStack(m_Opcode.fs, CRegInfo::FPU_Qword))
@ -8638,8 +8638,15 @@ void CX86RecompilerOps::COP1_D_FLOOR_W()
}
m_RegWorkingSet.ChangeFPURegFormat(m_Opcode.fd, CRegInfo::FPU_Double, CRegInfo::FPU_Dword, CRegInfo::RoundDown);
}
}
void CX86RecompilerOps::COP1_D_CVT_S()
{
if (FpuExceptionInRecompiler())
{
COP1_S_CVT(CRegInfo::RoundDefault, CRegInfo::FPU_Double, CRegInfo::FPU_FloatLow);
}
else
{
CompileCop1Test();
if (m_RegWorkingSet.RegInStack(m_Opcode.fd, CRegInfo::FPU_Double) || m_RegWorkingSet.RegInStack(m_Opcode.fd, CRegInfo::FPU_Qword))
@ -8652,8 +8659,15 @@ void CX86RecompilerOps::COP1_D_CVT_S()
}
m_RegWorkingSet.ChangeFPURegFormat(m_Opcode.fd, CRegInfo::FPU_Double, CRegInfo::FPU_Float, CRegInfo::RoundDefault);
}
}
void CX86RecompilerOps::COP1_D_CVT_W()
{
if (FpuExceptionInRecompiler())
{
COP1_S_CVT(CRegInfo::RoundDefault, CRegInfo::FPU_Double, CRegInfo::FPU_Dword);
}
else
{
CompileCop1Test();
if (m_RegWorkingSet.RegInStack(m_Opcode.fs, CRegInfo::FPU_Double) || m_RegWorkingSet.RegInStack(m_Opcode.fs, CRegInfo::FPU_Qword))
@ -8666,6 +8680,7 @@ void CX86RecompilerOps::COP1_D_CVT_W()
}
m_RegWorkingSet.ChangeFPURegFormat(m_Opcode.fd, CRegInfo::FPU_Double, CRegInfo::FPU_Dword, CRegInfo::RoundDefault);
}
}
void CX86RecompilerOps::COP1_D_CVT_L()
{
@ -9138,6 +9153,12 @@ void CX86RecompilerOps::CompileCheckFPUResult64(asmjit::x86::Gp RegPointer)
m_Assembler.mov(RegPointerValue, RegPointer);
RegPointer = RegPointerValue;
}
bool RegPointerProtect = m_RegWorkingSet.GetX86Protected(GetIndexFromX86Reg(RegPointer));
if (!RegPointerProtect)
{
m_RegWorkingSet.SetX86Protected(GetIndexFromX86Reg(RegPointer), true);
}
asmjit::x86::Gp TempReg2 = m_RegWorkingSet.Map_TempReg(x86Reg_Unknown, -1, false, false);
m_Assembler.fnstsw(asmjit::x86::ax);
m_Assembler.and_(asmjit::x86::ax, 0x3D);
@ -9192,6 +9213,11 @@ void CX86RecompilerOps::CompileCheckFPUResult64(asmjit::x86::Gp RegPointer)
CompileExit(m_CompilePC + 4, m_CompilePC + 4, ExitRegSet, ExitReason_Normal, false, &CX86Ops::JmpLabel);
m_Assembler.bind(ValueSame);
m_Assembler.bind(DoNoModify);
if (!RegPointerProtect)
{
m_RegWorkingSet.SetX86Protected(GetIndexFromX86Reg(RegPointer), false);
}
}
void CX86RecompilerOps::CompileCop1Test()
@ -10872,6 +10898,26 @@ void CX86RecompilerOps::CompileStoreMemoryValue(asmjit::x86::Gp AddressReg, asmj
}
}
void CX86RecompilerOps::COP1_D_Opcode(void (CX86Ops::*Instruction)(void))
{
CompileInitFpuOperation(CRegInfo::RoundDefault);
if (m_RegWorkingSet.RegInStack(m_Opcode.fs, CRegInfo::FPU_Any) ||
m_RegWorkingSet.RegInStack(m_Opcode.fd, CRegInfo::FPU_Any))
{
g_Notify->BreakPoint(__FILE__, __LINE__);
return;
}
asmjit::x86::Gp TempReg = m_RegWorkingSet.FPRValuePointer(m_Opcode.fs, CRegInfo::FPU_Double);
CompileCheckFPUInput(TempReg, FpuOpSize_64bit);
m_RegWorkingSet.SetX86Protected(GetIndexFromX86Reg(TempReg), false);
m_RegWorkingSet.PrepareFPTopToBe(m_Opcode.fd, m_Opcode.fs, CRegInfo::FPU_Double);
(m_Assembler.*Instruction)();
m_Assembler.mov(TempReg, (uint64_t)&m_TempValue64);
m_Assembler.fpuStoreQwordFromX86Reg(m_RegWorkingSet.StackTopPos(), TempReg, false);
CompileCheckFPUResult64(TempReg);
m_RegWorkingSet.SetFPTopAs(m_Opcode.fd);
}
void CX86RecompilerOps::COP1_D_Opcode(void (CX86Ops::*Instruction)(const asmjit::x86::Mem &))
{
CompileInitFpuOperation(CRegInfo::RoundDefault);

View File

@ -270,6 +270,7 @@ private:
asmjit::x86::Gp BaseOffsetAddress(bool UseBaseRegister);
void CompileLoadMemoryValue(asmjit::x86::Gp & AddressReg, asmjit::x86::Gp ValueReg, const asmjit::x86::Gp & ValueRegHi, uint8_t ValueSize, bool SignExtend);
void CompileStoreMemoryValue(asmjit::x86::Gp AddressReg, asmjit::x86::Gp ValueReg, const asmjit::x86::Gp & ValueRegHi, uint64_t Value, uint8_t ValueSize);
void COP1_D_Opcode(void (CX86Ops::*Instruction)(void));
void COP1_D_Opcode(void (CX86Ops::*Instruction)(const asmjit::x86::Mem &));
void SB_Const(uint32_t Value, uint32_t Addr);

View File

@ -233,11 +233,21 @@ void CX86Ops::CompX86regToVariable(const asmjit::x86::Gp & Reg, void * Variable,
}
}
void CX86Ops::Fabs(void)
{
fabs();
}
void CX86Ops::Fadd(const asmjit::x86::Mem & Mem)
{
fadd(Mem);
}
void CX86Ops::Fchs(void)
{
fchs();
}
void CX86Ops::Fdiv(const asmjit::x86::Mem & Mem)
{
fdiv(Mem);
@ -248,6 +258,11 @@ void CX86Ops::Fmul(const asmjit::x86::Mem & Mem)
fmul(Mem);
}
void CX86Ops::Fsqrt(void)
{
fsqrt();
}
void CX86Ops::Fsub(const asmjit::x86::Mem & Mem)
{
fsub(Mem);

View File

@ -44,9 +44,12 @@ public:
void CompConstToVariable(void * Variable, const char * VariableName, uint32_t Const);
void CompConstToX86reg(const asmjit::x86::Gp & Reg, uint32_t Const);
void CompX86regToVariable(const asmjit::x86::Gp & Reg, void * Variable, const char * VariableName);
void Fabs(void);
void Fadd(const asmjit::x86::Mem & Mem);
void Fchs(void);
void Fdiv(const asmjit::x86::Mem & Mem);
void Fmul(const asmjit::x86::Mem & Mem);
void Fsqrt(void);
void Fsub(const asmjit::x86::Mem & Mem);
void JaeLabel(const char * LabelName, asmjit::Label & JumpLabel);
void JaLabel(const char * LabelName, asmjit::Label & JumpLabel);

View File

@ -35,9 +35,33 @@ bool CEditNumber32::IsHexConvertableText(LPTSTR _text)
}
// Check
unsigned int i = 0;
if (wcslen(_text) >= 2)
unsigned int len = wcslen(_text);
if (len == 0)
{
if (_text[0] == L'0' && (_text[1] == L'x' || _text[1] == L'X'))
return false;
}
wchar_t c;
do
{
c = _text[i];
if (c == L'\n' || c == L'\r' || c == L' ')
{
i++;
continue;
}
break;
} while (i < len);
if (i == len)
{
return false;
}
if ((len - i) >= 2)
{
if (_text[i] == L'0' && (_text[i + 1] == L'x' || _text[i + 1] == L'X'))
{
if ((second == L'x' || second == L'X') && (!(start == 0 && end >= 2)))
{
@ -53,14 +77,16 @@ bool CEditNumber32::IsHexConvertableText(LPTSTR _text)
}
}
}
if (!bPaste) return bPaste;
if (wcslen(_text) >= 1)
if ((len - i) >= 1)
{
if (head == L'0' && (_text[0] == L'x' || _text[0] == L'X'))
bool bIsX = _text[i] == L'x' || _text[i] == L'X';
if (head == L'0' && bIsX)
{
i++;
}
if ((_text[0] == L'x' || _text[0] == L'X'))
if (bIsX)
{
if (head != L'0' && start == 0)
{
@ -73,11 +99,28 @@ bool CEditNumber32::IsHexConvertableText(LPTSTR _text)
}
}
if (!bPaste) return bPaste;
for (; i < wcslen(_text); i++)
for (; i < len; i++)
{
wchar_t c = _text[i];
c = _text[i];
if (!(c >= 48 && c <= 57 || c >= L'A' && c <= L'F' || c >= L'a' && c <= L'f' || c == L' '))
{
if (c == L'\n' || c == L'\r')
{
i++;
while (i < len)
{
c = _text[i];
if (c != L'\n' && c != L'\r' && c != L' ')
{
bPaste = false;
break;
}
i++;
}
// Effectively a trim, if all we have is newline, just ignore them
break;
}
bPaste = false;
break;
}
@ -97,29 +140,43 @@ void CEditNumber32::FormatClipboard()
if (hglb != nullptr)
{
lptstr = (LPTSTR)GlobalLock(hglb);
for (unsigned int i = 0; i < wcslen(lptstr); i++)
unsigned int len = wcslen(lptstr);
unsigned int fullCopySize = 1; // Null terminator
for (unsigned int i = 0; i < len; i++)
{
if (lptstr[i] != L'X' && lptstr[i] != L'x')
wchar_t c = lptstr[i];
if (c != L' ' && c != L'\n' && c != L'\r')
{
lptstr[i] = (char)toupper(lptstr[i]);
}
if (lptstr[i] == L'X')
{
lptstr[i] = L'x';
}
if (lptstr[i] == ' ' && (i < wcslen(lptstr)))
{
wcscpy(&lptstr[i], &lptstr[i + 1]);
fullCopySize++;
}
}
hglb = GlobalAlloc(GMEM_MOVEABLE, (wcslen(lptstr) + 1) * sizeof(TCHAR));
hglb = GlobalAlloc(GMEM_MOVEABLE, fullCopySize * sizeof(TCHAR));
if (hglb == nullptr)
{
CloseClipboard();
return;
}
lptstrCopy = (LPTSTR)GlobalLock(hglb);
memcpy(lptstrCopy, lptstr, (wcslen(lptstr) + 1) * sizeof(TCHAR));
for (unsigned int src = 0, dst = 0; src < len; src++)
{
wchar_t c = lptstr[src];
if (c == L' ' || c == L'\n' || c == L'\r')
{
continue;
}
if (c == L'X' || c == L'x')
{
lptstrCopy[dst++] = L'x';
}
else
{
lptstrCopy[dst++] = (wchar_t)toupper(c);
}
}
GlobalUnlock(lptstr);
GlobalUnlock(hglb);
SetClipboardData(CF_UNICODETEXT, hglb);