Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- {
- USLEEP Swap Masters Script
- created by matortheeternal
- Swaps the masters of plugins that rely on the USKP, UDGP, UHFP, or UDGP
- to rely on USLEEP instead. NOTE: You must have all of the unofficial
- patches and USLEEP loaded in xEdit to use this script to adjust the
- masters of a file.
- }
- unit UserScript;
- const
- cOldMasterFiles = 'Unofficial Skyrim Patch.esp'#44'Unofficial Dawnguard Patch.esp'#44
- 'Unofficial Hearthfire Patch.esp'#44'Unofficial Dragonborn Patch.esp';
- cNewMasterFile = 'Unofficial Skyrim Legendary Edition Patch.esp';
- var
- FilesList: TList;
- {
- GetFileHeader:
- Gets the file header of a file.
- Example usage:
- f := FileByName('Skyrim.esm');
- header := GetFileHeader(f);
- if not Assigned(ElementByPath(Header, 'Master Files')) then
- AddMessage(GetFileName(f) + ' has no master files!');
- }
- function GetFileHeader(f: IInterface): IInterface;
- begin
- if not Assigned(f) then
- raise Exception.Create('GetFileHeader: Input file is not assigned');
- if ElementType(f) <> etFile then
- raise Exception.Create('GetFileHeader: Input element is not a file');
- Result := ElementByIndex(f, 0);
- end;
- {
- AddFileToList:
- Adds the filename of an IwbFile to a stringlist, and its
- load order as an object paired with the filename.
- Example usage:
- slMasters := TStringList.Create;
- f := FileByName('Update.esm');
- AddPluginToList(f, slMasters);
- AddMessage(Format('[%s] %s',
- [IntToHex(slMasters.Objects[0], 2), slMasters[0]])); // [01] Update.esm
- }
- procedure AddFileToList(f: IInterface; var sl: TStringList);
- var
- filename: string;
- i, iNewLoadOrder, iLoadOrder: Integer;
- begin
- // raise exception if input file is not assigned
- if not Assigned(f) then
- raise Exception.Create('AddFileToList: Input file is not assigned');
- // raise exception if input stringlist is not assigned
- if not Assigned(sl) then
- raise Exception.Create('AddFileToList: Input TStringList is not assigned');
- // don't add file to list if it is already present
- filename := GetFileName(f);
- if sl.IndexOf(filename) > -1 then
- exit;
- // loop through list to determine correct place to
- // insert the file into it
- iNewLoadOrder := GetLoadOrder(f);
- for i := 0 to Pred(sl.Count) do begin
- iLoadOrder := Integer(sl.Objects[i]);
- // insert the file at the current position if we
- // reach the a file with a lower load order than it
- if iLoadOrder > iNewLoadOrder then begin
- sl.InsertObject(i, filename, TObject(iNewLoadOrder));
- exit;
- end;
- end;
- // if the list is empty, or if all files in the list
- // are at lower load orders than the file we're adding,
- // we add the file to the end of the list
- sl.AddObject(filename, TObject(iNewLoadOrder));
- end;
- {
- AddMastersToList:
- Adds the masters from a specific file to a specified
- stringlist.
- Example usage:
- slMasters := TStringList.Create;
- AddMastersToList(FileByName('Dragonborn.esm'), slMasters);
- }
- procedure AddMastersToList(f: IInterface; var sl: TStringList; sorted: boolean);
- var
- fileHeader, masters, master, masterFile: IInterface;
- i: integer;
- filename: string;
- begin
- // raise exception if input stringlist is not assigned
- if not Assigned(sl) then
- raise Exception.Create('AddMastersToList: Input TStringList not assigned');
- // add file's masters
- fileHeader := GetFileHeader(f);
- masters := ElementByPath(fileHeader, 'Master Files');
- if Assigned(masters) then
- for i := 0 to ElementCount(masters) - 1 do begin
- master := ElementByIndex(masters, i);
- filename := GetElementEditValues(master, 'MAST');
- masterFile := FileByName(filename);
- if Assigned(masterFile) and sorted then
- AddFileToList(masterFile, sl)
- else if sl.IndexOf(filename) = -1 then
- sl.AddObject(filename, TObject(GetLoadOrder(masterFile)));
- end;
- end;
- {
- RemoveMaster:
- Removes a master matching the specified string from
- the specified file.
- Example usage:
- f := FileByIndex(i);
- RemoveMaster(f, 'Update.esm');
- }
- procedure RemoveMaster(f: IInterface; masterFilename: String);
- var
- fileHeader, master, masters: IInterface;
- i: integer;
- sMaster: string;
- begin
- fileHeader := GetFileHeader(f);
- masters := ElementByPath(fileHeader, 'Master Files');
- // loop through the masteres in reverse
- for i := Pred(ElementCount(masters)) downto 0 do begin
- master := ElementByIndex(masters, i);
- sMaster := GetElementEditValues(master, 'MAST');
- if sMaster = masterFilename then begin
- Remove(master);
- break;
- end;
- end;
- end;
- {
- FileByName:
- Gets a file from a filename.
- Example usage:
- f := FileByName('Skyrim.esm');
- }
- function FileByName(s: string): IInterface;
- var
- i: integer;
- begin
- Result := nil;
- for i := 0 to FileCount - 1 do begin
- if GetFileName(FileByIndex(i)) = s then begin
- Result := FileByIndex(i);
- break;
- end;
- end;
- end;
- function HexFormID(rec: IInterface): string;
- begin
- Result := IntToHex(GetLoadOrderFormID(rec), 8);
- end;
- function GetName(rec: IInterface): string;
- begin
- Result := GetElementEditValues(rec, 'EDID');
- if (Result = '') then
- Result := StringReplace(Name(rec), HexFormID(rec), '', [rfReplaceAll]);
- end;
- function GetMasterOrdinal(f: IInterface; filename: string): Integer;
- var
- i: Integer;
- header, masters, master: IInterface;
- begin
- header := GetFileHeader(f);
- masters := ElementByPath(header, 'Master Files');
- for i := 0 to Pred(ElementCount(masters)) do begin
- master := ElementByIndex(masters, i);
- if GetElementEditValues(master, 'MAST') = filename then begin
- Result := i;
- break;
- end;
- end;
- end;
- procedure BuildMap(sOldMasters, sNewMaster: string; var slMap: TStringList);
- var
- mFile, f, rec: IInterface;
- i, j, index: Integer;
- sName, sHexID: string;
- slNewRecords, slMasters, slErrors: TStringList;
- begin
- mFile := FileByName(sNewMaster);
- if not Assigned(mFile) then
- raise Exception.Create('BuildMap: Master file '+sNewMaster+' isn''t loaded!');
- // create map
- slMap := TStringList.Create;
- slErrors := TStringList.Create;
- // get new records from new master file
- slNewRecords := TStringList.Create;
- AddMessage(#13#10'BuildMap: Processing new records in '+sNewMaster);
- for i := 0 to Pred(RecordCount(mFile)) do begin
- rec := RecordByIndex(mFile, i);
- if i mod 1000 = 0 then
- AddMessage(Format(' (%d/%d)', [i + 1, RecordCount(mFile)]));
- // if record is not an override, add it to new records
- if IsMaster(rec) then begin
- sName := GetName(rec);
- slNewRecords.Values[sName] := HexFormID(rec);
- end;
- end;
- AddMessage(' Found '+IntToStr(slNewRecords.Count)+' new records.');
- slNewRecords.SaveToFile(sNewMaster+'-new.txt');
- // find matching records in old masters
- slMasters := TStringList.Create;
- slMasters.StrictDelimiter := true;
- slMasters.CommaText := sOldMasters;
- AddMessage(#13#10'BuildMap: Mapping new records in old masters');
- for i := 0 to Pred(slMasters.Count) do begin
- AddMessage(' Mapping '+slMasters[i]);
- f := FileByName(slMasters[i]);
- if not Assigned(f) then
- raise Exception.Create('BuildMap: File '+slMasters[i]+' isn''t loaded!');
- // loop through file's records
- for j := 0 to Pred(RecordCount(f)) do begin
- rec := RecordByIndex(f, j);
- // if record is an override, skip it
- if not IsMaster(rec) then
- continue;
- // else look for a mapping for it
- sName := GetName(rec);
- sHexID := HexFormID(rec);
- index := slNewRecords.IndexOfName(sName);
- if index > -1 then begin
- // if mapping found, try to store it in the map
- if slMap.IndexOfName(sHexID) = -1 then
- // store the FormID mapping
- slMap.Values[sHexID] := slNewRecords.ValueFromIndex[index]
- else
- // if map already has a mapping for the FormID, print error
- slErrors.Add(Format(' FormID remapping conflict with %s: %s currently maps to %s',
- [sName, sHexID, slMap.Values[sHexID]]));
- end
- else begin
- // formID mapping not found
- slErrors.Add(Format(' No FormID mapping found for %s [%s]', [sName, sHexID]));
- end;
- end;
- end;
- // save map
- slMap.SaveToFile(sNewMaster + '-map.txt');
- // print errors
- if slErrors.Count > 0 then begin
- AddMessage(#13#10'BuildMap: Finished with errors');
- AddMessage(slErrors.Text);
- end
- else
- AddMessage(#13#10'BuildMap: Finished');
- // free memory
- slNewRecords.Free;
- slMasters.Free;
- slErrors.Free;
- end;
- procedure FilterStringList(var sl1, sl2: TStringList);
- var
- i: Integer;
- begin
- for i := Pred(sl1.Count) downto 0 do begin
- if sl2.IndexOf(sl1[i]) = -1 then
- sl1.Delete(i);
- end;
- end;
- procedure BuildDependencies(var slOldMasters, slDependencies: TStringList);
- var
- slMasters, sl: TStringList;
- filename: string;
- i, index: Integer;
- f: IInterface;
- begin
- slMasters := TStringList.Create;
- for i := 0 to Pred(FileCount) do begin
- f := FileByIndex(i);
- filename := GetFileName(f);
- // skip old masters
- if slOldMasters.IndexOf(filename) > -1 then
- continue;
- // get file's masters
- AddMastersToList(f, slMasters, true);
- // filter masters to only entries in slOldMasters
- FilterStringList(slMasters, slOldMasters);
- // if any masters left after filtering, add them to slDependencies
- if slMasters.Count > 0 then begin
- AddMessage(' '+filename+' has old masters');
- slDependencies.Values[filename] := slMasters.CommaText;
- slMasters.Clear;
- end;
- end;
- slMasters.Free;
- end;
- procedure HandleOverrides(f: IInterface; var slMasters, slMap: TStringList);
- var
- i, masterOrdinal, index: Integer;
- rec, mRec, mFile: IInterface;
- formOrdinal, oldFormID, newFormID: Cardinal;
- sHexID, sEdid, sFilename: string;
- slOperations: TStringList;
- begin
- // create stringlist
- slOperations := TStringList.Create;
- // get master ordinal
- masterOrdinal := GetMasterOrdinal(f, slMasters[0]);
- formOrdinal := masterOrdinal * $01000000;
- // loop through records
- for i := 0 to Pred(RecordCount(f)) do begin
- rec := RecordByIndex(f, i);
- // skip non-override records
- if IsMaster(rec) then
- continue;
- // evaluate master of record
- mRec := MasterOrSelf(rec);
- mFile := GetFile(mRec);
- sFilename := GetFileName(mFile);
- index := slMasters.IndexOf(GetFileName(mFile));
- // skip record if it's not in one of our master files
- if index = -1 then
- continue;
- // remap formID
- sHexID := HexFormID(mRec);
- index := slMap.IndexOfName(sHexID);
- if index = -1 then
- slOperations.Add(' No remapping found for '+sHexID)
- else try
- sHexID := slMap.ValueFromIndex[index];
- sEdid := GetElementEditValues(rec, 'EDID');
- newFormID := StrToInt('$' + sHexID);
- newFormID := formOrdinal + (newFormID and $00FFFFFF);
- slOperations.Add(Format(' Remapping FormID from [%s] to [%s] on %s',
- [IntToHex(oldFormID, 8), sHexID, sEdid]));
- SetLoadOrderFormID(rec, newFormID);
- except
- on x: Exception do
- slOperations.Add(' Exception remapping '+Name(rec)+': '+x.Message);
- end;
- end;
- // print operations
- if slOperations.Count > 0 then
- AddMessage(slOperations.Text);
- // free memory
- slOperations.Free;
- end;
- procedure SwapMasters(sOldMasters, sNewMaster: string; var slMap: TStringList);
- var
- DependencyList: TList;
- slOldMasters, slDependencies, sl: TStringList;
- filename, masterFilename: string;
- f: IInterface;
- i, j: Integer;
- begin
- // build map if it isn't given
- if not Assigned(slMap) then begin
- AddMessage(#13#10'SwapMasters: Map file not found. Building map...');
- BuildMap(sOldMasters, sNewMaster, slMap);
- end;
- // build initial lists
- slOldMasters := TStringList.Create;
- slOldMasters.StrictDelimiter := true;
- slOldMasters.CommaText := sOldMasters;
- AddMessage(slOldMasters.CommaText);
- // build list of files using old masters
- slDependencies := TStringList.Create;
- BuildDependencies(slOldMasters, slDependencies);
- AddMessage(#13#10'SwapMasters: Dependencies found');
- for i := 0 to Pred(slDependencies.Count) do
- AddMessage(' '+slDependencies[i]);
- // handle overrides
- for i := 0 to Pred(slDependencies.Count) do begin
- filename := slDependencies.Names[i];
- AddMessage(#13#10'SwapMasters: Handling overrides in '+filename);
- f := FileByName(filename);
- sl := TStringList.Create;
- sl.StrictDelimiter := true;
- sl.CommaText := slDependencies.ValueFromIndex[i];
- AddMessage(' '+sl.CommaText);
- HandleOverrides(f, sl, slMap);
- sl.Free;
- end;
- // change masters
- AddMessage(#13#10'SwapMasters: Swapping masters');
- for i := 0 to Pred(slDependencies.Count) do begin
- filename := slDependencies.Names[i];
- f := FileByName(filename);
- sl := TStringList.Create;
- sl.StrictDelimiter := true;
- sl.CommaText := slDependencies.ValueFromIndex[i];
- AddMessage(' Handling '+filename);
- // add new master
- AddMasterIfMissing(f, sNewMaster);
- // remove masters
- for j := 0 to Pred(sl.Count) do begin
- masterFilename := sl[j];
- RemoveMaster(f, masterFilename);
- end;
- end;
- // free memory
- slOldMasters.Free;
- slDependencies.Free;
- end;
- function Initialize: Integer;
- var
- filename: string;
- slMap: TStringList;
- begin
- // load map if it exists
- filename := cNewMasterFile + '-map.txt';
- if FileExists(filename) then begin
- slMap := TStringList.Create;
- AddMessage('Using map '+filename);
- slMap.LoadFromFile(filename);
- end;
- // swap the masters
- SwapMasters(cOldMasterFiles, cNewMasterFile, slMap);
- // free memory
- if Assigned(slMap) then
- slMap.Free;
- end;
- end.
Advertisement
Add Comment
Please, Sign In to add comment