library SKSE_Elys_Uncapper;{$R *.res}uses Windows, SysUtils, IniFiles, Classes;type PluginHandle = Longword; SKSEInterface = packed record skseVersion: Longword; runtimeVersion: Longword; editorVersion: Longword; isEditor: Longword; QueryInterface: procedure(id: Longword); cdecl; GetPluginHandle: function: PluginHandle; cdecl; end; PluginInfo = packed record const kInfoVersion: Longword = 1; var infoVersion: Longword; name: PAnsiChar; version: Longword; end; TPerkByLevel = record Level: Word; Perks: Byte; end;const kPluginHandle_Invalid = $FFFFFFFF; SKYRIM_VER = $04150000; SKSE_VER = $01040080; PLUGIN_VER = $01050050; oPLAYER = Pointer($0137DC6C); AlterationID = 18; ArcheryID = 8; AlchemyID = 16; ConjurationID = 19; BlockID = 9; LightArmorID = 12; DestructionID = 20; HeavyArmorID = 11; LockpickingID = 14; EnchantingID = 23; OneHandedID = 6; PickpocketID = 13; IllusionID = 21; SmithingID = 10; SneakID = 15; RestorationID = 22; TwoHandedID = 7; SpeechID = 17;var Enabled: Boolean = False; SkillEffectFormulasCap: Single; pSkillEffectFormulasCap: PSingle; FixSneakOver100CheckCode: packed Array [0 .. 5] of Byte = ( $9E, // sahf $9B, // wait $DF,$E0, // fstsw ax $90, // nop $73 // jae ); SkillCaps: packed Array [6 .. 23] of Single; SkillMults: Array [6 .. 23] of Single; PerksPerLevel: Array of TPerkByLevel; LastValidedSkill: Longword; CurrentSkillCap : Single; CurrentSkillLevel : Single; pSkillCapPatch: Pointer; pSkillCapPatch2: Pointer; pIncreasePerkPool : Pointer; pIncreaseSkillExp : Pointer; SLParam1, SLParam2 : Single; GetActorLevel: function(a1, a2: Integer; This: Pointer): Word; cdecl; CalculateExpForNextLevel: function( SkillLevel, SLParam1, SLParam2 : Single ): Single; cdecl; GetSkillCoefficients: function(a1, a2, a3 : Longint; a4, a5: PSingle): Byte; cdecl;function Overwrite(Address: Pointer; Data: Pointer; Size: Longword): Boolean;var OldFlag: Longword;begin Result := False; if VirtualProtect(Address, Size, PAGE_EXECUTE_READWRITE, @OldFlag) then Move(Data^, Address^, Size) else begin MessageBox(0, 'SKSE Elys Uncapper could not modify Skyrim in memory. PLEASE CLOSE THE GAME NOW TO AVOID POTENTIAL GAME INCONSISTANCIES OR CORRUPTION', 'Error', MB_ICONERROR); Exit; end; Result := True;end;function SetFF15Call(Address: Pointer; Target: Pointer): Boolean;var Jmp: packed record Code: Word; Target: Pointer; end;begin Jmp.Code := $15FF; Jmp.Target := Target; Result := Overwrite(Address, @Jmp, 6);end;function SetE8Call(Address: Pointer; Target: Pointer): Boolean;var Jmp: packed record Code: Byte; Target: Longword; end;begin Jmp.Code := $E8; Jmp.Target := Longword(Target) - Longword(Address) - 5; Result := Overwrite(Address, @Jmp, 5);end;Function ReplaceE8CallTarget(Caller: PByte; Target: Pointer): Boolean;var Address: Longword;begin Address := Longword(Target) - Longword(Caller) - 5; Result := Overwrite(Pointer(Caller + 1), @Address, 4);end;function hook_GetSkillCoefficients(a1, a2, a3 : Longint; a4, a5: PSingle): Byte; cdecl;begin Result := GetSkillCoefficients(a1, a2, a3, a4, a5); SLParam1 := a4^; SLParam2 := a5^end;procedure IncreasePerkPool;const PerkOffset = $6C9; // offset to update with each new Skyrim versionvar This: PByte; Level: Word; i: Integer; b: Integer;begin asm pushad mov This, eax end; Level := GetActorLevel(0, 0, This); b:= -1; for i := 0 to High(PerksPerLevel) do if Level < PerksPerLevel[i].Level then break else if Level = PerksPerLevel[i].Level then begin b := PerksPerLevel[i].Perks; break; end else b := PerksPerLevel[i].Perks; if b >= 0 then b := (This + PerkOffset)^ + b else b := (This + PerkOffset)^ + 1; if (This + PerkOffset)^ < b then (This + PerkOffset)^ := b; asm popad end;end;procedure IncreaseSkillExp_Sub(This: PByte; pExpEarned: PSingle); stdcall;begin if (PSingle(This + 8)^ = 0) and (CurrentSkillLevel < SkillCaps[LastValidedSkill]) then begin PSingle(This + 4)^ := 0; PSingle(This + 8)^ := PSingle(This + 4)^ + CalculateExpForNextLevel(CurrentSkillLevel+1, SLParam1, SLParam2); end; pExpEarned^ := pExpEarned^ * SkillMults[LastValidedSkill];end;procedure IncreaseSkillExp;asm mov [edx+6B4h], ebx lea ebx, [esp+24h] pushad push ebx push esi call IncreaseSkillExp_Sub popadend;procedure GetSkill;asm mov ecx,3F800000h mov LastValidedSkill, esiend;procedure IsValidSkill;asmlea eax, [edi-6]mov ebp, ecxmov LastValidedSkill, ediend;procedure SkillCapPatch;begin CurrentSkillCap := SkillCaps[LastValidedSkill]; asm fld CurrentSkillCap end;end;procedure SkillCapPatch2;asm mov eax, [esp+12] mov CurrentSkillLevel, eax jmp SkillCapPatchend;function InitOptions: Boolean;const INI_ERROR = 'Error: Skyrim_Elys_Uncapper.ini'; IS_INVALID_VALUE = 'http://forums.bethsoft.com/topic/1287595-relwip-skyrim-elys-uncapper/is not a valid value for'; MAX_LEVEL = 10000;var Filename: String; IniFile: TMemIniFile; Strings: TStringList; i, j: Integer; Level: Integer; Perks: Integer; PerksPerLevelIndex: Integer; function SafeReadInteger(const name: String; Default: Integer): Integer; var idx: Integer; begin idx := Strings.IndexOfName(Name); if idx < 0 then Result := Default else if TryStrToInt(Strings.ValueFromIndex[idx], Result) and (Result >= 0) and (Result <= MAX_LEVEL) then Exit; Result := Default; MessageBox(0, PChar(Strings.ValueFromIndex[idx] + IS_INVALID_VALUE + Name + '.'), INI_ERROR, MB_ICONERROR); end; function SafeReadFloat(const name: String; Default: Single): Single; var idx: Integer; begin idx := Strings.IndexOfName(Name); if idx < 0 then Result := Default else if TryStrToFloat(Strings.ValueFromIndex[idx], Result) and (Result >= 0) and (Result <= MAX_LEVEL) then Exit; Result := Default; MessageBox(0, PChar(Strings.ValueFromIndex[idx] + IS_INVALID_VALUE + Name + '.'), INI_ERROR, MB_ICONERROR); end; function SafeReadBool(const name: String; Default: Boolean): Boolean; var idx: Integer; begin idx := Strings.IndexOfName(Name); if idx < 0 then Result := Default else if not TryStrToBool(Strings.ValueFromIndex[idx], Result) then begin Result := Default; MessageBox(0, PChar(Strings.ValueFromIndex[idx] + IS_INVALID_VALUE + Name + '.'), INI_ERROR, MB_ICONERROR); Exit; end; end;begin Result := True; SetLength(Filename, MAX_PATH + 1); GetModuleFileNameW(HInstance, @Filename[1], MAX_PATH + 1); IniFile := TMemIniFile.Create(ExtractFilePath(Filename) + 'SKSE_Elys_Uncapper.ini'); PerksPerLevelIndex := 0; try Strings := TStringList.Create; try FormatSettings.DecimalSeparator := '.'; SetLength(TrueBoolStrs, 2); TrueBoolStrs[0] := 'True'; TrueBoolStrs[1] := '1'; SetLength(FalseBoolStrs, 2); FalseBoolStrs[0] := 'False'; FalseBoolStrs[1] := '0'; IniFile.ReadSectionValues('General', Strings); Enabled := SafeReadBool('bEnabled', False); SkillEffectFormulasCap := SafeReadInteger('iSkillEffectFormulasCap', 100); Strings.Clear; IniFile.ReadSectionValues('SkillCaps', Strings); SkillCaps[AlterationID] := SafeReadInteger('iAlteration', 100); SkillCaps[ArcheryID] := SafeReadInteger('iArchery', 100); SkillCaps[AlchemyID] := SafeReadInteger('iAlchemy', 100); SkillCaps[ConjurationID] := SafeReadInteger('iConjuration', 100); SkillCaps[BlockID] := SafeReadInteger('iBlock', 100); SkillCaps[LightArmorID] := SafeReadInteger('iLightArmor', 100); SkillCaps[DestructionID] := SafeReadInteger('iDestruction', 100); SkillCaps[HeavyArmorID] := SafeReadInteger('iHeavyArmor', 100); SkillCaps[LockpickingID] := SafeReadInteger('iLockpicking', 100); SkillCaps[EnchantingID] := SafeReadInteger('iEnchanting', 100); SkillCaps[OneHandedID] := SafeReadInteger('iOneHanded', 100); SkillCaps[PickpocketID] := SafeReadInteger('iPickpocket', 100); SkillCaps[IllusionID]:= SafeReadInteger('iIllusion', 100); SkillCaps[SmithingID] := SafeReadInteger('iSmithing', 100); SkillCaps[SneakID] := SafeReadInteger('iSneak', 100); SkillCaps[RestorationID]:= SafeReadInteger('iRestoration', 100); SkillCaps[TwoHandedID] := SafeReadInteger('iTwoHanded', 100); SkillCaps[SpeechID] := SafeReadInteger('iSpeech', 100); Strings.Clear; IniFile.ReadSectionValues('SkillExpGainMults', Strings); SkillMults[AlterationID] := SafeReadFloat('fAlteration', 1); SkillMults[ArcheryID] := SafeReadFloat('fArchery', 1); SkillMults[AlchemyID] := SafeReadFloat('fAlchemy', 1); SkillMults[ConjurationID] := SafeReadFloat('fConjuration', 1); SkillMults[BlockID] := SafeReadFloat('fBlock', 1); SkillMults[LightArmorID] := SafeReadFloat('fLightArmor', 1); SkillMults[DestructionID] := SafeReadFloat('fDestruction', 1); SkillMults[HeavyArmorID] := SafeReadFloat('fHeavyArmor', 1); SkillMults[LockpickingID] := SafeReadFloat('fLockpicking', 1); SkillMults[EnchantingID] := SafeReadFloat('fEnchanting', 1); SkillMults[OneHandedID] := SafeReadFloat('fOneHanded', 1); SkillMults[PickpocketID] := SafeReadFloat('fPickpocket', 1); SkillMults[IllusionID] := SafeReadFloat('fIllusion', 1); SkillMults[SmithingID] := SafeReadFloat('fSmithing', 1); SkillMults[SneakID] := SafeReadFloat('fSneak', 1); SkillMults[RestorationID] := SafeReadFloat('fRestoration', 1); SkillMults[TwoHandedID] := SafeReadFloat('fTwoHanded', 1); SkillMults[SpeechID] := SafeReadFloat('fSpeech', 1); Strings.Clear; IniFile.ReadSectionValues('PerksPerLevelAndAbove', Strings); SetLength(PerksPerLevel, Strings.Count); For i := 0 to Strings.Count - 1 do if TryStrToInt(Strings.Names[i], Level) and (Level > 0) and (Level <= MAX_LEVEL) then if TryStrToInt(Strings.ValueFromIndex[i], Perks) and (Perks >= 0) and (Perks <= 255) then begin PerksPerLevel[PerksPerLevelIndex].Level := Level; PerksPerLevel[PerksPerLevelIndex].Perks := Perks; Inc(PerksPerLevelIndex); end else MessageBox(0, PChar(Strings[i] + ' is not a correct Perks value for [PerksPerLevelAndAbove].'), INI_ERROR, MB_ICONERROR) else MessageBox(0, PChar(Strings[i] + ' is not a correct Level value for [PerksPerLevelAndAbove].'), INI_ERROR, MB_ICONERROR); finally Strings.Free; end; SetLength(PerksPerLevel, PerksPerLevelIndex); for j := High(PerksPerLevel) - 1 downto 1 do for i := 0 to j do if PerksPerLevel[i].Level > PerksPerLevel[i + 1].Level then begin Level := PerksPerLevel[i].Level; Perks := PerksPerLevel[i].Perks; PerksPerLevel[i].Level := PerksPerLevel[i + 1].Level; PerksPerLevel[i].Perks := PerksPerLevel[i + 1].Perks; PerksPerLevel[i + 1].Level := Level; PerksPerLevel[i + 1].Perks := Perks; end; except MessageBox(0, 'SKSE Elys Uncapper encountered an error while attempting to read SKSE_Elys_Uncapper.ini. The plugin is not enabled', 'Error', MB_ICONERROR); Result := False; end; IniFile.Free;end;Function SKSEPlugin_Query(const skse: SKSEInterface; var Info: PluginInfo): Boolean; cdecl;begin Info.infoVersion := PluginInfo.kInfoVersion; Info.name := 'SKSE_Elys_Uncapper'; Info.version := PLUGIN_VER; Result := False; if skse.isEditor <> 0 then Exit; if skse.skseVersion < SKSE_VER then begin MessageBox(0, 'SKSE Elys Uncapper requires SKSE Version 1.4.8 or above.', 'Error: Plugin not enabled', MB_ICONERROR); Exit; end; if skse.runtimeVersion <> SKYRIM_VER then begin MessageBox(0, 'SKSE Elys Uncapper only supports Skyrim Version 1.4.21.', 'Error: Plugin not enabled', MB_ICONERROR); Exit; end; Result := True;end;Function SKSEPlugin_Load(const skse: SKSEInterface): Boolean; cdecl;begin Result := False; If not InitOptions then Exit; if not Enabled then begin Result := True; Exit; end; GetActorLevel := Pointer($008C30F0); CalculateExpForNextLevel := Pointer($00751250); GetSkillCoefficients := Pointer($005A85F0); if not SetE8Call(Pointer($0075161F), @IsValidSkill) then // IsValidSkill Exit; if not SetE8Call(Pointer($007514F2), @GetSkill) then // GetSKill Exit; pSkillCapPatch := @SkillCapPatch; if not SetFF15Call(PByte(@CalculateExpForNextLevel)+7, @pSkillCapPatch) then // Skill Exp Curve Cap Exit; if not SetFF15Call(Pointer($0075174D), @pSkillCapPatch) then // Skill Cap Exit; if not SetFF15Call(Pointer($00751869), @pSkillCapPatch) then // Skill Cap Exit; pSkillCapPatch2 := @SkillCapPatch2; if not SetFF15Call(Pointer($0075164B), @pSkillCapPatch2) then // Skill Cap 2 Exit; if not ReplaceE8CallTarget(PByte($0075168B), @hook_GetSkillCoefficients) then // sub_650A70 hook; Exit; pIncreasePerkPool := @IncreasePerkPool; if not SetFF15Call(Pointer($0087BCB9), @pIncreasePerkPool) then // Perk Management. Dont forget update offset inside function Exit; pIncreaseSkillExp := @IncreaseSkillExp; if not SetFF15Call(Pointer($00751734), @pIncreaseSkillExp) then // Skill Exp Mult Exit; pSkillEffectFormulasCap := @SkillEffectFormulasCap; if not Overwrite(Pointer($005AA2F9), @pSkillEffectFormulasCap, 4) then // Uncap formulas Exit; if not Overwrite(Pointer($006C78EA), @FixSneakOver100CheckCode, 6) then // Sneak Formula Cap Fix Exit; Result := True;end;exports SKSEPlugin_Query, SKSEPlugin_Load;beginend.