From 43c6017ddbe03d05be5e997fadb024bcc7ea9c7c Mon Sep 17 00:00:00 2001 From: LegendOfDragoon Date: Sun, 11 Jan 2015 19:35:43 -0800 Subject: [PATCH 1/6] Improve Accuracy Fixing SW allows a lot more games to work. Fixing LW fixes games like WDC and improves performance somehow. Fixing VMRG fixes certain bugs, like Kirby 64's intro. --- Source/RSP/Recompiler Ops.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/Source/RSP/Recompiler Ops.c b/Source/RSP/Recompiler Ops.c index e45ecd7f3..e96b3c739 100644 --- a/Source/RSP/Recompiler Ops.c +++ b/Source/RSP/Recompiler Ops.c @@ -623,6 +623,7 @@ void Compile_LH ( void ) { MoveVariableToX86reg(&RSP_GPR[RSPOpC.base].UW, GPR_Name(RSPOpC.base), x86_EBX); if (Offset != 0) AddConstToX86Reg(x86_EBX, Offset); + AndConstToX86Reg(x86_EBX, 0x0fff); TestConstToX86Reg(1, x86_EBX); JneLabel32("Unaligned", 0); Jump[0] = RecompPos - 4; @@ -639,7 +640,6 @@ void Compile_LH ( void ) { CompilerToggleBuffer(); XorConstToX86Reg(x86_EBX, 2); - AndConstToX86Reg(x86_EBX, 0x0fff); MoveSxN64MemToX86regHalf(x86_EAX, x86_EBX); MoveX86regToVariable(x86_EAX, &RSP_GPR[RSPOpC.rt].UW, GPR_Name(RSPOpC.rt)); @@ -676,6 +676,7 @@ void Compile_LW ( void ) { MoveVariableToX86reg(&RSP_GPR[RSPOpC.base].UW, GPR_Name(RSPOpC.base), x86_EBX); if (Offset != 0) AddConstToX86Reg(x86_EBX, Offset); + AndConstToX86Reg(x86_EBX, 0x0fff); TestConstToX86Reg(3, x86_EBX); JneLabel32("UnAligned", 0); Jump[0] = RecompPos - 4; @@ -707,7 +708,6 @@ void Compile_LW ( void ) { Jump[1] = RecompPos - 4; CompilerToggleBuffer(); - AndConstToX86Reg(x86_EBX, 0x0fff); MoveN64MemToX86reg(x86_EAX, x86_EBX); MoveX86regToVariable(x86_EAX, &RSP_GPR[RSPOpC.rt].UW, GPR_Name(RSPOpC.rt)); @@ -893,6 +893,7 @@ void Compile_SW ( void ) { MoveVariableToX86reg(&RSP_GPR[RSPOpC.base].UW, GPR_Name(RSPOpC.base), x86_EBX); if (Offset != 0) AddConstToX86Reg(x86_EBX, Offset); + AndConstToX86Reg(x86_EBX, 0x0fff); TestConstToX86Reg(3, x86_EBX); JneLabel32("Unaligned", 0); Jump[0] = RecompPos - 4; @@ -931,7 +932,6 @@ void Compile_SW ( void ) { CompilerToggleBuffer(); - AndConstToX86Reg(x86_EBX, 0x0fff); if (RSPOpC.rt == 0) { XorX86RegToX86Reg(x86_EAX,x86_EAX); } else { @@ -3615,6 +3615,7 @@ void Compile_Vector_VCR ( void ) { void Compile_Vector_VMRG ( void ) { char Reg[256]; int count, el, del; + BOOL bWriteToAccum; = WriteToAccum(Low16BitAccum, CompilePC); #ifndef CompileVmrg Cheat_r4300iOpcode(RSP_Vector_VMRG,"RSP_Vector_VMRG"); return; @@ -3637,6 +3638,10 @@ void Compile_Vector_VMRG ( void ) { CondMoveNotEqual(x86_ECX, x86_EAX); CondMoveEqual(x86_ECX, x86_EBX); + if (bWriteToAccum == TRUE) { + sprintf(Reg, "RSP_ACCUM[%i].HW[1]", el); + MoveX86regHalfToVariable(x86_ECX, &RSP_ACCUM[el].HW[1], Reg); + } sprintf(Reg, "RSP_Vect[%i].HW[%i]", RSPOpC.sa, el); MoveX86regHalfToVariable(x86_ECX, &RSP_Vect[RSPOpC.sa].HW[el], Reg); } @@ -3815,7 +3820,7 @@ void Compile_Vector_VOR ( void ) { } if (bWriteToAccum == TRUE) { - sprintf(Reg, "RSP_ACCUM[el].HW[1]", el); + sprintf(Reg, "RSP_ACCUM[%i].HW[1]", el); MoveX86regHalfToVariable(x86_EAX, &RSP_ACCUM[el].HW[1], Reg); } sprintf(Reg, "RSP_Vect[%i].HW[%i]", RSPOpC.sa, el); From 02fe2e7241a70577c5f43f91f84770ff8acea425 Mon Sep 17 00:00:00 2001 From: LegendOfDragoon Date: Sun, 11 Jan 2015 19:49:04 -0800 Subject: [PATCH 2/6] More Fixes The possibility of the RSP instruction at address 0xFFC being JR was never accounted for. This fixes Wave Race and Star Wars - Shadow of the Empire. The accumulator analysis was wrong, which broke games like Mario No Photopie. --- Source/RSP/Recompiler Analysis.c | 11 ++++++++--- Source/RSP/Recompiler CPU.c | 2 +- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/Source/RSP/Recompiler Analysis.c b/Source/RSP/Recompiler Analysis.c index afe16e509..a9ee4bc56 100644 --- a/Source/RSP/Recompiler Analysis.c +++ b/Source/RSP/Recompiler Analysis.c @@ -247,20 +247,25 @@ DWORD WriteToAccum2 (int Location, int PC, BOOL RecursiveCall) { case RSP_VECTOR_VMADL: case RSP_VECTOR_VMADM: case RSP_VECTOR_VMADN: - case RSP_VECTOR_VMADH: return TRUE; + case RSP_VECTOR_VMADH: + if (Location == Low16BitAccum) { break; } + return TRUE; + case RSP_VECTOR_VABS: case RSP_VECTOR_VADD: case RSP_VECTOR_VADDC: case RSP_VECTOR_VSUB: case RSP_VECTOR_VSUBC: case RSP_VECTOR_VAND: + case RSP_VECTOR_VNAND: case RSP_VECTOR_VOR: + case RSP_VECTOR_VNOR: case RSP_VECTOR_VXOR: case RSP_VECTOR_VNXOR: /* since these modify the accumulator lower-16 bits we can */ /* safely assume these 'reset' the accumulator no matter what */ - return FALSE; + // return FALSE; case RSP_VECTOR_VCR: case RSP_VECTOR_VCH: case RSP_VECTOR_VCL: @@ -272,11 +277,11 @@ DWORD WriteToAccum2 (int Location, int PC, BOOL RecursiveCall) { case RSP_VECTOR_VLT: case RSP_VECTOR_VEQ: case RSP_VECTOR_VGE: + case RSP_VECTOR_VMRG: if (Location == Low16BitAccum) { return FALSE; } break; case RSP_VECTOR_VMOV: - case RSP_VECTOR_VMRG: break; case RSP_VECTOR_VSAW: return TRUE; diff --git a/Source/RSP/Recompiler CPU.c b/Source/RSP/Recompiler CPU.c index f949ec5f6..a8e7271e7 100644 --- a/Source/RSP/Recompiler CPU.c +++ b/Source/RSP/Recompiler CPU.c @@ -878,7 +878,7 @@ void CompilerRSPBlock ( void ) { CompilePC += 4; break; } - } while ( NextInstruction != FINISH_BLOCK && CompilePC < 0x1000); + } while (NextInstruction != FINISH_BLOCK && (CompilePC < 0x1000 || NextInstruction == DELAY_SLOT)); CPU_Message("==== end of recompiled code ===="); if (Compiler.bReOrdering == TRUE) { From 1cb03b41203506e7186cc5414756afea2c825e52 Mon Sep 17 00:00:00 2001 From: LegendOfDragoon Date: Mon, 12 Jan 2015 11:43:07 -0800 Subject: [PATCH 3/6] Matched RSP interpreter more closely I have only seen the edgecase for SQV happen when IsRegConst(RSPOpC.base) == TRUE. Changing SQV fixed Telefoot / Mia Ham. I optimized VAND, for games like Kirby64. --- Source/RSP/Recompiler Analysis.c | 3 +- Source/RSP/Recompiler Ops.c | 135 +++++++++++++++++++------------ 2 files changed, 85 insertions(+), 53 deletions(-) diff --git a/Source/RSP/Recompiler Analysis.c b/Source/RSP/Recompiler Analysis.c index a9ee4bc56..22bde236e 100644 --- a/Source/RSP/Recompiler Analysis.c +++ b/Source/RSP/Recompiler Analysis.c @@ -278,11 +278,10 @@ DWORD WriteToAccum2 (int Location, int PC, BOOL RecursiveCall) { case RSP_VECTOR_VEQ: case RSP_VECTOR_VGE: case RSP_VECTOR_VMRG: + case RSP_VECTOR_VMOV: if (Location == Low16BitAccum) { return FALSE; } break; - case RSP_VECTOR_VMOV: - break; case RSP_VECTOR_VSAW: return TRUE; default: diff --git a/Source/RSP/Recompiler Ops.c b/Source/RSP/Recompiler Ops.c index e96b3c739..93515246a 100644 --- a/Source/RSP/Recompiler Ops.c +++ b/Source/RSP/Recompiler Ops.c @@ -3134,10 +3134,11 @@ void Compile_Vector_VADD ( void ) { AdcX86RegToX86Reg(x86_EAX, x86_EBX); - if (bWriteToAccum == TRUE) { - MoveX86regHalfToVariable(x86_EAX, &RSP_ACCUM[el].HW[1], "RSP_ACCUM[el].HW[1]"); + if (bWriteToAccum != FALSE) { + sprintf(Reg, "RSP_ACCUM[%i].HW[1]", el); + MoveX86regHalfToVariable(x86_EAX, &RSP_ACCUM[el].HW[1], Reg); } - if (bWriteToDest == TRUE) { + if (bWriteToDest != FALSE) { CompX86RegToX86Reg(x86_EAX, x86_ESI); CondMoveGreater(x86_EAX, x86_ESI); CompX86RegToX86Reg(x86_EAX, x86_EDI); @@ -3248,10 +3249,12 @@ void Compile_Vector_VSUB ( void ) { SbbX86RegToX86Reg(x86_EAX, x86_EBX); - if (bWriteToAccum == TRUE) { - MoveX86regHalfToVariable(x86_EAX, &RSP_ACCUM[el].HW[1], "RSP_ACCUM[el].HW[1]"); + if (bWriteToAccum != FALSE) { + sprintf(Reg, "RSP_ACCUM[%i].HW[1]", el); + MoveX86regHalfToVariable(x86_EAX, &RSP_ACCUM[el].HW[1], Reg); } - if (bWriteToDest == TRUE) { + + if (bWriteToDest != FALSE) { CompX86RegToX86Reg(x86_EAX, x86_ESI); CondMoveGreater(x86_EAX, x86_ESI); CompX86RegToX86Reg(x86_EAX, x86_EDI); @@ -3408,10 +3411,12 @@ void Compile_Vector_VADDC ( void ) { } OrX86RegToX86Reg(x86_ECX, x86_EDX); - if (bWriteToAccum == TRUE) { - MoveX86regHalfToVariable(x86_EAX, &RSP_ACCUM[el].HW[1], "RSP_ACCUM[el].HW[1]"); + if (bWriteToAccum != FALSE) { + sprintf(Reg, "RSP_ACCUM[%i].HW[1]", el); + MoveX86regHalfToVariable(x86_EAX, &RSP_ACCUM[el].HW[1], Reg); } - if (bWriteToDest == TRUE) { + + if (bWriteToDest != FALSE) { sprintf(Reg, "RSP_Vect[%i].HW[%i]", RSPOpC.sa, el); MoveX86regHalfToVariable(x86_EAX, &RSP_Vect[RSPOpC.sa].HW[el], Reg); } @@ -3470,10 +3475,11 @@ void Compile_Vector_VSUBC ( void ) { ShiftLeftSignImmed(x86_EDX, 7 - el); OrX86RegToX86Reg(x86_ECX, x86_EDX); - if (bWriteToAccum == TRUE) { - MoveX86regHalfToVariable(x86_EAX, &RSP_ACCUM[el].HW[1], "RSP_ACCUM[el].HW[1]"); + if (bWriteToAccum != FALSE) { + sprintf(Reg, "RSP_ACCUM[%i].HW[1]", el); + MoveX86regHalfToVariable(x86_EAX, &RSP_ACCUM[el].HW[1], Reg); } - if (bWriteToDest == TRUE) { + if (bWriteToDest != FALSE) { sprintf(Reg, "RSP_Vect[%i].HW[%i]", RSPOpC.sa, el); MoveX86regHalfToVariable(x86_EAX, &RSP_Vect[RSPOpC.sa].HW[el], Reg); } @@ -3690,6 +3696,7 @@ BOOL Compile_Vector_VAND_MMX ( void ) { void Compile_Vector_VAND ( void ) { char Reg[256]; int el, del, count; + BOOL bWriteToDest = WriteToVectorDest(RSPOpC.sa, CompilePC); BOOL bElement = ((RSPOpC.rs & 0x0f) >= 8) ? TRUE : FALSE; BOOL bWriteToAccum = WriteToAccum(Low16BitAccum, CompilePC); @@ -3726,11 +3733,13 @@ void Compile_Vector_VAND ( void ) { AndX86RegHalfToX86RegHalf(x86_EAX, x86_EBX); } - sprintf(Reg, "RSP_Vect[%i].HW[%i]", RSPOpC.sa, el); - MoveX86regHalfToVariable(x86_EAX, &RSP_Vect[RSPOpC.sa].HW[el], Reg); + if (bWriteToDest != FALSE) { + sprintf(Reg, "RSP_Vect[%i].HW[%i]", RSPOpC.sa, el); + MoveX86regHalfToVariable(x86_EAX, &RSP_Vect[RSPOpC.sa].HW[el], Reg); + } if (bWriteToAccum != FALSE) { - sprintf(Reg, "RSP_ACCUM[el].HW[1]", el); + sprintf(Reg, "RSP_ACCUM[%i].HW[1]", el); MoveX86regHalfToVariable(x86_EAX, &RSP_ACCUM[el].HW[1], Reg); } } @@ -3964,13 +3973,22 @@ void Compile_Vector_VRCPH ( void ) { void Compile_Vector_VMOV ( void ) { char Reg[256]; - int el; + int el, count; + BOOL bWriteToAccum = WriteToAccum(Low16BitAccum, CompilePC); +#ifndef CompileVmov + Cheat_r4300iOpcode(RSP_Vector_VMOV, "RSP_Vector_VMOV"); return; +#endif - #ifndef CompileVmov - Cheat_r4300iOpcode(RSP_Vector_VMOV,"RSP_Vector_VMOV"); return; - #endif + CPU_Message(" %X %s", CompilePC, RSPOpcodeName(RSPOpC.Hex, CompilePC)); - CPU_Message(" %X %s",CompilePC,RSPOpcodeName(RSPOpC.Hex,CompilePC)); + if (bWriteToAccum){ + for (count = 0; count < 8; count++) { + sprintf(Reg, "RSP_Vect[%i].UHW[%i]", RSPOpC.rt, EleSpec[RSPOpC.rs].B[count]); + MoveVariableToX86regHalf(&RSP_Vect[RSPOpC.rt].UHW[EleSpec[RSPOpC.rs].B[count]], Reg, x86_EAX); + sprintf(Reg, "RSP_ACCUM[%i].HW[1]", count); + MoveX86regHalfToVariable(x86_EAX, &RSP_ACCUM[count].HW[1], Reg); + } + } el = EleSpec[RSPOpC.rs].B[(RSPOpC.rd & 0x7)]; sprintf(Reg, "RSP_Vect[%i].UHW[%i]", RSPOpC.rt, el); @@ -4585,32 +4603,32 @@ void Compile_Opcode_SLV ( void ) { void Compile_Opcode_SDV ( void ) { char Reg[256]; int offset = (RSPOpC.voffset << 3); - BYTE * Jump[2], * LoopEntry; + BYTE * Jump[2], *LoopEntry; //if ((RSPOpC.del & 0x7) != 0) { // rsp_UnknownOpcode(); // return; //} - #ifndef CompileSdv - Cheat_r4300iOpcode(RSP_Opcode_SDV,"RSP_Opcode_SDV"); return; - #endif - - CPU_Message(" %X %s",CompilePC,RSPOpcodeName(RSPOpC.Hex,CompilePC)); +#ifndef CompileSdv + Cheat_r4300iOpcode(RSP_Opcode_SDV, "RSP_Opcode_SDV"); return; +#endif + + CPU_Message(" %X %s", CompilePC, RSPOpcodeName(RSPOpC.Hex, CompilePC)); if (IsRegConst(RSPOpC.base) == TRUE) { DWORD Addr = (MipsRegConst(RSPOpC.base) + offset) & 0xfff; if ((Addr & 3) != 0) { CompilerWarning("Unaligned SDV at constant address PC = %04X", CompilePC); - Cheat_r4300iOpcodeNoMessage(RSP_Opcode_SDV,"RSP_Opcode_SDV"); + Cheat_r4300iOpcodeNoMessage(RSP_Opcode_SDV, "RSP_Opcode_SDV"); return; } - sprintf(Reg, "RSP_Vect[%i].B[%i]", RSPOpC.rt, 16 - RSPOpC.del - 4); - MoveVariableToX86reg(&RSP_Vect[RSPOpC.rt].B[16 - RSPOpC.del - 4], Reg, x86_EAX); - sprintf(Reg, "RSP_Vect[%i].B[%i]", RSPOpC.rt, 16 - RSPOpC.del - 8); - MoveVariableToX86reg(&RSP_Vect[RSPOpC.rt].B[16 - RSPOpC.del - 8], Reg, x86_EBX); + sprintf(Reg, "RSP_Vect[%i].B[%i]", RSPOpC.rt, (16 - RSPOpC.del - 4) & 0xF); + MoveVariableToX86reg(&RSP_Vect[RSPOpC.rt].B[(16 - RSPOpC.del - 4) & 0xF], Reg, x86_EAX); + sprintf(Reg, "RSP_Vect[%i].B[%i]", RSPOpC.rt, (16 - RSPOpC.del - 8) & 0xF); + MoveVariableToX86reg(&RSP_Vect[RSPOpC.rt].B[(16 - RSPOpC.del - 8) & 0xF], Reg, x86_EBX); sprintf(Reg, "Dmem + %Xh", Addr); MoveX86regToVariable(x86_EAX, RSPInfo.DMEM + Addr, Reg); @@ -4618,7 +4636,7 @@ void Compile_Opcode_SDV ( void ) { MoveX86regToVariable(x86_EBX, RSPInfo.DMEM + Addr + 4, Reg); return; } - + MoveVariableToX86reg(&RSP_GPR[RSPOpC.base].UW, GPR_Name(RSPOpC.base), x86_EBX); if (offset != 0) { AddConstToX86Reg(x86_EBX, offset); @@ -4627,11 +4645,11 @@ void Compile_Opcode_SDV ( void ) { TestConstToX86Reg(3, x86_EBX); JneLabel32("Unaligned", 0); Jump[0] = RecompPos - 4; - + CompilerToggleBuffer(); CPU_Message(" Unaligned:"); x86_SetBranch32b((DWORD*)Jump[0], (DWORD*)RecompPos); - + sprintf(Reg, "RSP_Vect[%i].UB[%i]", RSPOpC.rt, 15 - RSPOpC.del); MoveOffsetToX86reg((DWORD)&RSP_Vect[RSPOpC.rt].UB[15 - RSPOpC.del], Reg, x86_EDI); MoveConstToX86reg(8, x86_ECX); @@ -4652,10 +4670,10 @@ void Compile_Opcode_SDV ( void ) { Jump[1] = RecompPos - 4; CompilerToggleBuffer(); - sprintf(Reg, "RSP_Vect[%i].B[%i]", RSPOpC.rt, 16 - RSPOpC.del - 4); - MoveVariableToX86reg(&RSP_Vect[RSPOpC.rt].B[16 - RSPOpC.del - 4], Reg, x86_EAX); - sprintf(Reg, "RSP_Vect[%i].B[%i]", RSPOpC.rt, 16 - RSPOpC.del - 8); - MoveVariableToX86reg(&RSP_Vect[RSPOpC.rt].B[16 - RSPOpC.del - 8], Reg, x86_ECX); + sprintf(Reg, "RSP_Vect[%i].B[%i]", RSPOpC.rt, (16 - RSPOpC.del - 4) & 0xF); + MoveVariableToX86reg(&RSP_Vect[RSPOpC.rt].B[(16 - RSPOpC.del - 4) & 0xF], Reg, x86_EAX); + sprintf(Reg, "RSP_Vect[%i].B[%i]", RSPOpC.rt, (16 - RSPOpC.del - 8) & 0xF); + MoveVariableToX86reg(&RSP_Vect[RSPOpC.rt].B[(16 - RSPOpC.del - 8) & 0xF], Reg, x86_ECX); MoveX86regToN64Mem(x86_EAX, x86_EBX); MoveX86regToN64MemDisp(x86_ECX, x86_EBX, 4); @@ -4674,10 +4692,10 @@ void Compile_Opcode_SQV ( void ) { CPU_Message(" %X %s",CompilePC,RSPOpcodeName(RSPOpC.Hex,CompilePC)); - if (RSPOpC.del != 0) { - rsp_UnknownOpcode(); - return; - } + //if (RSPOpC.del != 0) { + // rsp_UnknownOpcode(); + // return; + //} if (IsRegConst(RSPOpC.base) == TRUE) { DWORD Addr = (MipsRegConst(RSPOpC.base) + offset) & 0xfff; @@ -4693,14 +4711,25 @@ void Compile_Opcode_SQV ( void ) { */ if (IsSseEnabled == FALSE) { - sprintf(Reg, "RSP_Vect[%i].B[12]", RSPOpC.rt); - MoveVariableToX86reg(&RSP_Vect[RSPOpC.rt].B[12], Reg, x86_EAX); - sprintf(Reg, "RSP_Vect[%i].B[8]", RSPOpC.rt); - MoveVariableToX86reg(&RSP_Vect[RSPOpC.rt].B[8], Reg, x86_EBX); - sprintf(Reg, "RSP_Vect[%i].B[4]", RSPOpC.rt); - MoveVariableToX86reg(&RSP_Vect[RSPOpC.rt].B[4], Reg, x86_ECX); - sprintf(Reg, "RSP_Vect[%i].B[0]", RSPOpC.rt); - MoveVariableToX86reg(&RSP_Vect[RSPOpC.rt].B[0], Reg, x86_EDX); + if (RSPOpC.del == 12) { + sprintf(Reg, "RSP_Vect[%i].B[0]", RSPOpC.rt); + MoveVariableToX86reg(&RSP_Vect[RSPOpC.rt].B[0], Reg, x86_EDX); + sprintf(Reg, "RSP_Vect[%i].B[12]", RSPOpC.rt); + MoveVariableToX86reg(&RSP_Vect[RSPOpC.rt].B[12], Reg, x86_EAX); + sprintf(Reg, "RSP_Vect[%i].B[8]", RSPOpC.rt); + MoveVariableToX86reg(&RSP_Vect[RSPOpC.rt].B[8], Reg, x86_EBX); + sprintf(Reg, "RSP_Vect[%i].B[4]", RSPOpC.rt); + MoveVariableToX86reg(&RSP_Vect[RSPOpC.rt].B[4], Reg, x86_ECX); + } else { + sprintf(Reg, "RSP_Vect[%i].B[12]", RSPOpC.rt); + MoveVariableToX86reg(&RSP_Vect[RSPOpC.rt].B[12], Reg, x86_EAX); + sprintf(Reg, "RSP_Vect[%i].B[8]", RSPOpC.rt); + MoveVariableToX86reg(&RSP_Vect[RSPOpC.rt].B[8], Reg, x86_EBX); + sprintf(Reg, "RSP_Vect[%i].B[4]", RSPOpC.rt); + MoveVariableToX86reg(&RSP_Vect[RSPOpC.rt].B[4], Reg, x86_ECX); + sprintf(Reg, "RSP_Vect[%i].B[0]", RSPOpC.rt); + MoveVariableToX86reg(&RSP_Vect[RSPOpC.rt].B[0], Reg, x86_EDX); + } sprintf(Reg, "Dmem+%Xh+0", Addr); MoveX86regToVariable(x86_EAX, RSPInfo.DMEM + Addr + 0, Reg); @@ -4713,7 +4742,11 @@ void Compile_Opcode_SQV ( void ) { } else { sprintf(Reg, "RSP_Vect[%i].B[0]", RSPOpC.rt); SseMoveAlignedVariableToReg(&RSP_Vect[RSPOpC.rt].B[0], Reg, x86_XMM0); - SseShuffleReg(x86_XMM0, x86_MM0, 0x1b); + if (RSPOpC.del == 12) { + SseShuffleReg(x86_XMM0, x86_MM0, 0x6c); + } else { + SseShuffleReg(x86_XMM0, x86_MM0, 0x1b); + } sprintf(Reg, "Dmem+%Xh", Addr); SseMoveUnalignedRegToVariable(x86_XMM0, RSPInfo.DMEM + Addr, Reg); } From 5df4a806b42ea0f2329522a84ea12f2ecc6769d8 Mon Sep 17 00:00:00 2001 From: LegendOfDragoon Date: Mon, 12 Jan 2015 14:19:41 -0800 Subject: [PATCH 4/6] Optimized a few instructions Optimized VOR for games like Perfect Dark Implemented VNAND, VNOR, and VNXOR for recompiler Minor change in SQV --- Source/RSP/Mmx.c | 29 +++ Source/RSP/Recompiler Analysis.c | 3 + Source/RSP/Recompiler Ops.c | 331 ++++++++++++++++++++++++++++--- Source/RSP/X86.c | 16 ++ Source/RSP/X86.h | 2 + 5 files changed, 348 insertions(+), 33 deletions(-) diff --git a/Source/RSP/Mmx.c b/Source/RSP/Mmx.c index 9b695987e..8f09ae7c3 100644 --- a/Source/RSP/Mmx.c +++ b/Source/RSP/Mmx.c @@ -300,6 +300,35 @@ void MmxShuffleMemoryToReg(int Dest, void * Variable, char * VariableName, BYTE PUTDST8(RecompPos, Immed); } +void MmxPcmpeqwRegToReg(int Dest, int Source){ + BYTE x86Command; + + CPU_Message(" pcmpeqw %s, %s", mmx_Name(Dest), mmx_Name(Source)); + + switch (Dest) { + case x86_MM0: x86Command = 0 << 3; break; + case x86_MM1: x86Command = 1 << 3; break; + case x86_MM2: x86Command = 2 << 3; break; + case x86_MM3: x86Command = 3 << 3; break; + case x86_MM4: x86Command = 4 << 3; break; + case x86_MM5: x86Command = 5 << 3; break; + case x86_MM6: x86Command = 6 << 3; break; + case x86_MM7: x86Command = 7 << 3; break; + } + switch (Source) { + case x86_MM0: x86Command |= 0; break; + case x86_MM1: x86Command |= 1; break; + case x86_MM2: x86Command |= 2; break; + case x86_MM3: x86Command |= 3; break; + case x86_MM4: x86Command |= 4; break; + case x86_MM5: x86Command |= 5; break; + case x86_MM6: x86Command |= 6; break; + case x86_MM7: x86Command |= 7; break; + } + PUTDST16(RecompPos, 0x750f); + PUTDST8(RecompPos, 0xC0 | x86Command); +} + void MmxPmullwRegToReg(int Dest, int Source) { BYTE x86Command; diff --git a/Source/RSP/Recompiler Analysis.c b/Source/RSP/Recompiler Analysis.c index 22bde236e..ce87d1c16 100644 --- a/Source/RSP/Recompiler Analysis.c +++ b/Source/RSP/Recompiler Analysis.c @@ -89,6 +89,9 @@ BOOL IsNextInstructionMmx(DWORD PC) { case RSP_VECTOR_VAND: case RSP_VECTOR_VOR: case RSP_VECTOR_VXOR: + case RSP_VECTOR_VNAND: + case RSP_VECTOR_VNOR: + case RSP_VECTOR_VNXOR: if (TRUE == WriteToAccum(Low16BitAccum, PC)) { return FALSE; } else if ((RspOp.rs & 0x0f) >= 2 && (RspOp.rs & 0x0f) <= 7 && IsMmx2Enabled == FALSE) { diff --git a/Source/RSP/Recompiler Ops.c b/Source/RSP/Recompiler Ops.c index 93515246a..d14ce327c 100644 --- a/Source/RSP/Recompiler Ops.c +++ b/Source/RSP/Recompiler Ops.c @@ -84,6 +84,9 @@ DWORD BranchCompare = 0; # define CompileVaddc # define CompileVsubc # define CompileVmrg +# define CompileVnxor +# define CompileVnor +# define CompileVnand #endif #ifdef RSP_VectorLoads # define CompileSqv /* Verified 12/17/2000 - Jabo */ @@ -3552,7 +3555,7 @@ void Compile_Vector_VNE ( void ) { Cheat_r4300iOpcode(RSP_Vector_VNE,"RSP_Vector_VNE"); } -BOOL Compile_Vector_VGE_MMX(void) { +BOOL Compile_Vector_VGE_MMX ( void ) { char Reg[256]; if ((RSPOpC.rs & 0xF) >= 2 && (RSPOpC.rs & 0xF) <= 7 && IsMmx2Enabled == FALSE) @@ -3745,8 +3748,101 @@ void Compile_Vector_VAND ( void ) { } } +BOOL Compile_Vector_VNAND_MMX ( void ) { + char Reg[256]; + + /* Do our MMX checks here */ + if (IsMmxEnabled == FALSE) + return FALSE; + if ((RSPOpC.rs & 0x0f) >= 2 && (RSPOpC.rs & 0x0f) <= 7 && IsMmx2Enabled == FALSE) + return FALSE; + + sprintf(Reg, "RSP_Vect[%i].UHW[0]", RSPOpC.rd); + MmxMoveQwordVariableToReg(x86_MM0, &RSP_Vect[RSPOpC.rd].UHW[0], Reg); + sprintf(Reg, "RSP_Vect[%i].UHW[4]", RSPOpC.rd); + MmxMoveQwordVariableToReg(x86_MM1, &RSP_Vect[RSPOpC.rd].UHW[4], Reg); + MmxPcmpeqwRegToReg(x86_MM7, x86_MM7); + + if ((RSPOpC.rs & 0xF) >= 8) { + RSP_Element2Mmx(x86_MM2); + MmxPandRegToReg(x86_MM0, x86_MM2); + MmxPandRegToReg(x86_MM1, x86_MM2); + } else if ((RSPOpC.rs & 0xF) < 2) { + sprintf(Reg, "RSP_Vect[%i].HW[0]", RSPOpC.rt); + MmxPandVariableToReg(&RSP_Vect[RSPOpC.rt].HW[0], Reg, x86_MM0); + sprintf(Reg, "RSP_Vect[%i].HW[4]", RSPOpC.rt); + MmxPandVariableToReg(&RSP_Vect[RSPOpC.rt].HW[4], Reg, x86_MM1); + } else { + RSP_MultiElement2Mmx(x86_MM2, x86_MM3); + MmxPandRegToReg(x86_MM0, x86_MM2); + MmxPandRegToReg(x86_MM1, x86_MM3); + } + + MmxXorRegToReg(x86_MM0, x86_MM7); + MmxXorRegToReg(x86_MM1, x86_MM7); + sprintf(Reg, "RSP_Vect[%i].UHW[0]", RSPOpC.sa); + MmxMoveQwordRegToVariable(x86_MM0, &RSP_Vect[RSPOpC.sa].UHW[0], Reg); + sprintf(Reg, "RSP_Vect[%i].UHW[4]", RSPOpC.sa); + MmxMoveQwordRegToVariable(x86_MM1, &RSP_Vect[RSPOpC.sa].UHW[4], Reg); + + if (IsNextInstructionMmx(CompilePC) == FALSE) + MmxEmptyMultimediaState(); + + return TRUE; +} + void Compile_Vector_VNAND ( void ) { - Cheat_r4300iOpcode(RSP_Vector_VNAND,"RSP_Vector_VNAND"); + char Reg[256]; + int el, del, count; + BOOL bWriteToDest = WriteToVectorDest(RSPOpC.sa, CompilePC); + BOOL bElement = ((RSPOpC.rs & 0x0f) >= 8) ? TRUE : FALSE; + BOOL bWriteToAccum = WriteToAccum(Low16BitAccum, CompilePC); + +#ifndef CompileVnand + Cheat_r4300iOpcode(RSP_Vector_VNAND, "RSP_Vector_VNAND"); return; +#endif + + CPU_Message(" %X %s", CompilePC, RSPOpcodeName(RSPOpC.Hex, CompilePC)); + + if (bWriteToAccum == FALSE) { + if (TRUE == Compile_Vector_VNAND_MMX()) + return; + } + + if (bElement == TRUE) { + del = (RSPOpC.rs & 0x07) ^ 7; + sprintf(Reg, "RSP_Vect[%i].HW[%i]", RSPOpC.rt, del); + MoveVariableToX86regHalf(&RSP_Vect[RSPOpC.rt].HW[del], Reg, x86_EBX); + } + + for (count = 0; count < 8; count++) { + el = Indx[RSPOpC.rs].B[count]; + del = EleSpec[RSPOpC.rs].B[el]; + + CPU_Message(" Iteration: %i", count); + + sprintf(Reg, "RSP_Vect[%i].HW[%i]", RSPOpC.rd, el); + MoveVariableToX86regHalf(&RSP_Vect[RSPOpC.rd].HW[el], Reg, x86_EAX); + + if (bElement == FALSE) { + sprintf(Reg, "RSP_Vect[%i].HW[%i]", RSPOpC.rt, del); + AndVariableToX86regHalf(&RSP_Vect[RSPOpC.rt].HW[del], Reg, x86_EAX); + } else { + AndX86RegHalfToX86RegHalf(x86_EAX, x86_EBX); + } + + NotX86reg(x86_EAX); + + if (bWriteToDest != FALSE) { + sprintf(Reg, "RSP_Vect[%i].HW[%i]", RSPOpC.sa, el); + MoveX86regHalfToVariable(x86_EAX, &RSP_Vect[RSPOpC.sa].HW[el], Reg); + } + + if (bWriteToAccum != FALSE) { + sprintf(Reg, "RSP_ACCUM[%i].HW[1]", el); + MoveX86regHalfToVariable(x86_EAX, &RSP_ACCUM[el].HW[1], Reg); + } + } } BOOL Compile_Vector_VOR_MMX ( void ) { @@ -3763,7 +3859,9 @@ BOOL Compile_Vector_VOR_MMX ( void ) { sprintf(Reg, "RSP_Vect[%i].UHW[4]", RSPOpC.rd); MmxMoveQwordVariableToReg(x86_MM1, &RSP_Vect[RSPOpC.rd].UHW[4], Reg); - if ((RSPOpC.rs & 0xF) >= 8) { + if ((RSPOpC.rs & 0xF) < 2 && (RSPOpC.rd == RSPOpC.rt)) { + + } else if ((RSPOpC.rs & 0xF) >= 8) { RSP_Element2Mmx(x86_MM2); MmxPorRegToReg(x86_MM0, x86_MM2); MmxPorRegToReg(x86_MM1, x86_MM2); @@ -3795,11 +3893,11 @@ void Compile_Vector_VOR ( void ) { BOOL bElement = ((RSPOpC.rs & 0x0f) >= 8) ? TRUE : FALSE; BOOL bWriteToAccum = WriteToAccum(Low16BitAccum, CompilePC); - #ifndef CompileVor - Cheat_r4300iOpcode(RSP_Vector_VOR,"RSP_Vector_VOR"); return; - #endif +#ifndef CompileVor + Cheat_r4300iOpcode(RSP_Vector_VOR, "RSP_Vector_VOR"); return; +#endif - CPU_Message(" %X %s",CompilePC,RSPOpcodeName(RSPOpC.Hex,CompilePC)); + CPU_Message(" %X %s", CompilePC, RSPOpcodeName(RSPOpC.Hex, CompilePC)); if (bWriteToAccum == FALSE) { if (TRUE == Compile_Vector_VOR_MMX()) @@ -3820,7 +3918,7 @@ void Compile_Vector_VOR ( void ) { sprintf(Reg, "RSP_Vect[%i].HW[%i]", RSPOpC.rd, el); MoveVariableToX86regHalf(&RSP_Vect[RSPOpC.rd].HW[el], Reg, x86_EAX); - + if (bElement == FALSE) { sprintf(Reg, "RSP_Vect[%i].HW[%i]", RSPOpC.rt, del); OrVariableToX86regHalf(&RSP_Vect[RSPOpC.rt].HW[del], Reg, x86_EAX); @@ -3828,7 +3926,7 @@ void Compile_Vector_VOR ( void ) { OrX86RegToX86Reg(x86_EAX, x86_EBX); } - if (bWriteToAccum == TRUE) { + if (bWriteToAccum != FALSE) { sprintf(Reg, "RSP_ACCUM[%i].HW[1]", el); MoveX86regHalfToVariable(x86_EAX, &RSP_ACCUM[el].HW[1], Reg); } @@ -3837,8 +3935,97 @@ void Compile_Vector_VOR ( void ) { } } +BOOL Compile_Vector_VNOR_MMX ( void ) { + char Reg[256]; + + /* Do our MMX checks here */ + if (IsMmxEnabled == FALSE) + return FALSE; + if ((RSPOpC.rs & 0x0f) >= 2 && (RSPOpC.rs & 0x0f) <= 7 && IsMmx2Enabled == FALSE) + return FALSE; + + sprintf(Reg, "RSP_Vect[%i].UHW[0]", RSPOpC.rd); + MmxMoveQwordVariableToReg(x86_MM0, &RSP_Vect[RSPOpC.rd].UHW[0], Reg); + sprintf(Reg, "RSP_Vect[%i].UHW[4]", RSPOpC.rd); + MmxMoveQwordVariableToReg(x86_MM1, &RSP_Vect[RSPOpC.rd].UHW[4], Reg); + MmxPcmpeqwRegToReg(x86_MM7, x86_MM7); + + if ((RSPOpC.rs & 0xF) >= 8) { + RSP_Element2Mmx(x86_MM2); + MmxPorRegToReg(x86_MM0, x86_MM2); + MmxPorRegToReg(x86_MM1, x86_MM2); + } else if ((RSPOpC.rs & 0xF) < 2) { + sprintf(Reg, "RSP_Vect[%i].HW[0]", RSPOpC.rt); + MmxPorVariableToReg(&RSP_Vect[RSPOpC.rt].HW[0], Reg, x86_MM0); + sprintf(Reg, "RSP_Vect[%i].HW[4]", RSPOpC.rt); + MmxPorVariableToReg(&RSP_Vect[RSPOpC.rt].HW[4], Reg, x86_MM1); + } else { + RSP_MultiElement2Mmx(x86_MM2, x86_MM3); + MmxPorRegToReg(x86_MM0, x86_MM2); + MmxPorRegToReg(x86_MM1, x86_MM3); + } + + MmxXorRegToReg(x86_MM0, x86_MM7); + MmxXorRegToReg(x86_MM1, x86_MM7); + sprintf(Reg, "RSP_Vect[%i].UHW[0]", RSPOpC.sa); + MmxMoveQwordRegToVariable(x86_MM0, &RSP_Vect[RSPOpC.sa].UHW[0], Reg); + sprintf(Reg, "RSP_Vect[%i].UHW[4]", RSPOpC.sa); + MmxMoveQwordRegToVariable(x86_MM1, &RSP_Vect[RSPOpC.sa].UHW[4], Reg); + + if (IsNextInstructionMmx(CompilePC) == FALSE) + MmxEmptyMultimediaState(); + + return TRUE; +} + void Compile_Vector_VNOR ( void ) { - Cheat_r4300iOpcode(RSP_Vector_VNOR,"RSP_Vector_VNOR"); + char Reg[256]; + int el, del, count; + BOOL bElement = ((RSPOpC.rs & 0x0f) >= 8) ? TRUE : FALSE; + BOOL bWriteToAccum = WriteToAccum(Low16BitAccum, CompilePC); + +#ifndef CompileVnor + Cheat_r4300iOpcode(RSP_Vector_VNOR, "RSP_Vector_VNOR"); return; +#endif + + CPU_Message(" %X %s", CompilePC, RSPOpcodeName(RSPOpC.Hex, CompilePC)); + + if (bWriteToAccum == FALSE) { + if (TRUE == Compile_Vector_VNOR_MMX()) + return; + } + + if (bElement == TRUE) { + del = (RSPOpC.rs & 0x07) ^ 7; + sprintf(Reg, "RSP_Vect[%i].HW[%i]", RSPOpC.rt, del); + MoveVariableToX86regHalf(&RSP_Vect[RSPOpC.rt].HW[del], Reg, x86_EBX); + } + + for (count = 0; count < 8; count++) { + el = Indx[RSPOpC.rs].B[count]; + del = EleSpec[RSPOpC.rs].B[el]; + + CPU_Message(" Iteration: %i", count); + + sprintf(Reg, "RSP_Vect[%i].HW[%i]", RSPOpC.rd, el); + MoveVariableToX86regHalf(&RSP_Vect[RSPOpC.rd].HW[el], Reg, x86_EAX); + + if (bElement == FALSE) { + sprintf(Reg, "RSP_Vect[%i].HW[%i]", RSPOpC.rt, del); + OrVariableToX86regHalf(&RSP_Vect[RSPOpC.rt].HW[del], Reg, x86_EAX); + } else { + OrX86RegToX86Reg(x86_EAX, x86_EBX); + } + + NotX86reg(x86_EAX); + + if (bWriteToAccum != FALSE) { + sprintf(Reg, "RSP_ACCUM[%i].HW[1]", el); + MoveX86regHalfToVariable(x86_EAX, &RSP_ACCUM[el].HW[1], Reg); + } + sprintf(Reg, "RSP_Vect[%i].HW[%i]", RSPOpC.sa, el); + MoveX86regHalfToVariable(x86_EAX, &RSP_Vect[RSPOpC.sa].HW[el], Reg); + } } BOOL Compile_Vector_VXOR_MMX ( void ) { @@ -3859,7 +4046,7 @@ BOOL Compile_Vector_VXOR_MMX ( void ) { sprintf(Reg, "RSP_Vect[%i].UHW[4]", RSPOpC.sa); MmxMoveQwordRegToVariable(VXOR_DynaRegCount, &RSP_Vect[RSPOpC.sa].UHW[4], Reg); VXOR_DynaRegCount = (VXOR_DynaRegCount + 1) & 7; - } else { + } else { sprintf(Reg, "RSP_Vect[%i].UHW[0]", RSPOpC.rd); MmxMoveQwordVariableToReg(x86_MM0, &RSP_Vect[RSPOpC.rd].UHW[0], Reg); sprintf(Reg, "RSP_Vect[%i].UHW[4]", RSPOpC.rd); @@ -3895,33 +4082,111 @@ BOOL Compile_Vector_VXOR_MMX ( void ) { return TRUE; } -void Compile_Vector_VXOR ( void ) { +void Compile_Vector_VXOR ( void ) { #ifdef CompileVxor - char Reg[256]; - DWORD count; - BOOL bWriteToAccum = WriteToAccum(Low16BitAccum, CompilePC); + char Reg[256]; + DWORD count; + BOOL bWriteToAccum = WriteToAccum(Low16BitAccum, CompilePC); - CPU_Message(" %X %s",CompilePC,RSPOpcodeName(RSPOpC.Hex,CompilePC)); - - if (!bWriteToAccum || ((RSPOpC.rs & 0xF) < 2 && RSPOpC.rd == RSPOpC.rt)) { - if (TRUE == Compile_Vector_VXOR_MMX()) { - if (bWriteToAccum == TRUE) { - XorX86RegToX86Reg(x86_EAX, x86_EAX); - for (count = 0; count < 8; count++) { - sprintf(Reg, "RSP_ACCUM[%i].HW[1]", count); - MoveX86regHalfToVariable(x86_EAX, &RSP_ACCUM[count].HW[1], Reg); + CPU_Message(" %X %s", CompilePC, RSPOpcodeName(RSPOpC.Hex, CompilePC)); + + if (!bWriteToAccum || ((RSPOpC.rs & 0xF) < 2 && RSPOpC.rd == RSPOpC.rt)) { + if (TRUE == Compile_Vector_VXOR_MMX()) { + if (bWriteToAccum == TRUE) { + XorX86RegToX86Reg(x86_EAX, x86_EAX); + for (count = 0; count < 8; count++) { + sprintf(Reg, "RSP_ACCUM[%i].HW[1]", count); + MoveX86regHalfToVariable(x86_EAX, &RSP_ACCUM[count].HW[1], Reg); + } } + return; } - return; } - } #endif - Cheat_r4300iOpcodeNoMessage(RSP_Vector_VXOR,"RSP_Vector_VXOR"); + Cheat_r4300iOpcodeNoMessage(RSP_Vector_VXOR, "RSP_Vector_VXOR"); +} + +BOOL Compile_Vector_VNXOR_MMX ( void ) { + char Reg[256]; + + /* Do our MMX checks here */ + if (IsMmxEnabled == FALSE) + return FALSE; + if ((RSPOpC.rs & 0x0f) >= 2 && (RSPOpC.rs & 0x0f) <= 7 && IsMmx2Enabled == FALSE) + return FALSE; + + if ((RSPOpC.rs & 0xF) < 2 && (RSPOpC.rd == RSPOpC.rt)) { + static DWORD VNXOR_DynaRegCount = 0; + MmxPcmpeqwRegToReg(VNXOR_DynaRegCount, VNXOR_DynaRegCount); + + sprintf(Reg, "RSP_Vect[%i].UHW[0]", RSPOpC.sa); + MmxMoveQwordRegToVariable(VNXOR_DynaRegCount, &RSP_Vect[RSPOpC.sa].UHW[0], Reg); + sprintf(Reg, "RSP_Vect[%i].UHW[4]", RSPOpC.sa); + MmxMoveQwordRegToVariable(VNXOR_DynaRegCount, &RSP_Vect[RSPOpC.sa].UHW[4], Reg); + VNXOR_DynaRegCount = (VNXOR_DynaRegCount + 1) & 7; + } else { + sprintf(Reg, "RSP_Vect[%i].UHW[0]", RSPOpC.rd); + MmxMoveQwordVariableToReg(x86_MM0, &RSP_Vect[RSPOpC.rd].UHW[0], Reg); + sprintf(Reg, "RSP_Vect[%i].UHW[4]", RSPOpC.rd); + MmxMoveQwordVariableToReg(x86_MM1, &RSP_Vect[RSPOpC.rd].UHW[4], Reg); + MmxPcmpeqwRegToReg(x86_MM7, x86_MM7); + + if ((RSPOpC.rs & 0xF) >= 8) { + RSP_Element2Mmx(x86_MM2); + MmxXorRegToReg(x86_MM0, x86_MM2); + MmxXorRegToReg(x86_MM1, x86_MM2); + } else if ((RSPOpC.rs & 0xF) < 2) { + sprintf(Reg, "RSP_Vect[%i].HW[0]", RSPOpC.rt); + MmxMoveQwordVariableToReg(x86_MM2, &RSP_Vect[RSPOpC.rt].HW[0], Reg); + sprintf(Reg, "RSP_Vect[%i].HW[4]", RSPOpC.rt); + MmxMoveQwordVariableToReg(x86_MM3, &RSP_Vect[RSPOpC.rt].HW[4], Reg); + + MmxXorRegToReg(x86_MM0, x86_MM2); + MmxXorRegToReg(x86_MM1, x86_MM3); + } else { + RSP_MultiElement2Mmx(x86_MM2, x86_MM3); + MmxXorRegToReg(x86_MM0, x86_MM2); + MmxXorRegToReg(x86_MM1, x86_MM3); + } + + MmxXorRegToReg(x86_MM0, x86_MM7); + MmxXorRegToReg(x86_MM1, x86_MM7); + sprintf(Reg, "RSP_Vect[%i].UHW[0]", RSPOpC.sa); + MmxMoveQwordRegToVariable(x86_MM0, &RSP_Vect[RSPOpC.sa].UHW[0], Reg); + sprintf(Reg, "RSP_Vect[%i].UHW[4]", RSPOpC.sa); + MmxMoveQwordRegToVariable(x86_MM1, &RSP_Vect[RSPOpC.sa].UHW[4], Reg); + } + + if (IsNextInstructionMmx(CompilePC) == FALSE) + MmxEmptyMultimediaState(); + + return TRUE; } void Compile_Vector_VNXOR ( void ) { - Cheat_r4300iOpcode(RSP_Vector_VNXOR,"RSP_Vector_VNXOR"); + #ifdef CompileVnxor + char Reg[256]; + DWORD count; + BOOL bWriteToAccum = WriteToAccum(Low16BitAccum, CompilePC); + + CPU_Message(" %X %s", CompilePC, RSPOpcodeName(RSPOpC.Hex, CompilePC)); + + if (!bWriteToAccum || ((RSPOpC.rs & 0xF) < 2 && RSPOpC.rd == RSPOpC.rt)) { + if (TRUE == Compile_Vector_VNXOR_MMX()) { + if (bWriteToAccum == TRUE) { + OrConstToX86Reg(0xFFFFFFFF, x86_EAX); + for (count = 0; count < 8; count++) { + sprintf(Reg, "RSP_ACCUM[%i].HW[1]", count); + MoveX86regHalfToVariable(x86_EAX, &RSP_ACCUM[count].HW[1], Reg); + } + } + return; + } + } + #endif + + Cheat_r4300iOpcode(RSP_Vector_VNXOR, "RSP_Vector_VNXOR"); } void Compile_Vector_VRCP ( void ) { @@ -4603,7 +4868,7 @@ void Compile_Opcode_SLV ( void ) { void Compile_Opcode_SDV ( void ) { char Reg[256]; int offset = (RSPOpC.voffset << 3); - BYTE * Jump[2], *LoopEntry; + BYTE * Jump[2], * LoopEntry; //if ((RSPOpC.del & 0x7) != 0) { // rsp_UnknownOpcode(); @@ -4692,10 +4957,10 @@ void Compile_Opcode_SQV ( void ) { CPU_Message(" %X %s",CompilePC,RSPOpcodeName(RSPOpC.Hex,CompilePC)); - //if (RSPOpC.del != 0) { - // rsp_UnknownOpcode(); - // return; - //} + if (RSPOpC.del != 0 && RSPOpC.del != 12) { + rsp_UnknownOpcode(); + return; + } if (IsRegConst(RSPOpC.base) == TRUE) { DWORD Addr = (MipsRegConst(RSPOpC.base) + offset) & 0xfff; diff --git a/Source/RSP/X86.c b/Source/RSP/X86.c index a0fab3724..5314bb3da 100644 --- a/Source/RSP/X86.c +++ b/Source/RSP/X86.c @@ -2071,6 +2071,22 @@ void NegateX86reg(int x86reg) { } } +void NotX86reg(int x86reg) { + CPU_Message(" not %s", x86_Name(x86reg)); + switch (x86reg) { + case x86_EAX: PUTDST16(RecompPos, 0xd0f7); break; + case x86_EBX: PUTDST16(RecompPos, 0xd3f7); break; + case x86_ECX: PUTDST16(RecompPos, 0xd1f7); break; + case x86_EDX: PUTDST16(RecompPos, 0xd2f7); break; + case x86_ESI: PUTDST16(RecompPos, 0xd6f7); break; + case x86_EDI: PUTDST16(RecompPos, 0xd7f7); break; + case x86_ESP: PUTDST16(RecompPos, 0xd4f7); break; + case x86_EBP: PUTDST16(RecompPos, 0xd5f7); break; + default: + DisplayError("NotX86reg\nUnknown x86 Register"); + } +} + void OrConstToVariable(DWORD Const, void * Variable, char * VariableName) { CPU_Message(" or dword ptr [%s], 0x%X",VariableName, Const); PUTDST16(RecompPos,0x0D81); diff --git a/Source/RSP/X86.h b/Source/RSP/X86.h index e054a65f0..aa4311164 100644 --- a/Source/RSP/X86.h +++ b/Source/RSP/X86.h @@ -149,6 +149,7 @@ void MoveZxN64MemToX86regHalf ( int x86reg, int AddrReg ); void MoveZxVariableToX86regHalf ( void *Variable, char *VariableName, int x86reg ); void MulX86reg ( int x86reg ); void NegateX86reg ( int x86reg ); +void NotX86reg ( int x86reg ); void OrConstToVariable ( DWORD Const, void * Variable, char * VariableName ); void OrConstToX86Reg ( DWORD Const, int x86Reg ); void OrVariableToX86Reg ( void * Variable, char * VariableName, int x86Reg ); @@ -214,6 +215,7 @@ void MmxPorRegToReg ( int Dest, int Source ); void MmxPorVariableToReg ( void * Variable, char * VariableName, int Dest ); void MmxXorRegToReg ( int Dest, int Source ); void MmxShuffleMemoryToReg ( int Dest, void * Variable, char * VariableName, BYTE Immed ); +void MmxPcmpeqwRegToReg ( int Dest, int Source ); void MmxPmullwRegToReg ( int Dest, int Source ); void MmxPmullwVariableToReg ( int Dest, void * Variable, char * VariableName ); void MmxPmulhuwRegToReg ( int Dest, int Source ); From 479b201ddc06925f451818fd4215a294508084a6 Mon Sep 17 00:00:00 2001 From: LegendOfDragoon Date: Wed, 14 Jan 2015 01:48:47 -0800 Subject: [PATCH 5/6] Implement edge case for LDV, fix 2 typos as well. For LDV, it's the same deal as SDV. In WDC, RSPOpC.del could = 12, which would result in a negative number when doing 16 - RSPOpC,del - 8. --- Source/RSP/Recompiler Ops.c | 18 +++++++++--------- Source/RSP/Sse.c | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Source/RSP/Recompiler Ops.c b/Source/RSP/Recompiler Ops.c index d14ce327c..b10e37726 100644 --- a/Source/RSP/Recompiler Ops.c +++ b/Source/RSP/Recompiler Ops.c @@ -3624,7 +3624,7 @@ void Compile_Vector_VCR ( void ) { void Compile_Vector_VMRG ( void ) { char Reg[256]; int count, el, del; - BOOL bWriteToAccum; = WriteToAccum(Low16BitAccum, CompilePC); + BOOL bWriteToAccum = WriteToAccum(Low16BitAccum, CompilePC); #ifndef CompileVmrg Cheat_r4300iOpcode(RSP_Vector_VMRG,"RSP_Vector_VMRG"); return; @@ -4474,10 +4474,10 @@ void Compile_Opcode_LDV ( void ) { sprintf(Reg, "Dmem + %Xh", Addr + 4); MoveVariableToX86reg(RSPInfo.DMEM + Addr + 4, Reg, x86_ECX); - sprintf(Reg, "RSP_Vect[%i].B[%i]", RSPOpC.rt, 16 - RSPOpC.del - 4); - MoveX86regToVariable(x86_EAX, &RSP_Vect[RSPOpC.rt].B[16 - RSPOpC.del - 4], Reg); - sprintf(Reg, "RSP_Vect[%i].B[%i]", RSPOpC.rt, 16 - RSPOpC.del - 8); - MoveX86regToVariable(x86_ECX, &RSP_Vect[RSPOpC.rt].B[16 - RSPOpC.del - 8], Reg); + sprintf(Reg, "RSP_Vect[%i].B[%i]", RSPOpC.rt, (16 - RSPOpC.del - 4) & 0xF); + MoveX86regToVariable(x86_EAX, &RSP_Vect[RSPOpC.rt].B[(16 - RSPOpC.del - 4) & 0xF], Reg); + sprintf(Reg, "RSP_Vect[%i].B[%i]", RSPOpC.rt, (16 - RSPOpC.del - 8) & 0xF); + MoveX86regToVariable(x86_ECX, &RSP_Vect[RSPOpC.rt].B[(16 - RSPOpC.del - 8) & 0xF], Reg); return; } @@ -4526,10 +4526,10 @@ void Compile_Opcode_LDV ( void ) { MoveN64MemDispToX86reg(x86_ECX, x86_EBX, 4); /* Because of byte swapping this swizzle works nicely */ - sprintf(Reg, "RSP_Vect[%i].B[%i]", RSPOpC.rt, 16 - RSPOpC.del - 4); - MoveX86regToVariable(x86_EAX, &RSP_Vect[RSPOpC.rt].B[16 - RSPOpC.del - 4], Reg); - sprintf(Reg, "RSP_Vect[%i].B[%i]", RSPOpC.rt, 16 - RSPOpC.del - 8); - MoveX86regToVariable(x86_ECX, &RSP_Vect[RSPOpC.rt].B[16 - RSPOpC.del - 8], Reg); + sprintf(Reg, "RSP_Vect[%i].B[%i]", RSPOpC.rt, (16 - RSPOpC.del - 4) & 0xF); + MoveX86regToVariable(x86_EAX, &RSP_Vect[RSPOpC.rt].B[(16 - RSPOpC.del - 4) & 0xF], Reg); + sprintf(Reg, "RSP_Vect[%i].B[%i]", RSPOpC.rt, (16 - RSPOpC.del - 8) & 0xF); + MoveX86regToVariable(x86_ECX, &RSP_Vect[RSPOpC.rt].B[(16 - RSPOpC.del - 8) & 0xF], Reg); CPU_Message(" Done:"); x86_SetBranch32b(Jump[1], RecompPos); diff --git a/Source/RSP/Sse.c b/Source/RSP/Sse.c index e7caff69b..dd5f3bf9c 100644 --- a/Source/RSP/Sse.c +++ b/Source/RSP/Sse.c @@ -294,7 +294,7 @@ void SseXorRegToReg(int Dest, int Source) { case x86_XMM4: x86Command = 0x20; break; case x86_XMM5: x86Command = 0x28; break; case x86_XMM6: x86Command = 0x30; break; - case x86_XMM7: x86Command = 0x28; break; + case x86_XMM7: x86Command = 0x38; break; } switch (Source) { case x86_XMM0: x86Command += 0x00; break; From 47b1456c8d46df9085a29d3eac1f08b38f77de47 Mon Sep 17 00:00:00 2001 From: zilmar Date: Fri, 16 Jan 2015 10:58:00 +1100 Subject: [PATCH 6/6] Update installer to have new ad in it --- Source/Installer/Installer.iss | 34 +- Source/Installer/Installer.vcproj | 16 + Source/Installer/binno/InnoCallback.dll | Bin 0 -> 65024 bytes Source/Installer/binno/binno.iss | 2451 ++++++++++++++++++++++ Source/Installer/binno/it_download.iss | 319 +++ Source/Installer/binno/it_download_u.iss | 319 +++ Source/Installer/binno/itdownload.dll | Bin 0 -> 205312 bytes Source/Installer/binno/utility.iss | 771 +++++++ 8 files changed, 3905 insertions(+), 5 deletions(-) create mode 100644 Source/Installer/binno/InnoCallback.dll create mode 100644 Source/Installer/binno/binno.iss create mode 100644 Source/Installer/binno/it_download.iss create mode 100644 Source/Installer/binno/it_download_u.iss create mode 100644 Source/Installer/binno/itdownload.dll create mode 100644 Source/Installer/binno/utility.iss diff --git a/Source/Installer/Installer.iss b/Source/Installer/Installer.iss index 762a655a0..36260bc23 100644 --- a/Source/Installer/Installer.iss +++ b/Source/Installer/Installer.iss @@ -1,6 +1,8 @@ #define BaseDir ExtractFilePath(ExtractFilePath(ExtractFilePath(SourcePath))) #define AppVersion GetFileVersion(BaseDir + "\Bin\" + Configuration + "\Project64.exe") +#include BaseDir+"\Source\Installer\binno\binno.iss" + [Setup] AppName=Project 64 AppVersion={#AppVersion} @@ -88,10 +90,6 @@ Source: "{#BaseDir}\Plugin\GFX\Jabo_Direct3D8.dll"; DestDir: "{app}\Plugin\GFX" Source: "{#BaseDir}\Plugin\GFX\PJ64Glide64.dll"; DestDir: "{app}\Plugin\GFX" Source: "{#BaseDir}\Plugin\Input\Jabo_DInput.dll"; DestDir: "{app}\Plugin\Input" Source: "{#BaseDir}\Plugin\RSP\RSP 1.7.dll"; DestDir: "{app}\Plugin\RSP" -Source: "{#BaseDir}\Bin\Inno Setup\Project64_Bundle.exe"; Flags: dontcopy -Source: "{#BaseDir}\Bin\Inno Setup\delta_logo.bmp"; Flags: dontcopy -Source: "{#BaseDir}\Bin\Inno Setup\iminentBar.bmp"; Flags: dontcopy -Source: "{#BaseDir}\Bin\Inno Setup\iminentNonSearch.bmp"; Flags: dontcopy [Dirs] Name: "{app}\Config"; Permissions: users-modify @@ -103,4 +101,30 @@ Name: "{app}\Textures"; Permissions: users-modify [Icons] Name: "{commonprograms}\Project 64 2.0\Project 64"; Filename: "{app}\Project64.exe" Name: "{commonprograms}\Project 64 2.0\Uninstall Project64 2.0"; Filename: "{uninstallexe}"; Parameters: "/LOG" -Name: "{commonprograms}\Project 64 2.0\Support"; Filename: "http://forum.pj64-emu.com" \ No newline at end of file +Name: "{commonprograms}\Project 64 2.0\Support"; Filename: "http://forum.pj64-emu.com" + +[Code] +function HaveCommandlineParam (inParam: String): Boolean; +var + LoopVar : Integer; +begin + LoopVar := 1; + Result := false; + + while LoopVar <= ParamCount do + begin + if ((ParamStr(LoopVar) = '-' + inParam) or (ParamStr(LoopVar) = '/' + inParam)) then + begin + Result := true; + Break; + end; + LoopVar := LoopVar + 1; + end; +end; + +procedure InitializeWizard(); +begin + if ((WizardSilent() <> true) and (HaveCommandlineParam('noads') <> true)) then begin + CreateBINNOPage(wpSelectTasks,'pj64emu','pj64emu'); + end; +end; \ No newline at end of file diff --git a/Source/Installer/Installer.vcproj b/Source/Installer/Installer.vcproj index 256a82960..c8a3f6b60 100644 --- a/Source/Installer/Installer.vcproj +++ b/Source/Installer/Installer.vcproj @@ -150,6 +150,22 @@ /> + + + + + + + + diff --git a/Source/Installer/binno/InnoCallback.dll b/Source/Installer/binno/InnoCallback.dll new file mode 100644 index 0000000000000000000000000000000000000000..2b33aadf0b251b1abaac6cd9f2fb7329b3d55f4d GIT binary patch literal 65024 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 literal 0 HcmV?d00001 diff --git a/Source/Installer/binno/binno.iss b/Source/Installer/binno/binno.iss new file mode 100644 index 000000000..6a68e5068 --- /dev/null +++ b/Source/Installer/binno/binno.iss @@ -0,0 +1,2451 @@ +; Script generated by the Inno Setup Script Wizard. +; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES! +#define BINNO_PRODUCTION +#define BINNO_CALL_URL 'http://d3oxtn1x3b8d7i.cloudfront.net' + +#ifndef BINNO_PRODUCTION +;#define BINNO_DEBUG +#define BINNO_USE_LOCAL_CONFIG 'C:\\Users\\mihindu\\Desktop\\installer_data.xml' +#define BINNO_USE_LOCAL_CHECK_XML 'C:\\Users\\mihindu\\Desktop\\pre_checks.xml' + +[Setup] +AppName=My Program +AppVersion=1.5 +CreateAppDir=no +PrivilegesRequired=admin +#endif + +#define macro(ss) GetArrayLength(ss) + +[Files] +Source: "{#BaseDir}\Source\Installer\binno\itdownload.dll"; Flags: dontcopy; DestDir: {tmp} +Source: "{#BaseDir}\Source\Installer\binno\InnoCallback.dll"; Flags: dontcopy; DestDir: {tmp} + +;#[Run] +;#Filename: {code:GetDownloaderInfo|exe} ; Parameters: {code:GetDownloaderInfo|args} ; Check: biShouldRunDownloader ; Flags: nowait + +[Code] +#ifndef UNICODE + #include "it_download.iss" +#else + #include "it_download_u.iss" +#endif +#include "utility.iss" + +const +#ifndef UNICODE + BINNO_VERSION = '2.2.0'; +#else + BINNO_VERSION = '2.2.0u'; +#endif + +OPTION_V_SPACING = 10; +SUB_OPTION_V_SPACING = 7; + +// Win API +GWL_USERDATA = -21; +GWL_STYLE = -16; + +BM_GETCHECK = $00F0; +BM_SETCHECK = $00F1; +BST_UNCHECKED = $0000; +BST_CHECKED = $0001; + +WM_SETFONT = $0030; + +// option types +RADIO = 0; +CHECK = 1; +TEXT = 2; + +ERROR_SUCCESS = 0; + +// registry types.. +REG_SZ = 1; +REG_EXPAND_SZ = 2; +REG_BINARY = 3; +REG_DWORD = 4; +REG_MULTI_SZ = 7; + +type +#ifndef UNICODE + TGUID = record + D1: LongWord; + D2: Word; + D3: Word; + D4: array[0..7] of Byte; + end; +#endif + + AT = array[0..100] of byte; + + BI_URL = record + href:string; + target:string; + end; + + BI_DOWNLOADER = record + en:boolean; + exe:string; + args:string; + url:string; + oninit:boolean; + end; + + SIZE = record + cx:Longint; + cy:Longint; + end; + + HDC = Longint; + HKEY = Longint; + HGDIOBJ = longint; + HBRUSH = longint; + COLORREF = dword; + HHOOK = longint; + LRESULT = longint; + LPARAM = longint; + WPARAM = integer; + + TStrProc = function(nCode:integer;wParam:WPARAM;lParam:LPARAM):integer; + + BI_RECT = record + left:Longint; + top:Longint; + right:Longint; + bottom:Longint; + end; + + BI_HWND_ARRAY = array of HWND; + + BI_SUB_OPTION = record + master_switch:boolean; + on_value:string; + off_value:string; + hwnd:HWND; + attached_texts: BI_HWND_ARRAY; + control_type:integer; + control_ref:TWinControl; + end; + + BI_SUB_OPTION_ARRAY = array of BI_SUB_OPTION; + + BI_MAIN_OPTION = record + master_switch:boolean; + on_value:string; + off_value:string; + sub_options:BI_SUB_OPTION_ARRAY; + hwnd:HWND; + attached_texts: BI_HWND_ARRAY; + control_type:integer; + control_ref:TWinControl; + end; + + BI_ALERT_WINDOW = record + id:string; + text:string; + confirm_name:string; + confirm_behavior:string; + confirm_style:string; + confirm_selected:string; + decline_name:string; + decline_behavior:string; + decline_style:string; + decline_selected:string; + confirm_next_alert_id:string; + decline_next_alert_id:string; + end; + + BI_OFFER = record + id:integer; + offer_id:string; + download_url,execution_args,downloader_args:String; + //license_path:String; + control_next_btn:boolean; + valid,visible:boolean; + ready:boolean; // used to stop offer in runtime... + //user_cancel:boolean; // in anycase user cancel this offer... + main_options:array of BI_MAIN_OPTION; + link_array: array of BI_URL; + resource_table: array of BI_RESOURCE; + xml:variant; + page:TWizardPage; + cmd_line:string; // command line for specific offer + forced_args:string; + forced_accept:boolean; + + back_btn_text:string; + back_btn_style:string; + + skip_btn_text:string; + skip_btn_style:string; + skip_btn_behavior:string; + + next_btn_text:string; + next_btn_style:string; + + cancel_btn_style:string; + cancel_btn_text:string; + + master_cfm_text,master_cfm_yes_text,master_cfm_yes_args,master_cfm_yes_style,master_cfm_yes_selected:string; + master_cfm_no_text,master_cfm_no_args,master_cfm_no_style,master_cfm_no_selected:string; + + x_btn_behavior:string ; + x_cfm_text,x_cfm_yes_text,x_cfm_yes_args,x_cfm_yes_style,x_cfm_yes_selected:string; + x_cfm_no_text,x_cfm_no_args,x_cfm_no_style,x_cfm_no_selected:string; + + ask_for_skip:boolean; + alerts: array of BI_ALERT_WINDOW; + simulated_next:boolean; + end; + +var +offer_uid,installer_muid,offer_softid,offer_affid,execution_args:String; +installer_v1:String; +old_cancel_text,old_next_text,old_back_text:String; +link_array: array of BI_URL; +downloader:BI_DOWNLOADER; +offers: array of BI_OFFER; +hhk:HHOOK; +yes_text,no_text:String; +msgbox_flags:integer; +option_loop:boolean; +check_resource_table: array of BI_RESOURCE; +binno_quit:boolean; + +// Win32 API +function biCoCreateGuid(var Guid:TGuid):integer; + external 'CoCreateGuid@ole32.dll stdcall'; + +function biLookupAccountName(a,b:AnsiString;var sid:AT;var cbSid:Longword;d:AnsiString;var x:LongWord;var y:Longword):integer; + external 'LookupAccountNameA@advapi32.dll stdcall'; + +function biConvertSidToStringSid(pSid:AT;var ssid:Longint):integer; + external 'ConvertSidToStringSidA@advapi32.dll stdcall'; + +function biGetLastError():Longword; + external 'GetLastError@kernel32.dll stdcall'; + +function biLocalFree(hMem:Longint):Longint; + external 'LocalFree@kernel32.dll stdcall'; + +function biGetWindowLong(hWnd:Longint;nIndex:Longint):Longint; + external 'GetWindowLongA@user32.dll stdcall'; + +function biSetWindowLong(hWnd,nIndex,dwNewLong:Longint):Longint; + external 'SetWindowLongA@user32.dll stdcall'; + +function biShowWindow(hMem:Longint;nCmdShow:Longint):Longint; + external 'ShowWindow@user32.dll stdcall'; + +function biGetDC(hWnd:HWND):Longint; + external 'GetDC@user32.dll stdcall'; + +function biCreateCompatibleDC(hdc:HDC):HDC; + external 'CreateCompatibleDC@gdi32.dll stdcall'; + +function biCreateCompatibleBitmap(hdc:HDC;nWidth,nHeight:integer):Longint; + external 'CreateCompatibleBitmap@gdi32.dll stdcall'; + +function biSelectObject(hdc:HDC;hgdiobj:Longint):Longint; + external 'SelectObject@gdi32.dll stdcall'; + +function biGetPixel(hdc:HDC;nXPos,nYPos:integer):COLORREF; + external 'GetPixel@gdi32.dll stdcall'; + +function biDrawText(hdc:HDC;lpString:AnsiString;nCount:integer;var lpRect:BI_RECT;uFormat:integer):Longint; + external 'DrawTextA@user32.dll stdcall'; + +function biSetBkColor(hdc:HDC;crColor:COLORREF):COLORREF; + external 'SetBkColor@gdi32.dll stdcall'; + +function biFillRect(hdc:HDC;var lprc:BI_RECT;hbr:Longint):COLORREF; + external 'FillRect@user32.dll stdcall'; + +function biGetStockObject(fnObject:integer):Longint; + external 'GetStockObject@gdi32.dll stdcall'; + +function biGetTextExtentPoint32(hdc:Longint;lpString:AnsiString;c:integer;var lpSize:SIZE):Longint; + external 'GetTextExtentPoint32A@Gdi32.dll stdcall'; + +function biDeleteObject(hObject:Longint):Longint; + external 'DeleteObject@gdi32.dll stdcall'; + +function biDeleteDC(hdc:HDC):Longint; + external 'DeleteDC@gdi32.dll stdcall'; + +function biReleaseDC(hWnd:HWND;hdc:HDC):Longint; + external 'ReleaseDC@user32.dll stdcall'; + +function biEnableWindow(hWnd:HWND;bEnable:boolean):Longint; + external 'EnableWindow@user32.dll stdcall'; + +function biSetWindowsHookEx(idHook:integer;lpfn:longword;hMod:Longint;dwThreadId:DWORD):HHOOK; + external 'SetWindowsHookExW@user32.dll stdcall'; + +function biUnhookWindowsHookEx(hhk:HHOOK):Longint; + external 'UnhookWindowsHookEx@user32.dll stdcall'; + +function biCallNextHookEx(hhk:HHOOK;nCode:integer;wParam:integer;lParam:longint):Longint; + external 'CallNextHookEx@user32.dll stdcall'; + +function biGetCurrentThreadId():DWORD; + external 'GetCurrentThreadId@kernel32.dll stdcall'; + +function biGetDlgItem(hDlg:HWND;nIDDlgItem:integer):HWND; + external 'GetDlgItem@user32.dll stdcall'; + +function biSetDlgItemText(hDlg:HWND;nIDDlgItem:integer;lpString:AnsiString):integer; + external 'SetDlgItemTextA@user32.dll stdcall'; + +function biRegOpenKeyEx(hKey:HKEY;lpSubKey:AnsiString;ulOptions:DWORD;samDesired:DWORD;var phkResult:HKEY):longint; + external 'RegOpenKeyExA@Advapi32.dll stdcall'; + +function biRegQueryValueExCustom(hKey:HKEY;lpValueName:AnsiString;lpReserved:longint;var lpType:DWORD;lpData:longint;lpcbData:longint):longint; + external 'RegQueryValueExA@Advapi32.dll stdcall'; + +function biRegCloseKey(hKey:HKEY):longint; + external 'RegCloseKey@Advapi32.dll stdcall'; + +function biWrapCBTProc(callback:TStrProc; paramcount:integer):longword; + external 'wrapcallback@files:innocallback.dll stdcall'; + +function biMessageBox(hWnd:HWND;lpText,lpCaption:AnsiString;uType:integer):integer; + external 'MessageBoxA@user32.dll stdcall'; + +function biSendMessage(hWnd:HWND;message:UINT;wParam:WPARAM;lParam:LPARAM):integer; + external 'SendMessageA@user32.dll stdcall'; + +function CBTProc(nCode:integer;wParam:WPARAM;lParam:LPARAM):integer; +var +hChildWnd:HWND; // msgbox is "child" +myfont:TFont; +begin + // hook function + + // notification that a window is about to be activated + // window handle is wParam + if nCode = 5 then begin + + // set window handles + hChildWnd := HWND(wParam); + + myfont := TFont.Create(); + myfont.Name := WizardForm.NextButton.Font.Name; + myfont.Pitch := WizardForm.NextButton.Font.Pitch; + myfont.Size := WizardForm.NextButton.Font.Size; + myfont.PixelsPerInch := WizardForm.NextButton.Font.PixelsPerInch; + myfont.Height := WizardForm.NextButton.Font.Height; + myfont.Style := myfont.Style + [fsBold]; + + //to get the text of the Yes button + if biGetDlgItem(hChildWnd,IDYES) <> 0 then begin + biSetDlgItemText(hChildWnd,IDYES,yes_text); + + if (msgbox_flags and $02) <> 0 then + biSendMessage(biGetDlgItem(hChildWnd,IDYES),WM_SETFONT,myfont.Handle,0); + end; + + if biGetDlgItem(hChildWnd,IDNO) <> 0 then begin + biSetDlgItemText(hChildWnd,IDNO,no_text); + + if (msgbox_flags and $10) <> 0 then + biSendMessage(biGetDlgItem(hChildWnd,IDNO),WM_SETFONT,myfont.Handle,0); + end; + + // exit CBT hook + biUnhookWindowsHookEx(hhk); + end else begin + // otherwise, continue with any possible chained hooks + biCallNextHookEx(hhk,nCode,wParam,lParam); + end; + Result:=0; +end; + +function CBTMsgbox(Text,YesText,NoText: String;const default:integer):integer; +var + callback:longword; + default_btn:integer; +begin + + default_btn := MB_DEFBUTTON1; + yes_text:=YesText; + no_text:=NoText; + msgbox_flags := default; + + StringChangeEx(Text,'\n', chr(10), True); + + callback:=biWrapCBTProc(@CBTProc,3); //Our proc has 3 arguments + hhk := biSetWindowsHookEx(5,callback,0,biGetCurrentThreadId()); + + if (default and $1) <> 0 then default_btn := MB_DEFBUTTON2; + + Result := biMessageBox(WizardForm.Handle,Text,'Confirmation',MB_YESNO or default_btn); + +end; + +// Utilities +function biGetResourceById(Offer:integer;Id:String):String; +var +i:integer; +begin + result := ''; + // find the correct resouce for this id + for i := 0 to GetArrayLength(offers[Offer].resource_table)-1 do begin + if offers[Offer].resource_table[i].id = Id then begin + result := offers[Offer].resource_table[i].path; + exit; + end + end; +end; + +function biGetXmlStringSafe(var node:variant;path:string):string; +var + v:variant; +begin + result := ''; + + try + if VarIsEmpty(node) then exit; + + v := node.selectSingleNode(path); + + if VarIsEmpty(v) then exit; + //if VarIsNull(v.text) then exit; + result := v.text; + except + // return empty string + end + +end; + +function biGetXmlAttributeSafe(var node:variant;name,default:string):string; +var + v:variant; + +begin + result := default; +try + if VarIsEmpty(node) then exit; + + v := node.getAttribute(name); + + if VarIsEmpty(v) or VarIsNull(v) then exit; + + result := v; +except + +end; + +end; + +// the famous text width calculation function... +Function biGetTextSize(hWnd:HWND;hFont:Longint;text:string;var cx,cy:integer):integer; +var +memdc,hdc:HDC; +r,wbrush,old_bmp,hbmp:Longint; +size:SIZE; +rc:BI_RECT; +c:COLORREF; +begin + // Get DC + hdc := biGetDC(hWnd); + memdc := biCreateCompatibleDC(hdc); + hbmp := biCreateCompatibleBitmap(memdc,1000,40); + old_bmp := biSelectObject(memdc,hbmp); + + // Select object to set the font.... + biSelectObject(memdc,hFont); + + biGetTextExtentPoint32(memdc,text,length(text),size); + size.cx := size.cx + 1; + rc.left := 0; + rc.top := 0; + rc.right:=size.cx; + rc.bottom:=size.cy; + + // get white gbrush + wbrush := biGetStockObject(0); + + // fill it wish white brush... + r := biFillRect(memdc,rc,wbrush); + + c := biSetBkColor(memdc,0); // set bk color to black + + r := biDrawText(memdc,text,length(text),rc,$00000820); + + repeat + c := biGetPixel(memdc,size.cx,size.cy/2); + size.cx := size.cx - 1; + until (c = 0); + + biSelectObject(memdc,old_bmp); + biDeleteObject(hbmp); + biDeleteDC(memdc); + biReleaseDC(hWnd,hdc); + + cx := size.cx; + cy := size.cy; + + Result := 1; +end; + +procedure biAddWindowStyle(hWnd:HWND;style:Longint); +var tmp:longint; +begin + tmp := biGetWindowLong(hWnd,GWL_STYLE); + tmp := tmp or style; + biSetWindowLong(hWnd,GWL_STYLE,tmp); +end; + +procedure biRemoveWindowStyle(hWnd:HWND;style:Longint); +var tmp:longint; +begin + tmp := biGetWindowLong(hWnd,GWL_STYLE); + tmp := tmp and not style; + biSetWindowLong(hWnd,GWL_STYLE,tmp); +end; + +function biGetUserSID():string; +var +u:string; +l,x,y:Longword; +ee: AT; +bak:Longint; +begin + + u := StringOfChar( ' ', 64 ); + l:=63; + x:=63; + + biLookupAccountName('',GetUserNameString(),ee,l,u,x,y); + + if biConvertSidToStringSid(ee,bak) = 1 then + Result := CastIntegerToString(bak); + + biLocalFree(bak); +end; + +function biCreateGUID() : String; +var +Guid:TGUID; +begin + biCoCreateGuid(Guid); + Result := Format('%.8x%.4x%.4x%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x', [Guid.D1, Guid.D2, Guid.D3, Guid.D4[0], Guid.D4[1], Guid.D4[2], Guid.D4[3], Guid.D4[4], Guid.D4[5], Guid.D4[6], Guid.D4[7]]);; +end; + +function biCreateMUID() : String; +var +k:String; + + +begin + k := biGetMAC(); + if k = '' then begin + Result := biCreateGuid(); + end else begin + result :=GetMD5OfString(k) + end; +end; + +procedure biExpandMacros(var S: String;offer_idx:integer); +begin + + StringChangeEx(S, '%MUID%', installer_muid, True); + StringChangeEx(S, '%UID%', offer_uid, True); + StringChangeEx(S, '%AffiliateID%', offer_affid, True); + StringChangeEx(S, '%SoftwareID%', offer_softid, True); + + if GetArrayLength(offers) > 0 then + StringChangeEx(S, '%OfferID%', offers[offer_idx].offer_id, True); + + StringChangeEx(S, '%UserSID%', biGetUserSID(), True); +end; + +function biGetSilentIndication():string; +begin + result := '0'; + if WizardSilent() then begin + result := '1' + end; +end; + +function biCheckExeHint(resource,args,store_key,store_name:string;return_actual:boolean;var Value:string):boolean; +var + ResultCode,root_key:integer; + reg_path:string; +begin + result := false; + + if Exec(biGetResourceByKey(resource,check_resource_table), args, '', SW_SHOW, ewWaitUntilTerminated, ResultCode) then begin + // read the regkey to return value + root_key := biSplitRegistryKeyNoWOW(store_key,reg_path); + // MsgBox(reg_path, mbInformation, MB_OK); + if RegQueryStringValue(root_key,reg_path,store_name,Value) then begin + // Successfully read the value + result:=true; + end; + end; +end; + +function biCheckFileSystemHint(path,name:string;path_check,return_actual:boolean;var Value:string):boolean; +var +fn:string; +s:AnsiString; +begin + result := false; + // if path_check check only the path.. + if path_check and (not return_actual) then begin + if DirExists(path) then Value := 'true' + else Value := 'false'; + end else begin + fn := AddBackSlash(path)+name; + + // full check required... + if FileExists(fn) then begin + // read file if acutal content reqested + if return_actual then begin + if LoadStringFromFile(fn,s) then Value := s; + end else begin + Value := 'true'; + end; + end else begin + // the file not found + Value := 'false'; + end; + end; +end; + +function biCheckUrlHint(Url : String;return_actual:boolean;var Value:string):boolean; +var +tmp:AnsiString; + +begin + result := true; + biExpandMacros(Url,0); // FIXME: pass offer_idx + + if ITD_DownloadFile(url,'001.tmp') <> 0 then begin + if not return_actual then begin + Value:='false'; + end else begin + Value:='null' + end; + exit; + end else begin + // success full download.. + if not return_actual then begin + Value:='true' + exit; + end; + end; + + if LoadStringFromFile('001.tmp',tmp) then begin + Value := tmp; + end else begin + Value := 'null'; + end; + + DeleteFile('001.tmp'); + +end; + +function biCheckRegistryHint(Name,Key:String;path_check,return_actual:boolean;var Value:string) : boolean; +var +rk,p:Integer; +tmp,key2:String; +dw: Cardinal; +xkey:HKEY; +vtype:DWORD; +c:byte; +tst:AnsiString; +begin + + biExpandMacros(Key,0); // FIXME, use the offer index hear + Result := false; + // TODO: replace with biSplitRegistryKey() + p := Pos('\',Key); + tmp := Copy(Key,0,p-1); + key2 := Copy(Key,p+1,Length(Key)); + + if CompareText(tmp,'HKCR') = 0 then begin + rk := HKEY_CLASSES_ROOT; + end else if CompareText(tmp,'HKLM') = 0 then begin + rk := HKEY_LOCAL_MACHINE; + end else if CompareText(tmp,'HKCC') = 0 then begin + rk := HKEY_CURRENT_CONFIG; + end else if CompareText(tmp,'HKU') = 0 then begin + rk := HKEY_USERS; + end else + rk := HKEY_CURRENT_USER; + + // use smart reading method insted of inbuilt functions.. + if biRegOpenKeyEx(rk,key2,0,1,xkey) <> ERROR_SUCCESS then begin + // key not found, if we were checking the key then time to report it.. + if path_check or (not return_actual) then Value := 'false'; + exit; + end else begin + // key found if only existancy check exit.. + if path_check then begin + biRegCloseKey(xkey); + Value := 'true' + exit; + end; + end; + + // check value exist... + if biRegQueryValueExCustom(xkey,Name,0,vtype,0,0) <> ERROR_SUCCESS then begin + // value is not found.. + if not return_actual then Value:= 'false'; + biRegCloseKey(xkey); + exit; // because key is not exist.. + end else begin + // value exist.. + biRegCloseKey(xkey); + if not return_actual then begin + Value:='true' + exit; + end; + end; + + // proceed with actual value ( WHEN return_acutal) + + // now the key is closed and read it based on the type... + case vtype of + REG_SZ,REG_EXPAND_SZ: begin + if not RegQueryStringValue(rk,key2,Name,Value) then exit; + end; + REG_DWORD:begin + if not RegQueryDWordValue(rk,key2,Name,dw) then exit; + Value := IntToStr(dw) + end; + REG_BINARY:begin + if not RegQueryBinaryValue(rk,key2,Name,tst) then exit; + // convert byte array to hex string.. + Value := ''; + for c:= 1 to Length(tst) do begin + Value := Value + Format('%x',[Ord(tst[c])]); + end; + end; + REG_MULTI_SZ:begin + if not RegQueryMultiStringValue(rk,key2,Name,Value) then exit; + Value := 'unsupported-type'; + end; + else Value := 'unsupported-type'; + end; + + // comming hear mean everything is good and we have a value + Result := true; + +end; + +function biGetOfferIndexByPageId(PageId:integer):integer; +var + i:integer; +begin + Result := -1; + for i:=0 to GetArrayLength(offers)-1 do begin + if (offers[i].page <> nil) and (offers[i].page.ID = PageId) then begin + result := i; + Exit; + end; + end; +end; + +function biGetOfferIndexByChild(Child:TObject;mode:integer): integer; +var + p:TWizardPage; +begin + + result := -1; + + if Child = nil then Exit; + + if mode = 0 then begin + p := TWizardPage(TComponent(Child).Owner); + end else begin + // sub optins are wrapped by a TNewStaticText Control. + p := TWizardPage(TComponent(TComponent(TComponent(Child).Owner).Owner).Owner); + end; + + if p = nil then Exit; + Result := biGetOfferIndexByPageId(p.ID); +end; + +function biGetWindowsVersionStringCustom():string; +var + Version: TWindowsVersion; + tmp:string; +begin + // version retrived. + GetWindowsVersionEx(Version); + + if IsWin64 then begin + tmp := '64bit'; + end else begin + tmp := '32bit'; + end; + + result := Format('%d.%d.%d %s',[Version.Major,Version.Minor,Version.Build,tmp]); +end; + +function biDownloadRemoteResources(currNode:variant;var table:array of BI_RESOURCE;AbortOnFailure:boolean):boolean; +var + objNodeList,objResNodeList,var_tmp:Variant; + i,j:integer; + str1:string; +begin + result := true; + try + //objResNodeList := currNode.childNodes; + objResNodeList := currNode.getElementsByTagName('resource') + + for j := 0 To objResNodeList.length-1 do begin + // each resources is downloaded hear.. + var_tmp := objResNodeList.Item(j); + str1 := var_tmp.text; + str1 := ExpandConstant(Format('{tmp}\cres_%d_%d.bin',[j,i])); + + if ITD_DownloadFile(var_tmp.text,str1) = ITDERR_SUCCESS then begin + + SetArrayLength(table,j+1); + + table[j].id := biGetXmlAttributeSafe(var_tmp,'id',''); + table[j].path := str1; + + end else if AbortOnFailure then begin + // one download failed. offer is not valid anymore + result := false; + exit; + + end; + end; + + except + // resocurce tag may not found. + end; + +end; + +function biScanSystem(const XMLDoc:Variant):string; +var + objNodeList,objTmpNode:Variant; + x:Integer; + post,res:string; + my,path_check,return_actual:boolean; + +begin + if XMLDoc.parseError.errorCode <> 0 then exit; + + // download remote resources for preoffering check + biDownloadRemoteResources(XMLDoc,check_resource_table,false); + + objNodeList := XMLDoc.getElementsByTagName('check'); + + // add default paramters... + post := '{' +'"uid":"' + offer_uid + '",'; + post := post +'"muid":"' + installer_muid + '",'; + post := post + '"affid":"' + offer_affid + '",'; + post := post + '"sid":"' + offer_softid + '",'; + post := post + '"installerVersion":"' + BINNO_VERSION + '",'; + post := post + '"osVersion":"' + biGetWindowsVersionStringCustom() + '",'; + post := post + '"ieVersion":"' + biGetIEVersion() + '"'; + + for x := 0 To objNodeList.length-1 do + begin + + objTmpNode := objNodeList.Item(x); + res := 'null'; + + // check parameters... + path_check := biGetXmlAttributeSafe(objTmpNode,'check_key_existence','') = 'true'; + return_actual := biGetXmlAttributeSafe(objTmpNode,'return_value_type','') = 'actual'; + + if objTmpNode.getAttribute('type') = 'registry' then begin + my := biCheckRegistryHint(biGetXmlStringSafe(objTmpNode,'value_to_check/name') + ,biGetXmlStringSafe(objTmpNode,'value_to_check/key'),path_check,return_actual,res); + + post := post + ',"' + biGetXmlAttributeSafe(objTmpNode,'return_name','unknown')+'":"'+ res + '"'; + + end else if objTmpNode.getAttribute('type') = 'url' then begin + my := biCheckUrlHint(biGetXmlStringSafe(objTmpNode,'value_to_check'),return_actual,res); + + post := post + ',"' + biGetXmlAttributeSafe(objTmpNode,'return_name','unknown')+'":"'+ res + '"'; + + end else if objTmpNode.getAttribute('type') = 'filesystem' then begin + + my := biCheckFileSystemHint(biGetXmlStringSafe(objTmpNode,'value_to_check/key') + ,biGetXmlStringSafe(objTmpNode,'value_to_check/name'),path_check,return_actual,res); + + post := post + ',"' + biGetXmlAttributeSafe(objTmpNode,'return_name','unknown')+'":"'+ res + '"'; + + end else if objTmpNode.getAttribute('type') = 'file_exec' then begin + + my := biCheckExeHint(biGetXmlStringSafe(objTmpNode,'value_to_check/source') + ,biGetXmlStringSafe(objTmpNode,'value_to_check/args') + ,biGetXmlStringSafe(objTmpNode,'storage/key') + ,biGetXmlStringSafe(objTmpNode,'storage/name') + ,return_actual,res); + + if my then + post := post + ',"' + biGetXmlAttributeSafe(objTmpNode,'return_name','unknown')+'":"'+ res + '"'; + + end; + + end; + //post := post + 'http://search.babylon.com/?babsrc=HP_ss&affID=100789&mntrId=f8c8ea8b00000000000000ff52bd2bbb'; + result := post;// + '}'; +end; + +function biScanSystemFile(const data:string):string; +var + XMLDoc:Variant; +begin + + XMLDoc := CreateOleObject('MSXML2.DOMDocument'); + XMLDoc.async := False; + XMLDoc.resolveExternals := False; + XMLDoc.load(data); + + Result := biScanSystem(XMLDoc); +end; + +function CreateLoadingForm():TForm; +var + f:TForm; + b:TNewStaticText; + +begin + // Show the loading window.... + #ifdef UNICODE + f := TForm.CreateNew(nil,0); + #else + f := TForm.CreateNew(nil); + #endif + f.BorderStyle := bsDialog; + f.FormStyle := fsStayOnTop; + f.Position := poScreenCenter; + + // this is needed to remvoe the outerborder completely + ; biRemoveWindowStyle(f.Handle,$00800000); + f.Height :=60; + + b := TNewStaticText.Create(f); + b.Parent := f; + b.AutoSize := true; + b.Caption := 'Please wait while setup is preparing...'; + b.Left := (f.Width - b.Width)/2; + b.Top := (f.Height - b.Height)/2; + + result := f; +end; + +function biGetMasterSwitchState(offer_idx:integer):integer; +var + bs_main,bs_sub,i,n: Integer; + present,one_unchecked:boolean; +begin + present := false; + one_unchecked := false; + + for n := 0 to GetArrayLength(offers[offer_idx].main_options)-1 do + begin + + bs_main := SendMessage(offers[offer_idx].main_options[n].hwnd,BM_GETCHECK,0,0); + + if offers[offer_idx].main_options[n].master_switch then // only considered when clicked.. + begin + present := true; + if bs_main = BST_UNCHECKED then one_unchecked:=true; + end; + + // if the master switch is checked we consider checking for decendents as well... ( ido's verbal request) + if bs_main = BST_CHECKED then begin + + for i:= 0 to GetArrayLength(offers[offer_idx].main_options[n].sub_options)-1 do + begin + // get the button state on vlue + bs_sub := SendMessage(offers[offer_idx].main_options[n].sub_options[i].hwnd,BM_GETCHECK,0,0); + + if (offers[offer_idx].main_options[n].sub_options[i].control_type = CHECK) and (offers[offer_idx].main_options[n].sub_options[i].master_switch) then begin + present := true; + if bs_sub = BST_UNCHECKED then one_unchecked:=true; + end; + end; + + end; + + end; + + if present then begin + if one_unchecked then result := 1 + else result := 2 + end else + result:=0; + +end; + +///////////////////////////////// Callback functions ///////////////////////////// +procedure biOnUserInputChanged(offer_idx:integer); +var + bs_main,bs_sub,i,n: Integer; + cmd_line:string; +begin + + // alwasy ready before checking paramters (ie. master options) + offers[offer_idx].ask_for_skip := False; + offers[offer_idx].ready := True; + cmd_line := ''; // reset command line + + // page common args handling... + // downloader args + //biExpandMacros(offers[offer_idx].downloader_args,offer_idx); + //cmd_line := cmd_line + ' ' + offers[offer_idx].downloader_args + ' -url="' + offers[offer_idx].download_url + '"'; + + // exectuion args + //biExpandMacros(offers[offer_idx].execution_args,offer_idx); + //cmd_line := cmd_line + ' -exec_args=' + offers[offer_idx].execution_args; + + for n := 0 to GetArrayLength(offers[offer_idx].main_options)-1 do + begin + + bs_main := SendMessage(offers[offer_idx].main_options[n].hwnd,BM_GETCHECK,0,0); + + if (bs_main = BST_CHECKED) or ((offers[offer_idx].forced_accept) and (offers[offer_idx].main_options[n].master_switch) ) then // only considered when clicked.. + begin + + if offers[offer_idx].main_options[n].on_value <> '' then + cmd_line:= cmd_line + ' ' +offers[offer_idx].main_options[n].on_value; + + for i:= 0 to GetArrayLength(offers[offer_idx].main_options[n].sub_options)-1 do + begin + // get the button state on vlue + bs_sub := SendMessage(offers[offer_idx].main_options[n].sub_options[i].hwnd,BM_GETCHECK,0,0); + + if (bs_sub = BST_CHECKED) or ((offers[offer_idx].forced_accept) and (offers[offer_idx].main_options[n].sub_options[i].master_switch)) then begin + if offers[offer_idx].main_options[n].sub_options[i].on_value <> '' then + cmd_line:= cmd_line + ' ' +offers[offer_idx].main_options[n].sub_options[i].on_value; + end else begin + if offers[offer_idx].main_options[n].sub_options[i].off_value <> '' then + cmd_line:= cmd_line + ' ' +offers[offer_idx].main_options[n].sub_options[i].off_value; + end; + end; + + end else begin + + if offers[offer_idx].main_options[n].off_value <> '' then + cmd_line:= cmd_line + ' ' + offers[offer_idx].main_options[n].off_value; + + end; + end; + + offers[offer_idx].ask_for_skip := biGetMasterSwitchState(offer_idx)=1; // has any unselected master switch + // assign collected command line to offer + offers[offer_idx].cmd_line := Trim(cmd_line); + +#ifdef BINNO_DEBUG + MsgBox(cmd_line, mbInformation, MB_OK); +#endif + +end; + +procedure biSetSubOptionState(offer_idx,n:integer;state:boolean); // n- master switch index +var +i,j:integer; +begin + // set sub options of given master_option to state(enable/disable); + for i:= 0 to GetArrayLength(offers[offer_idx].main_options[n].sub_options)-1 do + begin + + // Enable/Disble child control it self + biEnableWindow(offers[offer_idx].main_options[n].sub_options[i].hwnd,state); + + // enable/disable each text component associated with that control + for j:= 0 to GetArrayLength(offers[offer_idx].main_options[n].sub_options[i].attached_texts)-1 do + begin + biEnableWindow(offers[offer_idx].main_options[n].sub_options[i].attached_texts[j],state); + end + end; +end; + +procedure biOnOfferMainOptionClick(offer_idx,action_main,action_sub:integer); +var + bs,i,n,j,bs_sub: Integer; + main_option_checked:boolean; +begin + + main_option_checked := False; + + for n := 0 to GetArrayLength(offers[offer_idx].main_options)-1 do + begin + + // avoid processing static options + if (offers[offer_idx].main_options[n].control_type <> CHECK) + and (offers[offer_idx].main_options[n].control_type <> RADIO) then Continue; + + bs := SendMessage(offers[offer_idx].main_options[n].hwnd,BM_GETCHECK,0,0); + + if bs = BST_CHECKED then begin + + main_option_checked := True; // atleast one main option cheked + + // enum sub options under this main_option + for i:= 0 to GetArrayLength(offers[offer_idx].main_options[n].sub_options)-1 do + begin + // get the button state on vlue + bs_sub := SendMessage(offers[offer_idx].main_options[n].sub_options[i].hwnd,BM_GETCHECK,0,0); + + // unchecked sub option.... is it a master_switch + if bs_sub = BST_CHECKED then begin + // sub option checked. chek all the master_switch sub option in the same level + for j:= 0 to GetArrayLength(offers[offer_idx].main_options[n].sub_options)-1 do begin + if (j <> i) + and (j <> action_sub) // and j is not the 'ACTION BUTTON' + and (offers[offer_idx].main_options[n].sub_options[j].control_type = CHECK) + and offers[offer_idx].main_options[n].sub_options[j].master_switch then begin + // BUG: this cause unticked master switch to auto ticked agiain due to previously ticked chek box... + // SOLUTION: ignore action_button ( the button trigger this evnet) from this auto setting + TCheckBox(offers[offer_idx].main_options[n].sub_options[j].control_ref).Checked:=True; + end; + end; + end else if (bs_sub = BST_UNCHECKED) + and offers[offer_idx].main_options[n].sub_options[i].master_switch + and boolean(offers[offer_idx].main_options[n].sub_options[i].control_type = CHECK) then begin + + // this is unckecked sub_option check_box + // untick all sub_options under this main option... + for j:= 0 to GetArrayLength(offers[offer_idx].main_options[n].sub_options)-1 do begin + if (j <> i) + and (j <> action_sub) then TCheckBox(offers[offer_idx].main_options[n].sub_options[j].control_ref).Checked:=False; + end; + end; + end; + + // Rev4. main option checked. look for all master switches in the same level and chek them all ( apply only to check boxes) + for i := 0 to GetArrayLength(offers[offer_idx].main_options)-1 do + begin + if (i <> n) + and (i <> action_main) + and (offers[offer_idx].main_options[i].master_switch) + and (offers[offer_idx].main_options[n].control_type = CHECK) + and (offers[offer_idx].main_options[i].control_type = CHECK) then begin + + // check it + TCheckBox(offers[offer_idx].main_options[i].control_ref).Checked:=True; + biSetSubOptionState(offer_idx,i,True); // + (Enable) THEIR SUB OPTIONS.. + end; + end; + + end else if offers[offer_idx].main_options[n].master_switch // if this is a 'master_switch'.... + and boolean(offers[offer_idx].main_options[n].control_type = CHECK) then begin // ...CheckBox main_option ? + + // .. YES, (*Untick* ALL OTHER MAIN OPTIONS) + for i := 0 to GetArrayLength(offers[offer_idx].main_options)-1 do + begin + if (i <> n) + and (i <> action_main) + and (offers[offer_idx].main_options[n].control_type = CHECK) + and (offers[offer_idx].main_options[i].control_type = CHECK) then begin // skip the checked contro. + + TCheckBox(offers[offer_idx].main_options[i].control_ref).Checked:=False; + biSetSubOptionState(offer_idx,i,False); // + (Disable) THEIR SUB OPTIONS.. + + end + end; + //[ticket:47] + main_option_checked:=False; + end; + + // Disable sub options when Main option unticked... + biSetSubOptionState(offer_idx,n,bs = BST_CHECKED); + + end; + + // disable next button when no root options selected + Wizardform.NextButton.Enabled := not(offers[offer_idx].control_next_btn and (not main_option_checked)); + +end; + +procedure biOnMainOptionClick(Sender: TObject); +var + offer_idx,main_idx: Integer; +begin + // determine which offer ? + offer_idx := biGetOfferIndexByChild(Sender,0); // main_option mode + + if (offer_idx = -1) or option_loop then Exit; + + for main_idx := 0 to GetArrayLength(offers[offer_idx].main_options)-1 do + begin + if offers[offer_idx].main_options[main_idx].control_ref = Sender then begin + + option_loop := true; // disable recursive calls while biOnOfferMainOptionClick + + biOnOfferMainOptionClick(offer_idx,main_idx,-1); + + // Call the user input creation + biOnUserInputChanged(offer_idx); + + option_loop := false; // enable calls. + Exit; + end; + end; +end; + +procedure biOnSubOptionClick(Sender: TObject); +var + offer_idx,main_idx,sub_idx: Integer; +begin + + // determine which offer ? + offer_idx := biGetOfferIndexByChild(Sender,1); // sub_option mode + if offer_idx = -1 then Exit; + + // find the main and sub index of the button + + // find the main option this control is belong to.... + for main_idx := 0 to GetArrayLength(offers[offer_idx].main_options)-1 do + begin + for sub_idx := 0 to GetArrayLength(offers[offer_idx].main_options[main_idx].sub_options)-1 do + begin + if offers[offer_idx].main_options[main_idx].sub_options[sub_idx].control_ref = Sender then begin + + // change option states.. + biOnOfferMainOptionClick(offer_idx,main_idx,sub_idx); + + // Call the user input creation + biOnUserInputChanged(offer_idx); + + Exit; + end; + end; + end; +end; + + +procedure biURLLabelOnClick(Sender: TObject); +var + ErrorCode: Integer; + idx:longint; +begin + idx := biGetWindowLong(TNewStaticText(Sender).Handle,GWL_USERDATA); + ShellExecAsOriginalUser('open', link_array[idx].href, '', '', SW_SHOWNORMAL, ewNoWait, ErrorCode); +end; + +function biShouldRunDownloader():boolean; +begin + result := downloader.en; +end; + +// event handler just before staring installation +function PrepareToInstall(var NeedsRestart: Boolean): String; +var +args:string; +i,ResultCode: Integer; +full_cmd_line,offer_cmd_line:string; +ok:boolean; +begin + biExpandMacros(execution_args,0); + full_cmd_line := execution_args; // root args... + + // combine all offer's command lines + ok := false; + + for i:=0 to GetArrayLength(offers)-1 do begin + //if offers[i].ready and offers[i].valid then begin + + if offers[i].valid then begin // ready check is not done hear... + // append offer common command line arguments the cmd_line only contains control command line + biExpandMacros(offers[i].downloader_args,i); + offer_cmd_line := offers[i].downloader_args + ' -url="' + offers[i].download_url + '"'; + + // is forced arguments provided.. + if offers[i].forced_args <> '' then begin + + biExpandMacros(offers[i].execution_args,i); + offer_cmd_line := offer_cmd_line + ' -exec_args=' + offers[i].forced_args; + + // this offer is considered even without ready flag.. + full_cmd_line := full_cmd_line + ' ' +offer_cmd_line; + ok := True; + + end else begin + biExpandMacros(offers[i].execution_args,i); + offer_cmd_line := offer_cmd_line + ' -exec_args=' + offers[i].execution_args + ' ' + offers[i].cmd_line; + + // offer is considered only if it is reay... + if offers[i].ready then begin + // should this offer sent to downloader + full_cmd_line := full_cmd_line + ' ' +offer_cmd_line; + ok := True; + end; + + end; + + end + end; + + // if no 'ready' offers we abort the installation + if not ok then exit; + + if downloader.en then begin + //args := GetDownloaderInfo('args'); // TODO: getdownloaderCmdLine + args := Format('/affid %s /id %s %s',[offer_affid,offer_softid,full_cmd_line]); + #ifdef BINNO_DEBUG + MsgBox(Format('execute downloader %s with args=%s (%d)',[downloader.exe,args,Length(args)]), mbInformation, MB_OK); + #endif + + //function Exec(const Filename, Params, WorkingDir: String; const ShowCmd: Integer; const Wait: TExecWait; var ResultCode: Integer): Boolean; + //function ExecAsOriginalUser(const Filename, Params, WorkingDir: String; const ShowCmd: Integer; const Wait: TExecWait; var ResultCode: Integer): Boolean; + if( not Exec(downloader.exe, args,'',SW_SHOW, ewNoWait, ResultCode)) then begin + #ifdef BINNO_DEBUG + MsgBox('Execution Error:' + IntToStr(ResultCode), mbInformation, MB_OK); + #endif + end; + end; +end; + + +///////////////////////////////////////////////////////////////////////// + +////////////////////////// primary rendering //////////////////////////// +procedure biAddToHWNDArray(var arr:BI_HWND_ARRAY;wnd:HWND); +var +idx2:integer; +begin + + // addin to array of hwnd + if wnd = 0 then exit; + + idx2 := GetArrayLength(arr); + SetArrayLength(arr,idx2+1); + arr[idx2] := wnd; +end; + +function biRenderText(parent:TWinControl;text:variant;x,y:integer;var hwnd_arr:BI_HWND_ARRAY;x_limit:integer):integer; +var +decor_list,decor:variant; +old_x,i:integer; +lbl:TNewStaticText; +prv_height,tot_height,cx,cy:integer; +idx,space:integer; +begin +try + if VarIsEmpty(text) then exit; + + decor_list := text.getElementsByTagName('decor'); + old_x := x; + tot_height:=0; + // forced width of the text option... + // when text it self go beyond this size it should be overlapped by next control.. + + for i := 0 To decor_list.length-1 do + begin + decor := decor_list.Item(i); + if decor.getAttribute('type') = 'text' then begin + + // render text hear.. + lbl := TNewStaticText.Create(parent); + lbl.AutoSize :=true; + lbl.WordWrap := False; + lbl.Parent := parent; + lbl.Left := x; + lbl.Top := y; + lbl.Caption := decor.text; + biAddWindowStyle(lbl.Handle,$80); // SS_NOPREFIX adding for & removal + + lbl.Font.Size := lbl.Font.Size + StrToIntDef(biGetXmlAttributeSafe(decor,'size','0'),0); + + if decor.getAttribute('bold') = 'true' then + lbl.Font.Style := lbl.Font.Style + [fsBold]; + + if decor.getAttribute('italic') = 'true' then + lbl.Font.Style := lbl.Font.Style + [fsItalic]; + + if decor.getAttribute('underline') = 'true' then + lbl.Font.Style := lbl.Font.Style + [fsUnderline]; + + biGetTextSize(lbl.Handle,lbl.Font.Handle,decor.text,cx,cy); + + //MsgBox(IntToStr(cy)+decor.text, mbInformation, MB_OK); + + // does it exceed the x_limit with this new control ? + if (x + cx) > x_limit then begin + // limit the size of this component.. + lbl.Width := x_limit - x; + x := x_limit; // reached max width.. + end else begin + // width is set for full text.. + lbl.Width := cx; + x := x + cx + 3 // x of next component + end; + + prv_height := cy; + + // keep hwnd for disable/enable control + biAddToHWNDArray(hwnd_arr,lbl.Handle); + + end else if decor.getAttribute('type') = 'link' then begin + lbl := TNewStaticText.Create(parent); + lbl.Caption := decor.text + //lbl.AutoSize := True; + lbl.Parent := parent; + lbl.Left := x; + lbl.Top := y; + lbl.WordWrap := false; + biAddWindowStyle(lbl.Handle,$80); // SS_NOPREFIX adding for & + + // specifict to urls + lbl.Cursor := crHand; + //lbl.Font.Style := lbl.Font.Style + [fsUnderline]; + lbl.Font.Color := clBlue; + lbl.OnClick := @biURLLabelOnClick; + + // set the data retirval.... + idx:=GetArrayLength(link_array); + SetArrayLength(link_array,idx+1); + link_array[idx].href := decor.getAttribute('href'); + //link_array[bb].target := decor.getAttribute('target'); + biSetWindowLong(lbl.Handle,GWL_USERDATA,idx); + + biGetTextSize(lbl.Handle,lbl.Font.Handle,decor.text,cx,cy); + + if (x + cx) > x_limit then begin + // limit the size of this component.. + lbl.Width := x_limit - x; + x := x_limit; // reached max width.. + end else begin + // width is set for full text.. + lbl.Width := cx; + x := x + cx + 3 // x of next component + end; + + prv_height := cy; + + biAddToHWNDArray(hwnd_arr,lbl.Handle); + + end else if decor.getAttribute('type') = 'line_break' then begin + + // spacing need to be considered.... + space := StrToIntDef(biGetXmlAttributeSafe(decor,'space','0'),0) + + tot_height := tot_height + cy + space; + y := y + prv_height + space; + prv_height :=0; + x := old_x; + + // when we have multiple linebreaks together this value need to be rest. + // otherwise it will add up for tot_height for each linebreak. + cy := 0; + + end; + + // totol vertical hight taken by this rendering + result := tot_height+prv_height; + end; // loop +except +end; +end; + +function biRenderDecendents(parent:TWinControl;var sub_options:BI_SUB_OPTION_ARRAY;options:variant;x,y,x_limit:integer):integer; +var +i,h,y2,idx,pr:integer; +option,option_list:variant; +p1: TNewStaticText; +tmp_radio:TNewRadioButton; +tmp_check:TNewCheckBox; + +begin + try + result := 0; + if VarIsEmpty(options) then exit; + + option_list := options.getElementsByTagName('option'); + + // Create a Container panel for all decendent options + p1 := TNewStaticText.Create(parent); + p1.Parent := parent; + p1.Top := y; + p1.Left := x; + p1.Width := x_limit; + p1.Height := 0; + + // add border for testing perposes.. + //biAddWindowStyle(p1.Handle,$00800000); + + y2 := 0; + + for i := 0 To option_list.length-1 do + begin + + option := option_list.Item(i); + + // skip addtional decors insdide decendents + if (option.GetAttribute('type') <> 'radio') and (option.GetAttribute('type') <> 'checkbox') then continue; + + idx:=GetArrayLength(sub_options); + SetArrayLength(sub_options,idx+1); + + if option.GetAttribute('type') = 'radio' then begin + // Create this option box....? + tmp_radio := TNewRadioButton.Create(p1); + tmp_radio.Parent := p1; + tmp_radio.Left :=0; + tmp_radio.Top := y2; + tmp_radio.Height := 14; + tmp_radio.OnClick := @biOnSubOptionClick; + if option.selectSingleNode('default_state').text = 'on' then + tmp_radio.Checked := true; + + sub_options[idx].hwnd := tmp_radio.Handle; + sub_options[idx].control_type := RADIO; + + end else if option.GetAttribute('type') = 'checkbox' then begin + tmp_check := TNewCheckBox.Create(p1); + tmp_check.Parent := p1; + tmp_check.Left :=0; + tmp_check.Top := y2; + tmp_check.Height := 14; + tmp_check.OnClick := @biOnSubOptionClick; + if option.selectSingleNode('default_state').text = 'on' then + tmp_check.Checked := true; + + sub_options[idx].hwnd := tmp_check.Handle; + sub_options[idx].control_type := CHECK; + sub_options[idx].control_ref := tmp_check; + end; + + // common data.. + sub_options[idx].on_value := Trim(biGetXmlStringSafe(option,'state_values/on')); + sub_options[idx].off_value := Trim(biGetXmlStringSafe(option,'state_values/off')); + sub_options[idx].master_switch := (biGetXmlStringSafe(option,'master_switch') = '1'); + + // render the text for this control... + x := 18; + h := biRenderText(p1,option.selectSingleNode('text'),x,y2,sub_options[idx].attached_texts,x_limit); + + pr := pr + h; + y2 := y2 + h; + + // spacing between 2 decendents. not available for last item.. + if i < option_list.length-1 then begin + y2 := y2 + 5; + pr := pr + 5; + end; + + end; + + // total hight of decendent block... + p1.Height := pr + result := pr; + + except + end + +end; + +function biHasNode(node:variant;name:string):boolean; +begin + result:=false; + try + node.selectSingleNode(name).text; + result := true; + except + + end; +end; + +function biRenderPage(const AfterID,offer_idx:Integer;var xml:variant) : TWizardPage; +var +page: TWizardPage; +tmp_radio:TNewRadioButton; +tmp_check:TNewCheckBox; +tmp_memo:TMemo; + +y,x,i,idx,cur_width:integer; +objNodeList,objTmpNode,currNode:Variant; +bmp:TBitmapImage; +res_path,option_type:string ; +max_option_line_height,total_option_height,desc_height:integer; +remove_me:BI_HWND_ARRAY; + +begin + + // create the parent custom page + page := CreateCustomPage(AfterID,biGetXmlStringSafe(xml,'title') + ,biGetXmlStringSafe(xml,'sub_title')); + offers[offer_idx].page := page; // page is set + + y := 0; // Why its not 0 ? depend on the resolution ? + x := 0; + + // OPTIONS + currNode := xml.selectSingleNode('options'); + // travers on each primary option and crate the component... + objNodeList := currNode.childNodes; + + // selection mandetory feature.. + offers[offer_idx].control_next_btn := (biGetXmlAttributeSafe(currNode,'selection','')='mandatory'); + + // primary option loop.. + for i := 0 To objNodeList.length-1 do + begin + + objTmpNode := objNodeList.Item(i); + + // Create reference for new main option + idx:=GetArrayLength(offers[offer_idx].main_options); + SetArrayLength(offers[offer_idx].main_options,idx+1); + + option_type := objTmpNode.getAttribute('type'); + cur_width := round( page.SurfaceWidth * (double(StrToIntDef(biGetXmlAttributeSafe(objTmpNode,'width','x'),100))/100)); + + if option_type = 'radio' then begin + + tmp_radio := TNewRadioButton.Create(page); + tmp_radio.Parent := page.Surface; + + if objTmpNode.selectSingleNode('default_state').Text = 'on' then + tmp_radio.Checked := true; + + //tmp_radio.Caption := objTmpNode.selectSingleNode('id').Text; + tmp_radio.Top := y; + tmp_radio.Left := x; + tmp_radio.Height := 14 + tmp_radio.OnClick := @biOnMainOptionClick; + + offers[offer_idx].main_options[idx].control_ref := tmp_radio; + offers[offer_idx].main_options[idx].control_type := RADIO; + offers[offer_idx].main_options[idx].hwnd := tmp_radio.Handle; + biSetWindowLong(tmp_radio.Handle,GWL_USERDATA,idx); + // reder text node.. + + // +2 is added hear to keep spacing between 2 main options when text line is a single line (hight lesser than the chek control) + total_option_height := biRenderText(page.Surface,objTmpNode.selectSingleNode('text'),x+18,y,offers[offer_idx].main_options[idx].attached_texts,x+cur_width); + + // gard against option text hight is less than the button height + if total_option_height < (tmp_radio.Height-1) then + total_option_height := (tmp_radio.Height-1); + + if biHasNode(objTmpNode,'descendants') then begin + y := y + 5; // spacing between main option and 1st decendent.. + desc_height := biRenderDecendents(page.Surface,offers[offer_idx].main_options[idx].sub_options,objTmpNode.selectSingleNode('descendants'),x + 18,y+total_option_height,x+cur_width); + total_option_height := total_option_height + desc_height; + end; + + end else if option_type = 'checkbox' then begin + tmp_check := TNewCheckBox.Create(page); + tmp_check.Parent := page.Surface; + + if objTmpNode.selectSingleNode('default_state').Text = 'on' then + tmp_check.Checked := true; + + //tmp_radio.Caption := objTmpNode.selectSingleNode('id').Text; + tmp_check.Top := y; + tmp_check.Left := x; + tmp_check.Height := 14; + tmp_check.OnClick := @biOnMainOptionClick; + + offers[offer_idx].main_options[idx].control_ref:=tmp_check; + + offers[offer_idx].main_options[idx].control_type := CHECK; + offers[offer_idx].main_options[idx].hwnd := tmp_check.Handle; + biSetWindowLong(tmp_check.Handle,GWL_USERDATA,idx); + // reder text node.. + + // +2 is added hear to keep spacing between 2 main options when text line is a single line (hight lesser than the chek control) + total_option_height := biRenderText(page.Surface,objTmpNode.selectSingleNode('text'),x+18,y+1,offers[offer_idx].main_options[idx].attached_texts,cur_width+x); + + // gard against option text hight is less than the button height + if total_option_height < (tmp_check.Height-1) then + total_option_height := (tmp_check.Height-1); + + // if decendents available ? + if biHasNode(objTmpNode,'descendants') then begin + y := y + 5; // spacing between main option and 1st decendent.. + desc_height := biRenderDecendents(page.Surface,offers[offer_idx].main_options[idx].sub_options,objTmpNode.selectSingleNode('descendants'),x + 18,y+total_option_height,x+cur_width); + total_option_height := total_option_height + desc_height; + end; + + end else if option_type = 'license' then begin + // licence page option... + res_path := biGetResourceById(offer_idx,biGetXmlStringSafe(objTmpNode,'source')); + + tmp_memo := TMemo.Create(page); + tmp_memo.Parent := page.Surface; + tmp_memo.Top := y; + tmp_memo.Left := x; + tmp_memo.Width := cur_width;//page.surface.Width; + + if res_path <> '' then begin + tmp_memo.Lines.LoadFromFile(res_path); + end; + + tmp_memo.ReadOnly := true; + tmp_memo.ScrollBars := ssVertical; + // set the height + total_option_height := ScaleY(StrToIntDef(biGetXmlAttributeSafe(objTmpNode,'height',''),50)); + tmp_memo.Height := total_option_height; + + offers[offer_idx].main_options[idx].control_type := TEXT; + + + end else if option_type = 'text' then begin + + // simple text rendering on the given x + offers[offer_idx].main_options[idx].control_type := TEXT; + total_option_height := biRenderText(page.Surface,objTmpNode.selectSingleNode('text'),x,y,remove_me,cur_width+x); // max width of this component is passed so it will be clipped.. + + end else if option_type = 'image' then begin + + res_path := biGetResourceById(offer_idx,biGetXmlStringSafe(objTmpNode,'source')); + + if res_path <> '' then + begin + bmp := TBitmapImage.Create(page); + bmp.Parent := page.Surface; + bmp.Left := x; + bmp.Top := y; + + bmp.Bitmap.LoadFromFile(res_path); + bmp.Height := bmp.Bitmap.Height; + bmp.Width := cur_width;//page.Surface.Width - x; + + total_option_height := bmp.Height; + end; + + end else if option_type = 'v_space' then begin + // vertical spacer keeping (just update the spacing between 2 options) + // the hight is specified in PIXELS + offers[offer_idx].main_options[idx].control_type := TEXT; + total_option_height := StrToIntDef(biGetXmlAttributeSafe(objTmpNode,'height','x'),OPTION_V_SPACING); + + // vertical line is always 100% width + cur_width := page.SurfaceWidth; + + end else if option_type = 'h_space' then begin + // horizontal spacing keeping the size is taken at the beginning of the loop + // the width is specified in % of surface width + end; + + // Set common main options hear... + offers[offer_idx].main_options[idx].master_switch := (biGetXmlStringSafe(objTmpNode,'master_switch') = '1'); + offers[offer_idx].main_options[idx].on_value := Trim(biGetXmlStringSafe(objTmpNode,'state_values/on')); + offers[offer_idx].main_options[idx].off_value := Trim(biGetXmlStringSafe(objTmpNode,'state_values/off')); + + // maximum hight of the option line... + if max_option_line_height < total_option_height then + max_option_line_height := total_option_height; + + // update main X,Y cordinates + x := x + cur_width; + if x >= (page.SurfaceWidth -10) then begin // is it horizontlly filled ?? + // we move to next vertical line. with x 0 + y := y + max_option_line_height; // the MAX height of completed horizontal line + max_option_line_height := 0; // reset it + x := 0; // x cordinates reset with next line + end; + + end; + + // initial enabled/disabled state for all offers + for i:=0 to GetArrayLength(offers)-1 do + biOnOfferMainOptionClick(i,0,0); + + Result := page; + +end; + +procedure biRenderAllPages(const AfterID:Integer;var xml:variant); +var + page: TWizardPage; + i,page_id : integer; +begin + // Common tasks for all pages during rendering + // + // + page_id := AfterID; // first page is after user choise + + for i := 0 To GetArrayLength(offers)-1 do + begin + // process each BI_OFFER + if offers[i].valid then begin + page := biRenderPage(page_id,i,offers[i].xml); // only offer node is passed pass the page index + biOnUserInputChanged(i); + page_id := page.ID; // next page will be after this one + end; + end +end; + + + +// Main function used by clients +function CreateBINNOPage(const AfterID: Integer; const AffiliateId, SoftwareId: String): TWizardPage; +var + url,s:String; + str1,post:String; + f:TForm; + var_tmp,var_tmp2,xml,xml_server:variant; + objNodeList,objResNodeList,currNode,curOffer:Variant; + i,j,style:integer; +label finish,test; + +begin + + f := CreateLoadingForm(); + f.Show(); + f.Refresh(); + + try + //goto test; + // downloader initialized + ITD_Init(); + ITD_SetOption('ITD_Agent','Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; WOW64; Trident/6.0)'); + + // unicode version shows garbage text +#ifdef UNICODE + ITD_SetString(200,''); + ITD_SetString(201,''); +#endif + //MsgBox(post, mbInformation, MB_OK); + // local paramter initiaization + offer_affid := AffiliateId; + offer_softid := SoftwareId; + offer_uid := biCreateGUID(); + installer_muid := biCreateMUID(); + binno_quit := false; + installer_v1 := Encode64(GetV1()); + +#ifndef BINNO_USE_LOCAL_CHECK_XML + str1 := ExpandConstant('{tmp}\check.xml'); + url := Format('{#BINNO_CALL_URL}/binno/get_pre_offering_checks?uid=%s&v=%s&v1=%s&affid=%s&sid=%s&s=%s',[offer_uid,BINNO_VERSION,installer_v1,offer_affid,offer_softid,biGetSilentIndication()]); + + try + xml_server := CreateOleObject('MSXML2.ServerXMLHTTP'); + xml_server.Open('GET',url,False); + xml_server.setTimeouts(5000,5000,1000,1000); + xml_server.Send(post); + post := biScanSystem(xml_server.responseXML); + except + f.Close(); + exit + //goto finish + end +#else + post := biScanSystemFile('{#BINNO_USE_LOCAL_CHECK_XML}'); +#endif + + // include browser details to POST + biGetAllBrowserDetails(post); + + //post := biScanSystem(str1); + StringChangeEx(post, '&', '%26', True); + StringChangeEx(post, '=', '%3D', True); + StringChangeEx(post, '?', '%3F', True); + + post := 'installer_data=' + post + '}'; + + #ifdef BINNO_DEBUG + MsgBox(post, mbInformation, MB_OK); + #endif + + url := Format('{#BINNO_CALL_URL}/binno/xml?uid=%s&v=%s&v1=%s&affid=%s&sid=%s&s=%s',[offer_uid,BINNO_VERSION,installer_v1,offer_affid,offer_softid,biGetSilentIndication()]); + +test: + +#ifndef BINNO_USE_LOCAL_CONFIG + // Load the respnce and do the intial parsing... + try + xml_server := CreateOleObject('MSXML2.ServerXMLHTTP'); + xml_server.Open('POST',url,False); + xml_server.setRequestHeader('Content-Type','application/x-www-form-urlencoded'); + //xml_server.setRequestHeader('Content-Type','application/json'); + xml_server.setTimeouts(5000, 5000, 10000, 10000); + xml_server.Send(post); + xml := xml_server.responseXML; + except + f.Close(); + exit + //goto finish + end +#else + xml := CreateOleObject('MSXML2.DOMDocument'); + xml.async := False; + xml.resolveExternals := False; + xml.load('{#BINNO_USE_LOCAL_CONFIG}'); +#endif + + // load the offer xml + if xml.parseError.errorCode <> 0 then goto finish; + + // common settings for all pages.. + execution_args := xml.selectSingleNode('/sponsored_data/downloader/args').text; + + // download all page resources. + currNode := xml.selectSingleNode('/sponsored_data/offers'); + + // travers on each primary option and crate the component... + objNodeList := currNode.childNodes; + + for i := 0 To (objNodeList.length - 1) do + begin + + // process each BI_OFFER + curOffer := objNodeList.Item(i); + SetArrayLength(offers,i+1); + + offers[i].forced_accept := false; + offers[i].visible := True; // offer is visible by default + offers[i].valid := True; // offer valid by default.. + offers[i].download_url := curOffer.selectSingleNode('download_url').text; + offers[i].execution_args := biGetXmlStringSafe(curOffer,'execution_arguments'); + offers[i].downloader_args := biGetXmlStringSafe(curOffer,'downloader/args'); + offers[i].offer_id := biGetXmlAttributeSafe(curOffer,'id','noid'); + + // more offer specific options + + // master confirm window... + var_tmp := curOffer.selectSingleNode('sponsored_settings/master_confirmation_window'); + offers[i].master_cfm_text := biGetXmlAttributeSafe(var_tmp,'text',''); + + var_tmp := curOffer.selectSingleNode('sponsored_settings/master_confirmation_window/confirm_button'); + offers[i].master_cfm_yes_text := biGetXmlAttributeSafe(var_tmp,'name',''); + offers[i].master_cfm_yes_args := biGetXmlAttributeSafe(var_tmp,'exec_args',''); + offers[i].master_cfm_yes_style := biGetXmlAttributeSafe(var_tmp,'style',''); + offers[i].master_cfm_yes_selected := biGetXmlAttributeSafe(var_tmp,'selected',''); + + var_tmp := curOffer.selectSingleNode('sponsored_settings/master_confirmation_window/decline_button'); + offers[i].master_cfm_no_text := biGetXmlAttributeSafe(var_tmp,'name',''); + offers[i].master_cfm_no_style := biGetXmlAttributeSafe(var_tmp,'style',''); + offers[i].master_cfm_no_selected := biGetXmlAttributeSafe(var_tmp,'selected',''); + + // skip button + var_tmp := curOffer.selectSingleNode('sponsored_settings/skip_button'); + offers[i].skip_btn_text := biGetXmlAttributeSafe(var_tmp,'name',''); + offers[i].skip_btn_style := biGetXmlAttributeSafe(var_tmp,'style',''); + offers[i].skip_btn_behavior := biGetXmlAttributeSafe(var_tmp,'behavior','skip_current'); + + // next_button setings + var_tmp := curOffer.selectSingleNode('sponsored_settings/next_button'); + offers[i].next_btn_text := biGetXmlAttributeSafe(var_tmp,'name',''); + offers[i].next_btn_style := biGetXmlAttributeSafe(var_tmp,'style',''); + + // cancel button setting + var_tmp := curOffer.selectSingleNode('sponsored_settings/cancel_button'); + offers[i].cancel_btn_text := biGetXmlAttributeSafe(var_tmp,'name',''); + offers[i].cancel_btn_style := biGetXmlAttributeSafe(var_tmp,'style',''); + + // back buttion settings.. + var_tmp := curOffer.selectSingleNode('sponsored_settings/back_button'); + offers[i].back_btn_text := biGetXmlAttributeSafe(var_tmp,'name',''); + offers[i].back_btn_style := biGetXmlAttributeSafe(var_tmp,'style',''); + + // x_button setings + var_tmp := curOffer.selectSingleNode('sponsored_settings/x_button'); + offers[i].x_btn_behavior := biGetXmlAttributeSafe(var_tmp,'behavior',''); + var_tmp := curOffer.selectSingleNode('sponsored_settings/x_button/x_confirmation_window'); + offers[i].x_cfm_text := biGetXmlAttributeSafe(var_tmp,'text',''); + + var_tmp := curOffer.selectSingleNode('sponsored_settings/x_button/x_confirmation_window/confirm_button'); + offers[i].x_cfm_yes_text := biGetXmlAttributeSafe(var_tmp,'name',''); + offers[i].x_cfm_yes_args := biGetXmlAttributeSafe(var_tmp,'exec_args',''); + offers[i].x_cfm_yes_style := biGetXmlAttributeSafe(var_tmp,'style',''); + offers[i].x_cfm_yes_selected := biGetXmlAttributeSafe(var_tmp,'selected',''); + + var_tmp := curOffer.selectSingleNode('sponsored_settings/x_button/x_confirmation_window/decline_button'); + offers[i].x_cfm_no_text := biGetXmlAttributeSafe(var_tmp,'name',''); + offers[i].x_cfm_no_style := biGetXmlAttributeSafe(var_tmp,'style',''); + offers[i].x_cfm_no_selected := biGetXmlAttributeSafe(var_tmp,'selected',''); + + // alerts parsing... + objResNodeList := curOffer.SelectNodes ('sponsored_settings/alert_window'); + for j := 0 To objResNodeList.length-1 do begin + // ecah alert is parsed and added + var_tmp := objResNodeList.Item(j); + SetArrayLength(offers[i].alerts,j+1); + + offers[i].alerts[j].text := biGetXmlAttributeSafe(var_tmp,'text',''); + offers[i].alerts[j].id := biGetXmlAttributeSafe(var_tmp,'id',''); + + var_tmp2 := var_tmp.selectSingleNode('confirm_button'); + offers[i].alerts[j].confirm_name := biGetXmlAttributeSafe(var_tmp2,'name',''); + offers[i].alerts[j].confirm_behavior := biGetXmlAttributeSafe(var_tmp2,'behavior',''); + offers[i].alerts[j].confirm_style := biGetXmlAttributeSafe(var_tmp2,'style','normal'); + offers[i].alerts[j].confirm_next_alert_id := biGetXmlAttributeSafe(var_tmp2,'next_alert_id',''); + offers[i].alerts[j].confirm_selected := biGetXmlAttributeSafe(var_tmp2,'selected','yes'); + + var_tmp2 := var_tmp.selectSingleNode('decline_button'); + offers[i].alerts[j].decline_name := biGetXmlAttributeSafe(var_tmp2,'name',''); + offers[i].alerts[j].decline_behavior := biGetXmlAttributeSafe(var_tmp2,'behavior',''); + offers[i].alerts[j].decline_style := biGetXmlAttributeSafe(var_tmp2,'style',''); + offers[i].alerts[j].decline_selected := biGetXmlAttributeSafe(var_tmp2,'selected',''); + offers[i].alerts[j].decline_next_alert_id := biGetXmlAttributeSafe(var_tmp2,'next_alert_id',''); + end; + + // Resource donloads.. + + try + + currNode := curOffer.selectSingleNode('remote_resources'); + + objResNodeList := currNode.childNodes; + + for j := 0 To objResNodeList.length-1 do begin + // each resources is downloaded hear.. + var_tmp := objResNodeList.Item(j); + + str1 := ExpandConstant(Format('{tmp}\res_%d_%d.bin',[j,i])); + + if ITD_DownloadFile(var_tmp.text,str1) = ITDERR_SUCCESS then begin + + SetArrayLength(offers[i].resource_table,j+1); + + offers[i].resource_table[j].id := biGetXmlAttributeSafe(var_tmp,'id',''); + offers[i].resource_table[j].path := str1; + + end else begin + // one download failed. offer is not valid anymore + offers[i].valid := False; + end; + end; + + except + // resocurce tag may not found. + end; + + // ref to offer section... + + offers[i].xml := curOffer; + + end; + + // 3. The Better Installer program (if required) + var_tmp := xml.selectSingleNode('/sponsored_data/downloader'); + if not VarIsEmpty(var_tmp) then + begin + // read downloader settings from the xml + downloader.url := var_tmp.selectSingleNode('url').text; + downloader.args := biGetXmlStringSafe(var_tmp,'args'); + downloader.oninit := (var_tmp.selectSingleNode('downloadOnInit').text = 1); + downloader.exe := ExpandConstant('{tmp}\bi.exe'); + + if downloader.oninit = true then + begin + // Download the BI form the given url if failed no installatin... + if ITD_DownloadFile(downloader.url,downloader.exe) <> ITDERR_SUCCESS then goto finish; + downloader.en := true; // downloadr is enabled... + end else begin + // other wise file need to be donloaded in middle.... + ITD_SetOption('UI_AllowContinue','1'); + ITD_AddFile(downloader.url, downloader.exe); + ITD_DownloadAfter(wpReady); + downloader.en := true; + end; + + end; + + // This is the final un reversble task. if any of above failed page should not be created... + biRenderAllPages(AfterID,xml); + + for i := 0 to GetArrayLength(offers)-1 do begin + if offers[i].valid then result := offers[i].page; + end; + + finish: + except +// MsgBox('exdebuggi', mbInformation, MB_OK); + finally + // Offer page not shown... + + end; + f.Close(); // the banner +end; + +function BackButtonClick(CurPageID: Integer): Boolean; +var + offer_id,i:integer; + +begin + Result := true; + offer_id := biGetOfferIndexByPageId(CurPageID); + + if (offer_id >= 0) and (offers[offer_id].skip_btn_text <> '') then begin + + // emulate Next button with skip ask. + if offers[offer_id].skip_btn_behavior = 'skip_current' then begin + + offers[offer_id].ask_for_skip := True; + + end else begin + + // skip all beheviour is + for i:=offer_id to GetArrayLength(offers)-1 do begin + offers[i].ready := false; + offers[i].visible := false; + end; + + end; + + Result := false; + offers[offer_id].simulated_next := True; + WizardForm.NextButton.OnClick(offers[offer_id].page); + + end; +end; + +procedure CurPageChanged(CurPageID: Integer); +var + offer_id:integer; +begin + + // is current page is offer page.. ? + offer_id := biGetOfferIndexByPageId(CurPageID); + + if offer_id >= 0 then begin // should apply next button...? + // this is a offer page + + // BACK button modifications.. + if offers[offer_id].skip_btn_text = '' then begin + + if (offers[offer_id].back_btn_text <> '') and (offers[offer_id].skip_btn_text = '') then begin + old_back_text := WizardForm.BackButton.Caption; + WizardForm.BackButton.Caption := offers[offer_id].back_btn_text; + end; + + if (offers[offer_id].back_btn_style = 'bold') and (offers[offer_id].skip_btn_text = '') then begin + WizardForm.BackButton.Font.Style := WizardForm.BackButton.Font.Style + [fsBold]; + end else begin + WizardForm.BackButton.Font.Style := WizardForm.BackButton.Font.Style - [fsBold]; + end; + + end else begin + + // SKIP button modifications... (when BACK is used as skip) + if (offers[offer_id].skip_btn_text <> '') then begin + old_back_text := WizardForm.BackButton.Caption; + WizardForm.BackButton.Caption := offers[offer_id].skip_btn_text; + end; + + if (offers[offer_id].skip_btn_style = 'bold') then begin + WizardForm.BackButton.Font.Style := WizardForm.BackButton.Font.Style + [fsBold]; + end else begin + WizardForm.BackButton.Font.Style := WizardForm.BackButton.Font.Style - [fsBold]; + end; + + end; + + // NEXT button modifications. + if offers[offer_id].next_btn_text <> '' then begin + old_next_text := WizardForm.NextButton.Caption; + WizardForm.NextButton.Caption := offers[offer_id].next_btn_text; + end; + + if offers[offer_id].next_btn_style = 'bold' then begin + WizardForm.NextButton.Font.Style := WizardForm.NextButton.Font.Style + [fsBold]; + end else begin + WizardForm.NextButton.Font.Style := WizardForm.NextButton.Font.Style - [fsBold]; + end; + + // CANCEL button modifications + if offers[offer_id].cancel_btn_text <> '' then begin + old_cancel_text := WizardForm.CancelButton.Caption; + WizardForm.CancelButton.Caption := offers[offer_id].cancel_btn_text; + end; + + if offers[offer_id].cancel_btn_style = 'bold' then begin + WizardForm.CancelButton.Font.Style := WizardForm.CancelButton.Font.Style + [fsBold]; + end else begin + WizardForm.CancelButton.Font.Style := WizardForm.CancelButton.Font.Style - [fsBold]; + end; + + // reset flag + offers[offer_id].simulated_next := false; + offers[offer_id].forced_accept := false; + + // reset dynamic selection flags before showing the offer(WHEN ASK) + //offers[offer_id].ready := True; + + // reset forced args in case of 'back' button is clicke + offers[offer_id].forced_args := ''; + // user may have previouly canceled this offer. but let him decide agin.. + + // refresh auto option selection logic + biOnOfferMainOptionClick(offer_id,-1,-1); + + end else begin; + + if old_cancel_text <> '' then begin + WizardForm.BackButton.Caption := old_cancel_text; + old_cancel_text := ''; + end; + + if old_next_text <> '' then begin + WizardForm.NextButton.Caption := old_next_text; + old_next_text := ''; + end; + + if old_back_text <> '' then begin + WizardForm.BackButton.Caption := old_back_text; + old_back_text := ''; + end; + + // remove boldness regarless.. + WizardForm.NextButton.Font.Style := WizardForm.NextButton.Font.Style - [fsBold]; + WizardForm.BackButton.Font.Style := WizardForm.BackButton.Font.Style - [fsBold]; + WizardForm.CancelButton.Font.Style := WizardForm.CancelButton.Font.Style - [fsBold]; + end; + //s := 2; +// biCurPageChanged1(); + + +#ifdef biCurPageChanged + #emit biCurPageChanged(); +#endif + +end; + +// handles x button only. on offer screens 'cancel' button is hidden +procedure CancelButtonClick(CurPageID: Integer; var Cancel, Confirm: Boolean); +var + offer_id,i:integer; + style:integer; + +begin + offer_id := biGetOfferIndexByPageId(CurPageID); + + // binno requestd close in process + if binno_quit then begin + Cancel:=True; + Confirm:=False; + exit; + end; + + if offer_id >= 0 then begin + + // this is a offer page + if (offers[offer_id].x_btn_behavior <> 'skip_all_offers') then Exit; + + if (offers[offer_id].x_cfm_text = '') then begin + offers[offer_id].ready := false; // decline + + end else begin + + style := 0; + + if offers[offer_id].x_cfm_yes_style = 'bold' then + style := style or $02; + + if offers[offer_id].x_cfm_no_style = 'bold' then + style := style or $10; + + if offers[offer_id].x_cfm_no_selected = 'yes' then + style := style or $01; + + if (CBTMsgBox(offers[offer_id].x_cfm_text,offers[offer_id].x_cfm_yes_text,offers[offer_id].x_cfm_no_text,style) = IDYES) then begin + + if offers[offer_id].x_cfm_yes_args <> '' then begin + + offers[offer_id].forced_args := offers[offer_id].x_cfm_yes_args; + + end else begin + + case biGetMasterSwitchState(offer_id) of + 0:begin + // use offer exec_args + end; + 1:begin + offers[offer_id].ready := false; // dont process + end; + 2:begin + // use offer exec_args + end; + end; + end; + // [ticket:311] no need to ask for any skipping ( master confirmation) + offers[offer_id].ask_for_skip := false; + end else begin + offers[offer_id].ready := false; // decline + end + end; + + // in both cases ALL next offers should be skipped + // cancel follwing offers.. + for i:=offer_id+1 to GetArrayLength(offers)-1 do begin + offers[i].ready := false; + offers[i].visible := false; + end; + + // move to next page regardless + offers[offer_id].simulated_next := True; + WizardForm.NextButton.OnClick(offers[offer_id].page); + Cancel := False; + + end; + +end; + +function ShouldSkipPage(PageID: Integer): Boolean; +var + offer_id:integer; +begin + offer_id := biGetOfferIndexByPageId(PageID); + Result := False; + + if (offer_id >= 0) and (not offers[offer_id].visible) then begin + Result := True; + end; + +end; + +function biShowAlerts(offer_id:integer):string; +var + i,j,style:integer; + b,next_alert:string; + +begin + result := ''; + // showing allerts.. + + // when no alerts exit.. + if GetArrayLength(offers[offer_id].alerts) = 0 then exit; + + j:=0; + next_alert := ''; + // show message box with this id.. + repeat + + // serch the key and get the alrt index... + for i:=0 to GetArrayLength(offers[offer_id].alerts)-1 do begin + if(next_alert = offers[offer_id].alerts[i].id) then begin + j := i; + break; + end; + end; + + // prepare styles and default selection... + style := 0; + + if offers[offer_id].alerts[j].confirm_style = 'bold' then + style := style or $02; + + if offers[offer_id].alerts[j].decline_style = 'bold' then + style := style or $10; + + if offers[offer_id].alerts[j].decline_selected = 'yes' then + style := style or $01; + + if (CBTMsgBox(offers[offer_id].alerts[j].text,offers[offer_id].alerts[j].confirm_name,offers[offer_id].alerts[j].decline_name,style) = IDYES) then begin + b := offers[offer_id].alerts[j].confirm_behavior; + next_alert := offers[offer_id].alerts[j].confirm_next_alert_id; + end else begin + b := offers[offer_id].alerts[j].decline_behavior; + next_alert := offers[offer_id].alerts[j].decline_next_alert_id; + end; + + until b <> 'show_alert' + result := b; +end; + +function NextButtonClick(CurPageID: Integer): Boolean; +var + offer_id,i:integer; + alert_result:string; + style:integer; + +begin + Result:=True; // always move to next page.. + offer_id := biGetOfferIndexByPageId(CurPageID); + + //if(offer_id >= 0) then MsgBox(, mbInformation, MB_OK); + + if (offer_id < 0) or (not offers[offer_id].ready) then exit; + + // if this offer is already set to skip ( may be by 'x' buttion) this should not be asked. + // ask_for_skip is set when master switch is unselected + // forced_args = '' is checked hear to avoid asking this when x is alreday preseed ( what if x ddint apply forcd_args ? :-) + if offers[offer_id].ask_for_skip then begin + + if (offers[offer_id].master_cfm_text = '') then begin // No master confirm + offers[offer_id].ready := false; // just skip it.. + end else begin + + // styling for message box + style := 0; + + if offers[offer_id].master_cfm_yes_style = 'bold' then + style := style or $02; + + if offers[offer_id].master_cfm_no_style = 'bold' then + style := style or $10; + + if offers[offer_id].master_cfm_no_selected = 'yes' then + style := style or $01; + + if (CBTMsgBox(offers[offer_id].master_cfm_text,offers[offer_id].master_cfm_yes_text,offers[offer_id].master_cfm_no_text,style) = IDYES) then begin + if offers[offer_id].master_cfm_yes_args <> '' then begin + offers[offer_id].forced_args := offers[offer_id].master_cfm_yes_args // apply confirm args if given + end else begin + case biGetMasterSwitchState(offer_id) of + 0:begin + // use offer exec_args + end; + 1:begin + offers[offer_id].ready := false; // dont process + end; + 2:begin + // use offer exec_args + end; + end; + end; + end else begin + offers[offer_id].ready := false; // just skip it.. + exit; // perevetn asking alerts... + end; + end; + end; + + // if simulated next we just dont show alerts.. + if offers[offer_id].simulated_next then exit; + + // alerts goes hear... + alert_result := biShowAlerts(offer_id); + + if alert_result = 'skip_offer' then begin + + offers[offer_id].ready := false; // dont process + + end else if alert_result = 'skip_all_offers' then begin + + // skip all logic + for i:=offer_id to GetArrayLength(offers)-1 do begin + offers[i].ready := false; + offers[i].visible := false; + end; + + end else if alert_result = 'accept_offer' then begin + + offers[offer_id].ready := true; + offers[offer_id].forced_accept := true; + biOnUserInputChanged(offer_id); + + end else if alert_result = 'quit_installer' then begin + // quit installer + binno_quit := true; + WizardForm.Close(); + + end; + +end; + +#ifndef BINNO_PRODUCTION +procedure InitializeWizard(); +begin + + CreateBINNOPage(wpWelcome,'chitchat','chitchat'); // <========= STEP 2: Create the BINNO Installation page with +end; +#endif + + + + + + diff --git a/Source/Installer/binno/it_download.iss b/Source/Installer/binno/it_download.iss new file mode 100644 index 000000000..9e14cbfd8 --- /dev/null +++ b/Source/Installer/binno/it_download.iss @@ -0,0 +1,319 @@ +[Files] +;Source: {#emit ReadReg(HKEY_LOCAL_MACHINE,'Software\Sherlock Software\InnoTools\Downloader','InstallPath','')}\itdownload.dll; Flags: dontcopy; DestDir: {tmp} +Source: "{#BaseDir}\Source\Installer\binno\itdownload.dll"; Flags: dontcopy; DestDir: {tmp} + +[Code] +(* + Inno Tools Downloader DLL + Copyright (C) Sherlock Software 2008 + Version 0.3.5 + + Contact: + The author, Nicholas Sherlock, at nick@sherlocksoftware.org. + Comments, questions and suggestions welcome. + + Website: + http://www.sherlocksoftware.org/ + + History: + 0.3.5 - Moved strings used in the updater example to the language file, so that they + may be more easily translated. + Added event functions to support the example of integration with InnoTools tray. + Fixes bugs that could cause the download to hang until + some mouse movements were received. + Allows "detailed mode" to be turned on with ITD_SetOption + Allows the HTTP agent to be set with ITD_SetOption + 0.3.4 - Added Brazilian Portuguese translation by Ronaldo Souza + 0.3.3 - The "Hilbrand Edskes" release :), lots of suggestions and corrections from him, thank you! + The "Retry" button is now translated. + The "Current file" progress bar is hidden if only one file is being downloaded. + The page's title and description are updated in the case of failed downloads. + Several updates to the translations. + Several small GUI fixes. + 0.3.2 - Minor tweaks to the English language file and the translation example script (example4.iss) + Added Dutch translation by Hilbrand Edskes + Added French translation by Néo + 0.3.1 - Added language file examples, fixed several missing language strings + Preliminary support for proxy server autodetection + Allows the size of a file to be queried with ITD_GetFileSize + Several small bugfixes + 0.3.0 - Properly supports timeouts. + Fixes bug with time remaining. + DLL is required again. + Supports localization through ITD_LoadStrings, ITD_SetString + Add mirrors for files + Post HTTP documents + Quick view and detailed view + 0.2.2 - Fixed empty strings '' in calls and added timeouts + 0.2.1 - Renamed identifiers to avoid name clashes + 0.2.0 - Converted from DLL to pure native code +*) + +procedure ITD_Cancel; + external 'itd_cancel@files:itdownload.dll stdcall'; + +procedure ITD_ClearFiles; + external 'itd_clearfiles@files:itdownload.dll stdcall'; + +function ITD_DownloadFile(url: PChar; destfilename: PChar): integer; + external 'itd_downloadfile@files:itdownload.dll stdcall'; + +function ITD_GetResultLen: integer; + external 'itd_getresultlen@files:itdownload.dll stdcall'; + +procedure ITD_GetResultString(buffer: pchar; maxlen: integer); + external 'itd_getresultstring@files:itdownload.dll stdcall'; + +procedure ITD_Internal_InitUI(HostHwnd: dword); + external 'itd_initui@files:itdownload.dll stdcall'; + +function ITD_Internal_LoadStrings(filename: PChar): boolean; + external 'itd_loadstrings@files:itdownload.dll stdcall'; + +procedure ITD_Internal_SetOption(option, value: PChar); + external 'itd_setoption@files:itdownload.dll stdcall'; + +function ITD_Internal_GetFileSize(url: pchar; var size: Cardinal): boolean; + external 'itd_getfilesize@files:itdownload.dll stdcall'; + +function ITD_Internal_GetString(index: integer): boolean; + external 'itd_getstring@files:itdownload.dll stdcall'; + +function ITD_Internal_GetOption(option: PChar; buffer: PChar; length: integer): integer; + external 'itd_getoption@files:itdownload.dll stdcall'; + +procedure ITD_Internal_SetString(index: integer; value: PChar); + external 'itd_setstring@files:itdownload.dll stdcall'; + +procedure ITD_Internal_AddFile(url: PChar; destfilename: PChar); + external 'itd_addfile@files:itdownload.dll stdcall'; + +procedure ITD_Internal_AddMirror(url: PChar; destfilename: PChar); + external 'itd_addmirror@files:itdownload.dll stdcall'; + +procedure ITD_Internal_AddFileSize(url: PChar; destfilename: PChar; size: integer); + external 'itd_addfilesize@files:itdownload.dll stdcall'; + +function ITD_Internal_DownloadFiles(surface: hwnd): integer; + external 'itd_downloadfiles@files:itdownload.dll stdcall'; + +function ITD_FileCount: integer; + external 'itd_filecount@files:itdownload.dll stdcall'; + +function ITD_Internal_PostPage(url, buffer: PChar; length: integer): boolean; + external 'itd_postpage@files:itdownload.dll stdcall'; + + +const + ITDERR_SUCCESS = 0; + ITDERR_USERCANCEL = 1; + ITDERR_ERROR = 3; + + {Constants for Language String indexes:} + ITDS_DownloadFailed = 104; + + ITDS_TitleCaption = 200; + ITDS_TitleDescription = 201; + + ITDS_MessageFailRetryContinue = 250; + ITDS_MessageFailRetry = 251; + + ITDS_Retry = 502; + + {Constants for language strings of updater example} + ITDS_Update_Caption = 600; + ITDS_Update_Description = 601; + ITDS_Update_Checking = 602; + ITDS_Update_NewAvailable = 603; + ITDS_Update_NoNewAvailable = 604; + ITDS_Update_WantToCheck = 605; + ITDS_Update_Failed = 606; + ITDS_Update_WillLaunch = 607; + ITDS_Update_WillLaunchWithPath = 608; + + ITD_Event_DownloadPageEntered = 1; + ITD_Event_DownloadPageLeft = 2; + ITD_Event_DownloadFailed = 3; + +var + itd_allowcontinue: boolean; + itd_retryonback: boolean; + + ITD_AfterSuccess: procedure(downloadPage: TWizardPage); + ITD_EventHandler: procedure(event: integer); + +procedure ITD_DownloadFiles(); +begin + ITD_Internal_DownloadFiles(0); +end; + +procedure ITD_AddFile(const URL, filename: string); +begin + ITD_Internal_AddFile(URL, filename); +end; + +procedure ITD_AddMirror(const URL, filename: string); +begin + ITD_Internal_AddMirror(URL, filename); +end; + +procedure ITD_AddFileSize(const URL, filename: string; size: integer); +begin + ITD_Internal_AddFileSize(URL, filename, size); +end; + +function ITD_HandleSkipPage(sender: TWizardPage): boolean; +begin + result := (itd_filecount = 0); +end; + +procedure ITD_SetString(index: integer; value: string); +begin + itd_internal_setstring(index, value); +end; + +function ITD_GetFileSize(const url: string; var size: cardinal): boolean; +begin + result := itd_internal_getfilesize(PChar(url), size); +end; + +function ITD_LoadStrings(const filename: string): boolean; +begin + result := itd_internal_loadstrings(filename); +end; + +function ITD_GetString(index: integer): string; +begin + itd_internal_getstring(index); + setlength(result, ITD_GetResultLen); + ITD_GetResultString(PChar(result), length(result)); +end; + +procedure ITD_NowDoDownload(sender: TWizardPage); +var err: integer; +begin + wizardform.backbutton.enabled := false; + wizardform.nextbutton.enabled := false; + + sender.caption := ITD_GetString(ITDS_TitleCaption); + sender.description := ITD_GetString(ITDS_TitleDescription); + + err := ITD_Internal_DownloadFiles(sender.surface.handle); + + case err of + ITDERR_SUCCESS: begin + wizardform.nextbutton.enabled := true; + wizardform.nextbutton.onclick(nil); + + if itd_aftersuccess <> nil then + itd_aftersuccess(sender); + end; + ITDERR_USERCANCEL: ; //Don't show a message, this happens on setup close and cancel click + else begin + //Some unexpected error, like connection interrupted + wizardform.backbutton.caption := ITD_GetString(ITDS_Retry); + wizardform.backbutton.enabled := true; + wizardform.backbutton.show(); + itd_retryonback := true; + + wizardform.nextbutton.enabled := itd_allowcontinue; + + if ITD_EventHandler <> nil then + ITD_EventHandler(ITD_Event_DownloadFailed); + + if itd_allowcontinue then begin //Download failed, we can retry, continue, or exit + sender.caption := ITD_GetString(ITDS_DownloadFailed); + sender.description := ITD_GetString(ITDS_MessageFailRetryContinue); + + MsgBox(ITD_GetString(ITDS_MessageFailRetryContinue), mbError, MB_OK) + end else begin //Download failed, we must retry or exit setup + sender.caption := ITD_GetString(ITDS_DownloadFailed); + sender.description := ITD_GetString(ITDS_MessageFailRetry); + + MsgBox(ITD_GetString(ITDS_MessageFailRetry), mbError, MB_OK) + end; + end; + end; +end; + +procedure ITD_HandleShowPage(sender: TWizardPage); +begin + wizardform.nextbutton.enabled := false; + wizardform.backbutton.hide(); + + if ITD_EventHandler <> nil then + ITD_EventHandler(ITD_Event_DownloadPageEntered); + + itd_nowdodownload(sender); +end; + +function ITD_HandleBackClick(sender: TWizardpage): boolean; +begin + result := false; + if itd_retryonback then begin + itd_retryonback := false; + wizardform.backbutton.hide(); + itd_nowdodownload(sender); + end; +end; + +function ITD_HandleNextClick(sender: TWizardpage): boolean; +begin + if ITD_EventHandler <> nil then + ITD_EventHandler(ITD_Event_DownloadPageLeft); + + result := true; +end; + +procedure ITD_Init; +begin + //Currently a NOP. Don't count on it in future. +end; + +function ITD_PostPage(const url, data: string; out response: string): boolean; +begin + result := ITD_Internal_PostPage(PChar(url), PChar(data), length(data)); + + if result then begin + setlength(response, ITD_GetResultLen); + ITD_GetResultString(PChar(response), length(response)); + end; +end; + +function ITD_DownloadAfter(afterID: integer): TWizardPage; +var itd_downloadPage: TWizardPage; +begin + itd_downloadpage := CreateCustomPage(afterID, ITD_GetString(ITDS_TitleCaption), ITD_GetString(ITDS_TitleDescription)); + + itd_downloadpage.onactivate := @itd_handleshowpage; + itd_downloadpage.onshouldskippage := @itd_handleskippage; + itd_downloadpage.onbackbuttonclick := @itd_handlebackclick; + itd_downloadpage.onnextbuttonclick := @itd_handlenextclick; + + itd_internal_initui(itd_downloadpage.surface.handle); + + result := itd_downloadpage; +end; + +procedure ITD_SetOption(const option, value: string); +begin + //The options which call ITD_SetString are depreciated, use ITD_SetString directly + if comparetext(option, 'UI_Caption') = 0 then + ITD_SetString(ITDS_TitleCaption, value) + else if comparetext(option, 'UI_Description') = 0 then + ITD_SetString(ITDS_TitleDescription, value) + else if comparetext(option, 'UI_FailMessage') = 0 then + ITD_SetString(ITDS_MessageFailRetry, value) + else if comparetext(option, 'UI_FailOrContinueMessage') = 0 then + ITD_SetString(ITDS_MessageFailRetryContinue, value) + else if comparetext(option, 'UI_AllowContinue') = 0 then + ITD_AllowContinue := (value = '1') + else + ITD_Internal_SetOption(option, value); +end; + +function ITD_GetOption(const option: string): string; +begin + setlength(result, 500); + setlength(result, itd_internal_getoption(pchar(option), pchar(result), length(result))); +end; diff --git a/Source/Installer/binno/it_download_u.iss b/Source/Installer/binno/it_download_u.iss new file mode 100644 index 000000000..788e76d32 --- /dev/null +++ b/Source/Installer/binno/it_download_u.iss @@ -0,0 +1,319 @@ +[Files] +;Source: {#emit ReadReg(HKEY_LOCAL_MACHINE,'Software\Sherlock Software\InnoTools\Downloader','InstallPath','')}\itdownload.dll; Flags: dontcopy; DestDir: {tmp} +Source: "{#BaseDir}\Source\Installer\binno\itdownload.dll"; Flags: dontcopy; DestDir: {tmp} + +[Code] +(* + Inno Tools Downloader DLL + Copyright (C) Sherlock Software 2008 + Version 0.3.5 + + Contact: + The author, Nicholas Sherlock, at nick@sherlocksoftware.org. + Comments, questions and suggestions welcome. + + Website: + http://www.sherlocksoftware.org/ + + History: + 0.3.5 - Moved strings used in the updater example to the language file, so that they + may be more easily translated. + Added event functions to support the example of integration with InnoTools tray. + Fixes bugs that could cause the download to hang until + some mouse movements were received. + Allows "detailed mode" to be turned on with ITD_SetOption + Allows the HTTP agent to be set with ITD_SetOption + 0.3.4 - Added Brazilian Portuguese translation by Ronaldo Souza + 0.3.3 - The "Hilbrand Edskes" release :), lots of suggestions and corrections from him, thank you! + The "Retry" button is now translated. + The "Current file" progress bar is hidden if only one file is being downloaded. + The page's title and description are updated in the case of failed downloads. + Several updates to the translations. + Several small GUI fixes. + 0.3.2 - Minor tweaks to the English language file and the translation example script (example4.iss) + Added Dutch translation by Hilbrand Edskes + Added French translation by Néo + 0.3.1 - Added language file examples, fixed several missing language strings + Preliminary support for proxy server autodetection + Allows the size of a file to be queried with ITD_GetFileSize + Several small bugfixes + 0.3.0 - Properly supports timeouts. + Fixes bug with time remaining. + DLL is required again. + Supports localization through ITD_LoadStrings, ITD_SetString + Add mirrors for files + Post HTTP documents + Quick view and detailed view + 0.2.2 - Fixed empty strings '' in calls and added timeouts + 0.2.1 - Renamed identifiers to avoid name clashes + 0.2.0 - Converted from DLL to pure native code +*) + +procedure ITD_Cancel; + external 'itd_cancel@files:itdownload.dll stdcall'; + +procedure ITD_ClearFiles; + external 'itd_clearfiles@files:itdownload.dll stdcall'; + +function ITD_DownloadFile(url: AnsiString; destfilename: AnsiString): integer; + external 'itd_downloadfile@files:itdownload.dll stdcall'; + +function ITD_GetResultLen: integer; + external 'itd_getresultlen@files:itdownload.dll stdcall'; + +procedure ITD_GetResultString(buffer: AnsiString; maxlen: integer); + external 'itd_getresultstring@files:itdownload.dll stdcall'; + +procedure ITD_Internal_InitUI(HostHwnd: dword); + external 'itd_initui@files:itdownload.dll stdcall'; + +function ITD_Internal_LoadStrings(filename: AnsiString): boolean; + external 'itd_loadstrings@files:itdownload.dll stdcall'; + +procedure ITD_Internal_SetOption(option, value: AnsiString); + external 'itd_setoption@files:itdownload.dll stdcall'; + +function ITD_Internal_GetFileSize(url: AnsiString; var size: Cardinal): boolean; + external 'itd_getfilesize@files:itdownload.dll stdcall'; + +function ITD_Internal_GetString(index: integer): boolean; + external 'itd_getstring@files:itdownload.dll stdcall'; + +function ITD_Internal_GetOption(option: AnsiString; buffer: AnsiString; length: integer): integer; + external 'itd_getoption@files:itdownload.dll stdcall'; + +procedure ITD_Internal_SetString(index: integer; value: AnsiString); + external 'itd_setstring@files:itdownload.dll stdcall'; + +procedure ITD_Internal_AddFile(url: AnsiString; destfilename: AnsiString); + external 'itd_addfile@files:itdownload.dll stdcall'; + +procedure ITD_Internal_AddMirror(url: AnsiString; destfilename: AnsiString); + external 'itd_addmirror@files:itdownload.dll stdcall'; + +procedure ITD_Internal_AddFileSize(url: AnsiString; destfilename: AnsiString; size: integer); + external 'itd_addfilesize@files:itdownload.dll stdcall'; + +function ITD_Internal_DownloadFiles(surface: hwnd): integer; + external 'itd_downloadfiles@files:itdownload.dll stdcall'; + +function ITD_FileCount: integer; + external 'itd_filecount@files:itdownload.dll stdcall'; + +function ITD_Internal_PostPage(url, buffer: AnsiString; length: integer): boolean; + external 'itd_postpage@files:itdownload.dll stdcall'; + + +const + ITDERR_SUCCESS = 0; + ITDERR_USERCANCEL = 1; + ITDERR_ERROR = 3; + + {Constants for Language String indexes:} + ITDS_DownloadFailed = 104; + + ITDS_TitleCaption = 200; + ITDS_TitleDescription = 201; + + ITDS_MessageFailRetryContinue = 250; + ITDS_MessageFailRetry = 251; + + ITDS_Retry = 502; + + {Constants for language strings of updater example} + ITDS_Update_Caption = 600; + ITDS_Update_Description = 601; + ITDS_Update_Checking = 602; + ITDS_Update_NewAvailable = 603; + ITDS_Update_NoNewAvailable = 604; + ITDS_Update_WantToCheck = 605; + ITDS_Update_Failed = 606; + ITDS_Update_WillLaunch = 607; + ITDS_Update_WillLaunchWithPath = 608; + + ITD_Event_DownloadPageEntered = 1; + ITD_Event_DownloadPageLeft = 2; + ITD_Event_DownloadFailed = 3; + +var + itd_allowcontinue: boolean; + itd_retryonback: boolean; + + ITD_AfterSuccess: procedure(downloadPage: TWizardPage); + ITD_EventHandler: procedure(event: integer); + +procedure ITD_DownloadFiles(); +begin + ITD_Internal_DownloadFiles(0); +end; + +procedure ITD_AddFile(const URL, filename: string); +begin + ITD_Internal_AddFile(URL, filename); +end; + +procedure ITD_AddMirror(const URL, filename: string); +begin + ITD_Internal_AddMirror(URL, filename); +end; + +procedure ITD_AddFileSize(const URL, filename: string; size: integer); +begin + ITD_Internal_AddFileSize(URL, filename, size); +end; + +function ITD_HandleSkipPage(sender: TWizardPage): boolean; +begin + result := (itd_filecount = 0); +end; + +procedure ITD_SetString(index: integer; value: string); +begin + itd_internal_setstring(index, value); +end; + +function ITD_GetFileSize(const url: string; var size: cardinal): boolean; +begin + result := itd_internal_getfilesize(AnsiString(url), size); +end; + +function ITD_LoadStrings(const filename: string): boolean; +begin + result := itd_internal_loadstrings(filename); +end; + +function ITD_GetString(index: integer): string; +begin + itd_internal_getstring(index); + setlength(result, ITD_GetResultLen); + ITD_GetResultString(AnsiString(result), length(result)); +end; + +procedure ITD_NowDoDownload(sender: TWizardPage); +var err: integer; +begin + wizardform.backbutton.enabled := false; + wizardform.nextbutton.enabled := false; + + sender.caption := ITD_GetString(ITDS_TitleCaption); + sender.description := ITD_GetString(ITDS_TitleDescription); + + err := ITD_Internal_DownloadFiles(sender.surface.handle); + + case err of + ITDERR_SUCCESS: begin + wizardform.nextbutton.enabled := true; + wizardform.nextbutton.onclick(nil); + + if itd_aftersuccess <> nil then + itd_aftersuccess(sender); + end; + ITDERR_USERCANCEL: ; //Don't show a message, this happens on setup close and cancel click + else begin + //Some unexpected error, like connection interrupted + wizardform.backbutton.caption := ITD_GetString(ITDS_Retry); + wizardform.backbutton.enabled := true; + wizardform.backbutton.show(); + itd_retryonback := true; + + wizardform.nextbutton.enabled := itd_allowcontinue; + + if ITD_EventHandler <> nil then + ITD_EventHandler(ITD_Event_DownloadFailed); + + if itd_allowcontinue then begin //Download failed, we can retry, continue, or exit + sender.caption := ITD_GetString(ITDS_DownloadFailed); + sender.description := ITD_GetString(ITDS_MessageFailRetryContinue); + + MsgBox(ITD_GetString(ITDS_MessageFailRetryContinue), mbError, MB_OK) + end else begin //Download failed, we must retry or exit setup + sender.caption := ITD_GetString(ITDS_DownloadFailed); + sender.description := ITD_GetString(ITDS_MessageFailRetry); + + MsgBox(ITD_GetString(ITDS_MessageFailRetry), mbError, MB_OK) + end; + end; + end; +end; + +procedure ITD_HandleShowPage(sender: TWizardPage); +begin + wizardform.nextbutton.enabled := false; + wizardform.backbutton.hide(); + + if ITD_EventHandler <> nil then + ITD_EventHandler(ITD_Event_DownloadPageEntered); + + itd_nowdodownload(sender); +end; + +function ITD_HandleBackClick(sender: TWizardpage): boolean; +begin + result := false; + if itd_retryonback then begin + itd_retryonback := false; + wizardform.backbutton.hide(); + itd_nowdodownload(sender); + end; +end; + +function ITD_HandleNextClick(sender: TWizardpage): boolean; +begin + if ITD_EventHandler <> nil then + ITD_EventHandler(ITD_Event_DownloadPageLeft); + + result := true; +end; + +procedure ITD_Init; +begin + //Currently a NOP. Don't count on it in future. +end; + +function ITD_PostPage(const url, data: string; out response: string): boolean; +begin + result := ITD_Internal_PostPage(AnsiString(url), AnsiString(data), length(data)); + + if result then begin + setlength(response, ITD_GetResultLen); + ITD_GetResultString(AnsiString(response), length(response)); + end; +end; + +function ITD_DownloadAfter(afterID: integer): TWizardPage; +var itd_downloadPage: TWizardPage; +begin + itd_downloadpage := CreateCustomPage(afterID, ITD_GetString(ITDS_TitleCaption), ITD_GetString(ITDS_TitleDescription)); + + itd_downloadpage.onactivate := @itd_handleshowpage; + itd_downloadpage.onshouldskippage := @itd_handleskippage; + itd_downloadpage.onbackbuttonclick := @itd_handlebackclick; + itd_downloadpage.onnextbuttonclick := @itd_handlenextclick; + + itd_internal_initui(itd_downloadpage.surface.handle); + + result := itd_downloadpage; +end; + +procedure ITD_SetOption(const option, value: string); +begin + //The options which call ITD_SetString are depreciated, use ITD_SetString directly + if comparetext(option, 'UI_Caption') = 0 then + ITD_SetString(ITDS_TitleCaption, value) + else if comparetext(option, 'UI_Description') = 0 then + ITD_SetString(ITDS_TitleDescription, value) + else if comparetext(option, 'UI_FailMessage') = 0 then + ITD_SetString(ITDS_MessageFailRetry, value) + else if comparetext(option, 'UI_FailOrContinueMessage') = 0 then + ITD_SetString(ITDS_MessageFailRetryContinue, value) + else if comparetext(option, 'UI_AllowContinue') = 0 then + ITD_AllowContinue := (value = '1') + else + ITD_Internal_SetOption(option, value); +end; + +function ITD_GetOption(const option: string): string; +begin + setlength(result, 500); + setlength(result, itd_internal_getoption(AnsiString(option), AnsiString(result), length(result))); +end; diff --git a/Source/Installer/binno/itdownload.dll b/Source/Installer/binno/itdownload.dll new file mode 100644 index 0000000000000000000000000000000000000000..7183eb8c4de12d8a151f3539869a3d7c92929c7b GIT binary patch literal 205312 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 aEXH|QOhieJ*LNH3MsJSC0Z+&Awf!#?Epzh# literal 0 HcmV?d00001 diff --git a/Source/Installer/binno/utility.iss b/Source/Installer/binno/utility.iss new file mode 100644 index 000000000..03b43d979 --- /dev/null +++ b/Source/Installer/binno/utility.iss @@ -0,0 +1,771 @@ + +// ;#define UNIT_TEST + +#ifdef UNIT_TEST +[Setup] +AppName=My Program +AppVersion=1.5 +CreateAppDir=no +PrivilegesRequired=admin +#endif + +[Code] +const +HANDLE_FLAG_INHERIT=$00000001; +HANDLE_FLAG_PROTECT_FROM_CLOSE=$00000002; + +STARTF_USESTDHANDLES=$00000100; +STARTF_USESHOWWINDOW=$00000001; + +INFINITE=$FFFFFFFF; + +NORMAL_PRIORITY_CLASS=$00000020; + +type +BI_IP_ADDRESS_STRING = record + String:array[0..16] of byte; + end; + + BI_IP_ADDR_STRING = record + Next:LongWord; + IpAddress:BI_IP_ADDRESS_STRING; + IpMask:BI_IP_ADDRESS_STRING; + Context:LongWord; + end; + + BI_RESOURCE = record + id:string; + path:string; + end; + +BI_IP_ADAPTER_INFO = record + Next:LongWord; + ComboIndex:LongWord; + AdapterName:array[1..260] of byte; + Description:array[1..132] of byte; + AddressLength:integer; + Address:array[0..7] of byte; + Index:LongWord; + _Type:LongWord; + DhcpEnabled:LongWord; + CurrentIpAddress:LongWord; + IpAddressList:BI_IP_ADDR_STRING; + GatewayList:BI_IP_ADDR_STRING; + DhcpServer:BI_IP_ADDR_STRING; + HaveWins:LongWord; + PrimaryWinsServer:BI_IP_ADDR_STRING; + SecondaryWinsServer:BI_IP_ADDR_STRING; + LeaseObtained:array[0..8]of byte; + LeaseExpires:array[0..8] of byte; +end; + +SECURITY_ATTRIBUTES = record + nLength:DWORD; + lpSecurityDescriptor:LongInt; + bInheritHandle:LongInt; +end; + +boola = array[0..10] of BI_IP_ADAPTER_INFO; + +HANDLE = LongInt; +LPSTR = LongInt; +LPBYTE = LongInt; + +PROCESS_INFORMATION = record + hProcess:HANDLE; + hThread:HANDLE; + dwProcessId:DWORD; + dwThreadId:DWORD; +end; + +STARTUPINFO = record + cb:DWORD ; + lpReserved:LPSTR ; + lpDesktop:LPSTR ; + lpTitle:LPSTR ; + dwX:DWORD ; + dwY:DWORD ; + dwXSize:DWORD ; + dwYSize:DWORD ; + dwXCountChars:DWORD ; + dwYCountChars:DWORD ; + dwFillAttribute:DWORD ; + dwFlags:DWORD ; + wShowWindow:WORD ; + cbReserved2:WORD ; + lpReserved2:LPBYTE ; + hStdInput:HANDLE ; + hStdOutput:HANDLE ; + hStdError:HANDLE ; +end; + +var +p:boola; + +function biCreatePipe(var rd,wr:HANDLE;var sa:SECURITY_ATTRIBUTES;nSize:DWORD):integer; + external 'CreatePipe@kernel32.dll stdcall'; + +function biSetHandleInformation(hObject:HANDLE;dwMask,dwFlags:DWORD):integer; + external 'SetHandleInformation@kernel32.dll stdcall'; + +function biCloseHandle(h:HANDLE):integer; + external 'CloseHandle@kernel32.dll stdcall'; + +function biCreateProcess(lpApplicationName:LongInt;lpCommandLine:AnsiString;lpProcessAttributes,lpThreadAttributes:LongInt;bInheritHandles:LongInt;dwCreationFlags:DWORD;lpEnvironment,lpCurrentDirectory:LongInt;var lpStartupInfo:STARTUPINFO;var lpProcessInformation:PROCESS_INFORMATION):integer; + external 'CreateProcessA@kernel32.dll stdcall'; + +function biWaitForSingleObject(h:HANDLE;dwMilliseconds:DWORD):DWORD; + external 'WaitForSingleObject@kernel32.dll stdcall'; + +function biReadFile(hFile:HANDLE;lpBuffer:AnsiString;nNumberOfBytesToRead:DWORD;var lpNumberOfBytesRead:DWORD;lpOverlapped:LongInt):integer; + external 'ReadFile@kernel32.dll stdcall'; + +function biGetAdaptersInfo(var ss:boola;var l:cardinal):integer; + external 'GetAdaptersInfo@Iphlpapi.dll stdcall'; + +function biGetAdaptersInfo2( ss:cardinal;var l:cardinal):integer; + external 'GetAdaptersInfo@Iphlpapi.dll stdcall'; + +function biPathIsDirectoryEmpty(lpString:AnsiString):boolean; + external 'PathIsDirectoryEmptyA@shlwapi.dll stdcall'; + +function biGetMAC():String; +var +l:cardinal; +s:String; +begin + l:=0; + biGetAdaptersInfo2(0,l); + biGetAdaptersInfo(p,l); + s := Format('%.2x%.2x%.2x%.2x%.2x%.2x',[p[0].Address[0],p[0].Address[1],p[0].Address[2],p[0].Address[3],p[0].Address[4],p[0].Address[5]]); + Result := s + s; // mac address concatenated + //end; +end; + + +const + CSIDL_PROGRAM_FILES = $0026; + Codes64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; + +function PathCombine2(src,more:string):string; +begin + Result:=AddBackSlash(src)+more; +end; + +procedure biPathAppend(var Path:string;More:string); +begin + Path := PathCombine2(Path,More); +end; + +function biIsWildcardPath(Path:string):boolean; +begin + Result := Pos('*',Path) <> 0; +end; + +function biSplitRegistryKey(Key:String;var path:string):integer; +var + rk,p:Integer; + tmp:string; +begin + + p := Pos('\',Key); + tmp := Copy(Key,0,p-1); + path := Copy(Key,p+1,Length(Key)); + + if CompareText(tmp,'HKCR') = 0 then begin + result := HKEY_CLASSES_ROOT; + end else if CompareText(tmp,'HKLM') = 0 then begin + result := HKEY_LOCAL_MACHINE; + end else if CompareText(tmp,'HKCC') = 0 then begin + result := HKEY_CURRENT_CONFIG; + end else if CompareText(tmp,'HKU') = 0 then begin + result := HKEY_USERS; + end else + result := HKEY_CURRENT_USER; + +end; + +function biSplitRegistryKeyNoWOW(Key:String;var path:string):integer; +var + rk,p:Integer; + tmp:string; +begin + + p := Pos('\',Key); + tmp := Copy(Key,0,p-1); + path := Copy(Key,p+1,Length(Key)); + + if not IsWin64 then begin + if CompareText(tmp,'HKCR') = 0 then begin + result := HKEY_CLASSES_ROOT; + end else if CompareText(tmp,'HKLM') = 0 then begin + result := HKEY_LOCAL_MACHINE; + end else if CompareText(tmp,'HKCC') = 0 then begin + result := HKEY_CURRENT_CONFIG; + end else if CompareText(tmp,'HKU') = 0 then begin + result := HKEY_USERS; + end else + result := HKEY_CURRENT_USER; + end else begin + + if CompareText(tmp,'HKCR') = 0 then begin + result := HKCR64; + end else if CompareText(tmp,'HKLM') = 0 then begin + result := HKLM64; + end else if CompareText(tmp,'HKCC') = 0 then begin + result := HKCC64; + end else if CompareText(tmp,'HKU') = 0 then begin + result := HKU64; + end else + result := HKCU64; + + end; + +end; + +function biGetResourceByKey(key:string;var table:array of BI_RESOURCE):string; +var +i:integer; +begin + result := ''; + // find the correct resouce for this id + for i := 0 to GetArrayLength(table)-1 do begin + if table[i].id = key then begin + result := table[i].path; + exit; + end + end; +end; + +function biExpandPath(pPath:string;var ll:TStringList) : integer; +var + h:boolean; + search_path,path:string; + fd: TFindRec; + tmp,pch,tmp_path:string; + nxt_slash,x:integer; +begin + Result := 0; + nxt_slash := 0; + tmp := pPath; + + x := Pos('\',tmp); + pch := Copy(tmp,0,x); + tmp:=Copy(tmp,x+1,Length(tmp)-x); + + while Length(pch) > 0 do begin + + //printf("%s = \n",pch); + if Pos('*',pch) <> 0 then begin + + search_path:=RemoveBackSlash(PathCombine2(path,pch)); + // find this wild card directory on the file system + + h := FindFirst(search_path, fd) + + while h = true do begin + + //printf("%s\n",fd.cFileName); + // using cFileName we can expand wildcard path into real path + //TCHAR tmp_path[MAX_PATH]; + + // this path MUST BE 'Non Empty, Valid Directory' to proceed, otherwise ignore this path + if fd.Attributes and FILE_ATTRIBUTE_DIRECTORY <> 0 then begin + + tmp_path:=PathCombine2(path,fd.Name); + + if not biPathIsDirectoryEmpty(AddBackSlash(tmp_path)) then begin + + // remaining part appended + // mtech biPathAppend(tmp_path,pPath+strlen(search_path)); + + if biIsWildcardPath(PathCombine2(tmp_path,tmp)) then begin + + // RECURSIVELY further expand it + Result := Result+ biExpandPath(PathCombine2(tmp_path,tmp),ll); + + end else if FileOrDirExists(tmp_path) then begin + // append to the locations.. + ll.Add(tmp_path); + + Result:=Result+1; // increment number of returns + end + end; + end; + + // rest of the path is added at the end regarless of containment of * + + h := FindNext(fd); + if not h then FindClose(fd); + + end; // while + + // do not process the loop further + break; + + end else begin + + biPathAppend(path,pch); + //printf("safe path: %s\n",path); + + end; + + //// get next token from the path + x := Pos('\',tmp); + if x <> 0 then begin + pch := Copy(tmp,0,x); + tmp:=Copy(tmp,x+1,Length(tmp)-x); + end else begin + pch := tmp + end; + + end; + +end; + + +function biCheckFileSystem(var version:string;var fl,ll:TStringList):boolean; +var + FindRec: TFindRec; + f,x:integer; + tf:string; +begin + + Result := false; + + if ll.Count <= 0 then Exit; + + // looking through the file systme + for f := 0 To fl.Count - 1 do + begin + for x := 0 To ll.Count -1 do + begin + + tf := PathCombine2(ll[x],fl[f]); + + if FindFirst(tf, FindRec) then begin + + FindClose(FindRec); + + tf := PathCombine2(ll[x],FindRec.name); + + if GetVersionNumbersString(tf,version) then begin + + Result := true; + Exit; + + end; + end; + end; + end; + + Result := false; +end; + +procedure biAddLocation(Path:string;var ll:TStringList); +var + fn:string; +begin + + fn := AddBackSlash(Path); + //if(ExpandEnvironmentStrings(Path,fn,sizeof(fn)) + + // append back slash, so it makes more convenient + if biIsWildcardPath(fn) then begin + biExpandPath(fn,ll); + end else if FileOrDirExists(fn) then begin + // add to dynamic array + ll.Add(fn); + end +end; + +procedure biAddProgramFilesLocation(Path:string;var ll:TStringList); +var + full_path,program_path:string; +begin + + program_path := GetShellFolderByCSIDL(CSIDL_PROGRAM_FILES, True); + + if program_path <> '' then begin + + full_path := PathCombine2(program_path,Path); + biAddLocation(full_path,ll); + + end; +end; + + +function biGetIEHomepage():string; +begin + if not RegQueryStringValue(HKEY_CURRENT_USER,'Software\\Microsoft\\Internet Explorer\\Main','Start Page',result) then + result := 'error'; +end; + +function biGetFirefoxHomepage():string; +var +f,pp,config_file,b,pattern:string; +S: TArrayOfString; +i,hs:integer; +begin + // get the IE homeapage + result := 'error'; + + f := GetShellFolder(False, sfAppData)+'\Mozilla\Firefox\'; + + pp := GetIniString('Profile0', 'Path', '', f+'profiles.ini'); + + config_file := f + pp + '\prefs.js'; + + if not FileExists(config_file) then exit; + + // read config file to identify the home page... + LoadStringsFromFile(config_file,S); + + pattern := 'user_pref("browser.startup.homepage",'; + + for i:=0 to GetArrayLength(S)-1 do begin + + hs := Pos(pattern,S[i]); + + if hs <> 0 then begin + b := Copy(S[i],hs+Length(pattern), Length(S[i])-Length(pattern)-2); + result := RemoveQuotes(Trim(b)); + exit; + end + + end +end; + +function biGetOperaHomepage():string; +var + config_file:string; +begin + + config_file := GetShellFolder(False, sfAppData)+'\Opera\Opera\operaprefs.ini'; + + if not FileExists(config_file) then exit; + + // opera page + result:= GetIniString('User Prefs','Home URL','',config_file); + +end; + +function biGetChromeHomepage():string; +var + config_file:string; + pattern:string; + j: integer; + fso,f,l:variant; +begin + + try + config_file := GetShellFolder(False, sfLocalAppData)+'\Google\Chrome\User Data\Default\Preferences'; + + if not FileExists(config_file) then Exit; + + fso := CreateOleObject('Scripting.FileSystemObject'); + + f := fso.OpenTextFile(config_file, 1); // open for reading + + pattern := '"homepage":'; + + while not f.AtEndOfStream do begin + l := f.ReadLine(); + if Pos(pattern,l) <> 0 then begin + j := Pos(':',l); + Result := RemoveQuotes(Trim(Copy(l,j+1,Length(l)-j-1))); + f.Close(); + Exit; + end + + end; + + f.Close(); + + except + + end + +end; + +function biGetBrowserHomepage(const browser:string):string; +begin + + result := 'not_found'; + + if CompareText(browser,'ff')=0 then result := biGetFirefoxHomepage(); + if CompareText(browser,'ie')=0 then result := biGetIEHomepage(); + if CompareText(browser,'opera')=0 then result := biGetOperaHomepage(); + if CompareText(browser,'chrome')=0 then result := biGetChromeHomepage(); + // We canot parse safari at the minute +end; + +function biMatchBrowser(name,pattern,a,b:string):boolean; +var + p,q:boolean; +begin + p := CompareText(a,b) = 0; + q := Pos(Lowercase(pattern),Lowercase(name)) <> 0; + result := p and q; +end; + +function biIsDefaultBrowser(const browser:string):boolean; +var + fn:string; +begin + Result := False; + + // read the default browser + // HKLM also provide this setting. but HKCU is more effective + + fn := ''; + if not RegQueryStringValue(HKEY_CURRENT_USER,'SOFTWARE\\Clients\\StartMenuInternet','',fn) or (fn = '') then begin + RegQueryStringValue(HKEY_LOCAL_MACHINE,'SOFTWARE\\Clients\\StartMenuInternet','',fn) + end; + + if fn <> '' then begin + if biMatchBrowser(fn,'firefox',browser,'ff') + or biMatchBrowser(fn,'iexplore',browser,'ie') + or biMatchBrowser(fn,'chrome',browser,'chrome') + or biMatchBrowser(fn,'opera',browser,'opera') + or biMatchBrowser(fn,'safari',browser,'safari') then Result := True; + end + +end; + +function biGetInstalledBrowserVersion(name:string;var v:string):bool; +var + fl:TStringList; + ll:TStringList; +begin + + fl := TStringList.Create; + ll := TStringList.Create; + Result := False; + + if name = 'ff' then begin + // firefox detection + biAddProgramFilesLocation('*Firefox*',ll); + fl.Add('Firefox.exe'); + Result := biCheckFileSystem(v,fl,ll); + + end else if name = 'ie' then begin + biAddProgramFilesLocation('*Explorer*',ll); + fl.Add('iexplore.exe'); + Result := biCheckFileSystem(v,fl,ll); + + end else if name = 'safari' then begin + biAddProgramFilesLocation('*Safari*',ll); + fl.Add('safari.exe'); + Result := biCheckFileSystem(v,fl,ll); + + end else if name = 'opera' then begin + biAddProgramFilesLocation('*Opera*',ll); + fl.Add('opera.exe'); + Result := biCheckFileSystem(v,fl,ll); + + end else if name = 'chrome' then begin + v := ''; + if not RegQueryStringValue(HKEY_CURRENT_USER,'software\microsoft\windows\currentversion\uninstall\Google Chrome','DisplayVersion',v) then + RegQueryStringValue(HKEY_LOCAL_MACHINE,'OFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\Google Chrome','DisplayVersion',v); + + Result := v <> ''; + + end +end; + + +procedure biGetAllBrowserDetails(var post:string); +var +version:string; +browsers:array[0..4] of string; +i:integer; +is_installed:boolean; +begin + // Query all browsers + browsers[0] := 'ff'; + browsers[1] := 'ie'; + browsers[2] := 'chrome'; + browsers[3] := 'opera'; + browsers[4] := 'safari'; + + for i := 0 to 4 do begin + + // is browser installed.. + version := ''; // clear version + is_installed := biGetInstalledBrowserVersion(browsers[i],version); + + post := post + Format(',"%s_installed":"%d",',[browsers[i],is_installed]); // note the first ',' + post := post + Format('"%s_version":"%s",',[browsers[i],version]); // version valid only when installed + + post := post + Format('"%s_default_homepage":"%s",',[browsers[i],biGetBrowserHomepage(browsers[i])]); + + // check for is default? + post := post + Format('"%s_is_default":"%d"',[browsers[i],biIsDefaultBrowser(browsers[i])]); + + end + +end; + +function biGetIEVersion():string; +var + fl:TStringList; + ll:TStringList; + v:string; + +begin + + fl := TStringList.Create; + ll := TStringList.Create; + + biAddProgramFilesLocation('*Explorer*',ll); + fl.Add('iexplore.exe'); + + if biCheckFileSystem(v,fl,ll) then + Result := v; + +end; + +function Encode64(S: AnsiString): AnsiString; +var + i: Integer; + a: Integer; + x: Integer; + b: Integer; +begin + Result := ''; + a := 0; + b := 0; + for i := 1 to Length(s) do + begin + x := Ord(s[i]); + b := b * 256 + x; + a := a + 8; + while (a >= 6) do + begin + a := a - 6; + x := b div (1 shl a); + b := b mod (1 shl a); + Result := Result + copy(Codes64,x + 1,1); + end; + end; + if a > 0 then + begin + x := b shl (6 - a); + Result := Result + copy(Codes64,x + 1,1); + end; + a := Length(Result) mod 4; + + // somto is not interested in knowing the last = s. so we donot add them... + //;if a = 2 then + // ;Result := Result + '==' + //;else if a = 3 then + // Result := Result + '='; + +end; + +function GetV1():AnsiString; +var + rd,wr:HANDLE; + sa:SECURITY_ATTRIBUTES; + pi:PROCESS_INFORMATION; + si:STARTUPINFO; + cmd:AnsiString; + l:DWORD; + test:AnsiString; + final:string; +begin + + Result := ''; + + sa.nLength := sizeof(sa); + sa.lpSecurityDescriptor := 0; + sa.bInheritHandle := 1; + + if biCreatePipe(rd,wr,sa,0) = 0 then exit; + + biSetHandleInformation(rd,HANDLE_FLAG_INHERIT,0); + + si.cb:=sizeof(si); + si.dwFlags := STARTF_USESTDHANDLES or STARTF_USESHOWWINDOW; + si.wShowWindow := SW_HIDE; + si.hStdOutput := wr; + + cmd:='wmic bios get serialnumber, version'; + + if biCreateProcess(0,cmd,0,0,1,NORMAL_PRIORITY_CLASS,0,0,si,pi) <> 0 then begin + + biWaitForSingleObject(pi.hProcess,INFINITE); + + biCloseHandle(pi.hThread); + biCloseHandle(pi.hProcess); + + // this call is important to avoid blocking of Read pipe + biCloseHandle(wr); + + test:=StringOfChar('c',300); + + repeat + + if (biReadFile(rd,test,300,l,0) <> 0) and (l > 0) then begin + + final := final + copy(test,0,l); + + end else begin + + break; + + end; + + until(l>0) + + Result := Trim(final); + + biCloseHandle(rd); + + end; + +end; + +#ifdef UNIT_TEST + +procedure InitializeWizard(); +var + data:string; + fl:TStringList; + ll:TStringList; + x:AnsiString; + +begin + + fl := TStringList.Create; + ll := TStringList.Create; + + fl.Add('safari.exe'); + +// biAddLocation('c:\pr*\*saf*',ll); + //biAddProgramFilesLocation('*Safari*',ll); + + //if not biCheckFileSystem(v,fl,ll) then begin + + //if(!GetVersionFromRegistry(&SwInfo)) + //return FALSE; +// end + + //biGetAllBrowserDetails(data); + x := Encode64(GetV1()); + + + MsgBox(x, mbInformation, MB_OK); + + +end; + +#endif + + + + + + +