Jump to content
zilav

[WIPz] TES5Edit

Recommended Posts

If I wanted to write a script for xEdit, is there a place with documentation to assist in the process?  I actually have a script, but I need to modify it some.  Do I just need to figure it out on my own, or is there someplace I can get some help?

{
Comment
}
unit UserScript;

const
FormIDStart = $00050000;
FormIDLast = $00950000;

function Process(e: IInterface): integer;
var
TDirectory: TDirectory;
FORMID, FORMID2: cardinal;
fileiwb, check: IInterface;
i, j, k: integer;
files: TStringDynArray;
aFolder, q, s, t, m, x, zx, after, temp: string;
begin
aFolder := 'c:\OblivionLM\Data\sound\voice\Desert_AlikR.esp\';

fileiwb := FileByIndex(0);

FORMID := GetLoadOrderFormID(e);
AddMessage (IntToHex64(FORMID, 8));
q := (IntToHex64(FORMID, 8));
q := Copy(q, 3, 8);
q := '00' + q;
AddMessage (q);
for i := FormIDStart to FormIDLast do begin
check := RecordByFormID(fileiwb, i, True);
if not Assigned(check) then begin
AddMessage (IntToHex64(i, 8));
m := (IntToHex64(i, 8));
files := TDirectory.GetFiles(aFolder, '*.*', soAllDirectories);

for j := 0 to Pred(Length(files)) do begin
        s := files[j]; 
        
k := Pos(q, s);
if k > 0 then begin
AddMessage (s); 

x := StringReplace( S, q, m, [ rfReplaceAll, rfIgnoreCase ]);
AddMessage (x);

zx := ExtractFilePath(s);
AddMessage (zx);
after  := StringReplace(x, zx, '', [rfReplaceAll, rfIgnoreCase]);
AddMessage (after); 


     ShellExecute(
    TForm(frmMain).Handle,                  // parent window handle, use 0 for none
    'open',                                 // verb
    'cmd.exe',                              // application  
    '/C ren "' + s + '" "' + after + '"' ,
    ' ',                                     // working directory
    SW_HIDE                           // window mode
  );
  
end; 

end;
SetLoadOrderFormID(e, i);   
exit;

end;

end;

end;

end.

I believe this changes FormIDs within the plugin.  I don't need that.  I just need it to read the FormID associated with LIP files, and change the LIP files to match.

I can be persistent, so should be able to figure this out, but help is always welcome.

Share this post


Link to post
Share on other sites

xEdit has discord server.

You need to be more specific on what you want to do. As I see you want to rename .lip files because FormIDs have changed in a plugin, and perform some sort of a weird lookup matching in a cycle.

Lip file name consist of EditorID of QUST + EditorID of DIAL + FormID + Response Number. The only unique part in this name is FormID and the rest can repeat in quite a lot of files, so I don't understand the logic of how you match the old lip file name. This is like solving a single equation with several variables.

Share this post


Link to post
Share on other sites

First off, thanks for the help, zilav.

The script I posted is not mine, but I was told (I haven't "figured it out" for myself yet--I'm not familiar with the scripting language) that it checks the plugin for available FormIDs, renumbers those IDs appropriately (as in the "Change FormID" xEdit function), and finally takes those new IDs and renames the lip and mp3 (? I think, right?) accordingly.

I just want the script to get the appropriate FormID from the plugin, copy the rest of the name from the lip/mp3 file, substitute correct FormID in the string, then rename the lip/mp3 file.  All of the FormID changing has been done already.  Now, I just need to rename lip/mp3 files.

Direct scripting assistance is always welcome, but I don't mind going it alone and trying to learn the scripting language.  If that's the route I take, I'm curious if there's information to assist in learning the language?  If not, I'll just study existing scripts and post here with questions.

Thanks again.

EDIT:

Through the Github page, I found this.  As of this writing I haven't seen how much information it contains, but it's a start.  Anything else is more than welcome.

Share this post


Link to post
Share on other sites

I'm learning, and enjoying learning.  That xEdit documentation is quite helpful.  But I can't quite seem to figure out what the '$' symbol does?  What in God's name is the purpose of that?

From the supplied change LO script:

NewLoadOrder := StrToInt64('$' + s);

The script calls a GUI, gets user input (which is in the form of a string), the above line takes that string and converts it to an integer.  NP.  But what's the '$' doing?  I Googled for a minute or 5 but couldn't find a reference to that.

Share this post


Link to post
Share on other sites
Posted (edited)

The CKit names files with all those identifiers - but the game does not care what the name of file is ... As long as the record is pointing to correct name.
So, the lip/mp3 files can be renamed anything (MyLip001.lip for example) as long as the voice record points to the new file name not the CKit given name.

Right?

So, in your scenario - renumbering the formids - you don't really need to change the lip file names unless you changed where the records are pointing.

 

$ is symbol for String, it dates back to early programming languages

Edited by Jebbalon
added string thing

Share this post


Link to post
Share on other sites

'$' in Pascal is the same as '0x' in C - a prefix indicating a hex number in a string.

Alright, so you are changing FormIDs yourself and want to rename associated assets. First I suggest you to stop treating FormIDs as strings, they are integer 32 bits numbers. If you want to remove/zero the load order byte then

FormID and $00FFFFFF

will do that. To set load order byte (assuming that load order byte is already zero)

FormID or (order shl 24)

So the final code to change load order number in FormID will be

FormID := (FormID and $00FFFFFF) or (order shl 24);

This avoids all those string conversions and $.

Share this post


Link to post
Share on other sites

Forum has bugged out and I couldn't type in the same post, so lets continue.

Your logic is fine - you have some FormIDs space to change to, you cycle over it, check that FormID is free, then lookup matching lip file names by FormID (cycle over list of files in a directory and check with Pos for FormID with zero load order byte), rename file(s), change FormID of a record.

1) The code to get the list of lip files should be in Initialize function which is called once when the script starts, you don't want to populate that list over and over again for each processed record.

2) You need to process INFO records only, so if Signature(e) <> 'INFO' then Exit; should be the first line of code in Process function.

3) No need to cycle over FormIDs space for each processed record. Set the initial FormID number to look from using global integer variable in Initialize function, then in Process make a loop to increment it by 1 until FormID number is free. Something like

while Assigned(RecordByFormID(GetFile(e), LookupFormID, True)) do Inc(LookupFormID);

4) There is RenameFile function in Delphi for renaming files, try it instead (not sure if it is exposed for scripting).

Share this post


Link to post
Share on other sites

Thanks for the tips, zilav!  I will implement them and update the script.  I've been playing with the scripting functions (remember, the script posted above is not mine, I got it from someone else) and find it easy enough to learn.  I just have no familiarity with Pascal, so going is slow.  Googling, etc.  Prior to this, I've never even looked at a script for xEdit.  So, it'll take a minute to catch on.

If I have questions, I will post back.  Thanks again!

Share this post


Link to post
Share on other sites

Okay, zilav, I took a couple hours today and implemented your advice into the script I acquired.  Untested as of this writing, but it looks right to my novice eye.  I would very much appreciate it if you could scan it over and provide any needed feedback.

unit UserScript;

var
	voice_files: TStringDynArray;
	formidstart: Cardinal; 

function Initialize: integer;
var 
	TDirectory: TDirectory;
	voices_root: string;

begin
	formidstart := $00001000;
	voices_root := 'C:\Users\MaLonn\Downloads\Knights\sound\voice\knights.esp\';
	voice_files := TDirectory.GetFiles(voices_root, '*.*', soAllDirectories);
end;

function Process(e: IInterface): integer;
var
	oldid, newid: cardinal;
	j, k: integer;
	freeid, s, x, zx, new_name: string;

begin
	oldid := GetLoadOrderFormID(e);
	newid := (oldid and $00FFFFFF) + ($00 shl 24);
	AddMessage('Current file ID: ' + IntToHex64(newid));

	while Assigned(RecordByFormID(GetFile(e), formidstart, True)) do
		Inc(formidstart);
	for j := 0 to Length(voice_files) - 1 do 
	begin
	    s := voice_files[j]; 
	    k := Pos(IntToHex64(newid), s);
		if k > 0 then 
		begin 
			x := StringReplace(s, newid, IntToHex64(formidstart), [rfReplaceAll, rfIgnoreCase]);
			AddMessage (x);
			zx := ExtractFilePath(s);
			AddMessage (zx);
			new_name  := StringReplace(x, zx, '', [rfReplaceAll, rfIgnoreCase]);
			AddMessage(new_name); 

		    ShellExecute(
		    TForm(frmMain).Handle,                  // parent window handle, use 0 for none
		    'open',                                 // verb
		    'cmd.exe',                              // application  
		    '/C ren "' + s + '" "' + new_name + '"' ,
		    ' ',                                     // working directory
		    SW_HIDE                           // window mode
			);
		end; 
	end;
	SetLoadOrderFormID(e, formidstart);   
	exit;
end;

end.

I can't thank you enough for your patience and help.  Holding a noob's hand can be annoying at times.

Share this post


Link to post
Share on other sites

Looks fine (if RenameFile doesn't work, not a fan of starting cmd for each renaming).

The only possible issue is SetLoadOrderFormID - you provide formidstart with the order byte of 00 (which corresponds to the file loaded at index 0 which is almost always Oblivion.esm) but this function expects load order corrected FormID. This is also somewhat relevant to RecordByFormID functon, however it works with file index FormIDs (00XXXXXX are overrides of the first master of a plugin, 01XXXXXX overrides second master, etc. Order byte > highest master index in the header is a new record). So for example if the plugin you are renumbering is loaded at the index 2 and has a single master Oblivion.esm, then RecordByFormID should be used with 01XXXXXX formids and SetLoadOrderFormID with 02XXXXXX.

And you forgot about if Signature(e) <> 'INFO' then Exit;

Share this post


Link to post
Share on other sites

I did not even try RenameFile (yet).  The script is untested and I was going to play with that function during the testing process.  It's on my list of things to do, however.

Good catch (!) with the global "formidstart" and those functions.  But, this is a personal script and is designed to be run with two plugins only--master (00) and plugin (01).  Plugin is being groomed to merge into master, so it's okay.  That's the only way it's designed to work and I'll make a comment at the start of the script to remind me of this if time passes between uses (and I forget)

Question about that last line: aren't "DIAL" records recorded and named, thus need to be renamed as well?  I confess, I'm ignorant in regards to this topic.

Share this post


Link to post
Share on other sites

Gotcha, zilav.  Thanks again for the help.  I'm in a holding pattern for now, but when the time comes to run this script, I may have more questions.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Support us on Patreon!

Patreon
×
×
  • Create New...