Closed mike9k1 closed 3 years ago
Can be found referenced in the .exe as well, two offsets: 0x3E3CC00 and 0x3E3F910
The search continues...
UObject::FindObjects won't work for structs, but if you can find a class that makes use of the struct you can usually use it on that, seems there is one class that references it:
So to access that you'd need something like
auto GrassTypes = UObject::FindObjects<ULandscapeGrassType>():
for(auto& GrassType : GrassTypes)
{
for(int i = 0; i < GrassType->GrassVarieties.Num(); i++)
{
auto& Variety = GrassType->GrassVarieties[i];
Variety.GrassDensity = 2000;
}
GrassType->GrassDensity = 2000;
}
stick that somewhere that runs after the map has loaded and maybe it'll work, that's only really for testing though since FindObjects is expensive, if this works it'd be better to find where the GrassDensity value gets read by the code, hook that function, and modify it there instead.
Alternatively, maybe can find the uasset/uexp that defines these by grepping for the field names, and then use something like https://github.com/atenfyr/UAssetGUI to mod it inside the file - one of the assets is probably defining GrassDensity somewhere.
I had tried something like this previously:
g_LandscapeGrassType = ULandscapeGrassType::StaticClass()->GetDefault<ULandscapeGrassType>();
g_LandscapeGrassType->GrassDensity = 1600;
and
g_LandscapeGrassType =UObject::FindObject<ULandscapeGrassType>();
g_LandscapeGrassType->GrassDensity = 1600;
But it makes sense those didn't work. I'll give your example a shot as it looks like it actually iterates through any and all instances of ULandscapeGrassType. Not sure if perhaps it could be set as read-only at runtime.
I have also tried using notepad++ to search through all of pakchunk0 for any instances of 'GrassDensity', assuming it would be defined in the .uasset in plaintext similar to other properties. If any matches were found, I had thought of simply searching for 00 00 C8 43
in hex (400 in float. little endian) and replacing it with a higher value (i.e. 00 00 E1 44
). Alas, no luck there so far: albeit I am still searching pakchunk1, I don't expect to get any matches there as both the Blueprints and DataTables are all contained in pakchunk0.
It seems like this is a fairly common UProperty. There are some guides on tuning this within UE4 Editor -- https://worldofleveldesign.com/categories/ue4/grass-output-node-guide.php
I have also tried using notepad++ to search through all of pakchunk0 for any instances of 'GrassDensity', assuming it would be defined in the .uasset in plaintext similar to other properties. If any matches were found, I had thought of simply searching for
00 00 C8 43
in hex (400 in float. little endian) and replacing it with a higher value (i.e.00 00 E1 44
). Alas, no luck there so far: albeit I am still searching pakchunk1, I don't expect to get any matches there as both the Blueprints and DataTables are all contained in pakchunk0.
You searching the .pak file itself or the extracted contents? I think .pak uses compression, so searching the file probably wouldn't find things properly.
It could be possible they aren't using 400 as the value, hopefully if you do find any uasset that uses that field the UAssetGUI will be able to load it & let you edit without needing to search/replace.
I have also tried using notepad++ to search through all of pakchunk0 for any instances of 'GrassDensity', assuming it would be defined in the .uasset in plaintext similar to other properties. If any matches were found, I had thought of simply searching for
00 00 C8 43
in hex (400 in float. little endian) and replacing it with a higher value (i.e.00 00 E1 44
). Alas, no luck there so far: albeit I am still searching pakchunk1, I don't expect to get any matches there as both the Blueprints and DataTables are all contained in pakchunk0.You searching the .pak file itself or the extracted contents? I think .pak uses compression, so searching the file probably wouldn't find things properly.
lol I should clarify: I recursively searched the dump from the .pak (output from QuickBMS) containing all the .uassets/.uexps/etc ;p
(pakchunk1 finally finished:)
It could be possible they aren't using 400 as the value, hopefully if you do find any uasset that uses that field the UAssetGUI will be able to load it & let you edit without needing to search/replace.
Hmm, I might modify the routine above and see if I can get it to print out the existing value of GrassDensity from the ULandscapeGrassType instances to a MessageBox().
Edit: Looks like 400 --
(also possibly of note: it only finds one instance of this -- both the routine in the for loop and outside the loop (GrassType->GrassDensity) were modified in this case. It may be that GrassVarieties[ ] isn't populated? -- this is after the map is already loaded, for the record)
lol I should clarify: I recursively searched the dump from the .pak (output from QuickBMS) containing all the .uassets/.uexps/etc ;p
Ah good, just making sure :)
Edit: Looks like 400 --
Hmm, well if there's no mentions in the asset files it's likely it gets constructed at runtime, the code you linked at the start of the thread actually suggests that too: "FGrassVariety() : GrassMesh(nullptr) , GrassDensity(400)"
So the value is likely embedded in the EXE instead, probably as part of the ULandscapeGrassType classes constructor. Searching EXE for 400 would probably have a ton of hits though... the other values listed there aren't really that unique neither. There is a certain pattern that UE4 classes use when constructing themselves though that usually refs the constructor func directly, I'll try having a look for that in a sec.
(E: file offset 0x01540C18 in the unprotected steam EXE is the 400 value as a float, the code around there should be constructor for FGrassVariety, since it also mentions stuff like 10000.0f - I don't have the game installed right now but maybe worth trying to hex edit it)
(also possibly of note: it only finds one instance of this -- both the routine in the for loop and outside the loop (GrassType->GrassDensity) were modified in this case. It may be that GrassVarieties[ ] isn't populated? -- this is after the map is already loaded, for the record)
If it is just using the default constructor params it's likely they aren't setting any GrassVarieties, so makes sense.
I'd be kinda surprised if they were actually making use of this though, no uassets setting them & values being all the default UE4 values... still, probably worth checking if it makes any difference.
BTW there's a pretty neat cheat-engine table for messing with UE4 properties here: https://fearlessrevolution.com/viewtopic.php?p=167452#p167452, that can probably help to test stuff out instead of needing to build the DLL each time.
Not sure how well that'll work with DQXIS though, since there were some customized things like the UObject struct IIRC, but maybe that table could be updated to work with it.
(E: file offset 0x01540C18 in the unprotected steam EXE is the 400 value as a float, the code around there should be constructor for FGrassVariety, since it also mentions stuff like 10000.0f - I don't have the game installed right now but maybe worth trying to hex edit it)
I'll have a look at that offset. I had searched the exe for that value (00 00 C8 43
) and came up with like...44 matches, which wasn't too crazy. I started editing about 7 at a time and testing that, (ofc sometimes the game would crash when making changes to some offsets xD ), but I had enough success to rule out...maybe 35-40 of them.
I'll give that particular offset a shot by itself and see.
Edit: Looks like that was indeed it
Oddly, I feel like something has improved here, but i'm not entirely sure. It's certainly not quite as clear-cut as I would've hoped, if so. (edit: nvm it's literally just the mesh replacement mentioned in the first post)
Various other possibilities: ScaleX, ScaleY, ScaleZ (each are FFloatIntervals with a minimum and maximum value)
Also: Foliage.FoliageType.Density ?
https://github.com/emoose/DQXIS-SDK/blob/2ddc8bdb4f1556e797fce4928b4b9746378e7450/DQXI-SDK/SDK/Foliage_classes.h#L51-L156 (searching pakchunk0 for all references to "density"...)
Edit: Modifying the code for UFoliageType seems to show several different instances at any given time, each with a density value ranging from 20-100
auto FoliageVarieties = UObject::FindObjects<UFoliageType>();
for (auto& FoliageType : FoliageVarieties)
{
std::wstringstream wss;
wss << FoliageType->Density;
MessageBoxW(NULL, wss.str().c_str(), L"DQXIS-SDK", MB_ICONEXCLAMATION);
FoliageType->Density = 2000;
FFloatInterval ffi;
ffi.Min = 2.0f;
ffi.Max = 3.0f;
FoliageType->ScaleX = ffi;
FoliageType->ScaleY = ffi;
FoliageType->ScaleZ = ffi;
}
Could it be that the grass is defined as "foliage" instead?
(Sorry i'm just getting around to coming back to this)
So it looks like a lot of the grass is defined as foliage as foliage.DensityScale directly affects the grass
foliage.DensityScale 1
foliage.DensityScale 0.05
It may be that I need to modify what i'm doing above. I can see that whenever a new area is loaded several new foliage instances are created (each with 20-100 Density, typically) and i'm assuming that the previous ones are discarded as if I load back into a previous area those values are also reset.
Hmm, I'm guessing you tried the other foliage.* cvars to see if those helped at all too? At least with Tales of Arise the foliage.LODDistanceScale
cvar helped improve grass/small rocks/etc density & distance quite a bit (tho with a pretty big perf hit), but no idea about DQXIS.
Maybe worth checking in the UE4.18 src to see where foliage.DensityScale
is actually used, maybe something around there could show how it handles distance culling etc too.
E: seems there's a UFoliageType::CullDistance
property used for some checks, along with the UFoliageType::Density
that you mentioned before, not sure if that could be changed in SDK via FindObject etc though since the UFoliageType values get copied over to some other component early on, maybe worth seeing if any UAsset files mention UFoliageType anywhere & see if some editor tool can tweak them.
(btw speaking of Tales of Arise, on there I found that changing r.PostProcessingColorFormat to 1 in the engine.ini would lose 10+FPS for almost no quality difference, only thing I noticed it changing was edges of some foliage - maybe worth testing this in DQXIS & removing it from the lighting fix INI if there's hardly any difference there too)
Hmm, I'm guessing you tried the other foliage.* cvars to see if those helped at all too? At least with Tales of Arise the
foliage.LODDistanceScale
cvar helped improve grass/small rocks/etc density & distance quite a bit (tho with a pretty big perf hit), but no idea about DQXIS.
foliage.LODDistanceScale
is "15" in the recommended ini as it is, which is pretty overkill AFAIK, so i'm assuming that's not a factor here ;p . foliage.DensityScale
is also "7" in the recommended config. It wasn't until recently that we learned that the max value for that cvar is "1.0" xD
E: seems there's a
UFoliageType::CullDistance
property used for some checks, along with theUFoliageType::Density
that you mentioned before, not sure if that could be changed in SDK via FindObject etc though since the UFoliageType values get copied over to some other component early on, maybe worth seeing if any UAsset files mention UFoliageType anywhere & see if some editor tool can tweak them.
I've started doing some searches. So far no hits in Blueprints, a handful of hits in DataTables, but it was only DT_DebugCommandMacro and the like (I did see a few cvars that didn't look familiar from my notes earlier, but nothing that seemed too useful at the exact moment). Searching "Maps" now...
(btw speaking of Tales of Arise, on there I found that changing r.PostProcessingColorFormat to 1 in the engine.ini would lose 10+FPS for almost no quality difference, only thing I noticed it changing was edges of some foliage - maybe worth testing this in DQXIS & removing it from the lighting fix INI if there's hardly any difference there too)
I'll look into this. Maybe r.SceneColorFormat
as well.
This may only be recommended if you're taking super-high-quality screenshots or something. I can't tell much of a performance difference with or without but...there are probably other things making more of a difference in my case xD
We've discussed trying to make a more medium/medium-high settings config that strips away some of the most perf-heavy settings anyhow.
Edit: I see "CullDistance" (and "Density") present in F01_Map_Riverside.umap . Might be getting closer...
(also see 4 instances of FoliageType_InstancedStaticMesh
in the code blocks, however UE4 AssetEditor is unable to view/edit their properties)
Maybe https://github.com/atenfyr/UAssetGUI/releases/tag/v1.0.0.0-alpha.2 could help edit them, had some luck using that with ToA where UAssetEditor didn't want to work anyway.
It may have the same issues UAssetEditor does in this case. I have tried hex editing the .uexp to some (limited) success. It changed a couple instances but most still remained at 100-200 density (the rest could be getting pulled from another umap, however)
It looks like UAssetGUI might be a bit better here, actually. UAssetEditor was crashing the game after some edits. Thanks for the recommendation!
I'll keep digging around with this.
Aha neat, how many FoliageTypes are listed there though? would suck if hundreds needed to be changed...
Its possible the PostLoad function of it might be hookable, so SDK could change the value right after it's loaded in, but do you know if changing that Density value actually has any effect though? I tried searching UE4 src but didn't actually seem like anything was using it, maybe gets used by something outside of C/C++ code though.
I also noticed UE4 sets up some "class redirect" thing for FoliageType_InstancedStaticMesh
CLASS_REDIRECT("FoliageType_InstancedStaticMesh", "/Script/Foliage.FoliageType_InstancedStaticMesh");
I've seen some INI mods where they change properties by just adding a class name like that into the INI, don't really know how it works though, maybe something like
[/Script/Foliage.FoliageType_InstancedStaticMesh]
Density=20000
CullDistance.Min=0
CullDistance.Max=20000
Not sure if that's just changing the default value of InstancedStaticMesh before the data is actually loaded in (so would just get overwritten), or if that changes it afterwards...
Aha neat, how many FoliageTypes are listed there though? would suck if hundreds needed to be changed...
Usually only like 2-4 entries of FoliageType_InstancedStaticMesh in each umap. However, it seems like it's pulling values from elsewhere as when I check it in-game using the routine I added to DQXI-SDK it only seems to show a couple instances changed. A few cases I see don't have any "Density" property listed under FoliageType_InstancedStaticMesh (maybe it's initialized to "100" by default unless defined in the umap?)
I do, however, see a HUGE array (some 100-900) of SortedInstances under FoliageInstancedStaticMeshComponent, but they're all just IntProperty's of various values (also in the hundreds range).
Its possible the PostLoad function of it might be hookable, so SDK could change the value right after it's loaded in, but do you know if changing that Density value actually has any effect though? I tried searching UE4 src but didn't actually seem like anything was using it, maybe gets used by something outside of C/C++ code though.
I'm not led to believe that changing it at runtime has any effect based on my efforts so far (but it could easily be that i'm doing something wrong xD )
I also noticed UE4 sets up some "class redirect" thing for FoliageType_InstancedStaticMesh
CLASS_REDIRECT("FoliageType_InstancedStaticMesh", "/Script/Foliage.FoliageType_InstancedStaticMesh");
I've seen some INI mods where they change properties by just adding a class name like that into the INI, don't really know how it works though, maybe something like
[/Script/Foliage.FoliageType_InstancedStaticMesh] Density=20000 CullDistance.Min=0 CullDistance.Max=20000
Not sure if that's just changing the default value of InstancedStaticMesh before the data is actually loaded in, or if that changes it afterwards...
I'll give that a go. Couldn't hurt to try.
Edit: Ahh, no luck there either (yet).
I'll go ahead and attach some files. This is Cobblestone (non-destroyed).
Did you notice any difference after editing the Density value in the uasset/umap though? Like I said it didn't seem like UE4's src actually made any use of it, hopefully I just missed something though.
A few cases I see don't have any "Density" property listed under FoliageType_InstancedStaticMesh (maybe it's initialized to "100" by default unless defined in the umap?)
Yeah the UFoliageType constructor does seem to set it as 100 by default, could be worth trying to find the ctor in DQXIS and try changing it there.
I'm not led to believe that changing it at runtime has any effect based on my efforts so far (but it could easily be that i'm doing something wrong xD )
Might be because of the copying that I mentioned, I think the order it works is something like:
So using something like FindObject to find the UFoliageType and changing the values wouldn't really have any effect, since the values were already copied over via FFoliageMeshInfo::UpdateComponentSettings I'm not sure what actually causes FFoliageMeshInfo::UpdateComponentSettings to be used though, seems there's a few different ways it gets called, maybe there's some way to force it to copy everything over again, not sure.
PostLoad would probably be easiest bet to hook it & change the values though, but haven't actually checked whether it does get used before UpdateComponentSettings or not.
I'll give that a go. Couldn't hurt to try. Edit: Ahh, no luck there either (yet).
Ah too bad, would have been nice if things could be changed from the INI..
Seems UE4 wants the class to have a certain "Config" specifier attached to the class that says which INI to read from, and then each property that can be edited needs "Config" flag set: https://docs.unrealengine.com/4.26/en-US/ProductionPipelines/ConfigurationFiles/
I wonder if there's any way to patch the UE4 class code to do that, hmm...
E: huh, just noticed that DQXI added a UFoliageType::LODDistanceScale
field which normal UE4 doesn't include... probably worth looking into changing that too.
Did you notice any difference after editing the Density value in the uasset/umap though? Like I said it didn't seem like UE4's src actually made any use of it, hopefully I just missed something though.
I haven't noticed any difference yet. Even changing the ScaleX.Max and ScaleX.Min values listed seems to have no real impact. In part, that may be because most instances are not defined in the umap but are taking the default value of "100". (there might be a way to "insert" the "Density" property in the umap?)
I did just happen to experiment by changing the assigned mesh in the umap and got a small section of grass to disappear xD -- at least now I can identify what grass is being affected by changes in the umap (and which one of the 3). Might help me gain some grasp on what does what.
Yeah the UFoliageType constructor does seem to set it as 100 by default, could be worth trying to find the ctor in DQXIS and try changing it there.
Noted.
- UFoliageType::UFoliageType (constructor)
- data is read from the uasset into UFoliageType
- UFoliageType::PostLoad (doesn't actually exist, probably uses generic UObject::PostLoad, but since this is a virtual func we could hook it in the UFoliageType vftable IIRC)
- FFoliageMeshInfo::UpdateComponentSettings - takes in UFoliageType* as param and copies the settings to some other component
So using something like FindObject to find the UFoliageType and changing the values wouldn't really have any effect, since the values were already copied over via FFoliageMeshInfo::UpdateComponentSettings I'm not sure what actually causes FFoliageMeshInfo::UpdateComponentSettings to be used though, seems there's a few different ways it gets called, maybe there's some way to force it to copy everything over again, not sure.
This is something that i've wondered, actually. If something like foliage.RebuildFoliageTrees
(although my notes say that command specifically rebuilds the trees for non-grass foliage, lol) could work.
PostLoad would probably be easiest bet to hook it & change the values though, but haven't actually checked whether it does get used before UpdateComponentSettings or not.
Ah too bad, would have been nice if things could be changed from the INI..
Seems UE4 wants the class to have a certain "Config" specifier attached to the class that says which INI to read from, and then each property that can be edited needs "Config" flag set: https://docs.unrealengine.com/4.26/en-US/ProductionPipelines/ConfigurationFiles/
I wonder if there's any way to patch the UE4 class code to do that, hmm...
E: huh, just noticed that DQXI added a
UFoliageType::LODDistanceScale
field which normal UE4 doesn't include... probably worth looking into changing that too.
I've only seen LODDistanceScale a couple times in the FoliageType_InstancedStaticMesh. I believe the values were 1.0 and 2.0, respectively.
0x1527E1B into the Steam EXE should be the default Density value of 100 (float), maybe changing that will have some effect on the ones that don't have it set.
Also 0x1527FF5 should be LODDistanceScale's default value of 1 (float) too.
AFAIK these should affect both UFoliageType & FoliageType_InstancedStaticMesh
That was indeed it.
However, unfortunately, it didn't have any immediate impact on the grass seen in-game. (Might have made things worse in a couple spots in Emerald Coast actually...? -- I may look into that)
Oof. Looks like we might be better off trying some kind of grass mesh replacement at this point.
Edit: Komodo looks to be making some significant progress with mesh replacement. Some minor oddities/setbacks along the way. More details in the Modding Discord as they happen.
https://www.nexusmods.com/dragonquestxisdefinitiveedition/mods/102
The mad lad did it! Many thanks to you, emoose :) .
I'm still looking at draw distance for the grass (CullDistance) atm. I believe the default value for .Max is 3000 (having modified the same routine I used for Density) while .Min remains at 0 in almost every case. Changing it at runtime (my testing value was 7500 for this) didn't seem to make any change, however. But who knows (I may very well be looking at the wrong thing here or simply have a misconfigured cvar) lol
Will have a deeper look into it later on, but wanted to follow up here.
Nvm, I just had a misconfigured cvar from one of my many test configs. Draw distance should be plenty.
I'm going to go ahead and close this issue. Thanks again @emoose
https://github.com/emoose/DQXIS-UE4/blob/d6e712358234ba7d152995f3ebc0f65fe321960c/Engine/Source/Runtime/Landscape/Classes/LandscapeGrassType.h
FGrassVariety() : GrassMesh(nullptr) , GrassDensity(400) , bUseGrid(true) , PlacementJitter(1.0f) , StartCullDistance(10000.0f) , EndCullDistance(10000.0f) , MinLOD(-1) , Scaling(EGrassScaling::Uniform) , ScaleX(1.0f, 1.0f) , ScaleY(1.0f, 1.0f) , ScaleZ(1.0f, 1.0f) , RandomRotation(true) , AlignToSurface(true) , bUseLandscapeLightmap(false) , bReceivesDecals(true) { }
The default grass density value is set to "400" (this seems low compared to other titles). I see that it's defined in a few places -- would it be possible to tweak this value? (more importantly: would it have any benefit?)
Could probably also bump up ScaleX, ScaleY, and ScaleZ to maybe 1.5 or something. Again, that's assuming these values do what i'd expect them to.
Side note: I've replaced a couple of miscellaneous grass meshes successfully (i.e. SM_Highland_Grass_001) but ran into some issues with meshes that were scaled by the engine (i.e. SM_Grass_301). (They would just "disappear" when trying to replace them xD ). This just happened to be something I decided to look into as an alternative and thought i'd throw it out there after doing a little bit of digging.