Introduction
Mason is a compiler that creates Stratum plugins from a project directory. The resulting DLL fits into the BepInEx ecosystem identically to that of a hand-written DLL; it boasts all the same features as a handwritten BepInEx plugin, such as dependencies, incompatibilities, and process name restrictions, with much less of a hassle to setup and maintain.
The Book of Masonry is the documentation for Mason, and intends to detail every feature in a human-readable form. Some of these features are located in the project and config files, whose schemas you can find here.
Contributions to the book are always welcome, even ones as small as correcting a typo or simplifying a confusing sentence. You can make a suggestion directly to a contributor (such as Ash), create an issue on the repository, or click the notepad in the top right corner of the chapter to edit a file and make a pull request.
As A Book
As this documentation site most closely resembles a book, a few expectations should be established.
Terminology used in books has been adopted. Individual web pages, such as this introduction page, are referred to as chapters. Within chapters, headings denote sections. A group of similar chapters comprises a unit. These distinctions were made to avoid confusion between "section" in the sense of a heading and "section" in the sense of a chapter.
To avoid repetitive content, later chapters assume you have read the former chapters. If a former chapter references content in a later chapter, it will be noted.
Disclaimers
This book contains many examples of Mason project file features. To keep things concise, only relevant lines are displayed. Other information, most notably the version: 1
at the beginning, may be required but not shown.
Getting Started
The Getting Started unit is a pragmatic approach to learning the basics of Mason. It details, start to finish, how to create a Thunderstore package using Mason.
The chapters consist of:
- Installing Mason
- Preparing a Mason project
- Compiling the project into a runnable Stratum plugin
- Adding assets to create a noticable effect on the game
- Adding dependencies for required plugins
- Packaging the final result
Installation
Before Mason can be used, it first needs to be installed.
Mason can be found on Thunderstore or GitHub. It is recommended and assumed that you are using r2modman or Thunderstore Mod Manager, as the Stratum ecosystem leverages the Thunderstore ecosystem extensively.
It is recommended to create a new profile for the sole purpose of running Mason. This keeps start times slim and lowers the chances of a mod conflict while you are still developing your mod, but always remember to test your mod for conflicts later!
Preparation
To prepare a Mason project, first navigate to where your mods are being installed. For r2modman or Thunderstore Mod Manager users, this can be found in Settings > Locations > Browse profile folder
.
Once you have arrived at the folder, navigate to BepInEx/plugins
(you may have to run the game once for this folder to generate). Create a new directory, which will be the root of your Mason project. Your project path should look something like BepInEx/plugins/wip-mod
Populating the Project
Mason projects require some files before they can be compiled. Each of the following sections describe paths within your Mason project.
Project File
The project file contains the instructions that Mason should convert into runnable code. The project file is located at project.yaml
, and must contain
version: 1
at the start.
Wow! What an amazing project! In future examples, this will be omitted. However, it is critical that you include the version field in every project file. Failure to do so will result in the project not compiling.
Thunderstore Manifest
The Thunderstore manifest contains the metadata that Thunderstore requires in every package. To avoid redundancy, Mason reads from this manifest to create metadata for your plugin. The following fields are the bare minimum to compile a Mason project, and are not representative of your final manifest:
{
"author": "AuthorName",
"name": "ModName",
"version_number": "1.0.0",
"description": "This does something awesome, I only have 250 characters to explain!"
}
The author field is not part of the Thunderstore specification, however it is greatly recommended to include it. If not present, Mason will perform the following checks:
- Does the project directory name have the pattern
AuthorName-ModName
? - Does the
ModName
of the directory match theModName
of the manifest?
If the checks pass, it will use the AuthorName
from the project directory.
The author field must be a valid Thunderstore team name (same restrictions as a package name), so an author that does not match this will cause a failure.
Compilation
Compilation turns a Mason project into something runnable. We haven't added any assets yet, but that is OK. Mason can compile an empty Stratum plugin, and that's all you need to learn on this page.
How to Compile
To compile your project, simply launch the game modded. BepInEx will trigger Mason, which will then sift through all projects and compile them. The resulting project directory should look something like:
MyProject
├── bootstrap.dll
├── config.yaml
├── manifest.json
└── project.yaml
If not, something probably went wrong. Check your console/log for any errors from Mason. They may be global, thus preventing Mason from compiling any projects. They may also be specific to your project, in which case they would detail what you did wrong.
What Each File Is
Mason produces two files as a result of compilation: bootstrap.dll
and config.yaml
. These files serve two very distinct purposes.
bootstrap.dll
bootstrap.dll
, or the bootstrap file, is the code that gets run by BepInEx. This is the living embodiment of your project, and what makes it work. The bootstrap file does not require any of the other files present in order to function.
config.yaml
config.yaml
is a special file intended for use by standalone Mason installations. It is detailed further in the standalone unit, but for now it can be ignored.
Running the Result
If you have no errors, the project will run itself! The bootstrap file will be naturally discovered and executed by BepInEx, resulting in the project being ran. Currently, it has no assets, so nothing is performed.
Assets
Stratum plugins without assets, much like Thunderstore packages, are rarely useful. They're like a waterbottle without the water. They are also the most complex part of Stratum plugins.
This page is very YAML-heavy. If these files look alien to you, this website has a good breakdown of YAML syntax. It is very similar to JSON, just more terse and packs more features.
Adding Assets
First, create a resources
folder within your project. This is where all your resources will reside.
One Asset
Assuming we have a gun within the asset bundle my_gun
that we would like OtherLoader to load instantly, simply place the asset bundle in the resources folder. Using this simple project.yaml
would cause it to load:
assets:
runtime:
assets:
- path: my_gun
plugin: h3vr.otherloader
loader: item
In this example, the first assets
node declares the assets of the entire plugin. The second assets
node narrows it down to just the assets of the runtime stage.
The runtime assets node takes a sequence of assets, which is show above after the hyphen. path
is the path to the file, relative to the resources folder. plugin
is the BepInEx GUID of the plugin that contains the loader, and loader
is the name of the loader. Together, these three data make a single asset.
Multiple Assets
Say you are using OtherLoader's new on-demand loading feature. This requires two assets: one for the data asset bundle, and one for the late asset bundle. This example will use my_gun
and late_my_gun
correspondingly:
assets:
runtime:
sequential: true
assets:
- path: my_gun
plugin: h3vr.otherloader
loader: item_data
- path: late_my_gun
plugin: h3vr.otherloader
loader: item_first_late
The most notable difference in this example is the sequential
tag. By default, runtime assets are ran in parallel. sequential: true
specifies that it should load in series, causing them to be loaded one at a time. If it was not specified, the late asset bundle may load before the data asset bundle!
Multiple Assets in Distinct Stages
Again with OtherLoader's new on-demand loading feature: if you have multiple asset bundles for the data or late loader, you must use nested pipelines. A pipeline is just a collection of subpipelines and assets to run. In fact, the runtime
node is actually a pipeline.
The necessary project file looks slightly different, but reads approximately the same:
assets:
runtime:
sequential: true
nested:
- assets:
- path: data/*
plugin: h3vr.otherloader
loader: item_data
- assets:
- path: late/*
plugin: h3vr.otherloader
loader: item_first_late
This project file is intended for use with such a resources folder as:
resources
├── data
│ ├── first_gun
│ └── second_gun
└── late
├── late_first_gun
└── late_second_gun
This is also the most detailed example, so it may be helpful to see it in the context of the rest of the project file. The following example is a complete project file, contrary to the warning at the beginning of the book:
version: 1
dependencies:
hard:
h3vr.otherloader: 1.0.0
assets:
runtime:
sequential: true
nested:
- assets:
- path: data/*
plugin: h3vr.otherloader
loader: item_data
- assets:
- path: late/*
plugin: h3vr.otherloader
loader: item_first_late
For further explanation of assets, see the assets unit. This page specifically discusses the runtime stage
Dependencies
While the project does run and have assets, its lack of declared depenendencies makes it vulnerable to a dependency-not-found error. Adding dependencies to your project ensures all dependencies are present and loaded before your plugin runs.
To add a dependency to the project, use the dependency
mapping. Another mapping, hard
, is needed to specify that it is an absolute requirement. Within this mapping, the globally unique identifier (GUID) of the plugin is used as the key, and the minimum version required is the value. For OtherLoader, dependencies in the project file would look like:
dependencies:
hard:
h3vr.otherloader: 1.0.0
h3vr.otherloader
because OtherLoader's BepInEx GUID is such. Most loaders will have their GUID listed in their documentation, Thunderstore page, or GitHub README.
For more information on dependencies, see their chapter.
Packaging
Now that the mod is complete, it can be packaged! Be sure to run Mason before you package to ensure the bootstrap file is fully up to date with your project file and resources, and finish up any Thunderstore-required files.
Packaging a Stratum plugin is a little more complicated than a Deli mod, but it's just as easy. Our current project directory is as follows:
MyProject
├── resources
│ └── my_gun
├── bootstrap.dll
├── config.yaml
├── icon.png
├── manifest.json
├── project.yaml
└── README.md
The Thunderstore-required files must stay where they are, so we can add them like usual. Similarly, the bootstrap file is in the correct location for the package.
The config and project files can be ignored, as they are only used to generate the bootstrap file. They may also be included in the package, but serve no meaningful purpose.
This leaves only the resources folder. Because we require the resources folder to not be flattened, we must follow r2modman's style of packaging and place the resources folder within a plugin
folder.
With all of these factors considered, the following should be what your mod structure looks like:
MyMod
├── plugins
│ └── resources
│ └── my_gun
├── bootstrap.dll
├── icon.png
├── manifest.json
└── README.md
Zip the contents of the folder and it's ready to upload!
Converting Deli Mods
As Mason + Stratum is the replacement for Deli, creators should convert their Deli mods to Stratum plugins when they find it is possible. Conversion is performed automatically through Deliter (/dɪˈliːtər/
), which converts all Deli mods possible within the current profile. Mods may be converted by hand, but Deliter is faster.
Installing Deliter
Deliter can be be found on Thunderstore or GitHub.
Converting
Install all the mods you wish to convert, then launch the game. Deliter will convert all it can to Stratum plugins, even if it cannot convert the entire mod. Once the preloader finishes, it is done.
The directory of a delited mod should look similar to:
SomeMod
├── resources
├── AMod.deli.bak
├── bootstrap.dll
├── config.yaml
├── icon.png
├── manifest.json
├── project.yaml
└── README.md
AMod.deli.bak
is just a copy of the original mod file, in case the original mod is not available on Thunderstore for whatever reason.
The original file, in this example AMod.deli
, may actually still exist under some circumstances. This happens when a mod cannot be fully converted, but is instead partially or not at all converted. To check if the mod was converted at all, simply view if there are any assets in the project file. No assets means no conversion. In these scenarios, mods still require Deli.
Repackaging
As Deliter simply generates a Mason project, the steps listed on the packaging page of Getting Started can be followed with a few additions.
The most important thing to do when repackaging a delited mod is: do not include the .bak
file. This is the original Deli mod, which is already on Thunderstore within an older package version. It serves no functional purpose and can be omitted.
Partial conversions also need to be cared for more than complete conversions. You can package the partially converted Deli mod with no edits, but that could as much as double the size of the package.
If possible, skim through the manifest file of the Deli mod and check what files/folders are still used. Deliter is not smart enough to delete them, so you must do this manually.
The same goes for the resources folder; using the project file as a guide, trim any excess resources. This will result in a smaller Thunderstore package. For more advanced users, the standalone section details automatic packaging which will do this process for you.
Problem System
The problem system of Mason, which manifests itself as errors and warnings, exists to inform creators of problems that exist in their projects before they are ran. This increases the speed at which projects can be worked on, because you no longer need to wait for the game to launch before seeing if there are any problems. This system also integrates well with Visual Studio: Code, to the degree that you might not notice how it works, as discussed on that page.
Consider this warning:
warning at manifest.json(0,0): [C10] The author of the mod was infered by the directory name. Consider adding an 'author' property.
The Message
The most important element of this problem is the message:
The author of the mod was infered by the directory name. Consider adding an 'author' property.
The message is often what you will see first, and is fairly self explanatory. It explains the problem, and attempts to give instructions on how to resolve it.
Message Identifiers
Each message format (the message before it has any specific data inserted) has its own identifier, in this case C10
.
The character indicates which component of Mason emitted the problem, and the number is the unique ID of the message format within that component of Mason. The possible components are:
C
: compiler (used by patcher and standalone)S
: standaloneP
: patcher
Severity
The next datum of note is the severity. If you have any experience reading logs, this should be a breeze. Mason contains only two severities: warning
and error
. Warnings represent a potential future flaw, or that something can be done easier. Errors represent an issue in the project that causes it to be uncompilable.
Location
The last piece of information in the probem is its location. With this problem, it is manifest.json(0,0)
. Locations come in four kinds, with each being more specific than the last:
Global
Global errors are the rarest and are rarely due to the project itself. They are the every-day error of Mason, and are shown as .(0,0)
.
File and Folder
File/folder specific errors are due to problems with the file's presense, location, or holistic contents. They are shown as path/to/item(0,0)
. This example is a holistic error, as there is no specific point in the file that should have the author property, but the file as a whole should contain it.
File Range
File range errors are caused by a segment of text within the project. They take the form of path/to/item(X1,Y1-X2,Y2)
, where X1 is the beginning line, Y1 is the beginning column, X2 is the ending line, and Y2 is the ending column.
File Point
File point errors are mainly errors in the parsing libraries that Mason uses (code that reads JSON/YAML), which reports not ranges of text but points. They take the form of path/to/item(X,Y)
where X is the line number and Y is the column number.
Metadata
Mason makes available the rich metadata options of BepInEx plugins. Currently, the metadata available are:
- Dependencies upon other plugins
- Incompatibilities with other plugins
- Process name restrictions
Finding the GUID
Metadata that reference other plugins, such as dependencies and incompatibilities, require the GUID of other plugins. If you are not directly told the GUID of a plugin, how are you supposed to find it?
Plugins compiled using Mason always have a GUID with the format of Author-Name
. This is almost always identical to the dependency string on Thunderstore, but without the version. For example, nayr31-VeprHunter-1.0.0
would be nayr31-VeprHunter
. The only instances where this is not the case is when the team name (uploader) of the package is not the same as the author
field of the project's manifest, and is usually caused by reuploads and project forks.
Consider a project, MedicBag
, which is originally created by Dallas. Dallas uses Dallas
as his Thunderstore team name and author
field. The Thunderstore dependency string would be Dallas-MedicBag-x.y.z
and the GUID would be Dallas-MedicBag
. If Hoxton
uploaded his edit of the project, the project would have the dependency string Hoxton-MedicBag-x.y.z
. Given that Hoxton did not change the author
field and recompile the project, the GUID of his mod would remain Dallas-MedicBag
.
Plugins created from scratch (i.e. not using Mason) can have whatever they wish as their GUID. There are few restrictions on the format, and you will need the plugin creator to specify what the GUID is. They may provide documentation on the Thunderstore page, GitHub README, or other means.
Dependencies
Dependencies are a metadatum that instructs BepInEx on what plugins should load before your plugin, and asserts the version of the other plugins.
They come in two forms: hard and soft.
Hard Dependencies
Hard dependencies are the most common form of dependency and probably what you think of when you hear the word "dependency." A hard dependency requires another plugin to be present, and requires that it is at least a certain version. Given that these criteria are satisfied, your plugin will load after the other plugin. In 99% of cases (source: me), a hard dependency is what you need.
Hard Example
dependencies:
hard:
h3vr.otherloader: 1.0.0
This requires that OtherLoader 1.0.0 is present, and causes it to load before our plugin.
Soft Dependencies
Soft dependencies are the long lost brothers of hard dependencies. A soft dependency checks if a plugin is present, and if so, causes it to load before your plugin, but does not cause your plugin to fail if it is not present. Soft dependencies have niche use cases, so much so that I cannot think of a reason to use one in the context of Mason.
Soft Example
dependencies:
soft:
- wristimate
If Wristimate is present, the plugin will load after it. I don't know why you would want to do this, but I can't think of any good examples.
Dependency Tips
Good dependencies make a good plugin. Always depend on the the lowest possible version that has your loaders. This allows users to revert the loader plugin for whatever purpose is necessary, most notably it breaking.
Also, use as few dependencies as possible. The more dependencies, the more points of breakage in a plugin.
Incompatibilities
Incompatibilities are a metadatum much like the opposite of a hard dependency: they prevent your plugin from loading if the specified plugin is present. You may want to do this if a plugin obsoletes another plugin, and you want the user to notice and uninstall the old version.
Example
incompatibilities:
- MyAuthor-OlderModName
This example prevents the plugin from loading if MyAuthor-OlderModName
is present.
Processes
Processes are a metadatum that only allows the plugin to be loaded in certain games/applications. You should only use this if your loaders are available for multiple games. Given that the loaders are only available for one game and you have listed the loaders as dependencies, it follows that your plugin will only ever run on that one game.
Example
processes:
- h3vr.exe
This prevents the plugin from running on any game except for H3VR.
Asset Management
Assets are the most important element of nearly all Mason projects. A strong understanding of assets shows a fairly good understanding of Mason.
Lexicon
Before we continue, some definitions should be established:
A loader is a plugin that features some code to convert data into some effect in game. An asset is one or more resources, which are files and folders used by an asset, that are passed to a loader at a certain time. A stage is a Stratum process that executes every plugin, thus every asset.
For example, OtherLoader is a loader that turns asset bundles into spawnable guns in game. VeprHunter contains an asset bundle (which has the data of the gun), which then gets passed to OtherLoader. The process of passing the asset bundle to OtherLoader is the asset, but the asset bundle itself is a resource because it is used in an asset. This entire interaction is facilitated by the runtime stage, which runs every plugin's runtime callback (thus running every plugin's runtime assets) while the game is running.
While assets and resources are closely related, it is important to note the difference so the contents of a project can be clearly and concisely explained.
The Asset Definition
The remaining chapters of this unit cover much about assets, but not do not define them. Instead, assets an asset definition will be shown and dissected in this very section.
Yet another term to learn! Asset definition is simply the text within a project file that represents an asset; it is the way an asset is defined. Assets have the structure of:
path: gun_bundle
plugin: h3vr.otherloader
loader: item
but the properties can be reordered, of course.
An asset definition can be broken into the following components:
path
: the path to the resource, relative to the resources folderplugin
: the BepInEx GUID of the plugin that contains the loaderloader
: the name of the loader
The Setup Stage
Setup assets follow the WYSIWYG principle. They have no quirks and use a format extremely similar to Deli, in which assets are placed in a sequence:
assets:
setup:
- path: readme.txt
plugin: motds
loader: init
- path: readme confirm.txt
plugin: motds
loader: init
In this case, the readme.txt
asset runs to completion before the readme confirm.txt
asset begins. When the second asset finishes, the plugin's setup callback is complete.
The Runtime Stage
The runtime stage, being that it is not as restricted as the setup stage, has significantly more features and complexity.
Starting from Assets
The runtime and setup stage, although very different in abilities, both share the purpose of running assets.
Assuming the loaders are available for the runtime stage as well, the same asset sequence can be used with the runtime stage:
assets:
runtime:
assets:
- path: readme.txt
plugin: motds
loader: init
- path: readme confirm.txt
plugin: motds
loader: init
However, may produce a different result than the setup stage because of the sequentiality, which is the way the assets are executed. Setup is constrained into one sequentiality, hence it being just a list. Runtime contains two possible sequentialities, which are discussed in the first chapter of this unit. Runtime also contains other features, such as nested pipelines, which are possible because of sequentiality.
Sequentiality
Sequentiality is the way assets are executed. Using the wrong sequentiality for the job can result in bugs traditionally called race conditions.
If sequentiality allows creators to screw over their own projects, why bother with the feature at all? Consider this runtime stage:
assets:
runtime:
assets:
- path: guns/pistol
plugin: h3vr.otherloader
loader: item
- path: guns/rifle
plugin: h3vr.otherloader
loader: item
- path: guns/smg
plugin: h3vr.otherloader
loader: item
Because the default sequentiality is parallel, this runtime stage is running the assets in parallel. With parallel sequentiality, the plugin does not await assets to begin the next one. It begins guns/pistol
, guns/rifle
, and then guns/smg
before any of the previous assets have completed. The assets may finish in the same order, but it is never certain. This may result in bugs if creators do not factor in parallel processing.
Although it can cause problems, it pays dividends in accelerating the loading time. Each asset bundle is not dependent on any of the other asset bundles, so all asset bundles are able to load independently and thus simultaneously. These gun bundles may be massive, requiring solid chunks of time to decompress on a single thread. In series, these all compete for the same hardware resources and thus the total time to load is the sum of each individual load. When adjusted to be ran in parallel, though, the total loading time of the plugin is greatly reduced.
Overriding Sequentiality
If the default, parallel sequentiality does not suit your needs, you can override it with the sequential
property. This is especially useful for OtherLoader's on-demand loading feature:
assets:
runtime:
sequential: true
assets:
- path: pistol
plugin: h3vr.otherloader
loader: item_data
- path: late_pistol
plugin: h3vr.otherloader
loader: item_late
In this example, pistol
begins and finishes loading before late_pistol
begins loading. This prevents the late asset bundle, which should be loaded last, from being loaded earlier. To load the other guns in parallel in the same fashion, we need nested pipelines.
Nested Pipelines
We've covered sequentiality, which allows loading individual assets before others, but what if you wish to load entire batches of assets before others? That's where you need nested pipelines.
Nested pipelines have the same syntax as the runtime stage. In fact, the runtime stage is a pipeline! Nested pipelines are just a list of pipelines that are executed before the assets, in the same fashion as the assets. For example:
assets:
runtime:
sequential: true
nested:
- assets:
- path: rifle
plugin: h3vr.otherloader
loader: item_data
- path: pistol
plugin: h3vr.otherloader
loader: item_data
- assets:
- path: late_rifle
plugin: h3vr.otherloader
loader: item_late
- path: pistol
plugin: h3vr.otherloader
loader: item_late
In this example, the nested pipelines act like substages; all the assets of the first pipeline finish loading before any of the assets of the second pipeline begin.
Sometimes, you have many assets that can be loaded in parallel but some assets that must be loaded in series. Nested pipelines allow these two to be combined into one project:
assets:
runtime:
nested:
- sequential: true
assets:
- path: readme.txt
plugin: motds
loader: init
- path: readme confirm.txt
plugin: motds
loader: init
assets:
- path: rifle
plugin: h3vr.otherloader
loader: item
- path: pistol
plugin: h3vr.otherloader
loader: item
This example begins loading both guns at the same time, as well as the nested pipeline. The nested pipeline loads both readme files, but does so one at a time.
Globbing
Globbing, or the use of wildcards, is a feature you've likely used many times already. It allows multiple things, in this case files and folders, to be specified by one string. Currently, globbing is only enabled for the path
property of an asset definition:
assets:
runtime:
assets:
- path: guns/*
plugin: h3vr.otherloader
loader: item
Any time a path separator (/
) is used, the result of the glob is filtered to only the directories of the previous glob.
A Runtime Forewarning
Globbing can be another catalyst of runtime bugs. Consider:
assets:
runtime:
sequential: true
assets:
- path: guns/data/*
plugin: h3vr.otherloader
loader: item_data
- path: guns/late/*
plugin: h3vr.otherloader
loader: item_late
This project file appears to load all data asset bundles parallel, then late asset bundles in parallel. However, all a glob does is expand; an asset definition with a glob in a list is converted into many assets in the same list. This project would load all data asset bundles and late asset bundles in series.
To resolve this problem, simply create a nested pipeline for each asset definition:
assets:
runtime:
sequential: true
nested:
- assets:
- path: guns/data/*
plugin: h3vr.otherloader
loader: item_data
- assets:
- path: guns/late/*
plugin: h3vr.otherloader
loader: item_late
In this case, the globs will expand within the nested pipelines. This would load all data asset bundles in parallel, wait for all to complete, then begin to load all late asset bundles.
Glob Types
Mason supports many globs, which may be used to your delight.
Name
Name globs are globs that match files or folders within the directory, and may be composited together within one path segment (text between path separators). Literals (plain text) also fall under this category.
A composite name glob may be [cb]at*
, which would match cat
, bat
, batmobile
, cathode tube
and more, but not rat
, brat
, latissimus
, and many others.
Some name globs do not require any data to fulfill their purpose:
Glob | Purpose |
---|---|
* | Matches 0 or more characters |
? | Matches exactly 1 character |
while others do:
Glob | Purpose | Does Match | Does Not Match |
---|---|---|---|
[bc] | Matches any character within the brackets | b , c | a , d .. z , A .. Z , 0 .. 9 and more |
[!bc] | Matches any character except those within the brackets | a , d .. z , A .. Z , 0 .. 9 and more | b , c |
[0-5] | Matches any character between the two characters | 0 .. 5 | 6 .. 9 , a .. z , A .. Z and more |
[!0-5] | Matches any character except those between the two characters | 6 .. 9 , a .. z , A .. Z and more | 0 .. 5 |
Globstar (**
)
Globstar matches every file and empty folder within the current folder and any subfolders, as well as the current folder itself. This is often used to glob any file within a folder with a certain name, regardless of which directory it is in. As an example, gun/**/data/*
would match gun/data/something
, gun/blue/data/anything
, gun/green/chartreuse/data/you name it
Escape Characters
Globs can be escaped, causing the glob to become a literal. This can be done by putting a backslash (\
) before the brackets. For example, [brackets].txt
will only match b.txt
, r.txt
, a.txt
, and so on, but \[brackets\].txt
will only match the file named [brackets].txt
.
Be careful with escaping both brackets, as \[brackets].txt
will merely prefix the glob with a backslash. Similarly, [brackets\].txt
does not escape the glob, but adds backslash as a possible character that can be matched. As a backslash cannot be used in a file/folder name on Windows, both of the errorful globs will match nothing.
Standalone
Up until this point, only the patcher plugin has been used. While the patcher plugin is very easy to begin with, it leaves much to be desired in relation to its workflow. Needing to launch the game to compile a mod, reading the console for warnings/errors, and manually packaging are possible, but tedious actions.
The standalone version of Mason aims to streamline the workflow as much as possible, and is even cross-platform!
Installation
Mason can be installed as a NuGet tool or by downloading the EXE directly.
NuGet
NuGet is the easiest, but is only available if you have the .NET SDK installed. If you don't know what that is, proceed to the direct download. For those with the SDK, use
dotnet tool install --global mason
to install and
dotnet tool update --global mason
to update.
Direct Download
Direct download, while primitive, works without any additional programs. Visit the latest release and download the standalone zip for your OS. Extract this to a directory, then add it to your PATH (for Windows). Adding to PATH ensures Mason can be called from anywhere on your PC.
To update Mason, extract a newer release to the same folder as before. Delete all items before extracting to ensure there are no unused files.
Usage
Before calling standalone Mason, you must add a config.yaml
file to your project directory. This file instructs Mason on where it can find the DLLs it needs, as it does not know where the game files are like the patcher plugin does. This file may appear as:
directories:
bepinex: C:\Users\<user>\AppData\Roaming\Local\r2modmanPlus-local\profiles\H3VR\<profile>\BepInEx
managed: C:\Program Files (x86)\Steam\steamapps\common\H3VR\h3vr_Data\Managed
Additionally, this BepInEx directory must have Stratum installed.
Command Line
Invoking Mason from command line is as simple as invoking:
mason
from within a project directory.
Mason contains a help page, which like many other shell utilities, can be accessed via a --help
flag:
$ mason --help
mason 1.0.4
Copyright (C) 2021 Stratum Team
build Builds the mod, which outputs the plugin file.
pack Builds and zips the mod, which outputs the mod ready for upload to
Thunderstore.
help Display more information on a specific command.
version Display version information.
Visual Studio: Code
Mason integrates fairly nicely with Visual Studio: Code. With the full suite of options, Code offers manifest.json
and project.yaml
IntelliSense (auto-completion with descriptions and examples), build and pack tasks, and errors appear directly within the text files. The necessary files can be found in the quickstart repository, but you might as well just download the entire repository if you choose to use the files.
Tasks are organized as build tasks, meaning CTRL + SHIFT + B
(by default) prompts you with a list of options. Running one of them will either succeed, or bring focus to the problems panel. Problems will be underlined when possible in files, and Mason must be re-ran to update its reported problems.
Features
Standalone Mason, being that it is its own application, has some added features compared to the patcher plugin.
Automatic Packaging
As covered in the Getting Started article, packaging can be a bit tedious because of all the factors to consider, compared to Deli's simple "zip everything in the folder" doctrine. Also, it can be easy to forget to run Mason before packaging, causing you to package an old bootstrap file. Automatic packaging allows you to put in the same amount of effort as you did with Deli, but yield a more efficient result.
The pack command, mason pack
, first begins compilation. When Mason compiles a project, it must enumerate through each asset definition to execute the path
glob. Mason stores the matching files/folders in a buffer. For example, a project with this structure:
MyProject
├── resources
│ ├── data
│ │ ├── first_gun
│ │ └── second_gun
│ ├── banking info
│ │ ├── birthday.txt
│ │ ├── credit card.txt
│ │ ├── mother's maiden name.txt
│ │ └── social security number.txt
│ └── late
│ ├── late_first_gun
│ └── late_second_gun
├── bootstrap.dll
├── config.yaml
├── icon.png
├── manifest.json
├── project.yaml
└── README.md
and these assets:
assets:
runtime:
sequential: true
nested:
- assets:
- path: data/*
plugin: h3vr.otherloader
loader: item_data
- assets:
- path: late/*
plugin: h3vr.otherloader
loader: item_first_late
will only use the following resources:
.
├── data
│ ├── first_gun
│ └── second_gun
└── late
├── late_first_gun
└── late_second_gun
The stored paths are then used to determine which files are added to a Thunderstore package. The result of this example would be:
.
├── plugins
│ ├── resources
│ │ ├── data
│ │ │ ├── first_gun
│ │ │ └── second_gun
│ │ └── late
│ │ ├── late_first_gun
│ │ └── late_second_gun
│ └── bootstrap.dll
├── icon.png
├── manifest.json
└── README.md
As you can see, only the necessary files are packaged. It also places the resources folder within the plugins
folder for you. All from running one command!
Version Control with Git
Version control is essential to any long-lasting project. As the name implies, it manages versions of the project files. This allows an entire project to be reverted to an earlier point in time.
Git is by far the most popular version control system. Git may be initialized within a Mason project like any other directory. Git also has the added benefit of being easily hostable on websites such as GitHub, allowing for community collaboration and additional documentation for your mod.
This book will only cover Git features specific to Mason, not how to use it. There are already mountains of information on the subject that have been constructed much better than anything possible here.
Continuous Integration
Continuous integration is a feature of many source control websites that, as the name implies, continuously integrates source code changes into the product. For the purposes of Mason, continuous integration is just automatic packaging.
GitHub
Continuous integration with GitHub is simple. Simply copy this file into your project, using the same path. GitHub will run this script every time a push or pull request is made to the main
or master
branches that affect the contents of a Thunderstore package, and it will yield an artifact containing the Thunderstore package. Additionally, you may add a branch protection rule to the main branch and require that the script succeeds before a pull request can be merged.