Jump to content


  • Content count

  • Joined

  • Last visited

About fireundubh

  • Rank

Recent Profile Visitors

427 profile views
  1. Wrye Bash - All Games

    Hmm, I ran some quick tests in xEdit on SSE and FO4 ESMs. From what I've tested, CELL and WRLD have nonzero Form Versions.
  2. Wrye Bash - All Games

    No, don't expect parity; the Form Version can differ between records. The TES4 header record is really just another record. I wrote a rough Python script for how to read the Form Version for records in groups: find_form_version.py. It does not iterate through records or groups, so you'll need to implement that yourself however you want to do that in Wrye Bash. edit: Updated the script to iterate through groups and records, except CELL and WRLD, which I skipped simply because they require a bit more work than I intended to put into this. CELL and WRLD groups contain multiple levels of groups, so the script currently fails to parse those records. On the plus side, the script now logs the Form ID for each record, in addition to the Signature and Form Version, so you can open up a plugin in a hex editor and see the data structures yourself. edit 2: Wrote a third version that exports record metadata from all ESM/ESP files in a directory to JSON. It also uses struct.unpack and skips DIAL records, too. https://gist.github.com/fireundubh/af0994cb65ffb26d0810ee213a3b46fb Game: Tested on only SSE plugins Version: Tested on Python 3.6.4 Libraries: io, json, os, pathlib, struct, collections.defaultdict Helpful Resources: https://docs.python.org/3/library/struct.html https://docs.python.org/3/library/io.html https://www.devdungeon.com/content/working-binary-data-python
  3. Searching Github

    I recommend SmartGit. It's free for personal use. Steeper learning curve than SourceTree but far more polished and flexible. All Git clients are lightweight relative to Git itself. AstroGrep and Agent Ransack are excellent in-file search programs. AstroGrep is less featureful but open source. I use both. RegEx101 is the best visual RegEx debugger I've used.
  4. [FO4/WIP] SourceCraft - Papyrus Script Generator

    This is what writing 89 terminal menu scripts in 16 seconds looks like:
  5. [WIPz] TES5Edit

    In the project I'm working on, I just opted to avoid using FormID, Signature, EditorID, and LinksTo in favor of RegEx. I think it might even be faster, but I don't know if that's because I replaced looping over elements with preloading script properties and menu items into "dictionaries." (Too bad we can't use TDictionary, but SplitString with TStringDynArray seems to be okay.) I don't know. Which functions other than Check don't require a record? I can write some unit tests after I finish up my project.
  6. [WIPz] TES5Edit

    To use the FormID, EditorID, Signature, and other functions, you need to use the LinksTo function. Fact. The examples above are just examples to illustrate the issue.
  7. [WIPz] TES5Edit

    I know the difference between a FormID string and a record with a FormID. That's the problem: they're different and they're treated differently. LOL. Run this script on a TERM record with a VMAD subrecord that has two or more object properties, one of which contains the string "Player [00000014]" in the FormID field. unit UserScript; const PropertyRefPath = 'Value\Object Union\Object v2\FormID'; function Process(e: IInterface): Integer; var i: Integer; properties, p, ref: IInterface; begin properties := ElementByPath(e, 'VMAD\Script Fragments\Script\Properties'); for i := 0 to Pred(ElementCount(properties)) do begin p := ElementByIndex(properties, i); ref := LinksTo(ElementByPath(p, PropertyRefPath)); AddMessage(EditorID(ref)); end; end; end. That script will return results similar to: Applying script... AutoLoot_Perks [Apply Script done] Processed Records: 1, Elapsed Time: 00:00 Change EditorID to Signature and: Applying script... FLST [Apply Script done] Processed Records: 1, Elapsed Time: 00:00 This is bad. Either EditorID and Signature need to return values, or they need to raise exceptions. Right now, they return zero-length strings. You can't even use try-except here because the zero-length string is a valid result. So, instead of the above loop, you have to do something wonky create a unique EditorID function that takes two parameters: unit UserScript; const PropertyRefPath = 'Value\Object Union\Object v2\FormID'; function GetEditorID(mainRec, linkedRec: IInterface): String; begin Result := IfThen(GetNativeValue(mainRec) = $14, 'Player', EditorID(linkedRec)); end; function Process(e: IInterface): Integer; var i: Integer; properties, p, ref: IInterface; begin properties := ElementByPath(e, 'VMAD\Script Fragments\Script\Properties'); for i := 0 to Pred(ElementCount(properties)) do begin p := ElementByIndex(properties, i); ref := ElementByPath(p, PropertyRefPath); AddMessage(GetEditorID(ref, LinksTo(ref))); end; end; end. But what if you want to return a linked element from the loop? Well, if you want to also handle the Player, you can't do that directly. You have to do something like this: unit UserScript; const MatchingID = 'Player'; PropertyRefPath = 'Value\Object Union\Object v2\FormID'; function GetEditorID(mainRec, linkedRec: IInterface): String; begin Result := IfThen(GetNativeValue(mainRec) = $14, 'Player', EditorID(linkedRec)); end; function GetMatchingProperty(properties: IInterface; MatchingID: String): IInterface; var i: Integer; p, ref, linkedRef: IInterface; begin for i := 0 to Pred(ElementCount(properties)) do begin p := ElementByIndex(properties, i); ref := ElementByPath(p, PropertyRefPath); linkedRef := LinksTo(ref); if GetEditorID(ref, linkedRef) = MatchingID then break; end; if GetEditorID(ref, linkedRef) <> MatchingID then raise Exception.Create('Could not find matching property'); Result := ref; end; function Process(e: IInterface): Integer; var properties, ref: IInterface; begin properties := ElementByPath(e, 'VMAD\Script Fragments\Script\Properties'); ref := GetMatchingProperty(properties, MatchingID); AddMessage(GetEditorID(ref, LinksTo(ref))); end; end. If you didn't need to handle the Player differently, this script would be fine: unit UserScript; const MatchingId = 'AutoLoot_Perks'; PropertyRefPath = 'Value\Object Union\Object v2\FormID'; function Process(e: IInterface): Integer; var i: Integer; properties, p, ref: IInterface; edid: String; begin properties := ElementByPath(e, 'VMAD\Script Fragments\Script\Properties'); for i := 0 to Pred(ElementCount(properties)) do begin p := ElementByIndex(properties, i); ref := LinksTo(ElementByPath(p, PropertyRefPath)); edid := EditorID(ref); if edid = MatchingId then break; end; if edid <> MatchingId then raise Exception.Create('Could not find matching property'); // do something with the linked ref or edid AddMessage(Signature(ref)); end; end. So, I hope where words have failed, the code shows you what I'm talking about. In theory, EditorID, Signature, and LinksTo require an IInterface parameter, but the internal handling of $14 tricks these functions into treating a string as a record... ...as a record with no data, hence the fake record in my original post about this issue. TLDR: You can pass in a string to functions that require an IInterface parameter via LinksTo if that string is the $14 non-record record in a FormID field. Those functions that return a string will return a zero-length string.
  8. [WIPz] TES5Edit

    If you run EditorID on a field that contains the string "Player [00000014]," you'll get an empty string. In that case, further special handling would mean checking whether $14 was passed into EditorID, and either returning the string "Player" from EditorID, or throwing an exception. For example: procedure IwbMainRecord_EditorID(var Value: Variant; Args: TJvInterpreterArgs); var MainRecord: IwbMainRecord; begin Value := ''; if Supports(IInterface(Args.Values[0]), IwbMainRecord, MainRecord) then Value := StrUtils.IfThen(MainRecord.NativeValue = $14, 'Player', MainRecord.EditorID); end; The rest of the scripting functions that take an IInterface parameter (e.g., Signature, LinksTo) would also need similar treatment.
  9. [WIPz] TES5Edit

    What fake CELL? That was your own idea that you ruled out because there could be conflicts. My solution is a fake PLYR record that behaves in much the same way as the fake PLYR signature. We have .dat files that provide us a way of handling addresses that aren't in the ESMs. Why are we not using those .dat files to handle $14? Both the fake PLYR record and the fake PLYR signature are hacks, yes, but from xEdit's perspective, treating $14 as a string in a program that revolves around records is also a hack. That inconsistency has fallout: all other code that involves records has to account for $14 not being a record. When Elminster implemented $14 handling, there were no user scripts and, I think, the Referenced By feature also did not exist. Handling that inconsistency had some internal implications but that was it. However, we have user scripts and a Referenced By tab now - and thus a collection of external functions that return nothing or that don't throw exceptions when $14 is passed in, and a feature that can't be used to show references to $14. To me, you deal with this in one of two ways: implement a fake PLYR record, which fills in all the gaps without any drawbacks whatsoever; or implement further special handling of $14 in those functions (returning the right data or throwing exceptions) and somehow implement a way to show which records reference $14. One of those solutions is ready to be implemented right now through a simple pull request - and you're choosing "do nothing." Also, yes, technically, NULL should be a record because "NONE" is the actual signature of NULL references, so indeed, NONE would not be a fake record.
  10. [WIPz] TES5Edit

    Conflict with what!? What could possibly conflict with 00000014? No problems with the current implementation? I already listed them. A fake record for the Player serves the same exact purpose that the string approach does but without the drawbacks. The fake record solution causes zero issues, AFAIK, and corrects a hack that interferes with scripts and other xEdit features. I really don't understand your resistance here. You don't even have to do anything. I've already done the work. Maybe because there's only three people who write complex xEdit scripts: you, mator, and me. But I also remember a conversation once about seeing Player references, which is not really my goal here but remains a good side effect. I don't think I should have to use a Regular Expression to get the Editor ID or Signature of a CTDA reference, or use a TStringList as a dictionary, or any number of alternative ways to look up and compare data just because I have to work around the special handling of $14. Compare: // function RegExReplace(const ptrn, repl, subj: String): String; // var // re: TPerlRegEx; // output: String; // begin // re := TPerlRegEx.Create; // try // re.RegEx := ptrn; // re.Options := []; // re.Subject := subj; // re.Replacement := repl; // re.ReplaceAll; // output := re.Subject; // finally // re.Free; // Result := output; // end; // end; runsOn := Trim(RegExReplace('\[(.*)\]$', '', GetEditValue(ctdaRef))); With: runsOn := EditorID(LinksTo(ctdaRef)); The latter does not work because the strings-based approach does not return an Editor ID for the Player. You could then hack EditorID to return the right string for the Player. But then you'd be hacking in special handling for other functions, too, and it just gets out-of-hand trying to handle $14 differently than every other record. You also have the issue of a string masquerading as a record, so functions like LinksTo don't throw exceptions when the "record" is "Player [00000014]" and just return nothing, which makes debugging harder than necessary. You have a system in place for hardcoded forms, which you're already using in spades for every game. Why is adding Player really so bad?
  11. [WIPz] TES5Edit

    I don't see why a fake record is a problem. It's only used in the DAT files, which are not used by the game, CK, or any other plugin. (The PLYR group will need to be disabled in the Add popup menu, but I don't know how to do that.) It's just a means for xEdit to display and work with $14 in a way that does not interfere with other functionality, and in that sense, is far superior to the string-based approach. It also doesn't introduce conflicts as the hardcoded cell approach would. Looks like a win-win solution to me.
  12. [WIPz] TES5Edit

    @zilav @hlp Is there a better way to handle 00000014 than adding the Player as a string in wbInterface? The current approach means that all IInterface functions do not work on the Player address; they also don't throw exceptions. For example: Signature returns an empty string EditorID returns an empty string LinksTo returns nothing None of the ReferencedBy functions can return any results because the address is a string Would it be possible to create a PLYR group and record type for use in the .dat files, and use that PLYR record? edit: Looks like that's definitely possible. Created a wbRecord definition for PLYR with a wbEDID component. Modified a .dat file, added the PLYR group, and added a PLYR record with a Form ID of 00000014. (Used a script to change the Form ID.) Commented out almost all code where $14 was handled differently than other records. (Didn't touch the save editor. Left NewFormID handling alone.) And it works. I didn't comment out this because I'm not sure about the purpose: if wbUDRSetXESP and Supports(Add('XESP', True), IwbContainerElementRef, Cntr) then begin Cntr.ElementNativeValues['Reference'] := $14; Cntr.ElementNativeValues['Flags'] := 1; end; Any reason why xEdit should not handle the Player as a record this way? I uploaded a branch to GitHub with the above changes if you want to take a look. (Includes updated DAT files for all games.)
  13. Some of you may know I created a mod called Auto Loot for Fallout 4. Well, Auto Loot has a crazy holotape system for configuring the mod in-game, and that system involves 89 terminal scripts, 1,250 menu items, 55 global variables, 21 form lists, and 11 perks, spells, effects, actors, and actor references, and 1 cell. So, I wrote a script for xEdit to help me keep this mess under control. Here's what SourceCraft currently features: Automatically generates complete, ready-to-compile terminal scripts in Papyrus (.psc files) Support for GetGlobalValue and HasPerk menu items Uses tokenized templates (.txt files) for the overall script structure, terminal fragment functions, and code snippets Determines whether menu items are toggles or settings If menu items are toggles, determines whether menu items toggle "on" or "off" (0 or 1, AddPerk or RemovePerk) If menu items are settings, generates code that sets a GlobalVariable to the expected value If menu items are toggles or settings that should toggle or set multiple properties, generates code that uses a while loop to set those properties If menu items are settings that run on a reference, generates appropriate code (e.g., Player.AddPerk) Generates all required terminal fragment functions (no redundant code) Generates all required property declarations (no unused properties) Adds comments to terminal fragment functions with the menu item text Once I enhance, expand, and refactor the terminal script generator a bit more, I plan to break the script into modules and support other script types - where applicable. No screenshots - because this is all code - but here's a script I generated after getting while loop generation working.
  14. [WIPz] TES5Edit

    Tested again with same record - as new and override. If you hold Shift while loading the plugin to skip building references and then try to add a fragment, you'll get the error. If you let xEdit build references, that process does something that prevents the error from occurring. With the "inherited" keyword, the error won't occur regardless of whether you skipped building references and only one fragment will be added regardless of that second method call. I hope we're narrowing the issue down!
  15. [WIPz] TES5Edit

    @zilav It seems the solution is to add the "inherited" keyword before CheckCount in TwbArray.AssignInternal (line 14134 in wbImplementation.pas): inherited; CheckCount; CheckTerminator; end; However, I'm not really familiar with the "inherited" keyword, so while I know that CheckCount and CheckTerminator run, I don't know if those procedures are negatively impacted in any way. If that's the real solution though, you can make the commit, or I can throw up a pull request - and then I can look at making it possible to add properties (zEdit can do this!)

Support us on Patreon!