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 = $030A0000; SKSE_VER = $01040020; PLUGIN_VER = $01050040; oPLAYER = Pointer($01570334); 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; 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; SLParam1, SLParam2 : Single; GetActorLevel: function(a1, a2: Integer; This: Pointer): Word; cdecl; CalculateExpForNextLevel: function( SkillLevel, SLParam1, SLParam2 : Single ): Single; cdecl; sub_650A70: 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 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_650A70(a1, a2, a3 : Longint; a4, a5: PSingle): Byte; cdecl;begin Result := sub_650A70(a1, a2, a3, a4, a5); SLParam1 := a4^; SLParam2 := a5^end;function IncreasePerkPool(NewValue: Byte): Pointer; stdcall; // thiscallconst PerkOffset = $6C9; // offset to update with each new Skyrim versionvar This: PByte; Level: Word; i: Integer; b: Integer;begin asm push ecx // Thiscall fix (Useless in this case but better safe than sorry) mov This, ecx end; Level := GetActorLevel(0, 0, Pointer(oPLAYER^)); // Pointer to Player 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 := NewValue; if (This + PerkOffset)^ < b then (This + PerkOffset)^ := b; Result := This; asm pop ecx // Thiscall fix (Useless in this case but better safe than sorry) end;end;function IncreaseSkillExp(f: Single): Pointer; stdcall; // thiscallvar This: PByte;begin asm push ecx // Thiscall fix (Useless in this case but better safe than sorry) mov This, ecx end; 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; PSingle(This + 4)^ := PSingle(This + 4)^ + f * SkillMults[LastValidedSkill]; Result := This; asm pop ecx // Thiscall fix (Useless in this case but better safe than sorry) end;end;function GetSkill(i: Longword): Longword; cdecl;begin Result := i + 6; LastValidedSkill := Result;end;function IsValidSkill(Skill: Longword): Longbool; cdecl;begin Result := (Skill >= 6) and (Skill < 24); LastValidedSkill := Skillend;procedure SkillCapPatch;begin CurrentSkillCap := SkillCaps[LastValidedSkill]; asm fld CurrentSkillCap end;end;procedure SkillCapPatch2;asm mov eax, [ebp-4] 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.2 or higher.', '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.3.10.', '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($00A512D0); // Don't forget to update the call used with new Player pointer CalculateExpForNextLevel := Pointer($0088DB20); sub_650A70 := Pointer($00650A70); if not ReplaceE8CallTarget(PByte($0088DCE3), @IsValidSkill) then // IsValidSkill Exit; if not ReplaceE8CallTarget(PByte($0088DA62), @GetSkill) then // GetSKill Exit; pSkillCapPatch := @SkillCapPatch; pSkillCapPatch2 := @SkillCapPatch2; if not SetFF15Call(PByte(@CalculateExpForNextLevel)+9, @pSkillCapPatch) then // Skill Exp Curve Cap Exit; if not SetFF15Call(Pointer($0088DE80), @pSkillCapPatch) then // Skill Cap Exit; if not SetFF15Call(Pointer($0088DD27), @pSkillCapPatch2) then // Skill Cap 2 Exit; if not ReplaceE8CallTarget(PByte($0088DD6A), @hook_650A70) then // sub_650A70 hook; Exit; if not ReplaceE8CallTarget(PByte($009EA32D), @IncreasePerkPool) then // Perk Management. Dont forget update offset inside function Exit; if not ReplaceE8CallTarget(PByte($0088DE74), @IncreaseSkillExp) then // Skill Exp Mult Exit; if not Overwrite(Pointer($006537A8), @SkillEffectFormulasCap, 4) then // Uncap formulas Exit; if not Overwrite(Pointer($007D2024), @FixSneakOver100CheckCode, 6) then // Sneak Formula Cap Fix Exit; Result := True;end;exports SKSEPlugin_Query, SKSEPlugin_Load;beginend.