Skip to content

apocalyptech/w3scriptmerge

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

9 Commits
 
 
 
 
 
 

Repository files navigation

Witcher 3 CLI Script Merger

Disclaimer

You almost certainly want to be using the community-approved Witcher 3 Script Merger app, even if you're running Witcher 3 on Linux, with Proton/Wine. If you are a CLI-friendly Linux user going the Proton/Wine route, though, maybe this will be to your liking.

About

This is a simple commandline-only Python 3 script to merge mod scripts for the game Witcher 3. Witcher 3 mods often do their thing by altering various scripts bundled with the game, which the engine compiles on startup when changed. Once the engine sees an overridden script in one mod, it will ignore the same overridden script from any other mod. So, if there are two mods which alter the same script, only one of them will fully work. Therefore, something like a Script Merger is needed to do that! (For simple cases it's easy enough to do it by hand, of course, but that quickly grows tiresome even for a handful of mods.)

So, that's what this, and the "official" Script Merger does. That main merger is a GUI app which is quite featureful, and is probably what most people want, but running Windows game utils under Proton/Wine, when on Linux, is often a bit of a hassle. I'm personally happier in the commandline anyway, so I went ahead and wrote this CLI version.

The heavy lifting in here is all done with GNU diff/diff3, so make sure that those are available on your default $PATH. (It'd be shocking if they aren't, if you're on Linux.) The app itself is a single Python 3 script with no dependencies.

This util has been tested on the set of 39 mods (w/ 60 script files) which I ended up using for my Witcher 3 playthrough, and works fine for those. I'm sure that there are some edge cases where this might not do the right thing, but it should be pretty good for most use cases.

Installation / Usage

Just stick w3scriptmerge.py in your $PATH somewhere (~/bin seems like a good location) and run it in the same directory as your extracted mod directories. (This will generally be inside a mods directory in the Witcher 3 install root, but the util will happily work outside that dir, so long as the mod directories are found at the same level you are.)

The full output from the -h/--help options is as follows:

usage: w3scriptmerge.py [-h] [-w W3DIR] (-m | -d MOD_DIR) [-e EDITOR] [-n]
                        [--diff-command DIFF_COMMAND]

Witcher 3 CLI Mod Script Merger

optional arguments:
  -h, --help            show this help message and exit
  -w W3DIR, --w3dir W3DIR
                        Base install directory for Witcher 3 (default:
                        /games/Steam/steamapps/common/The Witcher 3)
  -m, --merge           Merge mods (default: False)
  -d MOD_DIR, --diff MOD_DIR
                        Show diff of specified mod dir to the vanilla basegame
                        scripts (default: None)
  -e EDITOR, --editor EDITOR
                        Editor to use when resolving merge conflicts (default:
                        vim)
  -n, --no-fix          Don't prompt the user to fix merge conflicts --
                        instead just report at the end (default: False)
  --diff-command DIFF_COMMAND
                        Command to use while showing diffs via the -d/--diff
                        option (default: diff -u --color=always)

Witcher 3 Base Dir

This script needs to know where to find the default/stock Witcher 3 scripts (possibly updated with the Community Patch), both for merging and for showing diffs. If you run this script from inside Witcher 3's main mods directory, it will detect the script location properly. Alternatively, you can specify the base directory with the -w/--w3dir argument, and/or update the script with your own default, if you like.

Merging Mods

To merge, change to a directory which contains your set of mod directories (likely Witcher 3's main mods dir, though it can be anywhere), and run it with the -m or --merge arg:

$ w3scriptmerge.py -m
Merging mods...
 ! modFOVTweak: conflicts in scripts/game/player/r4Player.ws.  Manually fix now [Y|n]? y
 -> Merged 8 mods with 15 scripts

Clearing out mod0000_apoc_merged...
Copying to mod0000_apoc_merged...
Done!

If there are any files which need manual fixes due to the merging, the app will prompt if you want to fix them. If you agree, the script will launch the editor specified by your $EDITOR environment variable (or vim, if that environment var is unset), but you can override that by specifying the -e/--editor option on the commandline. Alternatively, you can specify -n/--no-fix to tell the script to leave the merge as-is, and leave it for you to fix after the fact. If any problems remain in the merged files, they will be reported at the end of the output. Note that if three or more mods create conflicts in the same script file, letting the merge problems pile up could create an unwieldy mess.

The merge conflicts you see in the file will be very familiar to anyone used to working with commandline file diffs, or version control systems like git/svn/whatever where merge conflicts can pop up. For instance, my own conflict mentioned above us due to Natural Cat Vision and FOV Tweak adding a bit to the same spot in the same file, and looks like this while manually resolving:

<<<<<<< /tmp/tmpf6x6svik/mod0000_apoc_merged/scripts/game/player/r4Player.ws
        //++modNCV
        NCV = new CNCV in this;
        NCV.Init();
        AddTimer('CheckNCVLoop', 1.0, true);
        //--modNCV

||||||| /tmp/tmpf6x6svik/_basegame_/scripts/game/player/r4Player.ws

=======
        //modFOVTweak begin
        ModFOVTweakUpdateFOV();
        //modFOVTweak end


>>>>>>> /tmp/tmpf6x6svik/modFOVTweak/scripts/game/player/r4Player.ws

In that case, I want to keep both new stanzas, so I'd basically just remove all the failed-merge notation in there: any line that starts with <<<<<<<, |||||||, =======, or >>>>>>>. See the --merge section of the patch(1) manpage or various git merge-conflict resolution docs for some details on that format, if you're not familiar with it.

Regardless, the script outputs the complete merged set of scripts into the new mod directory mod0000_apoc_merged. This will include all scripts from all mods found in the dir, whether or not they actually required merging. This is one situation where its behavior might differ from the community-approved Script Merger: that app may only write out files which needed merging (I've never actually used it, myself, so I'm not sure).

Witcher 3 loads mods in case-insensitive ASCII sort order, with numbers first, then underscores, then letters, so unless you have some other mod which intentionally tries to be "first," this merged meta-mod will be loaded by W3 first. After the engine sees all the scripts in this mod dir, all the ones from the individual mod dirs will be ignored. (This is also what the main Script Merger does, though I believe its directory name is mod0000_MergedFiles.)

Merged files are written out using UTF-16 encoding, just like the stock Witcher 3 scripts. Sometimes mods themselves haven't distributed their scripts using that encoding, so in those cases the script in mod0000_apoc_merged won't be byte-identical to the original, even if no merging needed to take place for that file.

Viewing diffs

The script can also be used to show the differences between the "stock" Witcher 3 script files and the versions found in any mod directory. To do that, use the -d/--diff option, and specify a mod directory:

$ w3scriptmerge.py -d modGetFullXPFromQuestsNoMatterTheLevelCommunity/
--- /tmp/tmpt88qgzjt/_basegame_/scripts/game/r4Game.ws  2021-11-29 21:15:29.807520915 -0600
+++ /tmp/tmpt88qgzjt/modGetFullXPFromQuestsNoMatterTheLevelCommunity/scripts/game/r4Game.ws     2021-11-29 21:15:29.807520915 -0600
@@ -896,7 +896,7 @@
                }
                else
                {
-                       lvlDiff = rewrd.level - thePlayer.GetLevel();
+                       lvlDiff = 0;


                        if(FactsQuerySum("NewGamePlus") > 0)

By default, this mode calls out to the command diff -u --color=always, but you can specify an alternate diff command using the --diff-command argument.

Uninstallation

To remove the script itself, just remove it from wherever you stored it. To get rid of the merged meta-mod itself, just remove the mod0000_apoc_merged dir in your Witcher 3 mods dir. Since the script doesn't alter any of the vanilla mods, everything'll be put back in order.

TODO / Known Bugs

  • If a script in a mod uses an encoding other than UTF-16 or latin1/ISO-8859, the merging/processing is likely to fail.
  • Might be nice to be able to run -m/--merge from anywhere, and have it default to the install dir, if there aren't any mods found in the current dir.
  • As-is, for merging, the script can only do a full merge of all mods in the mod dir -- if you've got a set of already-merged files and want to merge in one more mod, you'll end up having to resolve any manual differences which were present in the originally-merged mods, even if the new one itself doesn't introduce any new problems. It probably wouldn't be hard to add in support to just merge a single mod dir into the already-merged set, but I'm basically through with Witcher 3 now, so I'm unlikely to add that feature myself.

License

All code in here is licensed under the 3-clause BSD license. See COPYING.txt for the full text of the license.

Changelog

  • December 11, 2021

    • Add support for both UTF-16 BOM markers, though I've yet to come across an example of the other
    • Added tracking of bundled files (via blob0.bundle, etc) to be able to report on potential conflicts with those, too
  • December 10, 2021

    • Came across a mod script encoded in UTF-8 but with a BOM mark; added support for handling that properly
  • November 30, 2021

    • Initial release
    • Tweaked failed-merge detection slightly

About

Witcher 3 CLI Mod Script Merger

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages