Mator

[xEdit] [pascal] WIP USLEEP Master Swapping Script

Nov 9th, 2015
218
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Pascal 13.93 KB | None | 0 0
  1. {
  2.   USLEEP Swap Masters Script
  3.   created by matortheeternal
  4.  
  5.   Swaps the masters of plugins that rely on the USKP, UDGP, UHFP, or UDGP
  6.   to rely on USLEEP instead.  NOTE: You must have all of the unofficial
  7.   patches and USLEEP loaded in xEdit to use this script to adjust the
  8.   masters of a file.
  9. }
  10.  
  11. unit UserScript;
  12.  
  13. const
  14.   cOldMasterFiles = 'Unofficial Skyrim Patch.esp'#44'Unofficial Dawnguard Patch.esp'#44
  15.     'Unofficial Hearthfire Patch.esp'#44'Unofficial Dragonborn Patch.esp';
  16.   cNewMasterFile = 'Unofficial Skyrim Legendary Edition Patch.esp';
  17.  
  18. var
  19.   FilesList: TList;
  20.  
  21.  
  22. {
  23.   GetFileHeader:
  24.   Gets the file header of a file.
  25.  
  26.   Example usage:
  27.   f := FileByName('Skyrim.esm');
  28.   header := GetFileHeader(f);
  29.   if not Assigned(ElementByPath(Header, 'Master Files')) then
  30.     AddMessage(GetFileName(f) + ' has no master files!');
  31. }
  32. function GetFileHeader(f: IInterface): IInterface;
  33. begin
  34.   if not Assigned(f) then
  35.     raise Exception.Create('GetFileHeader: Input file is not assigned');
  36.   if ElementType(f) <> etFile then
  37.     raise Exception.Create('GetFileHeader: Input element is not a file');
  38.    
  39.   Result := ElementByIndex(f, 0);
  40. end;
  41.  
  42. {
  43.   AddFileToList:
  44.   Adds the filename of an IwbFile to a stringlist, and its
  45.   load order as an object paired with the filename.
  46.  
  47.   Example usage:
  48.   slMasters := TStringList.Create;
  49.   f := FileByName('Update.esm');
  50.   AddPluginToList(f, slMasters);
  51.   AddMessage(Format('[%s] %s',
  52.     [IntToHex(slMasters.Objects[0], 2), slMasters[0]])); // [01] Update.esm
  53. }
  54. procedure AddFileToList(f: IInterface; var sl: TStringList);
  55. var
  56.   filename: string;
  57.   i, iNewLoadOrder, iLoadOrder: Integer;
  58. begin
  59.   // raise exception if input file is not assigned
  60.   if not Assigned(f) then
  61.     raise Exception.Create('AddFileToList: Input file is not assigned');
  62.   // raise exception if input stringlist is not assigned
  63.   if not Assigned(sl) then
  64.     raise Exception.Create('AddFileToList: Input TStringList is not assigned');
  65.    
  66.   // don't add file to list if it is already present
  67.   filename := GetFileName(f);
  68.   if sl.IndexOf(filename) > -1 then
  69.     exit;
  70.  
  71.   // loop through list to determine correct place to
  72.   // insert the file into it
  73.   iNewLoadOrder := GetLoadOrder(f);
  74.   for i := 0 to Pred(sl.Count) do begin
  75.     iLoadOrder := Integer(sl.Objects[i]);
  76.     // insert the file at the current position if we
  77.     // reach the a file with a lower load order than it
  78.     if iLoadOrder > iNewLoadOrder then begin
  79.       sl.InsertObject(i, filename, TObject(iNewLoadOrder));
  80.       exit;
  81.     end;
  82.   end;
  83.  
  84.   // if the list is empty, or if all files in the list
  85.   // are at lower load orders than the file we're adding,
  86.   // we add the file to the end of the list
  87.   sl.AddObject(filename, TObject(iNewLoadOrder));
  88. end;
  89.  
  90. {
  91.   AddMastersToList:
  92.   Adds the masters from a specific file to a specified
  93.   stringlist.
  94.  
  95.   Example usage:
  96.   slMasters := TStringList.Create;
  97.   AddMastersToList(FileByName('Dragonborn.esm'), slMasters);
  98. }
  99. procedure AddMastersToList(f: IInterface; var sl: TStringList; sorted: boolean);
  100. var
  101.   fileHeader, masters, master, masterFile: IInterface;
  102.   i: integer;
  103.   filename: string;
  104. begin
  105.   // raise exception if input stringlist is not assigned
  106.   if not Assigned(sl) then
  107.     raise Exception.Create('AddMastersToList: Input TStringList not assigned');
  108.  
  109.   // add file's masters
  110.   fileHeader := GetFileHeader(f);
  111.   masters := ElementByPath(fileHeader, 'Master Files');
  112.   if Assigned(masters) then
  113.     for i := 0 to ElementCount(masters) - 1 do begin
  114.       master := ElementByIndex(masters, i);
  115.       filename := GetElementEditValues(master, 'MAST');
  116.       masterFile := FileByName(filename);
  117.       if Assigned(masterFile) and sorted then
  118.         AddFileToList(masterFile, sl)
  119.       else if sl.IndexOf(filename) = -1 then
  120.         sl.AddObject(filename, TObject(GetLoadOrder(masterFile)));
  121.     end;
  122. end;
  123.  
  124. {
  125.   RemoveMaster:
  126.   Removes a master matching the specified string from
  127.   the specified file.
  128.  
  129.   Example usage:
  130.   f := FileByIndex(i);
  131.   RemoveMaster(f, 'Update.esm');
  132. }
  133. procedure RemoveMaster(f: IInterface; masterFilename: String);
  134. var
  135.   fileHeader, master, masters: IInterface;
  136.   i: integer;
  137.   sMaster: string;
  138. begin
  139.   fileHeader := GetFileHeader(f);
  140.   masters := ElementByPath(fileHeader, 'Master Files');
  141.   // loop through the masteres in reverse
  142.   for i := Pred(ElementCount(masters)) downto 0 do begin
  143.     master := ElementByIndex(masters, i);
  144.     sMaster := GetElementEditValues(master, 'MAST');
  145.     if sMaster = masterFilename then begin
  146.       Remove(master);
  147.       break;
  148.     end;
  149.   end;
  150. end;
  151.  
  152. {
  153.   FileByName:
  154.   Gets a file from a filename.
  155.  
  156.   Example usage:
  157.   f := FileByName('Skyrim.esm');
  158. }
  159. function FileByName(s: string): IInterface;
  160. var
  161.   i: integer;
  162. begin
  163.   Result := nil;
  164.   for i := 0 to FileCount - 1 do begin
  165.     if GetFileName(FileByIndex(i)) = s then begin
  166.       Result := FileByIndex(i);
  167.       break;
  168.     end;
  169.   end;
  170. end;
  171.  
  172. function HexFormID(rec: IInterface): string;
  173. begin
  174.   Result := IntToHex(GetLoadOrderFormID(rec), 8);
  175. end;
  176.  
  177. function GetName(rec: IInterface): string;
  178. begin
  179.   Result := GetElementEditValues(rec, 'EDID');
  180.   if (Result = '') then
  181.     Result := StringReplace(Name(rec), HexFormID(rec), '', [rfReplaceAll]);
  182. end;
  183.  
  184. function GetMasterOrdinal(f: IInterface; filename: string): Integer;
  185. var
  186.   i: Integer;
  187.   header, masters, master: IInterface;
  188. begin
  189.   header := GetFileHeader(f);
  190.   masters := ElementByPath(header, 'Master Files');
  191.   for i := 0 to Pred(ElementCount(masters)) do begin
  192.     master := ElementByIndex(masters, i);
  193.     if GetElementEditValues(master, 'MAST') = filename then begin
  194.       Result := i;
  195.       break;
  196.     end;
  197.   end;
  198. end;
  199.  
  200. procedure BuildMap(sOldMasters, sNewMaster: string; var slMap: TStringList);
  201. var
  202.   mFile, f, rec: IInterface;
  203.   i, j, index: Integer;
  204.   sName, sHexID: string;
  205.   slNewRecords, slMasters, slErrors: TStringList;
  206. begin
  207.   mFile := FileByName(sNewMaster);
  208.   if not Assigned(mFile) then
  209.     raise Exception.Create('BuildMap: Master file '+sNewMaster+' isn''t loaded!');
  210.  
  211.   // create map
  212.   slMap := TStringList.Create;
  213.   slErrors := TStringList.Create;
  214.  
  215.   // get new records from new master file
  216.   slNewRecords := TStringList.Create;
  217.   AddMessage(#13#10'BuildMap: Processing new records in '+sNewMaster);
  218.   for i := 0 to Pred(RecordCount(mFile)) do begin
  219.     rec := RecordByIndex(mFile, i);
  220.     if i mod 1000 = 0 then
  221.       AddMessage(Format('    (%d/%d)', [i + 1, RecordCount(mFile)]));
  222.     // if record is not an override, add it to new records
  223.     if IsMaster(rec) then begin
  224.       sName := GetName(rec);
  225.       slNewRecords.Values[sName] := HexFormID(rec);
  226.     end;
  227.   end;
  228.   AddMessage('    Found '+IntToStr(slNewRecords.Count)+' new records.');
  229.   slNewRecords.SaveToFile(sNewMaster+'-new.txt');
  230.  
  231.   // find matching records in old masters
  232.   slMasters := TStringList.Create;
  233.   slMasters.StrictDelimiter := true;
  234.   slMasters.CommaText := sOldMasters;
  235.   AddMessage(#13#10'BuildMap: Mapping new records in old masters');
  236.   for i := 0 to Pred(slMasters.Count) do begin
  237.     AddMessage('    Mapping '+slMasters[i]);
  238.     f := FileByName(slMasters[i]);
  239.     if not Assigned(f) then
  240.       raise Exception.Create('BuildMap: File '+slMasters[i]+' isn''t loaded!');
  241.     // loop through file's records
  242.     for j := 0 to Pred(RecordCount(f)) do begin
  243.       rec := RecordByIndex(f, j);
  244.       // if record is an override, skip it
  245.       if not IsMaster(rec) then
  246.         continue;
  247.       // else look for a mapping for it
  248.       sName := GetName(rec);
  249.       sHexID := HexFormID(rec);
  250.       index := slNewRecords.IndexOfName(sName);
  251.       if index > -1 then begin
  252.         // if mapping found, try to store it in the map
  253.         if slMap.IndexOfName(sHexID) = -1 then
  254.           // store the FormID mapping
  255.           slMap.Values[sHexID] := slNewRecords.ValueFromIndex[index]
  256.         else
  257.           // if map already has a mapping for the FormID, print error
  258.           slErrors.Add(Format('    FormID remapping conflict with %s: %s currently maps to %s',
  259.             [sName, sHexID, slMap.Values[sHexID]]));
  260.       end
  261.       else begin
  262.         // formID mapping not found
  263.         slErrors.Add(Format('    No FormID mapping found for %s [%s]', [sName, sHexID]));
  264.       end;
  265.     end;
  266.   end;
  267.  
  268.   // save map
  269.   slMap.SaveToFile(sNewMaster + '-map.txt');
  270.  
  271.   // print errors
  272.   if slErrors.Count > 0 then begin
  273.     AddMessage(#13#10'BuildMap: Finished with errors');
  274.     AddMessage(slErrors.Text);
  275.   end
  276.   else
  277.     AddMessage(#13#10'BuildMap: Finished');
  278.  
  279.   // free memory
  280.   slNewRecords.Free;
  281.   slMasters.Free;
  282.   slErrors.Free;
  283. end;
  284.  
  285. procedure FilterStringList(var sl1, sl2: TStringList);
  286. var
  287.   i: Integer;
  288. begin
  289.   for i := Pred(sl1.Count) downto 0 do begin
  290.     if sl2.IndexOf(sl1[i]) = -1 then
  291.       sl1.Delete(i);
  292.   end;
  293. end;
  294.  
  295. procedure BuildDependencies(var slOldMasters, slDependencies: TStringList);
  296. var
  297.   slMasters, sl: TStringList;
  298.   filename: string;
  299.   i, index: Integer;
  300.   f: IInterface;
  301. begin
  302.   slMasters := TStringList.Create;
  303.   for i := 0 to Pred(FileCount) do begin
  304.     f := FileByIndex(i);
  305.     filename := GetFileName(f);
  306.    
  307.     // skip old masters
  308.     if slOldMasters.IndexOf(filename) > -1 then
  309.       continue;
  310.    
  311.     // get file's masters
  312.     AddMastersToList(f, slMasters, true);
  313.     // filter masters to only entries in slOldMasters
  314.     FilterStringList(slMasters, slOldMasters);
  315.    
  316.     // if any masters left after filtering, add them to slDependencies
  317.     if slMasters.Count > 0 then begin
  318.       AddMessage('    '+filename+' has old masters');
  319.       slDependencies.Values[filename] := slMasters.CommaText;
  320.       slMasters.Clear;
  321.     end;
  322.   end;
  323.   slMasters.Free;
  324. end;
  325.  
  326. procedure HandleOverrides(f: IInterface; var slMasters, slMap: TStringList);
  327. var
  328.   i, masterOrdinal, index: Integer;
  329.   rec, mRec, mFile: IInterface;
  330.   formOrdinal, oldFormID, newFormID: Cardinal;
  331.   sHexID, sEdid, sFilename: string;
  332.   slOperations: TStringList;
  333. begin
  334.   // create stringlist
  335.   slOperations := TStringList.Create;
  336.  
  337.   // get master ordinal
  338.   masterOrdinal := GetMasterOrdinal(f, slMasters[0]);
  339.   formOrdinal := masterOrdinal * $01000000;
  340.  
  341.   // loop through records
  342.   for i := 0 to Pred(RecordCount(f)) do begin
  343.     rec := RecordByIndex(f, i);
  344.    
  345.     // skip non-override records
  346.     if IsMaster(rec) then
  347.       continue;
  348.      
  349.     // evaluate master of record
  350.     mRec := MasterOrSelf(rec);
  351.     mFile := GetFile(mRec);
  352.     sFilename := GetFileName(mFile);
  353.     index := slMasters.IndexOf(GetFileName(mFile));
  354.    
  355.     // skip record if it's not in one of our master files
  356.     if index = -1 then
  357.       continue;
  358.    
  359.     // remap formID
  360.     sHexID := HexFormID(mRec);
  361.     index := slMap.IndexOfName(sHexID);
  362.     if index = -1 then
  363.       slOperations.Add('    No remapping found for '+sHexID)
  364.     else try
  365.       sHexID := slMap.ValueFromIndex[index];
  366.       sEdid := GetElementEditValues(rec, 'EDID');
  367.       newFormID := StrToInt('$' + sHexID);
  368.       newFormID := formOrdinal + (newFormID and $00FFFFFF);
  369.       slOperations.Add(Format('    Remapping FormID from [%s] to [%s] on %s',
  370.         [IntToHex(oldFormID, 8), sHexID, sEdid]));
  371.       SetLoadOrderFormID(rec, newFormID);
  372.     except
  373.       on x: Exception do
  374.         slOperations.Add('    Exception remapping '+Name(rec)+': '+x.Message);
  375.     end;
  376.   end;
  377.  
  378.   // print operations
  379.   if slOperations.Count > 0 then
  380.     AddMessage(slOperations.Text);
  381.  
  382.   // free memory
  383.   slOperations.Free;
  384. end;
  385.  
  386. procedure SwapMasters(sOldMasters, sNewMaster: string; var slMap: TStringList);
  387. var
  388.   DependencyList: TList;
  389.   slOldMasters, slDependencies, sl: TStringList;
  390.   filename, masterFilename: string;
  391.   f: IInterface;
  392.   i, j: Integer;
  393. begin
  394.   // build map if it isn't given
  395.   if not Assigned(slMap) then begin
  396.     AddMessage(#13#10'SwapMasters: Map file not found.  Building map...');
  397.     BuildMap(sOldMasters, sNewMaster, slMap);
  398.   end;
  399.  
  400.   // build initial lists
  401.   slOldMasters := TStringList.Create;
  402.   slOldMasters.StrictDelimiter := true;
  403.   slOldMasters.CommaText := sOldMasters;
  404.   AddMessage(slOldMasters.CommaText);
  405.  
  406.   // build list of files using old masters
  407.   slDependencies := TStringList.Create;
  408.   BuildDependencies(slOldMasters, slDependencies);
  409.   AddMessage(#13#10'SwapMasters: Dependencies found');
  410.   for i := 0 to Pred(slDependencies.Count) do
  411.     AddMessage('    '+slDependencies[i]);
  412.  
  413.   // handle overrides
  414.   for i := 0 to Pred(slDependencies.Count) do begin
  415.     filename := slDependencies.Names[i];
  416.     AddMessage(#13#10'SwapMasters: Handling overrides in '+filename);
  417.     f := FileByName(filename);
  418.     sl := TStringList.Create;
  419.     sl.StrictDelimiter := true;
  420.     sl.CommaText := slDependencies.ValueFromIndex[i];
  421.     AddMessage('    '+sl.CommaText);
  422.     HandleOverrides(f, sl, slMap);
  423.     sl.Free;
  424.   end;
  425.  
  426.   // change masters
  427.   AddMessage(#13#10'SwapMasters: Swapping masters');
  428.   for i := 0 to Pred(slDependencies.Count) do begin
  429.     filename := slDependencies.Names[i];
  430.     f := FileByName(filename);
  431.     sl := TStringList.Create;
  432.     sl.StrictDelimiter := true;
  433.     sl.CommaText := slDependencies.ValueFromIndex[i];
  434.     AddMessage('    Handling '+filename);
  435.     // add new master
  436.     AddMasterIfMissing(f, sNewMaster);
  437.     // remove masters
  438.     for j := 0 to Pred(sl.Count) do begin
  439.       masterFilename := sl[j];
  440.       RemoveMaster(f, masterFilename);
  441.     end;
  442.   end;
  443.  
  444.   // free memory
  445.   slOldMasters.Free;
  446.   slDependencies.Free;
  447. end;
  448.  
  449. function Initialize: Integer;
  450. var
  451.   filename: string;
  452.   slMap: TStringList;
  453. begin
  454.   // load map if it exists
  455.   filename := cNewMasterFile + '-map.txt';
  456.   if FileExists(filename) then begin
  457.     slMap := TStringList.Create;
  458.     AddMessage('Using map '+filename);
  459.     slMap.LoadFromFile(filename);
  460.   end;
  461.  
  462.   // swap the masters
  463.   SwapMasters(cOldMasterFiles, cNewMasterFile, slMap);
  464.  
  465.   // free memory
  466.   if Assigned(slMap) then
  467.     slMap.Free;
  468. end;
  469.  
  470. end.
Advertisement
Add Comment
Please, Sign In to add comment