In this tutorial I will attempt to explain how we can use "dummy assets" to provide access to (otherwise unavailable) game resources (Functions, Blueprints, etc).
If you haven't read the first article in this series (Blueprint Example 101 - Hello World) please do so now.
In particular, I'm going to assume that you:
Let's imagine that you want to display a message on the screen but you've never done that before. You could do it by creating a Widget (that's basically a canvas for putting stuff on-screen) together with supporting Blueprint logic… but unless you're a graphical artist the results won't look very professional. The style will also be different to the rest of the game. It would be much better to use a game Blueprint to do it. That way it'll look good and match the existing style.
Great! But how do we do that? Let's figure it out…
If you want to follow along, creating the Blueprints as you go (and I recommend that you do) then you'll need to delete the dummy files that were installed as part of Tutorial #101. In other words, delete:
/Content/UI/HUD/Notifications/UI_BP_ErrorMessage.uasset/Content/UI/HUD/UI_BP_PhoenixHUDWidget.uassetFirst we need to know what game Blueprints are available. The good news is that since Unreal Engine is Open Source the functionality provided by the Engine itself is freely available. The bad news is that the same cannot be said of games built using the Engine, like Hogwarts Legacy ("HL"). Once a game has been “cooked” (especially if it's cooked into IOSTORE format, like HL is) everything inside will be hashed and converted into unreadable “byte code”.
Don't worry if you don't know what that means. The key point is that that game's internals have been scrambled in a way that we can neither reverse nor use. Not a great start!
Fortunately modders are an inventive bunch and have found ways to extract some of the information we need. We can then use a little deduction, educated guesswork, and trial-end-error to figure out the rest.
The first tool in our belt will be the CXXHeaders. As you may know, Unreal Engine games can be coded using Blueprints or C++. High-level functionality (like weapons, explosions, etc) are easiest to create in Blueprints, while lower-level stuff (handling inputs, memory allocation, etc) is easiest to create in C++. (Don't worry! We don't need to know any C++ to create Blueprint mods!) The good news is that if the game devs want to access any C++ stuff from their Blueprints, they have to provide enough information to the Blueprints that the Blueprints know how to use the C++ stuff. In technical parlance, the C++ stuff has to be “Reflected” in “Header Files”. These Header Files have to be embedded in the game, so modders have developed tools to extract them. Fortunately we don't need to know how to do that because Narknon (arguably the Godfather of HL modding) has already done it and posted the extracted CXXHeaders on the Hogwarts Legacy Modding Discord.
Head over to the Discord now and download the CXXHeaders from here.
So what's in this archive? About 1784 .hpp files.
What are they? We'll come to that in a minute…
So we want to find a Blueprint to display a message on-screen. How do we do that? Well, different modders use different tools, but basically we need to search those 1784 .hpp files for stuff relevant to the task.
I'll tell you how I do that, but if you can think of a better way, go for it!
I use a cmd window and the findstr command. In this case I'd start by searching for a Blueprint (whose name will therefore start with BP_) that mentions ErrorMessage because a Blueprint that displays an ErrorMessage on screen would be perfect for our current needs. Hence:
findstr /i ErrorMessage * | findstr /i BP_ | cut -c -200
This commands has three parts:
findstr /i ErrorMessage * : a case-insensitive search for ErrorMessage in all 1784 files.findstr /i BP_ : among the matches, only print the ones with BP_ in them.cut -c -200 : only print the first 200 characters of each line. You can ignore this (you may not have the cut command installed). It's just to keep the output neat.And here is the output:
As you can see there are various different matches, most of which aren't relevant (the BaseCursor, Creature stuff, HerbologyMenu, Vendor, etc). But in the middle there is a whole section about a Blueprint called UI_BP_ErrorMessage in a file called UI_BP_ErrorMessage.hpp. That sounds very promising!
Let's take a look at that file. Some modders use Visual C++ applications for this, but I find that Notepad++ is fine. Here's the file in Notepad++:
Not a very big file is it? But there's lots of useful stuff in here:
Blueprint “class” is UI_BP_ErrorMessage. (Ignore the extra U added onto the front and the _C at the end.):) is HUDElementGroup. (Again, ignore the extra U added onto the front.)Variable called ErrorMessage that's a reference (hence the *) to an object of class PhoenixTextBlock . (Ignore the extra U .)DisplayErrorMessage.HermesDisplayErrorMessage.Don't worry too much about what all of that means. We just need to know how to use it, not what it is. But in simple terms:
To find out what those two functions do, we'll have to create a dummy of the UI_BP_ErrorMessage Blueprint, create some logic to use it, and test it in the game.
If you've watched any Youtube videos about Unreal Engine Blueprints, you'll have seen people testing their Blueprints inside the UE Editor. Unfortunately we can't do that when we're modding HL because the game's code is not available to us. We can only test and debug stuff by compiling it, cooking it, and loading it into the game. That can be a painful process, as you can imagine. Each time you make a mistake it can take several minutes to re-compile, re-cook, re-start the game and re-load the mod. But there's no other way I'm afraid. This is one of the things that makes modding HL (and all UE games) a bit of a challenge.
In the following sections I describe how to use UModel to search for a folder name inside the game files. I used UModel for this because (at the time of writing) I didn't know that FModel has a search function. Now that I know it does, I would probably recommend FModel for this because it's much easier to use. For instructions on how to use FModel see the following sections of this guide:
Install FModel - which explains how to install it.Finding Data Assets - which explains how to search for something (in this case Data Asset's but you can adapt the procedure to search for other things).We need to create a dummy of the Blueprint, plus the Functions and Variables inside it that we intend to use. But first we need to know where to put the dummy Blueprint. It has to go in the exact same folder in our project as the real Blueprint file goes in the game. To discover what that folder is we need to run UModel (which used to be called “Unreal Engine Viewer” or UEViewer).
For our purposes any recent version of UModel will probably do, but if you ever want to browse through the game's animations you'll need a specific version, so you may as well install that one from the outset.
UModel from https://www.gildor.org/en/projects/umodel and unzip it somewhere convenient.umodel_acl_2.1.exe from here.umodel_acl_2.1.exe into your Umodel directory.umodel.exe, umodel_64.exe, umodel_acl_2.0a.exe and umodel_acl_2.0b.exe. You won't need them.UModel can be a bit fiddly to use the first time, but it's okay once you get the hang of it. UModel also doesn't like mods for some reason, so if you're planning to do anything serious with it you should move your ~mods folder to your Desktop. But we're just looking for file paths so we probably don't need to bother on this occasion.
Open a cmd window, cd to your UModel folder and type the following (all on one line):
umodel_acl_2.1.exe -path="X:\WHEREVER\Steam\steamapps\common\Hogwarts Legacy\Phoenix\Content\Paks" -game=hog -gui
(where X:\WHEREVER is the drive and folder where you installed Steam).
Hit "Okay" on the "Startup Options" window and UModel will load all your game files (plus a few mods on this occasion):
Tick “Flatview” and next to "Filter" type UI_BP_ErrorMessage:
So that's our Blueprint's full path: /Content/UI/HUD/Notifications/UI_BP_ErrorMessage. (As I mentioned in Tutorial #103, which was actually published before this one, whenever you see /Game/ mentioned anywhere, replace it with /Content/.)
Back in the Unreal Editor Content Browser create the following folders:
/Content/UI//Content/UI/HUD//Content/UI/HUD/NotificationsIn the Content Browser, open the UI/HUD/Notifications folder. Right click it and select Blueprint Class:
In the window that appears click “All Classes” and then search for the parent class of our Blueprint, which was HUDElementGroup, click it then hit Select.
Rename the new Blueprint to UI_BP_ErrorMessage:
Double click it, then click [UI_BP_ErrorMessage] on the left:
This is our raw Blueprint, but the real Blueprint had references to Functions and Variables. We need to add the ones that we might use:
Variable called ErrorMessage that's a reference to a PhoenixTextBlock.HermesDisplayErrorMessage that has 2 inputs: Caller (a Reference to an Object) and String (a String).Click on “Graph” (top right of the screen) to reveal the Blueprint 's Event Graph and properties. This is where we can add our Functions and Variables:
Add the ErrorMessage Variable and the HermesDisplayErrorMessage Function (with inputs as shown):
Compile and Save, then hit the X top right to exit that Blueprint.
Bring up our Level Blueprint again and select the DisplayMessage Function.
Now we can right click, untick Context Sensitive and search for HermesDisplayErrorMessage:
Select it and (as if by magic) a new node will appear:
To connect this up:
String is obvious - that's the message we want to display.Caller is less obvious but we'll try Biped_Player and see what happens.Target is least obvious (from the name), but Target generally refers to the asset's parent. So what's the parent of the HermesDisplayErrorMessage Function? Well, it's UI_BP_ErrorMessage of course. It even says so right there on the node!Where do we get UI_BP_ErrorMessage from? We've defined a dummy Blueprint with that name but we need to supply HermesDisplayErrorMessage with a Reference to the actual instance of the UI_BP_ErrorMessage Blueprint that's created in the game. How do we do that? Well, one way would be to find whatever creates UI_BP_ErrorMessage in the game and see if we can get a Reference from inside that. If not, we'd need to find whatever created that asset and check there… continuing up the tree until we found what we need. That process is described in the section after this one. But what if none of them contain the reference we're after?
In the section after this one I'm going to search up the parent tree to find a reference to
UI_BP_ErrorMessage.I have subsequently discovered a much easier way to do this (see point #4 below), so read this section then skip the next one unless you're interested in some of the more advanced details of Unreal Engine.
Usually one of the following will provide a reference for anything you need (and we've seen examples of some of these already):
Get Node's: Some things have a Get Node built-in which will provide you with a reference. This is typically something for which there will only ever be one. For example there will only every be one UIManager so UIManager includes a Get Node to provide a reference to it. Another example is the Map Subsystem for which there is in fact only one node: Get MapSubsystem.GetAllActorsOfClass: Anything that's considered to be an Actor by Unreal Engine can be found using GetAllActorsOfClass. This will return an array of the the Actor's of that Class. You'll need to search through this array to find the one you need. If you're not sure if what you're after is an Actor just bring up the node and click the dropdown. If it's listed it's an Actor . If not, it isn't. UNLESS the name starts with BP_ in which case it's a blueprint class which you will need to dummy to make it appear in the list.GetActorOfClass: If you're confident that there is only one Actor of the type you're after, you can use GetActorOfClass instead. This will return a single reference. We saw an example of this in tutorial #101 with PhoenixHUD.GetAllWidgetsOfClass: Anything that's considered to be a Widget by Unreal Engine can be found using GetAllWidgetsOfClass. This will return an array of the the Widget's of that Class. You'll need to search through this array to find the one you need. If you're not sure if what you're after is a Widget just bring up the node and click the dropdown. If it's listed it's a Widget. If not, it isn't. UNLESS the name starts with UI_BP_ in which case it's a blueprint class which you will need to dummy to make it appear in the list. UI_BP_ErrorMessage is an example of this.GetComponentsByClass: Anything that's considered to be a Component by Unreal Engine can be found using GetComponentsByClass. This will return an array of the the Components's of that Class. You'll need to search through this array to find the one you need. If you're not sure if what you're after is a Component just bring up the node and click the dropdown. If it's listed it's a Component. If not, it isn't. Components are quite an advanced feature which I won't be covering in this tutorial.GetComponentByClass: If you're confident that there is only one Component of the type you're after, you can use GetComponentByClass instead. This will return a single reference.When I find myself looking for a Reference I generally proceed as follows:
Get Node (i.e. point #1 above).Node to provide a reference (i.e. the first step of “the hard way”, below).Node from points #2 to #6 in the list above.It doesn't really matter which approach you use, as long as it works, but some methods are easier than others.
Most readers can skip this section – see the section before this one instead!
If we want to find a Reference to the actual instance of the Blueprint that's created in the game we could find whatever creates UI_BP_ErrorMessage in the game and see if we can get a Reference from that. If not, we we could find whatever created that asset and check there… continuing up the tree until we find what we need. If none of them contain the reference we're after, we'll need to use some other method (see the previous section).
What creates a UI_BP_ErrorMessage? We need to check our CXXHeaders again to look for clues:
The line I've marked looks promising. Let's take a look at that file (I've hidden some lines so we can see the important bits):
So UI_BP_ErrorMessage is a Blueprint that's created by the UI_BP_PhoenixHUDWidget Blueprint, whose parent class is PhoenixHUDWidget.
To get a reference to UI_BP_ErrorMessage we're going to have to create a dummy of UI_BP_PhoenixHUDWidget. None of the other Variables or Functions in the file look relevant so we only need to add the UI_BP_ErrorMessage Variable to our dummy.
First let's check UModel to find out what folder to put it in:
It's in /Content/UI/HUD/. To create the dummy asset /Content/UI/HUD/UI_BP_PhoenixHUDWidget, follow the same procedure as last time and you should produce this:
Compile and Save, then hit the X top right to exit the dummy Blueprint.
Now let's return to our Level Blueprint, right click and do a context-insensitive search for UI_BP_PhoenixHUDWidget:
We can now cast something as a UI_BP_PhoenixHUDWidget:
If we connect the output of that Cast to the input of HermesDisplayErrorMessage the editor automatically re-Casts the Reference, which is handy:
Okay, we're making progress, but now we need to find something to cast into a UI_BP_PhoenixHUDWidget.
Whoa! Hold on a minute!
What the hell is a Cast , I hear you say?
Well, it goes like this. Let's imagine you create a Blueprint for a Goblin Assassin, which is special type of Goblin that has a crossbow. In technical parlance it's a Child of the Parent Blueprint Class for a Goblin. This Goblin Assassin Class inherits all the properties of a Goblin, but it also has crossbow so it has extra stuff.
Now let's imagine that a bunch of Goblins attack the Player, who casts Ancient Magic Throw at one of them. The Ancient Magic Throw 's Targetting Blueprint must now decide whether that spell disarms the Goblin or not. If the Goblin is a Goblin Assassin then it does, if not it doesn't. But the Blueprint isn't told that the target is a Goblin Assassin . In fact it doesn't even know that it's a Goblin. All it knows is that it's some sort of Enemy. The reason it doesn't know this stuff is because the Blueprint would require hundreds of inputs (one for each sub-type of Enemy) to achieve that. Instead it has a single input for Enemy and it must figure out what type of Enemy it is later. Once it's figured that out it can Cast the Enemy Blueprint into a Goblin Assassin Blueprint to gain access to the extra properties it needs to disarm the Goblin Assassin.
Does that make sense? Don't worry if not. All we need to know for now is that there's no Function to give us the UI_BP_PhoenixHUDWidget we're after (if there was we would have been offered it when we did the search) so we'll need to find a Reference to the UI_BP_PhoenixHUDWidget ‘s Parent Class, Cast that as a UI_BP_PhoenixHUDWidget and hope it's the one we're looking for.
Of course the fact that something has the right Parent Class doesn’t mean it's the right object, but we'll cross that bridge when we come to it. In this particular case we have reason to be optimistic, because there is probably only one HUD and hopefully only one PhoenixHUDWidget… so whatever we find with the right Parent Class will probably be the thing we're after.
Note: For technical reasons Casts are quite resource-expensive so are generally to be avoided. But on this occasion we have no choice.
So we can now Cast something into a UI_BP_PhoenixHUDWidget. But what?
To answer that question we need to know more about UI_BP_PhoenixHUDWidget. Let's search our CXXHeaders for PhoenixHUDWidget and see what we find:
There are two promising entries: UserWidget and HUDWidgetRef. The second sounds more likely, but let's try UserWidget first. If we drag a line off the Object pin of the Cast to UI_BP_PhoenixHUDWidget node and search for UserWidget… there's nothing that looks particularly relevant. Now let's do the same for HUDWidgetRef:
Get HudWidgetRef might be interesting! Let's give that a try:
Okay, that Node is looking for a Target (which usually means Parent). What's the parent of HUDWidgetRef? Well, HUDWidgetRef was defined in Phoenix.hpp so let's have a look at that file:
So HUDWidget is a Reference (pointer) to a PhoenixHUDWidget (but we knew that already) and is defined inside the PhoenixHUD Blueprint. So the Parent is PhoenixHUD, which is handy because we already have that as a Variable. Let's drag in a Get PhoenixHUD node:
Perfect! Just one more thing to do…
We need some Events to Initialise the mod and call our DisplayMessage Function:
Note the DebugMessage text. We also need an Initialize Function:
Now Compile, Save, assign our Level Blueprint (i.e. our .umap file) a PakChunk, Cook it and test it!
Don't forget you'll need to use the Modloader to load the mod.
When you hit your chosen key (in my case ALT-NUMPAD5) you should see:
Don't worry about the square brackets around the [text]. We can get rid of those.
But more importantly…
OUR DUMMY ASSET WORKED!
Woohoo! 🥳
The DisplayMessage Function we've created here isn't quite the same as the one (by Darkstar) that we used in Tutorial #101. This one doesn't remove the [square brackets] yet and doesn't use Darkstar's GetPlayer Function. The version we used in Tutorial #101 is the one to use going forward.
The dummy assets we've created are also slightly different to Darkstar's, which include more Variables, but those variables aren't needed for this project so I omitted them.
The next Tutorial is already up: Blueprint Example 103 - Toggle Slow Motion.