Skip to content

Instantly share code, notes, and snippets.

@cehoffman
Last active June 5, 2018 17:41
Show Gist options
  • Save cehoffman/4750614 to your computer and use it in GitHub Desktop.
Save cehoffman/4750614 to your computer and use it in GitHub Desktop.
Skyrim Redone ReProccer Fixes for TES5Edit
{
Fix Armor BODT fields erroneously added from ReProccer
Fix keywords and fields lost from USKP due to mods between USKP and ReProccer
not including USKP fixes.
Fix infiltration keywords that are missing on obviously Bandit, Thalmor,
Stormcloak, Imperial, Forsworn. Also remove infiltration keywords from cloaks,
because it doesn't make sense for you to infiltrate wearing a cloak instead
of a chest piece
Fix Guard Dialogue Overhaul keywords that are lost in ReProccer setup.
Fix alternate texture settings that are incorrectly copied in Reproccer for
armors.
}
unit UserScript;
var
armors, summaryList, hasMaster: TStringList;
{ Used to get an idea of what the Name, Path, and Signature for elements in a
container are in order to know what to use for coding }
procedure PrintContainedElements(container: IInterface);
var
i: integer;
el: IInterface;
begin
for i := 0 to ElementCount(container) - 1 do begin
el := ElementByIndex(container, i);
AddMessage(Name(el) + ' | ' + Signature(el) + ' | ' + GetEditValue(el));
end;
end;
{ It it assumed that main is the top level record, e.g an esm or esp.
The form_id is the form_id as seen in TES5Edit or when inspecting the element
on the record it originates from, i.e. the top 2 bytes reference that record.
The keyword is also given in a form equivalent to form_id. }
function RemoveKeywordOnFormID(main: IInterface; form_id: integer; keyword: integer): boolean;
var
rec: IInterface;
begin
form_id := LoadOrderFormIDToFileFormID(main, form_id);
rec := RecordByFormID(main, form_id, True);
result := false;
if Assigned(rec) then begin
result := RemoveKeyword(rec, keyword);
end;
end;
{ Similar to RemoveKeywordOnFormID but adds instead of removes }
function AddKeywordOnFormID(main: IInterface; form_id: integer; keyword: integer): boolean;
var
rec: IInterface;
begin
form_id := LoadOrderFormIDToFileFormID(main, form_id);
rec := RecordByFormID(main, form_id, True);
result := false;
if Assigned(rec) then begin
result := AddKeyword(rec, keyword);
end;
end;
{ Simply for debugging, prints the keyword with EDID and FormID }
procedure PrintKeywords(keywords: IInterface);
var
i: integer;
container, key: IInterface;
begin
if Signature(keywords) = 'KWDA' then begin
container := GetContainer(keywords);
end else begin
container := keywords;
keywords := ElementBySignature(container, 'KWDA');
end;
for i := 0 to ElementCount(keywords) - 1 do begin
key := ElementByIndex(keywords, i);
AddMessage(IntToHex64(GetNativeValue(key), 8) + ' ' + GetEditValue(key));
end;
end;
{ The form id is in the form seen in TES5Edit interface }
function RemoveKeyword(keywords: IInterface; keyword: integer): boolean;
var
i: integer;
container, key: IInterface;
begin
if Signature(keywords) = 'KWDA' then begin
container := GetContainer(keywords);
end else begin
container := keywords;
keywords := ElementBySignature(container, 'KWDA');
end;
result := false;
// It assumed all form ids for keywords are given in load order form which
// is the form id you see in the TES5Edit Interface. It is converted to the
// FormID needed for this file before setting.
keyword := LoadOrderFormIDToFileFormID(GetFile(keywords), keyword);
for i := 0 to ElementCount(keywords) - 1 do begin
key := ElementByIndex(keywords, i);
if GetNativeValue(key) = keyword then begin
AddMessage('Removing keyword ' + GetEditValue(key) + ' on record ' +
GetEditValue(ElementBySignature(container, 'EDID')));
Remove(key);
result := true;
end;
end;
end;
{ Removes keywords that are seen twice on a record }
procedure RemoveDuplicateKeywords(keywords: IInterface);
var
i, j, max: integer;
edited: boolean;
keyword: string;
container: IInterface;
begin
if Signature(keywords) = 'KWDA' then begin
container := GetContainer(keywords);
end else begin
container := keywords;
keywords := ElementBySignature(container, 'KWDA');
end;
edited := false;
max := ElementCount(keywords) - 2;
for i := 0 to max do begin
keyword := GetEditValue(ElementByIndex(keywords, i));
for j := ElementCount(keywords) - 1 downto i + 1 do begin
if GetEditValue(ElementByIndex(keywords, j)) = keyword then begin
Remove(ElementByIndex(keywords, j));
AddMessage('Removing keyword ' + keyword + ' on record ' +
GetEditValue(ElementBySignature(container, 'EDID')));
max := max - 1;
edited := true;
end;
end;
end;
if edited then SetNativeValue(ElementBySignature(container, 'KSIZ'), ElementCount(keywords));
end;
{ Helper to copy classes of keywords based on a common substring in EDID of keyword }
procedure CopyKeywordsContainingStrToRecord(keywords: IInterface; keyword: string; dest: IInterface);
var
i, form_id: integer;
container, key, main: IInterface;
begin
if Signature(keywords) = 'KWDA' then begin
container := GetContainer(keywords);
end else begin
container := keywords;
keywords := ElementBySignature(container, 'KWDA');
end;
main := GetFile(container);
for i := 0 to ElementCount(keywords) - 1 do begin
key := ElementByIndex(keywords, i);
if Pos(keyword, GetEditValue(key)) > 0 then begin
form_id := FileFormIDToLoadOrderFormID(main, GetNativeValue(key));
AddKeyword(dest, form_id);
end;
end;
end;
function HasKeyword(keywords: IInterface; keyword: integer): boolean;
var
i: integer;
container: IInterface;
begin
if Signature(keywords) = 'KWDA' then begin
container := GetContainer(keywords);
end else begin
container := keywords;
keywords := ElementBySignature(container, 'KWDA');
end;
result := false;
for i := 0 to ElementCount(keywords) - 1 do begin
if GetNativeValue(ElementByIndex(keywords, i)) = keyword then begin
result := true;
break;
end;
end;
end;
function AddKeyword(keywords: IInterface; keyword: integer): boolean;
var
i: integer;
container, ksize, el: IInterface;
begin
if Signature(keywords) = 'KWDA' then begin
container := GetContainer(keywords);
end else begin
container := keywords;
keywords := ElementBySignature(container, 'KWDA');
end;
if not ElementExists(container, 'KSIZ') then begin
Add(container, 'KSIZ', True);
end;
ksize := ElementBySignature(container, 'KSIZ');
// It assumed all form ids for keywords are given in load order form which
// is the form id you see in the TES5Edit Interface. It is converted to the
// FormID needed for this file before setting.
keyword := LoadOrderFormIDToFileFormID(GetFile(keywords), keyword);
if not HasKeyword(keywords, keyword) then begin
el := ElementAssign(keywords, HighInteger, nil, False);
SetNativeValue(el, keyword);
AddMessage('Adding keyword ' + GetEditValue(el) + ' to record ' +
GetEditValue(ElementBySignature(container, 'EDID')));
SetNativeValue(ksize, ElementCount(keywords));
result := true;
end else begin
result := false;
end;
end;
{ Unused right now, but don't want to delete since it could be useful }
function HasKeywordContaining(keywords: IInterface; keyword: string): boolean;
var
i: integer;
begin
if Signature(keywords) <> 'KWDA' then begin
keywords := ElementBySignature(keywords, 'KWDA');
end;
if Signature(keywords) <> 'KWDA' then begin
result := false;
end;
for i := 0 to ElementCount(keywords) - 1 do begin
if Pos(keyword, GetEditValue(ElementByIndex(keywords, i))) > 0 then begin
result := true;
Exit;
end;
end;
end;
function GetLoadFile(rec: string): IInterface;
var
i: integer;
begin
result := nil;
for i := 0 to FileCount - 1 do begin
if SameText(rec, GetFileName(FileByIndex(i))) then begin
result := FileByIndex(i);
exit;
end;
end;
end;
function MyHasMaster(main: IInterface; filename: string): boolean;
var
i, j: integer;
store: string;
begin
main := GetFile(main);
store := GetFileName(main) + '-' + filename;
result := false;
if Assigned(hasMaster.Values[store]) then begin
// AddMessage('Saved ' + store + ' ' + hasMaster.Values[store]);
if hasMaster.Values[store] = 't' then result := true;
// result := hasMaster.Values[store] <> 't';
// if result and (hasMaster.Values[store] = 'f') then begin
// AddMessage('Setting master shortcut failed');
// end;
exit;
end else begin
hasMaster.Values[store] := 'f';
end;
for i := 0 to MasterCount(main) - 1 do begin
for j := 0 to FileCount - 1 do begin
if SameText(GetFileName(FileByIndex(j)), GetFileName(MasterByIndex(main, i))) then begin
result := true;
hasMaster.Values[store] := 't';
exit;
end;
end;
end;
end;
function WillBeRedundantLevelList(main: IInterface; rec: IInterface; a: TStringList): boolean;
var
i, j, form_id: integer;
tmp, el1, el2: IInterface;
b: TStringList;
str: string;
begin
result := false;
form_id := FileFormIDToLoadOrderFormID(GetFile(rec), FormID(rec));
for i := FileCount - 1 downto 0 do begin
if not MyHasMaster(main, GetFileName(FileByIndex(i))) then continue;
tmp := FileByIndex(i);
el1 := tmp;
try
tmp := RecordByFormID(tmp, LoadOrderFormIDToFileFormID(tmp, form_id), false);
except on E : Exception do
// A continue statement seems to fail at jumping to next for loop
tmp := FileByIndex(0);
end;
if GetFileName(GetFile(tmp)) <> GetFileName(el1) then continue;
el1 := ElementByPath(tmp, 'EDID');
el2 := ElementByPath(rec, 'EDID');
if GetEditValue(el1) <> GetEditValue(el2) then begin
AddMessage('Abort on EDID');
exit;
end;
el1 := ElementByPath(tmp, 'OBND');
el2 := ElementByPath(rec, 'OBND');
if GetEditValue(el1) <> GetEditValue(el2) then begin
AddMessage('Abort on OBND');
exit;
end;
el1 := ElementByPath(tmp, 'LVLD');
el2 := ElementByPath(rec, 'LVLD');
if GetEditValue(el1) <> GetEditValue(el2) then begin
AddMessage('Abort on LVLD');
exit;
end;
el1 := ElementByPath(tmp, 'LVLG');
el2 := ElementByPath(rec, 'LVLG');
if GetEditValue(el1) <> GetEditValue(el2) then begin
AddMessage('Abort on LVLG');
exit;
end;
b := SummarizeLevelList(tmp);
try
if a.Count <> b.Count then begin
AddMessage('Abort on count mismatch');
exit;
end;
for j := 0 to a.Count - 1 do begin
str := a.Strings[j];
i := b.IndexOf(str);
if i < 0 then begin
AddMessage('Abort on b not having entity');
exit;
end;
if integer(b.Objects[i]) <> integer(a.Objects[j]) then begin
AddMessage('Abort on Entity Matches');
exit;
end;
end;
finally
b.Free;
end;
result := true;
exit;
end;
end;
function IsRedundantLevelList(rec: IInterface): boolean;
var
i, j, form_id: integer;
main, tmp, alt_rec: IInterface;
a, b: TStringList;
str: string;
begin
result := false;
main := GetFile(rec);
form_id := FileFormIDToLoadOrderFormID(main, FormID(rec));
for i := MasterCount(main) - 1 downto 0 do begin
try
tmp := MasterByIndex(main, i);
tmp := RecordByFormID(tmp, LoadOrderFormIDToFileFormID(tmp, form_id), true);
if not Assigned(tmp) then begin
Continue;
end;
except
on E : Exception do
Continue;
end;
result := true;
for j := 0 to ElementCount(tmp) - 1 do begin
alt_rec := ElementByIndex(tmp, j);
if GetEditValue(ElementByName(rec, Name(alt_rec))) <> GetEditValue(alt_rec) then begin
result := false;
break;
end;
end;
a := SummarizeLevelList(rec);
b := SummarizeLevelList(tmp);
try
if a.Count <> b.Count then exit;
for j := 0 to a.Count - 1 do begin
str := a.Strings[j];
i := b.IndexOf(str);
if i < 0 or integer(b.Objects[i]) <> integer(a.Objects[j]) then exit;
end;
finally
a.Free;
b.Free;
end
xit;
end;
end;
function SummarizeLevelList(rec: IInterface): TStringList;
var
summary, tmpList: TStringList;
str: string;
i, j, form_id: integer;
tmp, lli, alt_rec: IInterface;
begin
tmpList := TStringList.Create;
str := GetFileName(GetFile(rec)) + '-' + IntToStr(FormID(rec));
i := summaryList.IndexOf(str);
if i >= 0 then begin
tmpList.Assign(summaryList.Objects[i]);
result := tmpList;
exit;
end else begin
summaryList.AddObject(form_id, tmpList);
end
// Sum up the number of times each item appears in this record only
summary := TStringList.Create;
result := summary;
alt_rec := ElementByName(rec, 'Leveled List Entries');
for i := 0 to ElementCount(alt_rec) - 1 do begin
lli := ElementByIndex(alt_rec, i);
form_id := GetNativeValue(ElementByPath(lli, 'LVLO\Reference'));
form_id := FileFormIDtoLoadOrderFormID(GetFile(lli), form_id);
// Create a unique reference to this entry, e.g. reference, level and
// count and separate each by a delimiter for easy separation later
str := IntToStr(form_id) + ';' +
GetEditValue(ElementByPath(lli, 'LVLO\Level')) + ';' +
GetEditValue(ElementByPath(lli, 'LVLO\Count'));
j := summary.IndexOf(str);
if j >= 0 then begin
// if Assigned(summary.Values[str]) then begin
summary.Objects[j] := integer(summary.Objects[j]) + 1;
// summary.Values[str] := IntToStr(StrToInt(summary.Values[str]) + 1);
end else begin
summary.AddObject(str, TOBject(1));
// summary.Values[str] := '1';
end;
end;
tmpList.Assign(summary);
end;
procedure RunningSummaryOfLevelLists(base: IInterface;
root: TStringList;
lists: TStringList);
var
i, j, k, form_id: integer;
str: string;
rec: IInterface;
temp_list, summary: TStringList;
begin
for i := 0 to ElementCount(base) - 1 do begin
rec := ElementByIndex(base, i);
form_id := FileFormIDtoLoadOrderFormID(GetFile(base), FormID(rec));
str := IntToHex64(form_id, 8);
// Create a reference holder for this specific list
j := root.IndexOf(str);
if j >= 0 then begin
temp_list := root.Objects[j];
end else begin
temp_list := TStringList.Create;
temp_list.Duplicates := dupError;
root.AddObject(str, temp_list);
end;
// Track the latest mod needed for level list
if lists <> nil then lists.Values[str] := GetFileName(GetFile(base));
// Sum up the number of times each item appears in this record only
summary := SummarizeLevelList(rec);
// Go over the references found and add them to a running summary of stats.
for j := 0 to summary.Count - 1 do begin
str := summary.Strings[j];
k := temp_list.IndexOf(str);
if k >= 0 then begin
if integer(temp_list.Objects[k]) < integer(summary.Objects[j]) then begin
temp_list.Objects[k] := summary.Objects[j];
// temp_list.Objects[k] := TObject(integer(temp_list.Objects[k]) +
// integer(summary.Objects[j]));
end;
end else begin
temp_list.AddObject(str, summary.Objects[j]);
end;
end;
end;
end;
procedure PrintLeveledListSummary(summary: TStringList);
var
i, count: integer;
format: TStringList;
begin
format := TStringList.Create;
format.Delimiter := ';';
for i := 0 to summary.Count - 1 do begin
count := integer(summary.Objects[i]);
format.DelimitedText := summary.Strings[i];
format[0] := IntToHex64(StrToInt(format[0]), 8);
AddMessage(format[0] + ' lvl: ' + format[1] + ' count: ' + format[2] +
' total: ' + IntToStr(count));
end;
format.Free;
end;
procedure RemoveItemsFromLevelList(main: IInterface;
llid: integer;
llref: integer;
lllvl: integer;
llcount: integer;
removals: integer);
var
form_id, i: integer;
rec, lle: IInterface;
begin
try
rec := RecordByFormID(main, LoadOrderFormIDToFileFormID(main, llid), true);
form_id := LoadOrderFormIDToFileFormID(main, llref);
except
on E : Exception do begin
AddMessage('Unable to find ' + IntToHex64(llid, 8) + ' for cleaning');
exit;
end;
end;
AddMessage(IntToHex64(llid, 8) + ' removing ' + IntToHex64(llref, 8) +
' completely (' + IntToStr(removals) + ')');
// if FileFormIDToLoadOrderFormID(main, FormID(rec)) <> StrToInt('$' + str) then continue;
lle := ElementByName(rec, 'Leveled List Entries');
for i := ElementCount(lle) - 1 downto 0 do begin
rec := ElementByIndex(lle, i);
// Seems like crap I have to wrap each part of statement in parens
if (form_id = GetNativeValue(ElementByPath(rec, 'LVLO\Reference'))) and
(lllvl = GetNativeValue(ElementByPath(rec, 'LVLO\Level'))) and
(llcount = GetNativeValue(ElementByPath(rec, 'LVLO\Count'))) then begin
// AddMessage('Removing ' + IntToHex64(llref, 8) + ' lvl: ' + IntToStr(lllvl) + ' count: ' + IntToStr(llcount));
removals := removals - 1;
Remove(rec);
if removals <= 0 then exit;
end;
end;
end;
{ Merging Leveled Lists follows a basic algorithm. The last LL from
Skyrim.esm, Dawnguard.esm, Hearthfires.esm, Dragonborn.esm, USKP, USKP
- Dawnguard, USKP - Hearthfire, USKP - Dragonborn. From here items are
added to the leveled list from other mods. }
procedure CompactLeveledList(patch: IInterface; llist: string);
var
str: string;
exists, identical: boolean;
i, j, k, form_id: integer;
tmp, rec, alt_rec, lli: IInterface;
skll, ll, mll, tmpll, dll, spliter, a, b: TStringList;
begin
skll := TStringList.Create;
ll := TStringList.Create;
mll := TSTringList.Create;
dll := TStringList.Create;
AddMessage('Working on ' + llist);
try
for i := 0 to FileCount - 1 do begin
tmp := FileByIndex(i);
// Don't include ourself in the data accumulation or the main skyrim folder
// because if a main skyrim load list is changed the change is absolutely
// desired.
if SameText(GetFileName(tmp), GetFileName(patch)) or
SameText(GetFileName(tmp), 'Skyrim.esm') then begin
continue;
end;
if SameText(GetFileName(tmp), 'Update.esm') or
SameText(GetFileName(tmp), 'Dawnguard.esm') or
SameText(GetFileName(tmp), 'HearthFires.esm') or
SameText(GetFileName(tmp), 'Dragonborn.esm') or
SameText(GetFileName(tmp), 'Unofficial Skyrim Patch.esp') or
SameText(GetFileName(tmp), 'Unofficial Dawnguard Patch.esp') or
SameText(GetFileName(tmp), 'Unofficial Hearthfire Patch.esp') or
SameText(GetFileName(tmp), 'Unofficial Dragonborn Patch.esp') or
SameText(GetFileName(tmp), 'SkyRe_Main.esp') then begin
// Iterate over all the level lists of the specified type and store
// that the given file contains the level list. Also gather stats on
// this module outside of accumulation between mods
RunningSummaryOfLevelLists(GroupBySignature(tmp, llist), dll, skll);
end;
RunningSummaryOfLevelLists(GroupBySignature(tmp, llist), ll, mll);
end;
CleanMasters(patch);
// Get mods that need to be in master list
for i := 0 to mll.Count - 1 do begin
AddMasterIfMissing(patch, mll.Values[mll.Names[i]])
end;
// Cleanup old consolidation of lists in preparation of new combination
Remove(GroupBySignature(patch, llist));
// At this point skll contains a reference to the final level list from the
// official sources (including the unofficial patches). ll contains a list of
// all leveled lists which other mods included with all entries combined into
// single instances that specify how many times that entry should exist in
// the list
// for k := 0 to MasterCount(patch) - 1 do begin
// AddMessage(GetFileName(MasterByIndex(patch, k)));
// end;
// Loop over the level lists and recreate them from the summation of all mods
for i := 0 to ll.Count - 1 do begin
str := ll.Strings[i];
j := StrToInt('$' + str);
// AddMessage('Working on ' + str);
// Use one of the good masters as base if it is overriden, otherwise use
// the last mod in the load order as base
if Assigned(skll.Values[str]) then begin
lli := GetLoadFile(skll.Values[str]);
end else begin
lli := GetLoadFile(mll.Values[str]);
end;
try
form_id := LoadOrderFormIDToFileFormID(lli, j);
lli := RecordByFormID(lli, form_id, True);
except on E : Exception do
AddMessage('Got an exception on ' E.Message);
continue;
end;
// Check if this will be redudant before doing the actual copy
if WillBeRedundantLevelList(patch, lli, ll.Objects[i]) then begin
// AddMessage('Skipping would be redundant record ' + ll.Strings[i]);
continue;
end;
// Use the winning master record, e.g. one with most correct stats for
// level list for base of new list;
rec := wbCopyElementToFile(lli, patch, False, True);
alt_rec := ElementByName(rec, 'Leveled List Entries');
// Remove all the entires for a fresh start to number of entries
for j := ElementCount(alt_rec) - 1 downto 0 do begin
Remove(ElementByIndex(alt_rec, j));
end;
// Copy over references data from accumulated mods
tmpll := ll.Objects[i];
spliter := TStringList.Create;
try
spliter.Delimiter := ';';
for j := 0 to tmpll.Count - 1 do begin
spliter.DelimitedText := tmpll.Strings[j];
form_id := LoadOrderFormIDToFileFormID(patch, StrToInt64(spliter[0]));
// Create the number of entries for this reference
for k := integer(tmpll.Objects[j]) - 1 downto 0 do begin
lli := ElementAssign(alt_rec, HighInteger, nil, False);
SetNativeValue(ElementByPath(lli, 'LVLO\Reference'), form_id);
SetEditValue(ElementByPath(lli, 'LVLO\Level'), spliter[1]);
SetEditValue(ElementByPath(lli, 'LVLO\Count'), spliter[2]);
end;
end;
finally
spliter.Free
end;
SetNativeValue(ElementByPath(rec, 'LLCT'), ElementCount(alt_rec));
// Go backward through the load order and if we find a list equal to the
// one we made, delete the one we made. I wish I can a better way of
// going through this but overrides doesn't seem to work for what I want
// if IsRedundantLevelList(rec) then begin
// AddMessage('Removing the redundant record ' + ll.Strings[i]);
// Remove(rec);
// end;
end;
// SyncLevelListRemovals(patch, llist, 'Dragonborn.esm', 'SkyRe_Main.esp');
SyncLevelListRemovals(patch, llist, 'HearthFires.esm', 'SkyRe_Main.esp');
SyncLevelListRemovals(patch, llist, 'Dawnguard.esm', 'SkyRe_Main.esp');
SyncLevelListRemovals(patch, llist, 'Skyrim.esm', 'SkyRe_Main.esp');
SyncLevelListRemovals(patch, llist, 'Skyrim.esm', 'Unofficial Skyrim Patch.esp');
SyncLevelListRemovals(patch, llist, 'Dawnguard.esm', 'Unofficial Dawnguard Patch.esp');
SyncLevelListRemovals(patch, llist, 'HearthFires.esm', 'Unofficial Hearthfire Patch.esp');
SyncLevelListRemovals(patch, llist, 'Dragonborn.esm', 'Unofficial Dragonborn Patch.esp');
rec := GroupBySignature(patch, 'LVLI');
for i := 0 to ElementCount(rec) - 1 do begin
alt_rec := ElementByIndex(rec, i);
tmp := ElementByName(alt_rec, 'Leveled List Entries');
if ElementCount(tmp) < GetElementNativeValues(alt_rec, 'LLCT') then begin
SetElementNativeValues(alt_rec, 'LLCT', ElementCount(tmp));
end;
end;
finally
for i := 0 to ll.Count - 1 do begin
ll.Objects[i].Free;
end;
ll.Free;
for i := 0 to dll.Count - 1 do begin
dll.Objects[i].Free;
end;
dll.Free;
skll.Free;
mll.Free;
end;
end;
procedure SyncLevelListRemovals(main: IInterface; llist: string; base: string; master: string);
var
i, j, k, form_id: integer;
str: string;
rec: IInterface;
dll, ll, tmpll, spliter, reproc, format: TStringList;
begin
dll := TStringList.Create;
ll := TSTringList.Create;
reproc := TStringList.Create;
format := TStringList.Create;
format.Delimiter := ';';
AddMessage('Cleaning ' + GetFileName(GetFile(main)) + ' ' + llist +
' using base ' + base + ' and master ' + master);
try
RunningSummaryOfLevelLists(GroupBySignature(GetLoadFile(base), llist), dll, nil);
RunningSummaryOfLevelLists(GroupBySignature(GetLoadFile(master), llist), ll, nil);
RunningSummaryOfLevelLists(GroupBySignature(main, llist), reproc, nil);
for i := 0 to ll.Count - 1 do begin
str := ll.Strings[i];
// AddMessage('Looking for ' + str);
// Skip this if it isn't in the final mod file
if reproc.IndexOf(str) < 0 then continue;
// form_id := LoadOrderFormIDToFileFormID(rec, str);
j := dll.IndexOf(str);
if j >= 0 then begin
// See which records from dll do not exist on the ll version, e.g. ll
// has removed them and the patch object should have them removed as
// well. This needs to look at the count value for each set of
// reference/level/count values and use the smaller one in ll if found.
tmpll := dll.Objects[j];
spliter := ll.Objects[i];
for j := 0 to tmpll.Count - 1 do begin
// Get the ref/level/count object and see if ll has it
k := spliter.IndexOf(tmpll.Strings[j]);
if k >= 0 then begin
// If it has it, then make sure there was not a decrease in total
// occurances.
if integer(tmpll.Objects[j]) > integer(spliter.Objects[k]) then begin
format.DelimitedText := tmpll.Strings[j];
RemoveItemsFromLevelList(main, StrToInt('$' + str), StrToInt(format[0]),
StrToInt(format[1]), StrToInt(format[2]),
integer(tmpll.Objects[j]) - integer(spliter.Objects[k]));
end;
end else begin
// If it doesn't have it then it should be removed
format.DelimitedText := tmpll.Strings[j];
RemoveItemsFromLevelList(main, StrToInt('$' + str), StrToInt(format[0]),
StrToInt(format[1]), StrToInt(format[2]),
integer(tmpll.Objects[j]));
end;
end;
end;
end;
except
on E : Exception do begin
AddMessage(E.Message);
end;
finally
for i := 0 to ll.Count - 1 do begin
ll.Objects[i].Free;
end;
ll.Free;
for i := 0 to dll.Count - 1 do begin
dll.Objects[i].Free;
end;
dll.Free;
for i := 0 to reproc.Count - 1 do begin
reproc.Objects[i].Free;
end;
reproc.Free;
format.Free;
end;
end;
function Initialize: integer;
var
i, j, k: integer;
form_id: LongWord;
str: string;
has_mod2, has_mod4: boolean;
reproccer, uskp, sic, gdo, imm_wep: IInterface;
tmp, rec, alt_rec: IInterface;
lli: IInterface;
a: TStringList;
begin
for i := 0 to FileCount - 1 do begin
tmp := FileByIndex(i);
str := GetFileName(tmp);
if SameText(str, 'ReProccer.esp') then begin
reproccer := tmp;
end else if SameText(str, 'Unofficial Skyrim Patch.esp') then begin
uskp := tmp;
end else if SameText(str, 'Skyrim Immersive Creatures.esm') then begin
sic := tmp;
end else if SameText(str, 'Guard Dialogue Overhaul.esp') then begin
gdo := tmp;
end else if SameText(str, 'Immersive Weapons.esp') then begin
imm_wep := tmp;
end;
end;
{ This all require at least ReProccer.esp for fixes }
if not Assigned(reproccer) then begin
MessageDlg('You must have ReProccer.esp loaded', mtConfirmation, [mbOk], 0);
result := 1;
Exit;
end;
// Store the form id of armors in reproccer for easy lookup when searching
// for dreamcloth version of armors in MOD4/MOD2 fix step
armors := TStringList.Create;
summaryList := TStringList.Create;
hasMaster := TStringList.Create;
CompactLeveledList(reproccer, 'LVLI');
CompactLeveledList(reproccer, 'LVLN');
CompactLeveledList(reproccer, 'LVSP');
// ReProccer has a bug that adds BODT records even when there is a BOD2 one
// which is an unwanted side effect. This goes through all armors and removes
// the BODT field when a BOD2 one exists.
tmp := GroupBySignature(reproccer, 'ARMO');
for i := 0 to ElementCount(tmp) - 1 do begin
rec := ElementByIndex(tmp, i);
armors.AddObject(GetEditValue(ElementBySignature(rec, 'FULL')), TObject(FormID(rec)));
if ElementExists(rec, 'BOD2') then begin
RemoveElement(rec, 'BODT');
end;
RemoveDuplicateKeywords(rec);
// Add Thalmor Infiltration to Armors that have Thalmor in the name
if Pos('Thalmor', GetEditValue(ElementBySignature(rec, 'EDID'))) > 0 then begin
AddKeyword(rec, $69037d2b);
end;
// Add Forsworn Infiltration to Armors that have Forsworn in the name
if Pos('Forsworn', GetEditValue(ElementBySignature(rec, 'EDID'))) > 0 then begin
AddKeyword(rec, $6903a8a9);
end;
// Add Stormcloak Infiltration to Armors that have Stormcloak in the name
if Pos('Stormcloak', GetEditValue(ElementBySignature(rec, 'EDID'))) > 0 then begin
AddKeyword(rec, $69037d2f);
end;
// Add Bandit Infiltration to Armors that have Bandit in the name
if Pos('Bandit', GetEditValue(ElementBySignature(rec, 'EDID'))) > 0 then begin
AddKeyword(rec, $6903a8aa);
end;
// Add Imperial Infiltration to Armors that have Imperial in the name
if Pos('Imperial', GetEditValue(ElementBySignature(rec, 'EDID'))) > 0 then begin
AddKeyword(rec, $69037d31);
end;
// Since it isn't always possible to create load order form ids, e. g. the
// item comes later in the load order than the requested esp it is being
// made for it is necessary to wrap this in a try/except block
if Assigned(uskp) then try
form_id := FileFormIDtoLoadOrderFormID(reproccer, FormID(rec));
form_id := LoadOrderFormIDToFileFormID(uskp, form_id);
alt_rec := RecordByFormID(uskp, form_id, True);
if Assigned(alt_rec) then begin
CopyKeywordsContainingStrToRecord(alt_rec, 'USKP', rec);
end;
except
on E : Exception
// AddMessage('Got Exception ' + E.Message);
end;
if Assigned(gdo) then try
form_id := FileFormIDtoLoadOrderFormID(reproccer, FormID(rec));
form_id := LoadOrderFormIDToFileFormID(gdo, form_id);
alt_rec := RecordByFormID(gdo, form_id, True);
if Assigned(alt_rec) then begin
CopyKeywordsContainingStrToRecord(alt_rec, 'GDO', rec);
end;
except
on E : Exception
end;
end;
// Need to fix MO4S and MO2S entries in Reproccer and since the OverrideCount
// function appears to be broken we need to iterate over all masters of
// ReProccer and then all armors of those to see which ones have the
// MOD4/MOD2 fields that need correction.
for i := 0 to MasterCount(reproccer) do begin
tmp := GroupBySignature(MasterByIndex(reproccer, i), 'ARMO');
if not Assigned(tmp) then Continue;
for j := 0 to ElementCount(tmp) - 1 do begin
rec := ElementByIndex(tmp, j);
try
form_id := FileFormIDtoLoadOrderFormID(MasterByIndex(reproccer, i), FormID(rec));
form_id := LoadOrderFormIDToFileFormID(reproccer, form_id);
alt_rec := RecordByFormID(reproccer, form_id, True);
if not Assigned(alt_rec) then Continue;
has_mod2 := ElementExists(rec, 'MOD2\MO2S');
has_mod4 := ElementExists(rec, 'MOD4\MO4S');
if has_mod2 then begin
// AddMessage('Updating MOD2 on ' + IntToHex64(form_id, 8) +
// ' ' + GetEditValue(ElementBySignature(rec, 'EDID')));
wbCopyElementToRecord(ElementBySignature(rec, 'MOD2'), alt_rec, False, True);
end;
if has_mod4 then begin
// AddMessage('Updating MOD4 on ' + IntToHex64(form_id, 8) +
// ' ' + GetEditValue(ElementBySignature(rec, 'EDID')));
wbCopyElementToRecord(ElementBySignature(rec, 'MOD4'), alt_rec, False, True);
end;
if not has_mod2 and not has_mod4 then Continue;
// Need to also fix the dreamcloth version of armors created
str := GetEditValue(ElementBySignature(rec, 'FULL')) + ' [Dreamcloth]';
k := armors.IndexOf(str);
if k >= 0 then begin
alt_rec := RecordByFormId(reproccer, Integer(armors.Objects[k]), True);
if has_mod2 then begin
// AddMessage('Updating MOD2 on ' +
// GetEditValue(ElementBySignature(alt_rec, 'EDID')));
wbCopyElementToRecord(ElementBySignature(rec, 'MOD2'), alt_rec, False, True);
end;
if has_mod4 then begin
// AddMessage('Updating MOD4 on ' +
// GetEditValue(ElementBySignature(alt_rec, 'EDID')));
wbCopyElementToRecord(ElementBySignature(rec, 'MOD4'), alt_rec, False, True);
end;
end;
except
on E : Exception do
AddMessage('Got Exception ' + E.Message);
end;
end;
end;
{ UKSP fixes numerous sounds for weapons and a few critical multipliers for
weapons and those values are lost because of other mods in load order, like
SkyRe itself }
tmp := GroupBySignature(reproccer, 'WEAP');
for i := 0 to ElementCount(tmp) - 1 do begin
rec := ElementByIndex(tmp, i);
RemoveDuplicateKeywords(rec);
if Assigned(uskp) then try
form_id := FileFormIDtoLoadOrderFormID(reproccer, FormID(rec));
form_id := LoadOrderFormIDToFileFormID(uskp, form_id);
alt_rec := RecordByFormID(uskp, form_id, True);
if Assigned(alt_rec) then begin
CopyKeywordsContainingStrToRecord(alt_rec, 'USKP', rec);
end;
if ElementExists(alt_rec, 'VNAM') then begin
wbCopyElementToRecord(ElementBySignature(alt_rec, 'VNAM'), rec, False, True);
end;
if ElementExists(alt_rec, 'BIDS') then begin
wbCopyElementToRecord(ElementBySignature(alt_rec, 'BIDS'), rec, False, True);
end;
if ElementExists(alt_rec, 'BAMT') then begin
wbCopyElementToRecord(ElementBySignature(alt_rec, 'BAMT'), rec, False, True);
end;
if ElementExists(alt_rec, 'TNAM') then begin
wbCopyElementToRecord(ElementBySignature(alt_rec, 'TNAM'), rec, False, True);
end;
if ElementExists(alt_rec, 'INAM') then begin
wbCopyElementToRecord(ElementBySignature(alt_rec, 'INAM'), rec, False, True);
end;
if ElementExists(alt_rec, 'NAM9') then begin
wbCopyElementToRecord(ElementBySignature(alt_rec, 'NAM9'), rec, False, True);
end;
if ElementExists(alt_rec, 'NAM8') then begin
wbCopyElementToRecord(ElementBySignature(alt_rec, 'NAM8'), rec, False, True);
end;
if ElementExists(alt_rec, 'CNAM') then begin
wbCopyElementToRecord(ElementBySignature(alt_rec, 'CNAM'), rec, False, True);
end;
if ElementExists(alt_rec, 'CRDT\% Mult') then begin
SetNativeValue(ElementByPath(rec, 'CRDT\% Mult'),
GetNativeValue(ElementByPath(alt_rec, 'CRDT\% Mult')));
end;
if ElementExists(alt_rec, 'CRDT\Damage') then begin
SetNativeValue(ElementByPath(rec, 'CRDT\Damage'),
GetNativeValue(ElementByPath(alt_rec, 'CRDT\Damage')));
end;
except
on E : Exception
// AddMessage('Got Exception while fixing weapons from USKP: ' + E.Message);
end;
// Guard Dialogue Overhaul uses keywords on weapons to trigger some
// dialogue options
if Assigned(gdo) then try
form_id := FileFormIDtoLoadOrderFormID(reproccer, FormID(rec));
form_id := LoadOrderFormIDToFileFormID(gdo, form_id);
alt_rec := RecordByFormID(gdo, form_id, True);
if Assigned(alt_rec) then begin
CopyKeywordsContainingStrToRecord(alt_rec, 'GDO', rec);
end;
except
on E : Exception
// AddMessage('Got Exception while fixing weapons from GDO: ' + E.Message);
end;
// Immersive Weapons has a few first person models that are not copied
// correctly by ReProccer
if Assigned(imm_wep) then try
form_id := FileFormIDtoLoadOrderFormID(reproccer, FormID(rec));
form_id := LoadOrderFormIDToFileFormID(imm_wep, form_id);
alt_rec := RecordByFormID(imm_wep, form_id, True);
if Assigned(alt_rec) then begin
if ElementExists(alt_rec, 'WNAM') then begin
wbCopyElementToRecord(ElementBySignature(alt_rec, 'WNAM'), rec, False, True);
end;
end;
except
on E : Exception
end;
end;
{ Fix Frostfall armors that get wrong infiltration }
// Remove Bandit infiltration keyword
RemoveKeywordOnFormID(reproccer, $140415c8, $6903a8aa);
RemoveKeywordOnFormID(reproccer, $140415c9, $6903a8aa);
// Add Stormcloak infiltration keyword
AddKeywordOnFormID(reproccer, $140415c8, $69037d2f);
AddKeywordOnFormID(reproccer, $140415c9, $69037d2f);
{ Fix Winter is Coming Cloaks }
// Remove Bandit infiltration keyword
// Leather Fur Trim cloaks
RemoveKeywordOnFormID(reproccer, $35013203, $6903a8aa);
RemoveKeywordOnFormID(reproccer, $35013204, $6903a8aa);
RemoveKeywordOnFormID(reproccer, $35013205, $6903a8aa);
// Leather Rare Fur cloaks
RemoveKeywordOnFormID(reproccer, $35016302, $6903a8aa);
RemoveKeywordOnFormID(reproccer, $35016303, $6903a8aa);
RemoveKeywordOnFormID(reproccer, $35016304, $6903a8aa);
// Steel Fur Trim cloaks
RemoveKeywordOnFormID(reproccer, $3501aed6, $6903a8aa);
RemoveKeywordOnFormID(reproccer, $3501aed7, $6903a8aa);
RemoveKeywordOnFormID(reproccer, $3501aed8, $6903a8aa);
// Steel Rare Fur cloaks
RemoveKeywordOnFormID(reproccer, $3501BA03, $6903a8aa);
RemoveKeywordOnFormID(reproccer, $3501BA04, $6903a8aa);
RemoveKeywordOnFormID(reproccer, $3501BA05, $6903a8aa);
{ Fix Immersive Armors }
// Armored hoods, either remove from these or add to none armored fur hoods
RemoveKeywordOnFormID(reproccer, $3b001d8f, $6903a8aa);
RemoveKeywordOnFormID(reproccer, $3b0022f5, $6903a8aa);
RemoveKeywordOnFormID(reproccer, $3b0022fd, $6903a8aa);
RemoveKeywordOnFormID(reproccer, $3b0048da, $6903a8aa);
RemoveKeywordOnFormID(reproccer, $3b0048da, $6903a8aa);
RemoveKeywordOnFormID(reproccer, $3b0048db, $6903a8aa);
RemoveKeywordOnFormID(reproccer, $3b0048dc, $6903a8aa);
RemoveKeywordOnFormID(reproccer, $3b0048dd, $6903a8aa);
RemoveKeywordOnFormID(reproccer, $3b0048de, $6903a8aa);
RemoveKeywordOnFormID(reproccer, $3b0048df, $6903a8aa);
RemoveKeywordOnFormID(reproccer, $3b0ba99b, $6903a8aa);
RemoveKeywordOnFormID(reproccer, $3b0ba99c, $6903a8aa);
// Einherjar Armor - Found on many bandits on my playthrough
RemoveKeywordOnFormID(reproccer, $3b00803e, $6903a8aa);
RemoveKeywordOnFormID(reproccer, $3b00803f, $6903a8aa);
RemoveKeywordOnFormID(reproccer, $3b008040, $6903a8aa);
// Copy bounds of book from USKP not inlcuded in SkyRe_Main
// $10fd60 - OBND
// Need to copy record from SkyRe_Main first (VenderItemCluter
// AddKeywordOnFormID(reproccer, $4c3c6, $914e9)
// AddKeywordOnFormID(reproccer, $4c3c8, $914e9)
// AddKeywordOnFormID(reproccer, $8632c, $914e9)
// AddKeywordOnFormID(reproccer, $98620, $914e9)
// AddKeywordOnFormID(reproccer, $98621, $914e9)
// AddKeywordOnFormID(reproccer, $98623, $914e9)
// AddKeywordOnFormID(reproccer, $98624, $914e9)
// AddKeywordOnFormID(reproccer, $98625, $914e9)
// AddKeywordOnFormID(reproccer, $98626, $914e9)
// AddKeywordOnFormID(reproccer, $98627, $914e9)
// AddKeywordOnFormID(reproccer, $b9bd0, $914e9)
// AddKeywordOnFormID(reproccer, $b9bd2, $914e9)
// AddKeywordOnFormID(reproccer, $b9bd4, $914e9)
// AddKeywordOnFormID(reproccer, $b9bd6, $914e9)
// AddKeywordOnFormID(reproccer, $b9bd8, $914e9) // copy over OBND
// AddKeywordOnFormID(reproccer, $b9bde, $914e9)
// AddKeywordOnFormID(reproccer, $f08f1, $914e9)
// AddKeywordOnFormID(reproccer, $f08f3, $914e9)
// AddKeywordOnFormID(reproccer, $f08f5, $914e9)
// AddKeywordOnFormID(reproccer, $f08f6, $914e9)
// AddKeywordOnFormID(reproccer, $f08f8, $914e9)
// AddKeywordOnFormID(reproccer, $f08f9, $914e9)
// AddKeywordOnFormID(reproccer, $f08fa, $914e9) // OBND
// AddKeywordOnFormID(reproccer, $f08fb, $914e9) // OBND
// AddKeywordOnFormID(reproccer, $f2012, $106e1f) // OBND
// AddKeywordOnFormID(reproccer, $f2012, $106e20)
// AddKeywordOnFormID(reproccer, $f2013, $106e1f) // OBND
// AddKeywordOnFormID(reproccer, $f2013, $106e20)
// AddKeywordOnFormID(reproccer, $f2014, $106e1f) // OBND
// AddKeywordOnFormID(reproccer, $f2014, $106e20)
// AddKeywordOnFormID(reproccer, $f2015, $106e1f) // OBND
// AddKeywordOnFormID(reproccer, $f2015, $106e20)
// AddKeywordOnFormID(reproccer, $f5d07, $914e9)
// AddKeywordOnFormID(reproccer, $f5d08, $914e9) // OBND
// AddKeywordOnFormID(reproccer, $fed17, $106e1f) // OBND
// AddKeywordOnFormID(reproccer, $fed17, $106e20)
result := 0;
end;
function Process(e: IInterface): integer;
begin
// everything is done in initialization, terminate
result := 0;
end;
function Finalize: integer;
var
i: integer;
begin
armors.Free;
for i := 0 to summaryList.Count - 1 do begin
summaryList.Objects[i].Free
end;
summaryList.Free;
hasMaster.Free;
end;
end.
// vim: set ft=delphi commentstring=//\ %s :
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment