29 posts / 0 new
Last post
ponyrider0
ponyrider0's picture
Member
Offline
Last seen: 9 hours 59 min ago
Joined: 07/22/2016 - 05:01
Karma: 662
Developing OBSE plugins for Morroblivion

First, I need to give a BIG thank you to llde for his tips on how to build the obse library and plugins!! (original post by him here: https://tesrenewal.com/comment/84757#comment-84757)  Following your directions, the build succeeded on the first attempt!  Awesome!

Now that I'm finally able to build OBSE plugins, here are some of the things that I have on my todo list:

- A better Universal Silent Voice plugin which plays one of Oblivion's generic greeting dialogs in the appropriate race and gender whenever you start dialog.
- Custom EquipItem function to allow for an arbitrary number of clothing/armor slots.
- Also Custom EquipItem: Disable the cast-on-equip bug when Equipping OnTarget items like Ring of Fireball
-   Implement Morrowind-style combat: miss-miss-miss-miss-hit



 

ponyrider0
ponyrider0's picture
Member
Offline
Last seen: 9 hours 59 min ago
Joined: 07/22/2016 - 05:01
Karma: 662
llde's original post for

llde's original post for convenience:

llde wrote:

There are several reasons for this:

 

First the original OBSE v21 source code is bugged. Compiling a plugin make the compiler and the linker search for symbols that do not exist in a plugin project (ShowRuntimeError and similia).

 

You will need to use my fork: https://github.com/llde/Oblivion-Script-Extender  (Target Release only. Debug Target is not buildable)

 

The other errors are caused by MSVC2015  or above compiler.

 

1) The use of reference in va_start is illegal in C++ standard. It was silently allowed on  previous MSVC version. From VS2015 you need to define _CRT_NO_VA_START_VALIDATION in the project. Note that this piece of code probably never worked completly as intended

 

2)A struct use the hashmap type, but in VS2015 it was deprecated. However has I don't know ATM if this type is an abstraction on a game implementend object, it may not be safe to blindly replace it with a different, albeit similar in scope, type as unordered_map. You need to define _SILENCE_STDEXT_HASH_DEPRECATION_WARNINGS in preprocessor defines to allow this.

 

However what you wanted to do may be really difficult on the native side.

 

The game doesn't seem to export function in the VTBLs  to manage equipment and slot in a raw fashion. (However many functions are still to be decoded) So all this code should be hand written (you may also need to do an assembly hook)

 

However... Alenet in the new version of OR equipment mode /combat mode is working (or the work is already done) to extend the actual number of slots adding a new weapon slot. You may extend his work when the source is published (He may accept a pull request), or it may be persuaded to add a "robe" specific slot.

 

Raubkopiesäbel
Raubkopiesäbel's picture
Member
Offline
Last seen: 1 month 2 weeks ago
Joined: 05/02/2014 - 09:04
Karma: 89
ponyrider0 wrote:

ponyrider0 wrote:

-   Implement Morrowind-style combat: miss-miss-miss-miss-hit

Will hopefully be optional. 

Greeting 
roxon_55
roxon_55's picture
Contributor
Offline
Last seen: 31 min 54 sec ago
Joined: 04/24/2010 - 22:03
Karma: 1793
Dose not  Need to Be Optional

Dose not  Need to Be Optional If Controlled Blade\Strength\Health  Skills

Raubkopiesäbel
Raubkopiesäbel's picture
Member
Offline
Last seen: 1 month 2 weeks ago
Joined: 05/02/2014 - 09:04
Karma: 89
roxon_55 wrote:

roxon_55 wrote:

Dose not  Need to Be Optional If Controlled Blade\Strength\Health  Skills

The main motivation for Morroblivion is for me that I can play Morrowind with the Oblivion combat system, because it is more realistic and more fun.  

It would be a pity which one the other features then can not use. 

Greeting 

ponyrider0
ponyrider0's picture
Member
Offline
Last seen: 9 hours 59 min ago
Joined: 07/22/2016 - 05:01
Karma: 662
I've been combing over the

I've been combing over the internet for more information on Oblivion's internals to do the Universal Silent Voice update and the EquipItem slot implementation, but not having much luck.  The one bit of luck was that the memory address to hook into the EquipItem function was easy to find in the OBSE source code.  I briefly tried to set up a debugger/disassembler environment with Visual Studio 2015 but it is beyond my simple brain... Visual C++ 5 was so much more intuitive to use, and it already supported cross-compiling to RISC, mobile and Version Control back then: 20 frickin years ago.  Gah!

Well, anyway, with regards to a USV update, I'm going to have to reach out to Ely to see if I can get source code or at least some more direction as to what to modify.  If I can get get it working, my goal would be to dynamically choose the best MP3 source files for any NPC -- whether it is Oblivion voices, Voice-acted voices or CG voices -- that way, we won't have to deal with huge overlapping, disorganized directories of mp3s.  Maybe I could even dynamically generate the CG voices and forego a library of mp3s entirely.  Too bad Microsoft's voice technology is far behind what's offered by Apple or even Amazon.

And as for the EquipItem hack, right now, I've made a enough progress with the script-only version of More-Armor-Slots that I think I can back-burner this project for the time being.

llde
llde's picture
Member
Offline
Last seen: 2 months 2 weeks ago
Joined: 09/17/2013 - 07:04
Karma: 361
For the disassembler I

For the disassembler I recommend Ida Pro with the script from JRoush. Note that many parts of  the code is horribly complicated, and many things were not decoded.

You might want to reach Alenet or Tiawar for the EquipItem.

For the Universal Silent Voice update: I wanted to do something similar myself, however never found the correct entrypoint.

In my plan, I would have update USV to fix the "localized race folder"  beheviour. In practice when a dialogue start the game search for the corresponding sound in the specific race folder but it search for it by the "Name" field and not by the "Editor ID". I wanted to do a sort of fallback cascade:  Check Localized name -> Check English Name -> Check EditorIds.

And only after checking all three it would have loaded the empty sound. How this sound to you?

If you is able to contact Elys and obtain a copy of the source code or at least the entrypoint I may be able to help you on this.

ponyrider0
ponyrider0's picture
Member
Offline
Last seen: 9 hours 59 min ago
Joined: 07/22/2016 - 05:01
Karma: 662
Cool -- your ideas for USV

Cool -- your ideas for USV sound similar to mine.  On top of your voice file search functionality, I would also like to add an optional ability to search different races (if no altmer, then look for dunmer, then look for imperial, etc.)  In the case of the computer-generated voice files, they are all under the imperial race, so doing a fallback to that for every other race would allow for a less complicated CG-voice setup with full voicing of dialog.  Of course, if we could generate voices from a voice-synth SDK in real-time, that would be even more awesome.  It's too bad MacOS is far more advanced than Windows in this regard -- they have voice libraries for hundreds of languages, multiple accents for English language and even multiple voices for each accent.

I'll try to contact Elys through the Nexus contact page and will let you know what happens.

ponyrider0
ponyrider0's picture
Member
Offline
Last seen: 9 hours 59 min ago
Joined: 07/22/2016 - 05:01
Karma: 662
Good news!

Good news!

I got a reply back from Elys with very helpful information / source code.  I will try to re-implement as a C++ plugin and will then post my code here.... 

Update: It's actually (in my opinion) a very elegant solution, the silent-voice function hook seems to be at a point where the game engine code has just failed at loading the voice file from disk.  Elys then overwrites the memory address in the stackbuffer (ESP + 0x64) where the filename should go and then is probably jumping back to the start of that function to re-attempt to load the filename from disk. 

I will hopefully have a working re-implementation of USV written in C++/asm in a week.  From there, we will need to figure out how to read the original filename.  Hopefully, this original filename is intact at the memory address that Elys uses to overwrite with the filename of the silent MP3 (ESP + 0x64).  The original filename will then allow us to generate alternative filename paths to search for the voice file.

PS - I don't think dynamically generating synthesized voice files on the fly (in "real-time") is feasible at the moment with affordable hardware... the steps needed would be to synthesize the full voice file, encode into mp3, write to disk, then pass the filename to the game-engine.  On my computer, just loading the voice file from disk alone is enough to cause stuttering and the loss of the first few milliseconds of audio as the game-engine skips ahead to maintain proper animation sync.... Of course, if I write directly into buffered disk cache instead of the physical disk and then if I'm able to pass the memory address directly to the game-engine then we might shave off enough milliseconds to make it work (the bottleneck/latency would then be how fast the CPU can generate the synthesized voice and encode into mp3). --hehe, unfortunately, this method also assumes that we can access and read the dialog text to be synthesized.

llde
llde's picture
Member
Offline
Last seen: 2 months 2 weeks ago
Joined: 09/17/2013 - 07:04
Karma: 361
Shouldn't be too much work to

Shouldn't be too much work to reimplement in C++. Or in a memory safe language like Rust.

Also digging inside OBSE sourcecode it seems that TESDialogue structure is not aviable.

ponyrider0
ponyrider0's picture
Member
Offline
Last seen: 9 hours 59 min ago
Joined: 07/22/2016 - 05:01
Karma: 662
Alright, I had some time

Alright, I had some time today and made a quick working test version.  FYI, the path-name of the original missing voice file is indeed at that memory address that I mentioned above!  All we need to do now is parse the original pathname, modify it to what we want and write it back into memory.

Update:  I just confirmed that I can successfully replace the string with a filename found inside a BSA!  I've attached a test plugin with preliminary source code.  The source code files are made for llde's version of obse source.  Just drop it into the obse_plugin_example directory.

If you want to try out this plugin, all you need to do is put the test_usv.dll into \Data\OBSE\Plugins directory.  Then remove Elys_USV.dll.  When you run Oblivion, all missing voice files will be replaced by a line from Socucius Ergalla.  A test_usv.log file will be generated in the \Oblivion main folder with output of all the missing original filenames that it redirected.

To do: 
1. Implement hooks for dialog subtitles and lip files.
2. Parse original pathname string.
3. Replace original pathname with redirected path.

AttachmentSize
File test_usv.7z60.88 KB
roxon_55
roxon_55's picture
Contributor
Offline
Last seen: 31 min 54 sec ago
Joined: 04/24/2010 - 22:03
Karma: 1793
This is Running test_usv in

This is Running test_usv in Plugins.

Plugin List plus test_usv.log in second z7 file

Edit:

Removed Clip.

AttachmentSize
File obse_test.7z850 bytes
ponyrider0
ponyrider0's picture
Member
Offline
Last seen: 9 hours 59 min ago
Joined: 07/22/2016 - 05:01
Karma: 662
haha -- it looks/sounds funny

haha -- it looks/sounds funny when put into a video, but the plugin is working as intended.  This version is just to demonstrate that I could rewrite Elys_USV -- intercepting missing voice files and replace it with a test voice file of my choosing.  I chose Socucius Ergala instead of using a silent MP3 so you can more easily see that it's my plugin and not Elys_USV that is running.  Now I need to finish the next few steps on my to-do list before it's actually usable in gameplay (sorry, should have made that more clear).

roxon_55
roxon_55's picture
Contributor
Offline
Last seen: 31 min 54 sec ago
Joined: 04/24/2010 - 22:03
Karma: 1793
No Prob will wait for the


No Prob will wait for the next 1.

ponyrider0
ponyrider0's picture
Member
Offline
Last seen: 9 hours 59 min ago
Joined: 07/22/2016 - 05:01
Karma: 662
Test_USV_2:

Test_USV_2:
- Completed implementing remaining hooks (dialog subtitles, general subtitles, .LIP file loading).
- Unfortunately, I can't figure out a CTD when calling the Oblivion internal CheckFile() routine to check for existence of a LIP file.  I wrote a workaround replacement with PathFileExists() but this is worthless since it doesn't check inside BSAs.
- I'm going to put the lip files aside and focus on implementing the voice file redirection code for now.

UPDATE: okay, I really didn't put the CheckFile routine aside... I kept searching through all the offsets in OBSE and found an indirect match with the offset for the FileFinder object... BINGO: FileFinder->FindFile() looks like an exact match for what Elys calls CheckFile()... now I just need to figure out what I did wrong when calling it.... FIXED!!!

UPDATE2:
Test_USV_3: fully re-implements Elys_USV.dll.  You will still need Elys_USV.mp3 and Elys_USV.lip, just remove the Elys_USV.dll and replace with test_usv.dll.  The \Oblivion\test_usv.log will contain information of all missing MP3/LIP files.  I'm going to start working on a generic greeting behavior when you talk to someone (ala Baldur's Gate) and a race name redirection for the computer-generated voices mod.  I'll probably rename the plugin to something else too, since I've advanced out of just "testing".

 

AttachmentSize
File test_usv_3.7z62.17 KB
junkacc
junkacc's picture
Member
Offline
Last seen: 4 months 5 days ago
Joined: 11/21/2013 - 03:49
Karma: 16
Wow, does this mean the huge

Wow, does this mean the huge computer generated voice file can now be optimized and further more improved upon with better voices?

AWESOME!!!

ponyrider0
ponyrider0's picture
Member
Offline
Last seen: 9 hours 59 min ago
Joined: 07/22/2016 - 05:01
Karma: 662
Test_USV_4:

Test_USV_4:
- The first version to now extend Elys_USV.dll!! 
- This version will search for missing greetings and insert a generic Oblivion greeting, otherwise the silent voice will take over. 
- Unfortunately, the generic greetings will go by quickly (about 3-4 seconds), so you may miss dialog if it's a long greeting -- but the real question is: why aren't you using the Protocolled Dialogs Mod???  Seriously, go install it right now.  It gives you a dialog history window for the current conversation.

To Do:
- Implement fallback system to look in the Imperial race directory for users of the computer-generated voices mod.
- Implement random generic greetings instead of one phrase per race.
- Create framework to receive information from ESP script.

ALSO:  As I mentioned above, OBSE allows plugins to communicate with ESP scripts.  That means we could potentially get information on race, sex, disposition, class, and the actual dialog text...  And that means: real-time synthesized voices may be back on!  Of course, not anytime soon.

AttachmentSize
File test_usv_4.7z63.71 KB
ponyrider0
ponyrider0's picture
Member
Offline
Last seen: 9 hours 59 min ago
Joined: 07/22/2016 - 05:01
Karma: 662
Test_USV_5:

Test_USV_5:
- Things are getting interesting!  Users of the computer-generated voice mod rejoice!  Missing voice files will now fallback to searching the Imperial race -- so you can get rid of all those symlinks and just keep an Imperial race folder!
- If no voice files are found in the Imperial race, the fallback cascade continues:  next, the plugin checks for generic greetings.  If no generic greetings are present, it falls back to Elys_USV.mp3/lip files.
- I haven't thought of a good name for the plugin yet, so it stays in "test" form until I do, hehe.

To Do:
- Nothing for now!  I'm going to put creating the ESP script communications system on hold, so I can focus on finishing all of my mainquest script patches.  After that, I'll clean up the source code and maybe rename it to something other than "test".
- If anyone wants to pick up where I left off, feel free!  Post your progress on this thread.

Thanks again to Elys, llde and the entire Morroblivion/TESRenewal community!
 

AttachmentSize
File test_usv_5.7z64.55 KB
roxon_55
roxon_55's picture
Contributor
Offline
Last seen: 31 min 54 sec ago
Joined: 04/24/2010 - 22:03
Karma: 1793
( Kool )  More Life to

( Kool )  More Life to Morroblivion, did not capture the first section

Thanks,, dont let that Spark go Out.

Do you want test-usv.log

Edit:

Question: Doe's the Dialog that has no Audio Listing, Record it self in the Log. There is some Dialog up in Solstheim has no Audio.

AttachmentSize
File test_usv_05.7z13.58 MB
ponyrider0
ponyrider0's picture
Member
Offline
Last seen: 9 hours 59 min ago
Joined: 07/22/2016 - 05:01
Karma: 662
Thanks, roxon_55.

Thanks, roxon_55.

If you find dialog without audio, it should be recorded in the log with Ely_usv.mp3 as the replacement voicefile like this:

test_usv: Silent Voice Hook: replacing 'data\sound\voice\cm partners.esm\redguard\f\cmpartnersquest_cmshare_00010c97_1.mp3' with 'Data\OBSE\Plugins\elys_usv.mp3'

​You can look at the original filepath that the elys_usv.mp3 replaces to find out more about where that dialog came from.  In the above example, it is "cm partners.esm" and the quest is "cmpartnersquest", the topic is "cmshare" and the actual dialog line has the (non-load order) Form ID 00010c97.  In the future, one thing we could do with this plugin is update it to detect mod names and try to redirect it to another mod directory.  That way, we can store all related audio in one directory for simplicity.

Elys
Elys's picture
Offline
Last seen: 5 months 3 weeks ago
Joined: 05/28/2017 - 11:17
Karma: 5
Hi,

Hi,

When using:

strcpy(pSoundFile, NewSoundFile);

You have to make sure than the length of the string pointed by pSoundfile is at least bigger or equal to the length of the string pointed by NewSoundfile.

This was not an issue for the original USV because due to the sound path structure used by Oblivion, I knew that the Silent file pathname would be shorter in any case.

But if the code happens to overwrite the string with a longer one, you risk ended up with corrupted memory if the excess characters overwrite memory already used for something else.

In that case it might be better to create a new temporary string and replace the pointer directly. This might have some consequences and possibly require more work but I have no idea anymore since I forgot all about when I was inspecting the Oblivion code.

ponyrider0
ponyrider0's picture
Member
Offline
Last seen: 9 hours 59 min ago
Joined: 07/22/2016 - 05:01
Karma: 662
Hi Elys! 

Hi Elys! 

Yes, thanks for pointing that out -- I was cringing as I wrote that code but got caught up with the excitement that things were actually working that I forgot to go back to address this issue. 

Maybe I'm reading this incorrectly but, from your inline assembly code, it would seem like Oblivion is passing a fixed-length array char[100] as an argument to the Voice function (hence the entire string being on the stack rather than just a pointer).  Would it be safe for us to just pop the pSoundFile off the stack and push NewSoundFile onto the stack? 

Oh wait, nevermind about the char[100].  haha, my mistake: I was counting down and not up when advancing memory addresses in my head.  But still, pSoundFile is being assigned the memory address: ESP + 64h rather than the actual value stored at that address, correct?  Sorry for that off-topic newbie Intel Assembly question.  In any case, NewSoundFile is actually a static fixed-length array of char[MAX_PATH], so we should be able to pass a pointer to it without much extra work.

Elys
Elys's picture
Offline
Last seen: 5 months 3 weeks ago
Joined: 05/28/2017 - 11:17
Karma: 5
I don't have Oblivion

I don't have Oblivion installed anymore but just looking at the assembly code I made in SilentVoiceHook, yes you are right, I remembered incorrectly, the string is on the stack and not passed by pointer.

Now if the string is pushed as a fixed max length record (so regardless of the actual string length), then it will be safe indeed to just keep overwriting as it is now.

It's most likely. There just needs to be sure of that, which you can easily know by looking at the hooked function prototype in Oblivion disassembly, or by setting a breakpoint and look at the stack in action.

ponyrider0
ponyrider0's picture
Member
Offline
Last seen: 9 hours 59 min ago
Joined: 07/22/2016 - 05:01
Karma: 662
Cool, thanks again for your

Cool, thanks again for your help!! 

UPDATE (with corrections): 
Alright, I've traced through the disassembly code in the sections of the Oblivion.exe which call LipsHook and SilentVoiceHook. 

In the case of LipsHook, the game engine allocates 4+256+4 bytes of stack space using command [sub esp, 108h] which is used to store one DWORD at [esp+104h] and then 256 bytes used only for storing the string of the filename path starting at [esp+4h] before the allocated space is finally removed from the stack with [add esp, 108h] at the end of the function.

In the case of SilentVoiceHook, the game engine allocates a chunk of stack space [sub esp, 158h] and stores one DWORD at [esp+154h] and the filename string at [esp+50h].  Another 20 bytes or [14h] is added to the stack prior to SilentVoiceHook being called, where pSoundFile can be found at the adjusted address of [esp+64h].  Just as with LipsHook, the memory address from the start of the string at [esp+50h] to the first DWORD at [esp+154h] is not used to store any other information.

In other words, the engine appears to be using a fixed-length array of 256 characters. I will modify the code to observe this maximum string length.  No need to worry about memory corruption. 

Latest Source Code with 256 char limit sanity checks here:

AttachmentSize
File test_usv_6.7z64.85 KB
ponyrider0
ponyrider0's picture
Member
Offline
Last seen: 9 hours 59 min ago
Joined: 07/22/2016 - 05:01
Karma: 662
test_usv_7:

test_usv_7:

- This version fixes a bug where the generic greeting would play multiple times in a row for mult-part greetings.

Also, I think a good way to make this plugin easily customizable is with an INI file where users can store original/replacement string pairs.  Then the plugin can just go down the list of string pairs until it finds a working match.  for example:

Morrowind_ob.esm=Oblivion.esm

redguard=imperial

nord=imperial

The plugin will sequentially go down the list of string pairs, replacing the first term with the second term and then testing to see if the resulting file exists.  Once we have that INI reading function implemented, maybe we should call the plugin Voice_Redirector or USV_Redirector.  Or maybe just call it USV_Update.  Any comments, feedback, suggestions? 

 

AttachmentSize
File test_usv_7.7z64.85 KB
ponyrider0
ponyrider0's picture
Member
Offline
Last seen: 9 hours 59 min ago
Joined: 07/22/2016 - 05:01
Karma: 662
Future To-Do:

Future To-Do:
- Implement INI string search/replacment system (see notes in previous post).
- ESP Script communication to receive information on speaker, disposition, class, quest variables and actual dialog text.
- Insert dynamic length silence, probably with a command similar to this:

ffmpeg -i "concat:input1.mpg|input2.mpg|input3.mpg" -c copy output.mpg

- Find someone to work on all of the above, and maybe move this source code to github.

roxon_55
roxon_55's picture
Contributor
Offline
Last seen: 31 min 54 sec ago
Joined: 04/24/2010 - 22:03
Karma: 1793
Any way of making the log

Any way of making the log file longer than 5kb while you are upgrading.

Go with the New Name..Voice_Redirector it is what it is.

ponyrider0
ponyrider0's picture
Member
Offline
Last seen: 9 hours 59 min ago
Joined: 07/22/2016 - 05:01
Karma: 662
The log can grow larger than

The log can grow larger than 5kb, but right now it gets over-written with a new log every time you start the game.  I can either move the old one into a backup file like "test_usv.log0", or just continually add more messages to the end of test_usv.log each time the game is run.  Let me know what sounds best to everyone.

ponyrider0
ponyrider0's picture
Member
Offline
Last seen: 9 hours 59 min ago
Joined: 07/22/2016 - 05:01
Karma: 662
I will have the first version

I will have the first version of the "voicefile_redirector.dll" plugin posted on Github within the next day or two.  FYI, I found a really convenient WinAPI function that spits out a buffer of multiple null-terminated strings with a final null to mark end of buffer:  GetPrivateProfileSection().  The null-terminated strings are already in this format:

key=string

Which means I can just parse these strings as orig_string=replacement_string and quickly implement the INI-customizable voicefile redirection function.  Hopefully I can get this implemented within the next 1-2 weeks.

Update:
github repository is here: https://github.com/ponyrider0/voicefile-redirector