Jump to content

Recommended Posts

Posted

Papyrus Pypline


Pipeline for compiling Papyrus scripts, built in Python.

  • Easy to integrate with IDE/Notepad++, or use from command line.
  • Ability to parse C like preprocessor directives.
    Also provides built-in __LINE__ and __SCRIPT_NAME that you can use in your preprocessor directives, and converts binary literals (0b01011101) to decimal numbers, so you can use binary literals in your Papyrus source scripts.
  • Automatically modifies .pas assembly to replace User/Computer name and Script path.
  • Multithreaded processing (able to utilize your AMD Ass Ripper or whatnot to the max).
  • Flexible configuration, keeps your project's Papyrus sources and dependencies separate from the game.
  • Colorful console output (designed, tested, and approved by real circus clowns!).
  • Ability to resize and move the console window to desired position, and change font, overriding OS defaults on the fly.

 

Requires Python 3, and Papyrus Compiler from Bethesda (comes with CK).
Not sure about exact Python version - i am using 3.13.1, and i think it may even work with 3.8 or 3.9, but you'll have to try and see what happens.
If in doubt, use 3.13.1 or newer.
 

Usage
First, set it up using the information in Install Instructions tab above.
Then you can run it to process a single specific script in your project:
    _compile.py "d:/your skyrim project folder/the script.psc" [options]
or to process all scripts in a folder:
    _compile.py "d:/your skyrim project folder" [options]
with the [options] being any/none of the following strings separated by white space:

  • d, or dbg, or debug to process the scripts for "debug" and automatically define "DEBUG" variable you can use in preprocessor defines.
    Otherwise the scripts are processed for "release", which automatically defines "NODEBUG" variable, and instructs the compiler to use optimizations.
    Depending on your preprocessor defines and conditions that use "DEBUG" or "NODEBUG" variables, it can modify the processed scripts by adding/removing debug traces, lines or blocks of code, etc.
  • p, or prep, or preprocess to run only preprocessing, without compiling to .pas or assembling the scripts into .pex.
    Can be useful when you want to recompile a single script that depends on other scripts in your project - running this at least once, ensures that all the other scripts in your project get preprocessed and can be found and understood by the Papyrus Compiler, and after that you can keep compiling single scripts one by one or as you make changes without having to run this again (when compiling all scripts in a folder, this is automatically run first for all of them anyway, so for that you don't need it).

This can be of course run from your IDE/Notepad++ (how to set it up for NP++ is described on the Install Instructions tab).

 

Optional: You may also want the additional download file below with Papyrus Includes.
It contains "dummy-fied" versions of various source scripts - SL, SLP+, DD, DD NG, what have you, including Skyrim itself.
These source scripts do not contain actual code, only function signatures and variable declarations.
Their purpose is to use them as dependencies when compiling your projects, and they help in two ways:

  1. Since there is no actual code the compiler/assembler would need to wade through, your scripts will compile slightly faster.
  2. Since there is no actual code, it may decrease the number of dependencies you would otherwise need, and your scripts will compile faster.
    (if a code, that would require some dependency, is not there now, then you won't need that dependency => less to do for the compiler)
    As long as you are not compiling these scripts (which you aren't doing, why would you), you don't need them to contain any actual code - only function signatures and variable declarations are what a compiler needs from dependencies to compile your scripts.

Obviously, DO NOT mix these with any actual real source files!
(eg.: for the love of Todd, don't compile them to put them in your Game's Data/scripts folder!)

Dummyfied Papyrus Includes 2025-11-12-0514.7z

 

Spoiler

 

 


  • Submitter
  • Submitted
    11/12/2025
  • Category
  • Requirements
    Python 3 (maybe 3.9, tested on 3.13.1+)
    Papyrus Compiler for Skyrim from Bethesda (comes with the Creation Kit i think?)
  • Regular Edition Compatible
    Yes
  • Install Instructions

    Unpack the archive into a project folder, setup NP++ shortcuts, and do not forget the actual Configuration (see below).
    (the pipeline is intended to live inside your project folder, so you can customize it per project if need be. You can put it just about anywhere, but i am not covering that kind of setup here, so you will have to figure that out yourself).
    The folder structure should look like this:
     

       My Project
        │
        ├── PapyrusPypline
        │     ├── preprocessor
        │     │     └── preprocessor.py
        │     ├── __init__.py
        │     ├── assemble.py
        │     ├── ...
        │     └── preprocess.py
        │
        ├── _common.inc
        ├── _compile.py
        ├── _project.inc
        └── your project's Source Scripts

     

    Then setup Notepad++ shortcuts to run the _compile.py with current folder (for mass compile of all scripts) or current file (to compile the script on active tab).
    You do that by editing the "C:\Program Files\Notepad++\shortcuts.xml" (your path may be different, maybe you have it somewhere in your User folder).
    The relevant part of shortcuts XML should looks like this:
    (the shortcuts are set to Ctrl+F5/F6 and Shift+Ctrl+F5/F6 - you can change them later in the Shortcut Mapper):

    <UserDefinedCommands>
       ...
       <Command name="Papyrus RELEASE" Ctrl="yes" Alt="no" Shift="yes" Key="116">&quot;$(CURRENT_DIRECTORY)\_compile.py&quot; &quot;$(FULL_CURRENT_PATH)&quot;</Command>
       <Command name="Papyrus DEBUG" Ctrl="yes" Alt="no" Shift="no" Key="116">&quot;$(CURRENT_DIRECTORY)\_compile.py&quot; &quot;$(FULL_CURRENT_PATH)&quot; debug</Command>
       <Command name="Payprus Recompile All DEBUG" Ctrl="yes" Alt="no" Shift="no" Key="117">&quot;$(CURRENT_DIRECTORY)\_compile.py&quot; &quot;$(CURRENT_DIRECTORY)&quot; debug</Command>
       <Command name="Papyrus Recompile All RELEASE" Ctrl="yes" Alt="no" Shift="yes" Key="117">&quot;$(CURRENT_DIRECTORY)\_compile.py&quot; &quot;$(CURRENT_DIRECTORY)&quot; release</Command>
       <Command name="Papyrus Preprocess All DEBUG" Ctrl="yes" Alt="no" Shift="no" Key="118">&quot;$(CURRENT_DIRECTORY)\_compile.py&quot; &quot;$(CURRENT_DIRECTORY)&quot; preprocess debug</Command>
       <Command name="Papyrus Preprocess All RELEASE" Ctrl="yes" Alt="no" Shift="yes" Key="118">&quot;$(CURRENT_DIRECTORY)\_compile.py&quot; &quot;$(CURRENT_DIRECTORY)&quot; preprocess release</Command>
       ...
    </UserDefinedCommands>

     

    As mentioned, the above structure is just a suggestion, based on how i like it.
    You can pretty much set it up any way you want.

     

     

    Configuration

    All the paths and parameters are set inside the _compile.py, which is the main reason why to keep it inside a project folder, so it can be customized per project.

    Spoiler
    ################################################################################
    from PapyrusPypline import *
    ################################################################################
    
    # Papyrus Compiler automatically inserts your User name and Computer name into
    # the compiled .pex file, which is... not ideal, so here you can specify what
    # to use instead of your real credentials.
    cfg.username = "Default"
    cfg.computer = "Unknown"
    
    # Script source includes.
    # Paths where Papyrus Compiler will look for any dependencies (vanilla Skyrim script sources, SKSE, etc.)
    # Any relative paths will be assumed as relative to the script sources we are compiling.
    #
    # IMPORTANT: The order matters!!!
    # If a script of same name exists in multiple paths, compiler will use the first script of that name it finds.
    # For example, SKSE script sources must be listed ABOVE vanilla Skyrim sources,
    # to make sure the SKSE scripts are used instead of the vanilla Skyrim ones.
    paths.includes = [
    	"a:/Games/#SETUP/Skyrim AE [GOG]/Papyrus Includes (Dummy)/skse64 2.2.6",
    	"a:/Games/#SETUP/Skyrim AE [GOG]/Papyrus Includes (Dummy)/ConsoleUtil Extended 1.1.0",
    	"a:/Games/#SETUP/Skyrim AE [GOG]/Papyrus Includes (Dummy)/SkyUI 5.2",
    	"a:/Games/#SETUP/Skyrim AE [GOG]/Papyrus Includes (Dummy)/Skyrim 1.6.1378",
    ]
    
    # Where to copy the processed source scripts and compiled scripts intended for publishing.
    # If set to a relative path, the path will be assumed relative to the script sources we are compiling.
    paths.source_scripts   = "./source/scripts"
    paths.compiled_scripts = "./scripts"
    
    # If set to 1, the compiled and source scripts will be automatically
    # copied into './scripts' and './source/scripts' folders in game's Data folder.
    # (do not forget to set the `paths.game` variable below)
    cfg.copy_to_game = 0
    
    # Path to the Game folder (used only if `cfg.copy_to_game` is set to 1).
    # If set to a relative path, the path will be assumed relative to the script sources we are compiling.
    #paths.game = "d:/GOG/Skyrim Anniversary Edition"
    paths.game = "./game"
    
    # Files with preprocessor defines to apply when preprocessing (order matters).
    # If set to a relative path, the path will be assumed relative to the script sources we are compiling.
    paths.macros = [
    	"./_common.inc",
    	"./_project.inc",
    ]
    
    # Paths to Papyrus Assembler and Compiler.
    # If set to a relative path, the path will be assumed relative to the script sources we are compiling.
    paths.assembler = "a:/Games/#SETUP/Skyrim AE [GOG]/Papyrus Pypline/Papyrus Compiler/PapyrusAssembler.exe"
    paths.compiler  = "a:/Games/#SETUP/Skyrim AE [GOG]/Papyrus Pypline/Papyrus Compiler/PapyrusCompiler.exe"
    
    # Whether to keep the .pas files.
    # The only reason to keep them is if you want to examine them, otherwise keep this set to 0.
    cfg.keep_pas_files = 0
    
    # How many threads to use for all tasks (except for Compile which has its own setting).
    # If set to zero, the number will be determined by `multiprocessing.cpu_count()`.
    cfg.default_threads = 24
    
    # How many threads to use for Compile process.
    # If you want to use faster multi threading (set `cfg.compile_subprocess = 0` below), but your CPU is getting too hot,
    # you can try lowering amount of compile threads to find a sweet spot where the process is still faster, but doesn't cook so much.
    # If set to zero, the number will be determined by `multiprocessing.cpu_count()`.
    cfg.compile_threads = 12
    
    # How to run the Compile process.
    #   0: Run multiple PapyrusCompiler threads (up to `cfg.compile_threads`), one thread per file.
    #      (may be faster, but IT WILL BE COOKING YOUR CPU)
    #   1: Run a single PapyrusCompiler subprocess, leaving the compiler binary itself to take care of multithreading.
    #      (may be slower, but RECOMMENDED, because it won't torture your CPU)
    cfg.compile_subprocess = 1
    
    # Console Progress bar width (in characters).
    cfg.progress_bar_width = 36
    
    # Console window font [font name, width, height].
    # If you want the console window to use a different font than your OS default, you can set it here.
    #   NOTE: Tested only on Windows 10, not sure if it works on Win 11 - if not, just comment it out.
    cfg.font = [
    	"Consolas", 8, 16
    	#"Lucida Console", 8, 16
    ]
    
    # Console window position [x, y] (in pixels).
    # Use if you have console windows set to use placement by OS, but want this window always at same position.
    #   NOTE: Tested only on Windows 10, not sure if it works on Win 11 - if not, just comment it out.
    cfg.window_pos = [
    	400, 250
    ]
    
    # Console window size [columns, rows].
    # Use if you want to override your default console window size.
    #   NOTE: Tested only on Windows 10, not sure if it works on Win 11 - if not, just comment it out.
    cfg.window_size = [
    	100, 35
    ]
    
    # Console window buffer size [columns, rows].
    # Use if you want to override your default console window buffer size.
    # (make sure to set the same column value as in `cfg.window_size` above, otherwise you may be unable to scroll the window)
    #   NOTE: Tested only on Windows 10, not sure if it works on Win 11 - if not, just comment it out.
    cfg.console_buffer_size = [
    	100, 9000
    ]
    
    
    
    
    
    ################################################################################
    if __name__ == '__main__':
    	run()
    ################################################################################


    .

 

Posted
6 hours ago, Fraying9981 said:

Could you be the one? Could you be the one that takes me from being a passive commentor and downloader to a mod developer?

 

Thanks for publishing this! I want to try it out.

I do not know if it is 100% new-mod-developer-friendly, probably isn't, so let me know if you run into any problem with this.

Posted (edited)

Found a little bug...

PapyrusPypline/preprocess.py, line 111:

    debug = "-DNODEBUG" if cfg.debug == 0 else "-DDEBUG"


It is a leftover from when i was using external gcc preprocessor, where the DEBUG/NODEBUG was defined on command line when running the preprocessor (the "-D" was a command line argument for the gcc).
I recently changed it from using gcc to a preprocessor implemented in Python (didn't have time to write it yet, so for now it's using the py-c-preprocessor by Lambosaurus), but forgot to change what is the string "debug" variable set to.
As a result, the preprocessor defines variable called "-DDEBUG" or "-DNODEBUG", which then doesn't match the variable names used by conditions in the default "_common.inc".

Short story, to fix this, the line must be changed to:
    debug = "NODEBUG" if cfg.debug == 0 else "DEBUG"

 

 

I am not going to upload a new file just to fix this, so if this bothers anybody, please change that line yourself..
I will upload a new version after i rewrite a few things, or if more bugs get accumulated.

Edited by Roggvir
Posted

Also, on the description page i used wrong name "__SCRIPT_FILE__" instead of the correct "__SCRIPT_NAME" (i fixed it on the description page now).

Posted (edited)

Added some Usage instructions to the description page, to explain the two command line options available.

Edited by Roggvir
Posted

I uploaded new version 1.1.0 and deleted the previous version (mainly because it's filename wouldn't be now in line with how i name the files).

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
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...