I've finally updated Skyrim to 1.4.21 today but I don't have good news.
The new Skyrim version - assembly language wise -, is a lot different, unlike previous versions who shared about the same code for the part related to what the Uncapper have to modify.
Basically that means, that excepted for new Uncapper features that required additional Skyrim code inspection, each time Skyrim was updated, I just had to find where the code modified by the Uncapper was moved around in the new version, and just update the Uncapper to match the new code addresses.
Unfortunately I don't have the time, and not really the motivation, to start again from scratch the Skyrim code inspection, to allow the Uncapper to be compatible with the Skyrim version 1.4.21
There is other Skyrim related plugins that deal about alternative leveling system for Level/Perks/Bonuses....
I hope someone will release something at least equivalent to the Uncapper for all the people that have enjoyed the Uncapper so far.
If that can help, below is the Pascal/Delphi (UNICODE) source code for the latest Uncapper version 1.5.4 I have released for Skyrim 1.3.10
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.