#Patch Manager
1 messages · Page 2 of 1
lmao
I mean, we're not doing anything wrong, the user has to own the files first to be able to access them that way, and they can do the same with Asset Studio or any other similar tool
True
Alright cool, I think I implemented a simple ISelectable structure for parts, now its time to implement the actual engine for testing purposes
@north mist I am going to move the interface I made to shared instead
I am getting a circular dependency error
yeah that makes sense
Damn, I'm actually writing a whole lotta shit to make my system work
I mean I am basically running a programming language so
I wonder if I can speed this up w/ worker threads at some point, but my intuiton says no because they are all acting on the same object when they run and I can't really tell if they are acting on different objects
public override void ExecuteIn(Environment environment)
{
foreach (var attribute in Attributes)
{
switch (attribute)
{
case RequireModAttribute requireModAttribute when
!environment.GlobalEnvironment.Universe.AllMods.Contains(requireModAttribute.Guid):
case RequireNotModAttribute requireNotModAttribute when
environment.GlobalEnvironment.Universe.AllMods.Contains(requireNotModAttribute.Guid):
return;
}
}
var snapshot = environment.Snapshot();
var patcher = new SassyTextPatcher(snapshot, this);
environment.GlobalEnvironment.Universe.RegisterPatcher(patcher);
}
Register patcher is an action in the Universe state of my patching engine that can register a patcher into the execution engine, but what it does is set in the constructor of the universe
Oops I made a 500 line file containing interop conversions between Values and managed objects
Just so I can write builtin functions in an easier way
damn
@north mist the next PR I will do (not doing it just yet) will again be insane (this is just one commit because I need to remember to commit more often)
Its a lot of the meat of the engine
amazing stuff
It has a lot of stuff in it, like automatic registration of rulesets, and automatic registration of builtin libraries as well, plus method interop code (that I stole from another one of my projects because why reinvent the wheel)
My next step is to set up a local test system to test the patches
I has to go but I got it to register a patch from my standalone testing project!
Gods we are drawing ever closer to a working sysrem
When I'm free I'm actually gonna try and run a patch on multiple json files
I'm off to bed, I was quite busy today, but I hope to be able to spend more time on this tomorrow
no fucking way, no fucking way, no fucking way @north mist I know you are sleeping but I ran my patch on a JSON file and it fucking worked!!!
Notice the different in the amount of hydrogen
Technically I ran it on all these json files :3
In the morning I want to take a look at the actual caching instead of just storing flat JSON files, and into adding support for the specialized cases like the gimbal module
Hmm, I need to add a custom integer data type aaa
Gimme one minute
Cuz I think something like this may error if I don't
Integer support has been added, it wasn't as much work as I thought it would be
just means I have 3 more cases for every operation ran
Anyways if you want to work w/ this tomorrow munix
https://github.com/jan-bures/PatchManager/pull/7
Sorry that its another >100 file change
Wait did I really just write an MVP for a DSL/programming language ... in less than a week? Wow I mustve been really at a lack for things to do
insane in the best possible way

I should write an architecture breakdown of the engine
That would be very helpful
Aight time to pull out google slides
Me realizing I made a slight mistake that doesnt require too much to change
Writing a general purpose patching language ^
So a few notes
A ruleset is technically what transforms the textual data into the ISelectable, which ISelectables are the backbone of the engine
There is a general purpose JSON selectable called JTokenSelectable, and a base selectable called BaseSelectable, but rolling your own is not too hard
Then from ISelectables come IModifiables, which are returned from calling OpenModification on a selectable, if it is modifiable otherwise, null, and those modifiables are where the true transformations take place, and there are also some useful ones like JTokenModifiable and CustomJTokenModifiable which is where you can create custom "adapters" in the modifiables fields to more closely match the selectable you are modifying
The overview of the engine itself is simple enough
It starts by ingesting all files and storing those starting with _ as libraries then after its ingested all patches from every mod it runs each file that is not a library, which then registers any selection block in there as an ITextPatcher via a function passed to the universe state of the execution engine
Then when each of those text patchers is run, it takes the data and transforms it according to the leftmost ruleset on the top block and then runs all the selections henceforfh
Then the modifications read and write from the IModifiables
pinning just in case
but it sounds straightforward enough
Anyways the next parts are yours while I start expanding the library of the engine and maybe actually add loops into functions
I don't know if I want to enable someone to be able to say do
@while true {} but meh there are other ways to do that in any case
Looks like my builtin function code was slightly borked but I fixed it!
@use 'builtin:debug';
:parts {
$ignored: debug-log($current["partName"]);
}
So now a patch program like this works
now we need a custom VS Code language server extension for this 
for full intellisense
This is the debug-log function btw
/// <summary>
/// Logs a value into the console for debugging
/// </summary>
/// <param name="universe">The universe in which this function is being called</param>
/// <param name="v">The value to log</param>
[SassyMethod("debug-log")]
public static void Log(Universe universe, DataValue v)
{
universe.MessageLogger(v.ToString());
}
It auto fills in the universe parameter when it encounters it
Well shit it looks like I'm adding closures somewhat lol
Why, I'm not sure
though it does mean you can return actions/funcs from builtin functions if you really wish to
I don't think I can really do the opposite way though
(That is have a function w/ an argument of type Action/Func<...>, unless I want to do dynamic methods)
I don't think we need that lmao
Ehh ... scss has higher order functions which is what this enables
I mean if you wanna do it, sure
oh you were talking about that part
yeah
private static int InvokeComparison(Environment env, PatchFunction comparator, DataValue a, DataValue b)
{
var result = comparator.Execute(env, new List<PatchArgument>
{
new PatchArgument {
ArgumentDataValue = a
},
new PatchArgument {
ArgumentDataValue = b
},
});
if (result.IsInteger)
{
// A simple are these less
return (int)result.Integer;
}
if (result.IsReal)
{
return (int)result.Real;
}
throw new TypeConversionException(result.Type.ToString().ToLowerInvariant(), "integer");
}
/// <summary>
/// Sorts a list, using a comparison function if one is provided otherwise uses a default comparison algorithm
/// </summary>
/// <param name="env"></param>
/// <param name="dataValues"></param>
/// <param name="comparator">The function to sort with, default null (due to closures not existing in the program yet), follows the C# comparison function</param>
/// <returns></returns>
[SassyMethod("list.sort")]
public static List<DataValue> Sort(Environment env, List<DataValue> dataValues, PatchFunction comparator = null)
{
var copy = new List<DataValue>(dataValues);
Comparison<DataValue> comparison =
comparator != null ? (x, y) => InvokeComparison(env, comparator, x, y) : DefaultComparison;
copy.Sort(comparison);
return copy;
}
The only place I see it possibly necessary is somewhere like here, but meh
I mean can't we just do something similar to meta.get-function() and meta.call()?
I am basically doing that yes, but this is more so on the interop side of things I'm talking about
gotcha
For example
/// <summary>
/// Gets a function from the global environment
/// </summary>
/// <param name="globalEnvironment">The global environment that is automatically filled in</param>
/// <param name="name">The name of the function to get</param>
/// <returns>A closure representing that function</returns>
[SassyMethod("get-function")]
public static PatchFunction GetFunction(GlobalEnvironment globalEnvironment, string name)
{
return globalEnvironment.AllFunctions[name];
}
/// <summary>
/// Invoke a closure w/ the given arguments
/// </summary>
/// <param name="env">The environment to run the closure in</param>
/// <param name="closure"></param>
/// <param name="arguments"></param>
/// <returns></returns>
[SassyMethod("closure.invoke")]
public static DataValue Invoke(Environment env, PatchFunction closure, List<PatchArgument> arguments)
{
return closure.Execute(env, arguments);
}
I'm not sure how much people are going to use these features, but they are there if someone perchance does want to use them
@use 'builtin:debug';
@use 'builtin:functional';
$test-closure: @function($v) {
$ignored: debug-log($v);
};
:parts {
$ignored: $test-closure:invoke($current["partName"]);
}
Somehow this works
But yeah this is mostly me being slightly bored more than anything
Or even this
@use 'builtin:debug';
@use 'builtin:functional';
:parts {
$x: @function() {
$y: debug-log($current["partName"]);
}:invoke();
}
Which I can actually see some use for, if you want to do a long calculation on one value but not mess up some scope, pollute the global scope, or just anything like that
that's pretty cool
It also allows for an easy map function that can be used like this
$list:map(@function($x) { @return x*2; });
Thanks!
Shit I just made a way to make lists as closures
@use 'builtin:functional';
@use 'builtin:list';
$list-function: get-function("list.create");
$list-123: $list-function:bind(1,2,3);
$list-1234: $list-123:bind(4):invoke(); // Returns [1,2,3,4]
$list-12345: $list-123:bind(4):invoke(5); // Returns [1,2,3,4,5]
The binding is in lieu of the varargs that scss has that I haven't added yet
That's the point
Anyways time to add while loops and for loops
Though technically I don't need them, its kinda such that I don't want to blow the stack w/ recursion
Technically different than sass but I dislike their syntax anyways
@for $list : $val {
//...
}
@for $list : $idx, $val {
//...
}
@for $dict : $key, $val {
//...
}
@for 0,10 : $val {
//...
}
@while $a > $b {
//...
}
idk, I think the @each $val in $list syntax might be more readable for non-programmers
but that's just my opinion
It might be, the issue is I want to reserve as little keywords as possible to stop people from having to use strings instead of literals
Also this kinda stuff hopefully goes in libraries that non programmers use instead of non programmers having to interact w/ it directly
i guess
I can understand your disagreement
I can reserve @each and do @for x through/to y, it just means that in and through/to have to be reserved
Which in the context of KSP2 shouldn't pose too much a problem
What I mean is just that I personally envisioned us sticking as closely to SCSS as possible, making more so a superset of it, so that we can mostly stick to their docs and stuff like that, instead of having a language that's familiar yet with some significant syntax differences
Though I understand your reasons for not doing that
No I get that, ehh, I can do each/for
A minor different in control flow structures though
@else if
// is now
@else-if
eh that seems fine
Do I add interpolation to selection blocks, w/ the caveat that it makes incremental patching nigh on impossible in the future
@function test() {
@each $k,$v in $y {
}
}
Coolio it'll be something like this for now
@function test() {
@for $x from $y to $z {
}
}
Aaa for loops are "fun"
Why the fuck do the part jsons use smart quotes
"An “Evolution” in wing design, the HPW-1000 is everything you can ask for in a heavy wing. It can be built to accommodate large cargo craft, passenger jets, and even massive SSTO’s. Bring us your blueprint, schematic, or napkin sketch, and we’ll make it a rea
lity."
Like don't tell me you wouldn't interpret that as a syntax error by just looking at it
Anyways, I can't do any real tests until we actually have a way to register and run patchers in ksp2
Well imma sleep
Munix, any update on your side of things so I can look at integration?
I was really busy over the weekend with family stuff, but I'll finish some work stuff now and I'll try to get it up and running asap
I (think) I have a good idea of what I want to do
Gotcha
so I've got most of it in place but my biggest issue right now is that I have no clue how we're going to be assigning all the patch files to their corresponding addressable labels
or is the plan just to try to run every patch file on every JSON file?
I guess that makes more sense, since technically one patch file could try to patch multiple completely unrelated areas of the game
This is why the ITextPatcher takes the label as well, whatever implements, can match the label (which my system does)
But yes we run every patch on every JSON file
but only some will fully execute, others will quit early if the label does not match the ruleset
And also yes, this
But patch files actually all run at the start and register the patch functions which are the ITextPatchers
I pushed a hopefully-nearly-finished version
there's one issue I know of so far, the zip files whose data is stored in memory for some reason refuse to get saved, or rather, their files stay at 0 B
the issue is somewhere in the implementation of the PatchManager.Core.Cache.Archive class
alright so that issue is solved and cache is now getting saved fine
the issue I have now is that all attempts at running any patches scream Ruleset: parts does not exist!
Did you load the parts assembly
aaaand I think I know why lmao
yeah exactly, I didn't
only took me typing it to realize
I ran into that same issue w/ my manual testing
jesus fucking christ
I've been trying to debug for like an hour why my cache keeps getting invalidated/deleted
I completely forgot about this piece of shit in the PatchManagerPlugin

I guess this means we're getting somewhere?
[Error : Unity Log] [Debug] (GetAttributeRuleSet) failed to find attribute set called KERBALATTRIBUTES_EVA
[Error : Unity Log] [General] Object reference not set to an instance of an object
at KSP.Game.KerbalVarietySystem.GenerateCustomKerbalAttributesFromRawData (System.Collections.Generic.Dictionary`2[TKey,TValue] rawCustomKerbalData, KSP.Game.KerbalAttributes& kerbalAttributes, System.String attributeSetName) [0x00044] in <ef61a348eb874c99b6bdcbcf875cc384>:0
at KSP.Game.KerbalVarietySystem.CreateCustomKerbalByName (System.String customKerbalName, System.String attributeSetName) [0x00026] in <ef61a348eb874c99b6bdcbcf875cc384>:0
at KSP.Game.KerbalVarietySystem.TryCreateCustomKerbalByName (System.String customKerbalName, KSP.Game.KerbalAttributes& kerbalAttributes, System.String attributeSetName) [0x00000] in <ef61a348eb874c99b6bdcbcf875cc384>:0
at KSP.Game.KerbalRosterManager.GenerateCustomKerbalLookups () [0x0005d] in <ef61a348eb874c99b6bdcbcf875cc384>:0
at KSP.Game.KerbalRosterManager.OnUpdate (System.Single deltaTime) [0x00038] in <ef61a348eb874c99b6bdcbcf875cc384>:0
at KSP.Game.GameInstance.Update () [0x000bf] in <ef61a348eb874c99b6bdcbcf875cc384>:0
well that's unfortunate, I kinda didn't realize until now that by Cecil-patching the game assembly, we lose the ability to debug it with dnSpy
and another related thing, though it's obvious, but I thought I'd mention it anyway - disabling a mod in the mod list can't do anything about disabling the mod's preload patchers
so when you attempt to disable Patch Manager, you "brick" your game, because the patcher will run, and it indirectly depends on the main plugin assembly having run
We can attempt to check the disabled plugins file using the patch manager patcher
Yeah, we really should
Just check if patchmanagers guid is in the list and return early if it is
@north mist do you have the latest changes you made pushed?
Cache branch?
yep
so the two main current issues are that while the cache does get saved fine on the first run, but it doesn't seem to get used, and then on the next start it does get used, but we get that error I posted above
I'm assuming the first thing has something to do with rewinding streams and whatnot
but the second one is a bit more difficult to debug, as far as I can tell, the files we provide to the game that are related to the error are the exact same as the original ones
eh it's probably because I changed some stuff in the project files
Version check working lol
But yeah imma test some stuff
Damn thats a lot of labels
I mean for now if we know some specific ones are causing issues, we can just add them to a blacklist or something
specifically the kerbal attributes/variety stuff
How early do you get these errors
when loading a campaign (after launching the game with the cache already existing)
Interesting error, but thats expected (I really want copy clicking errors)
oh yeah, we need to just filter out stuff that isn't JSON
I think the issue is that its calling this function
public void LoadResourceLocations<T>(object key, Action<IList<IResourceLocation>> resultCallback) where T : UnityEngine.Object
{
if (AssetProvider.IsComponent(typeof(T)))
{
Debug.LogError("AssetProvider cannot load components/monobehaviours in batch.");
return;
}
Addressables.LoadResourceLocationsAsync(key, typeof(T)).Completed += delegate(AsyncOperationHandle<IList<IResourceLocation>> results)
{
if (results.Status != AsyncOperationStatus.Succeeded)
{
Action<IList<IResourceLocation>> resultCallback2 = resultCallback;
if (resultCallback2 != null)
{
resultCallback2(null);
}
Addressables.Release<IList<IResourceLocation>>(results);
return;
}
Action<IList<IResourceLocation>> resultCallback3 = resultCallback;
if (resultCallback3 == null)
{
return;
}
resultCallback3(results.Result);
};
}
Before LoadAssetByLabel?
Meaning we might want to hijack that function as well
That may be why it isn't using the cache on first run
if (typeof(T) == typeof(TextAsset))
{
if (!CacheManager.CacheValidLabels.Contains(label))
{
PatchingManager.RebuildCache(label);
}
var found = Locators.LocateAll(label, typeof(T), out var locations);
if (found)
{
Addressables.LoadAssetsAsync(locations, assetLoadCallback).Completed += onCompletedCallback;
return;
}
}
Also I'm gonna make sure we only replace text assets
yeah, sure
Also this won't backup
var backup = text;
try
{
var wasPatched = patcher.TryPatch(label, ref text);
if (wasPatched)
{
patchCount++;
}
}
catch (Exception e)
{
Console.WriteLine($"Patch errored due to: {e.Message}");
text = backup;
}
You need to do new string(text)
yeah
I'm still wondering how we break the kerbal loading
And we can't debug at all via dnspy?
(I didn't even know that was possible anyways)
doesn't seem to be working, when the patcher file is there, it tells me that the assembly is not loaded, when I remove it, I can do the debugging just fine
You can manually edit the assembly?
wdym?
Like, load up the patchmanager.core assembly and the KSP2 assembly into dnspy, and edit the ksp2 function w/ a call to the patchmanager.core one and just not use the preload patcher
I mean technically for debugging purposes we could just save the result of the Cecil patching into a DLL, replace the game's assembly with that temporarily and debug with that
Thats literally what I was suggesting but done manually
yeah
this would be a bit easier I was assuming
since we're already doing it
but just in memory
I guess ... editing assemblies in dnSpy is very easy though
I wouldn't know, never tried 😆
Literallly just press edit method C#
heh
And you get a code editor
cool
Only problem w/ manually editing it is that the patcher is internal which I can change lol
And boom, simple as that!
Literally the exact same code I emitted using mono.cecil
And good thing is, even w/ the modified assembly, if we forget to remove the preload patcher it should just run as normal
@north mist do you need unity to be set up in debug mode?
I'm pretty sure you do
Done
Lol
as if
@north mist I found the problem, something is causing the keey in here to be an empty string
huh
OH
OHHH
lmao
So somehow the name isn't being passed through to the delegate?
name corresponds to PrimaryKey
so it should start with KerbalAttributes_
even if it ends with .zip
No it does
otherwise it wouldn't be loaded at all
WAIT, I think it fails here
@north mist do we store the Text Assets name at all?
We have it cached
But thats what gets assigned to the key in the dictionary
I don't even know how I would provide a name
it only takes one parameter and that's the content of the file
I mean
I can just assign it, for some reason I thought it was readonly
but it isn't
Yeah, then just do that lol
I mean you have the asset name here lol
Seems not impossible
yeah I mean I just did
var asset = new TextAsset(archive.ReadFile(file))
{
name = Path.GetFileNameWithoutExtension(file)
};
probably without the "WithoutExtension" part
since I don't give them any extensions anyway
Aight, push it!
just wanna quickly solve the issue with the cache not being used when its first created/invalidated
or not, I can just push that later
alright, it's there
oh and I think I know what the issue is
oh?
the rebuilding of the cache is async
so we try to access it before the zip file has a chance to be saved
before this handler has a chance to run
this gets called
I'm pretty sure
and since it can't find anything because there's no entry for the label, it just defaults to the game's assets
Anyway to make it not async?
I can just add a callback argument to RebuildCache which will contain the call, instead of putting it below
yep, that's the one that I was running (though I probably tweaked the number just to make the effect more obvious, I think I made it +10)
yeah lmao
They should just steal space warps
though I just discovered today (after having it there for over three months) that you can click to expand/collapse the text of error messages
which is pretty nice
I think I just need to add a generic json patcher and we got something worthy of a testing release
yeah, definitely
I'm really grateful for your help with the whole DSL side of things, that's really not something I have a lot of experience with
Surprisingly out of all the things we've done ... its the one I have the most experience w/
I can tell lmao
you didn't cease to amaze me with the speed with which you were writing this stuff
Lol, fair
{
// STUBBED UNTIL DATA IS AVAILABLE
"attributeName":"FACEPAINT",
"dependsOn":["BODY"],
"attributeRangeRuleKey":"STUB_FACEPAINT_OPTIONS"
//applyFunction":"ApplyFacePaint", --$$
},
THEY USE COMMENTS IN THEIR FREAKING JSON FILES AAAAAAA

apparently Newtonsoft.Json supports both single- and multi-line comments
so I could even use them in the swinfo.json like I wanted
Why don't we?
I don't know, I just never looked up if it's implemented in the Newtonsoft library I guess lmao
I realized it was when implementing the generic json patching stuff I already have
I'm just gonna quickly add a way to pass filename to the patchers
And thats used as the #name in generic patchers
they are using jsonc then.
that much is obvious
alright, loading of patched assets when cache is invalidated is fixed
Cool, I'm gonna have to pull that into my branch'
pushed
well, I don't think that this is half bad considering I made the repo exactly a week ago
and I barely had anything at that point
my side is pushed, now time to try a different type of patch though
I ... had 50 gb in my recycle bin wtf
gonna do the quarter size solar system
now that's a good test
Theres an issue w/ it
There is zero ifnformation about orbital parameters in it, and also there are prefabs that need to be scaled I think...
Which ... yeah is kinda hard
where are you looking for the orbital parameters?
in the bodies json
you'll probably have to also patch the galaxy definition
that's what we were modifying with Hyperion and I'm pretty sure just that allowed us to rescale the planets
Where is that?
the key is GalaxyDefinition_Default
Which doesn't show up in the cache
yeah, looks like it doesn't use LoadByLabel
GameManager.Instance.Game.Assets.Load<TextAsset>(this._data.SavedGame.GalaxyDefinitionKey, new Action<TextAsset>(this.OnGalaxyDefinitionLoaded), true);
we might have to patch all the Load... methods in the AssetProvider
Aight, thats gonna be a pain, but yeah I suppose we should
I'm just gonna do a quick gravity patch then, see if that works
You think you can do that? I can write the cecil patches if necessary
sure, I'll look into it
I think i did affect the gravity its hard to show a direct showing of
[Info :Patch Manager] Patched AsteroidD01 with 1 patches. Total: 1
[Info :Patch Manager] Patched Minmus with 1 patches. Total: 2
[Info :Patch Manager] Patched AsteroidA01 with 1 patches. Total: 3
[Info :Patch Manager] Patched AsteroidB01 with 1 patches. Total: 4
[Info :Patch Manager] Patched Mun with 1 patches. Total: 5
[Info :Patch Manager] Patched Eve with 1 patches. Total: 6
[Info :Patch Manager] Patched Dres with 1 patches. Total: 7
[Info :Patch Manager] Patched Ike with 1 patches. Total: 8
[Info :Patch Manager] Patched Kerbol with 1 patches. Total: 9
[Info :Patch Manager] Patched Bop with 1 patches. Total: 10
[Info :Patch Manager] Patched AsteroidC01 with 1 patches. Total: 11
[Info :Patch Manager] Patched Eeloo with 1 patches. Total: 12
[Info :Patch Manager] Patched AsteroidE01 with 1 patches. Total: 13
[Info :Patch Manager] Patched Jool with 1 patches. Total: 14
[Info :Patch Manager] Patched Gilly with 1 patches. Total: 15
[Info :Patch Manager] Patched Pol with 1 patches. Total: 16
[Info :Patch Manager] Patched Kerbin with 1 patches. Total: 17
[Info :Patch Manager] Patched Duna with 1 patches. Total: 18
[Info :Patch Manager] Patched Vall with 1 patches. Total: 19
[Info :Patch Manager] Patched Laythe with 1 patches. Total: 20
[Info :Patch Manager] Patched Tylo with 1 patches. Total: 21
[Info :Patch Manager] Patched Moho with 1 patches. Total: 22
Yep
@north mist
https://github.com/jan-bures/PatchManager/pull/8
I think we did good
Anyways @north mist I think I am done for the next hour or 2
yeah same, I'm gonna go watch a show with my brother
one thing I just realized we need to take into account is that when a mod is added/removed/updated/disabled/enabled, we should also rebuild the cache, since the available parts/other things might have changed
Space Warp 1.3.0 allows you to easily test this
Awesome
I haven't really done anything today to be honest, I plopped myself out in the sun in the morning and am still here at 6pm lmao
I'll get on it now though
alright so I'm having a bit of an issue with this
the method for rebuilding the cache is "async" in the sense that it needs to call Addressables.LoadAssetsAsync<TextAsset>(...).Completed += ... and the Completed handler is where the cache archive gets saved
however, I need to be able to patch either AsyncOperationHandle<TAsset> LoadAssetAsync<TAsset>(object key) or bool LocateAssetInExternalData(object key, System.Type T, out IResourceLocation location)
and those both have a return value and/or out parameter that I'm not able to assign while the methods are running because the archive is not saved yet at that point
I tried to just .WaitForCompletion() on the Addressables.LoadAssetsAsync() method call but it threw an error saying something about not being able to reenter an Update method, which "might be caused by calling WaitForCompletion"
so that's apparently not an option
I can't really see how to do this unless we move the cache rebuilding back out of the AssetProvider methods into a flow action at the start of the game load
this means we need to know upfront what labels need to be patched before they're actually being loaded by the AssetProvider
and I don't think that's currently possible?
they're asynchronous in the sense that they return an AsyncOperationHandle
we need to call them because we need to load the addresable assets if we want to patch them
and while there is an API to await the handles (I think), you can't do that in a synchronous method
and we're patching synchronous methods (the ones in AssetProvider)
I mean but you should be able to
how?
also, Unity has a synchronous addressables workflow: https://docs.unity3d.com/Packages/[email protected]/manual/SynchronousAddressables.html
but like I said, when using it, the game hangs and you get an error about not being able to use WaitForCompletion from inside an Update method
which is because AssetProvider.LoadByLabel gets called from an Update method somewhere, directly or indirectly
you can do asynccontext.runtask the inner task?
I can't find any class like that in .NET, is that a library?
for the record I also tried just Addressables.LoadAssetsAsync(...).Task.Result, which again results in a deadlock
Apparently, it wasnt entirely made clear on first look
Ah...
I just get stuck on this
when debugging the code, it gets to the line with .Result and hangs there
Hmm, im not sure, are we sure we can't just call the synchronous versions of the methods, what do those return?
there aren't any
this is the only way
and that throws an error
hmm .. idk then
We cant really know every label before load nor do we want to load everything that soon do we?
I mean it doesn't matter when we do the patching, since what we do is load the original asset, patch it, save it to cache, release it, and load the patched asset from cache, anyway
and the patching will only happen once/when new mods are installed
as for knowing the label, for the ":parts" patcher type we know that it will be "parts_data", same with any other specialized patcher types
and for the generic json one, doesn't the patch need to specify the file the be patched, anyway?
in most cases the filename should correspond to the label
though I agree it's not optimal
alright, we're getting somewhere now
the preloader patch is not even needed with this new approach, because the only two methods I'm now patching in AssetProvider are not generic, so a Harmony patch is enough
(the new approach being I load all the existing keys when the patch cache is invalid, and I simply try to run the RebuildCache method for all the labels)
it's considerably slow when you run it for the first time (the last time I tried took 73s to go through all the labels, and I have a pretty beefy machine)
but on subsequent game loads it's barely noticeable
obviously, since it's cached
and I think I can still optimize the initial patching process
there's just one issue now - it seems like it works a bit too well lmao
since the game also uses the AssetProvider to create some game objects, and the methods for this internally also use LocateAsset(s)InExternalData, which I use to supply my own patched assets to the game, there seem to be some errors with duplicate objects being created during the first loading, and that freezes the game
so I just need to somehow fine-tune this
hm, and this time when I tried to run the game after deleting the cache, the rebuilding took 27 seconds
and again some issues with the kerbal attributes stuff
and this being the freezing exception
do u have a patch on minmus?
nope
but the way it works right now, the cache contains all text assets, not just patched ones
Still faster than KSP1
Even though I switched to Harmony-only in this version, I might still have to use the preload patcher to fix the game object creation issue
Well it tries to run all patches on all addressable labels
And then after that's done once, the results are cached so it doesn't need to be done again until mods change or patches are changed
Likely normal load times
Im thinking we should release this relatively soon, is there anything i should do?
I mean, I can push what I have now, if you feel like having a look at those errors
Ill take a crack at it tomorrow
To elaborate
We hardcode a list of things we know are going to be loaded via loadbylabel, and patch those at that time and everything else beforehand, skipping over those keys
That was my idea
What that means is that, if an update adds a new key, itll be patched, but we can add it to the list if we know itll be loadbylabelled
I mean sure, we can do that, but like 99% of all asset loading happens either at game load or at campaign load
so I don't see the added value in waiting for the actual labels to be loaded
But really, this just affects first load times, and not really, just distributes the tjme
So analyzing it that way, yeah there isnt a point
We should only cache what we need to though ... disk space and all that
Did you push the latest stuff munix?
Yep, it's in the cache branch again
Aight, gonna check it out now
This one is quite obviously both the cache data and original data being added to the planet definitions
I don't see how that could happen though
since if a patched version is located, it's returned, and the original method doesn't actually run
at least that's how it's supposed to be
I could have made a mistake obviously
hmm...
[HarmonyPatch(typeof(AssetProvider), nameof(AssetProvider.LocateAssetInExternalData))]
[HarmonyPrefix]
// ReSharper disable once InconsistentNaming
private static bool LocateAssetInExternalData(object key, Type T, out IResourceLocation location, ref bool __result)
{
location = null;
if (Locators.LocateAll(key.ToString(), T, out var patchedLocations))
{
location = patchedLocations[0];
__result = true;
return false;
}
foreach (var registeredResourceLocator in Assets._registeredResourceLocators)
{
if (registeredResourceLocator.Locate(key, T, out var locations))
{
location = locations[0];
__result = true;
return false;
}
}
return false;
}
this always returns false
which is wrong I think?
wait no, its right
my brain failed
oh yeah lmao, I mean, I basically just included the original code of the method, it could probably just be
[HarmonyPatch(typeof(AssetProvider), nameof(AssetProvider.LocateAssetInExternalData))]
[HarmonyPrefix]
// ReSharper disable once InconsistentNaming
private static bool LocateAssetInExternalData(object key, Type T, out IResourceLocation location, ref bool __result)
{
location = null;
if (Locators.LocateAll(key.ToString(), T, out var patchedLocations))
{
location = patchedLocations[0];
__result = true;
return false;
}
return true;
}
I guess
you also never set __result to false at any point, but I don't think thats the issue here
it defaults as false no?
yeah, that should either be before the final return statement, or just keep the method as I edited it here, since that leaves it to the original method to decide if it can find the asset or not
if we don't
Wait munix
when does that error appear
because to me it seems as if its an error appearing down the call chain of LoadByLabel
Munix, is it possible to split all the rebuilding patch flow actions into one per label?
So that it shows that it is doing something
protected override void DoAction(Action resolve, Action<string> reject)
{
this._game.UI.SetLoadingBarText(base.Description);
this._resolve = resolve;
GameManager.Instance.Game.Assets.Load<TextAsset>(this._data.SavedGame.GalaxyDefinitionKey, new Action<TextAsset>(this.OnGalaxyDefinitionLoaded), true);
}
That's what starts off the chain of calls that eventually causes the crash
foreach (string reference in keys)
{
List<IResourceLocation> list = this.LocateAssetsInExternalData(reference);
if (list.Count > 0)
{
Addressables.LoadAssetsAsync<TAsset>(list, assetLoadCallback, Addressables.MergeMode.Union).Completed += value;
}
}
Addressables.LoadAssetsAsync<TAsset>(keys, assetLoadCallback, Addressables.MergeMode.Union).Completed += value;
So essentially, its a bug in KSP2 code @north mist
Well, maybe not, but it always loads the keys from addressables even if it exists in ExternalData
Ahh, yeah, that makes sense
screaming
I mean, it would make sense that they want you to be able to add your own items in a mod and still load the original ones from the game
Seems like the switch from patching all the individual loading methods in AssetProvider to just patching the LocateAsset(s)InExternalData was premature
but how do they expect you to override stuff
Its Async, so modifying it to be able to be overriden would cause race conditions
I mean ...
We could modify this function
public void RegisterBodyFromData(CelestialBodyCore jsonData)
{
string bodyName = jsonData.data.bodyName;
this._celestialBodyLookup.Add(bodyName, jsonData);
}
To
public void RegisterBodyFromData(CelestialBodyCore jsonData)
{
string bodyName = jsonData.data.bodyName;
this._celestialBodyLookup.TryAdd(bodyName, jsonData);
}
but that might cause a race condition as I said
isnt the parts one async too?
almost every insertNameHereProvider has that Register function
so PartsProvider, CelestialBodyProvider, ResourceProvider etc
so every one of those would be a race condition no?
Yeah, it will be better to just go back to our previous approach with the preload patches of the individual AssetProvider loading methods
So that we can keep the behavior of the ExternalData methods intact
The issue is also that this makes stuff like deletion of planets impossible through this method
because it will always load internal data
well depends
prefix it and, case deletion just dont add it to _celestialBodyLookup
not optimal obviously
but deletion has another problem
to properly delete a planet, you also need to remove any reference to it in other files
such as Interstellar map (or wtv it was that had the cordinates) and the galaxy definition
.
Oh wait nevermind, I didn't read the code properly at first lmao
That's dumb, why are they calling methods from the Addressables class outside of the AssetProvider, when they have that class for addressables abstractions
Jesus
No that was from w/in assetprovider
Oh well then that's fine
So we have to basically rewrite every dang method in asset provider then?
Yeah, I already had that basically done before 0.1.3, but then I rewrote it to this
I should hopefully still have it shelved

i back u up on asking dakota about taht then lmao
the thing is, if any change does come from it, we'll only be able to use it in the next update, thats 3 months from now
I hope so
And it won't need to be every method, either, I think we should be able to just patch this one specifically causing issues
I do have the method to switch the original one for, now I'm trying to figure out your arcane IL runes
in the patcher class
Its not as arcane as you think, its just bouncing the call
it laods all the arguments and pushes them onto the stack and ccalls
think this might work to find the target method?
since there are multiple Load ones, I need to find the one with a string[] parameter
it should
remember to do all the make generic stuff
var methodInModule = targetMethod.Module.ImportReference(extractedMethod);
var generic = methodInModule.MakeGeneric(targetMethod.GenericParameters.ToArray());
If you don't you'll get preload errors
yeah, I'm just modifying the original one you wrote, so all that should still be in place
anything that needs to be changed here you think?
make sure its the right number of arguments
including self?
oh yeah, that's a good point lmao
Instruction.Create(OpCodes.Ldarg, 4);
ah, makes sense
alright, so I'm kinda getting somewhere
the patched method ran
but it didn't fix the issue
lmao
Oof
Also more than 4, 0 is the first, but im skipping that due to it being unnecessary here
As assetprovider is all but a static class
yeah, makes sense
alright so one other things that's kinda annoying right now is that some of the labels look like paths, which is messing with the way I'm saving the zip files with the labels as their names
it's not the biggest issue right now but the wall of errors I keep getting because of it is pretty annoying
though since the name of the archive is saved in inventory.json, I can just strip them from the file name without causing any issues I guess
Easy fix .Replace("/",SOMETHING_ELSE) and vice versa
yeah, I don't even need to do it backwards
this also seems interesting
the method that was supposedly causing the issue was the Load<TAsset>(string[] keys, ...) one, right?
oh I guess it could also have been Load<TAsset>(IList<AssetReferenceT<TAsset>> assetRefs, ...)
though to be completely honest, I went through the code of CelestialBodyProvider and couldn't find any uses of either of those, and they're the only ones that match that screenshot you sent, I only see calls to Load<TAsset>(string key, ...) and LoadByLabel
Can I see what you wrote
This load function for referencfe
@north mist
this is the one its calling
The object keyObject one
yeah, that's what I thought, and that in turn calls LoadAssetAsync
so I don't see how that could duplicate the data
Well I have to dip for the rest of the night, so I'll get to it tomorrow
gotcha, I'll try to debug it and fix it
huh, so I think I finally managed to patch the actual method that was causing issues, but now the IL patch is not working
Invalid IL code in KSP.Assets.AssetProvider:LoadAssetsAsync<T_REF> (string,System.Action`1<T_REF>): IL_0000: ldarg.1
you're passin IL_0000?
that doesnt seem right
what r u doing to check if youre on the right il?
what I have now is
var coreType = coreAssembly.MainModule.Types.First(t => t.Name == "AssetProviderPatch");
var extractedMethod = coreType.Methods.First(m => m.Name == "LoadAssetsAsync");
var targetType = assemblyDefinition.MainModule.Types.Single(t => t.Name == "AssetProvider");
var targetMethod = targetType.Methods.Single(m => m.Name == "LoadAssetsAsync" && m.HasGenericParameters);
// Remove every single instruction from the body of the methods
// Emit call to our extracted method
var methodInModule = targetMethod.Module.ImportReference(extractedMethod);
var generic = methodInModule.MakeGeneric(targetMethod.GenericParameters.ToArray());
targetMethod.Body.Instructions.Clear();
targetMethod.Body.Instructions.Add(Instruction.Create(OpCodes.Ldarg_1));
targetMethod.Body.Instructions.Add(Instruction.Create(OpCodes.Ldarg_2));
targetMethod.Body.Instructions.Add(Instruction.Create(OpCodes.Call, generic));
targetMethod.Body.Instructions.Add(Instruction.Create(OpCodes.Ret));
updated from this which worked fine
var coreType = coreAssembly.MainModule.Types.First(t => t.Name == "AssetProviderPatch");
var extractedMethod = coreType.Methods.First(m => m.Name == "LoadByLabel");
var targetType = assemblyDefinition.MainModule.Types.Single(t => t.Name == "AssetProvider");
var targetMethod = targetType.Methods.Single(m => m.Name == "LoadByLabel" && m.HasGenericParameters);
// Remove every single instruction from the body of the methods
// Emit call to our extracted method
var methodInModule = targetMethod.Module.ImportReference(extractedMethod);
var generic = methodInModule.MakeGeneric(targetMethod.GenericParameters.ToArray());
targetMethod.Body.Instructions.Clear();
targetMethod.Body.Instructions.Add(Instruction.Create(OpCodes.Ldarg_1));
targetMethod.Body.Instructions.Add(Instruction.Create(OpCodes.Ldarg_2));
targetMethod.Body.Instructions.Add(Instruction.Create(OpCodes.Ldarg_3));
targetMethod.Body.Instructions.Add(Instruction.Create(OpCodes.Call, generic));
targetMethod.Body.Instructions.Add(Instruction.Create(OpCodes.Ret));
ah youre doing it like that... i dont think i can help you xD
wait
couldn't tell ya
my guess is its not finding the right method
thus the ILCode 000 being invalid
which from what i know is always valid for a valid method
but im no expert on this
hm, that's weird, this is the signature of the method
and there's only one, no overloads
iirc async methods need other opcodes too
it's not async
thats the location
yeah i mean IL_0000 is always valid no?
yeah got mislead by the name
that means its the first op
if it helps in any way, this is the method in AssetProviderPatch that I'm trying to call
I meant the IL stack size, but it defaults to 8 so it dont matter
oh lmao
Push the entire code and ill figure it out tomorrow
yeah I was just about to do that, it's useless to be trying to figure this out at 4am when I'm barely awake
Wait a second
y'know loadassetsasync doesn't get called from that chain, right?
Wait it does
i'm dumb
But no, its actually load asset async
not loadassetsasync
also gods, we need to make it more visible what patch manager is doing, I keep feeling like its busted while loading
Looking at an emitted copy of the assembly in DNSpy, there is zero issue w/ this???
Excuse me, wtf
I fixed
it
how?
targetMethod.Body.Instructions.Add(Instruction.Create(OpCodes.Ldarg_S,1));
targetMethod.Body.Instructions.Add(Instruction.Create(OpCodes.Ldarg_S,2));
I changed it to Ldarg_S
for some reason, I was just testing something, and it loaded
wtf
C# why
WHY
As for why LoadCelestialBodyData still crashes it
Well, because, @north mist patched LoadAssetsAsync rather than LoadAssetAsync
lmao
Why did that fix it, like seriously
But wait, LOadAssetAsync should already be looking into external data???"
Wait, then how is this breaking at all
[Info :Galaxy Definition Load Actions] Loading from: GalaxyDefinition_Default
[Info :Galaxy Definition Load Actions] at PatchManager.Core.Patches.Runtime.GalaxyDefinitionTestPatch.OnGalaxyDefinitionLoaded (UnityEngine.TextCore.Text.TextAsset asset) [0x00023] in <6bb337ee19bc4b21b57fd2d85604a659>:0
at KSP.Game.Load.LoadCelestialBodyDataFilesFlowAction.DMD<KSP.Game.Load.LoadCelestialBodyDataFilesFlowAction::OnGalaxyDefinitionLoaded> (KSP.Game.Load.LoadCelestialBodyDataFilesFlowAction , UnityEngine.TextAsset ) [0x00000] in <f07798ad7fdc4402ab44d85c99f8e7b0>:0
at KSP.Assets.AssetProvider+<>c__DisplayClass11_0`1[TAsset].<Load>b__1 (UnityEngine.ResourceManagement.AsyncOperations.AsyncOperationHandle`1[TObject] handle) [0x00000] in <f07798ad7fdc4402ab44d85c99f8e7b0>:0
at DelegateList`1[T].Invoke (T res) [0x00000] in <11e2f4eda1124a9f83368725a730d057>:0
at UnityEngine.ResourceManagement.AsyncOperations.AsyncOperationBase`1[TObject].InvokeCompletionEvent () [0x00000] in <11e2f4eda1124a9f83368725a730d057>:0
at UnityEngine.ResourceManagement.AsyncOperations.AsyncOperationBase`1[TObject].UnityEngine.ResourceManagement.AsyncOperations.IAsyncOperation.InvokeCompletionEvent () [0x00000] in <11e2f4eda1124a9f83368725a730d057>:0
at UnityEngine.ResourceManagement.ResourceManager.ExecuteDeferredCallbacks () [0x00000] in <11e2f4eda1124a9f83368725a730d057>:0
at UnityEngine.ResourceManagement.ResourceManager.Update (System.Single unscaledDeltaTime) [0x00000] in <11e2f4eda1124a9f83368725a730d057>:0
at MonoBehaviourCallbackHooks.Update () [0x00000] in <11e2f4eda1124a9f83368725a730d057>:0
I only get one load message
wait a second
Wait a fucking second
we've been barking up the wrong tree
I have a suspicion I need to confirm
We've somehow been duplicating the celestial bodies?
lemme check this
So, I think we might still need to be patching loadbylabel
[Info :Galaxy Definition Load Actions] AsteroidD01 added 2x
[Info :Galaxy Definition Load Actions] Minmus added 2x
[Info :Galaxy Definition Load Actions] AsteroidA01 added 2x
[Info :Galaxy Definition Load Actions] AsteroidB01 added 2x
[Info :Galaxy Definition Load Actions] Mun added 2x
[Info :Galaxy Definition Load Actions] Eve added 2x
[Info :Galaxy Definition Load Actions] Dres added 2x
[Info :Galaxy Definition Load Actions] Ike added 2x
[Info :Galaxy Definition Load Actions] Kerbol added 2x
[Info :Galaxy Definition Load Actions] Bop added 2x
[Info :Galaxy Definition Load Actions] AsteroidC01 added 2x
[Info :Galaxy Definition Load Actions] Eeloo added 2x
[Info :Galaxy Definition Load Actions] AsteroidE01 added 2x
[Info :Galaxy Definition Load Actions] Jool added 2x
[Info :Galaxy Definition Load Actions] Gilly added 2x
[Info :Galaxy Definition Load Actions] Pol added 2x
[Info :Galaxy Definition Load Actions] Kerbin added 2x
[Info :Galaxy Definition Load Actions] Duna added 2x
[Info :Galaxy Definition Load Actions] Vall added 2x
[Info :Galaxy Definition Load Actions] Laythe added 2x
[Info :Galaxy Definition Load Actions] Tylo added 2x
[Info :Galaxy Definition Load Actions] Moho added 2x
Yep, we are duplicating every body
__instance._game.Assets.LoadByLabel<TextAsset>("celestial_bodies",null, x =>
{
source.LogInfo($"Result callback called {n++} times");
foreach (var text in x)
{
if (numbers.ContainsKey(text.name))
{
numbers[text.name] += 1;
}
else
{
numbers.Add(text.name,1);
}
source.LogInfo($"{text.name} added {numbers[text.name]}x");
}
GameManager.Instance.Game.Assets.ReleaseAsset(x);
});
debug code for that
Sop the issue ... ultimately stems from LoadByLabel
now i sleep
Yes, I know, that's what my new code was fixing
Since LoadByLabel calls LoadAsset__s__Async
To be fair I don't know if it did fix it, but at least I had the right method
this just gives me another error
wtf
the only thing that's different between LoadByLabel/Load (which I was able to patch successfully) and LoadAssetsAsync is the fact that the latter has a return type of AsyncOperationHandle<IList<T>>, while the first two were void
aaahhhh this is so frustrating
H-how??
I was able to compile and run it
I wouldve gotten a preload error otherwise?
Wait it literally doesnt like the opcode??
Yeah I have no clue, neither this version nor the one with _1 and _2 run for me
wait I'm dumb, I got the same error, hence why I was able to load
but I didn't see a preload error which is what it shouldve been?
I know the fix to that error
I didn't know that Mono.cecil is that strict
Lemme try something
(never debug code when your half asleep)
Also spacewarps console is acting very glitchy for me
[Error : BepInEx] Failed to run [PatchManager.PreloadPatcher.Patcher] when patching [Assembly-CSharp]. This assembly will not be patched. Error: System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.ArgumentOutOfRangeException: Specified argument was out of the range of valid values.
at Mono.Collections.Generic.Collection`1[T].get_Item (System.Int32 index) [0x00009] in <6034b380a22b41a596c9dc29d282c0a9>:0
at PatchManager.PreloadPatcher.Patcher.Patch (Mono.Cecil.AssemblyDefinition& assemblyDefinition) [0x00183] in C:\Users\arall\PatchManager\src\PatchManager.PreloadPatcher\Patcher.cs:74
at (wrapper managed-to-native) System.Reflection.MonoMethod.InternalInvoke(System.Reflection.MonoMethod,object,object[],System.Exception&)
at System.Reflection.MonoMethod.Invoke (System.Object obj, System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) [0x00032] in <695d1cc93cca45069c528c15c9fdd749>:0
--- End of inner exception stack trace ---
at System.Reflection.MonoMethod.Invoke (System.Object obj, System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) [0x00048] in <695d1cc93cca45069c528c15c9fdd749>:0
at System.Reflection.MethodBase.Invoke (System.Object obj, System.Object[] parameters) [0x00000] in <695d1cc93cca45069c528c15c9fdd749>:0
at BepInEx.Preloader.Patching.AssemblyPatcher+<>c__DisplayClass11_0.<AddPatchersFromDirectory>b__3 (Mono.Cecil.AssemblyDefinition& pAss) [0x0000c] in <fc9d7fbc6dcb44cf87be11d8d92ae161>:0
at BepInEx.Preloader.Patching.AssemblyPatcher.PatchAndLoad (System.String[] directories) [0x001b5] in <fc9d7fbc6dcb44cf87be11d8d92ae161>:0
Ah this error makes no sense, but also makes sense
there are no arguments to the method we are patching????
targetMethod.Parameters[1]
wtf
I was using that
it has to have two
even when I was debug logging the FullName of both extractedMethod and targetMethod, they had the correct signatures
with the same return type and the same two parameters
Wait no I'm being dumb again
[Info :Patch Manager Preload] Parameter: key
[Info :Patch Manager Preload] Parameter: assetLoadCallback
Apparently the Parameters array doesn't contain the this
ah
by the way I also tried changing the extractedMethod to not be static, and added Ldarg_0, but no bueno
And we are back to square one
[EXC 07:54:54.478] InvalidProgramException: Invalid IL code in KSP.Assets.AssetProvider:LoadAssetsAsync<T_REF> (string,System.Action`1<T_REF>): IL_0000: ldarg.s 1
KSP.Assets.AssetProvider.LoadByLabel[T] (System.String label, System.Action`1[T] assetLoadCallback, System.Action`1[T] resultCallback) (at <f07798ad7fdc4402ab44d85c99f8e7b0>:0)
KSP.Game.DifficultyOptionsDataManager.Load () (at <f07798ad7fdc4402ab44d85c99f8e7b0>:0)
KSP.Game.GameManager.InitializeDifficultyOptionsManager (System.Action resolve, System.Action`1[T] reject) (at <f07798ad7fdc4402ab44d85c99f8e7b0>:0)
KSP.Game.Flow.GenericFlowAction.DoAction (System.Action resolve, System.Action`1[T] reject) (at <f07798ad7fdc4402ab44d85c99f8e7b0>:0)
KSP.Game.Flow.FlowAction.Do (System.Action`1[T] resolve, System.Action`2[T1,T2] reject) (at <f07798ad7fdc4402ab44d85c99f8e7b0>:0)
KSP.Game.Flow.SequentialFlow.NextFlowAction () (at <f07798ad7fdc4402ab44d85c99f8e7b0>:0)
KSP.Game.Flow.SequentialFlow.Update () (at <f07798ad7fdc4402ab44d85c99f8e7b0>:0)
yeah I'm just really confused by all this
it worked just fine with Load and LoadByLabel, so why doesn't it work with LoadAssetsAsync
the only visible difference is that those were void and LoadAssetsAsync returns AsyncOperationHandle
And its so damn odd that the base starts w/ a ldarg just fine??
Lemme try something else
Ahh, great, a new error
Just crashing w/o any error
Ahh
public AsyncOperationHandle<IList<T>> LoadAssetsAsync<T>(string key, Action<T> assetLoadCallback)
{
/*
An exception occurred when decompiling this method (06008365)
ICSharpCode.Decompiler.DecompilerException: Error decompiling UnityEngine.ResourceManagement.AsyncOperations.AsyncOperationHandle`1<System.Collections.Generic.IList`1<T>> KSP.Assets.AssetProvider::LoadAssetsAsync<T>(System.String,System.Action`1<T>)
---> System.NullReferenceException: Object reference not set to an instance of an object.
at ICSharpCode.Decompiler.ILAst.ILInlining.InlineOneIfPossible(ILBlockBase block, List`1 body, Int32 pos, Boolean aggressive) in D:\a\dnSpy\dnSpy\Extensions\ILSpy.Decompiler\ICSharpCode.Decompiler\ICSharpCode.Decompiler\ILAst\ILInlining.cs:line 218
at ICSharpCode.Decompiler.ILAst.ILInlining.InlineAllInBlock(ILBlock block) in D:\a\dnSpy\dnSpy\Extensions\ILSpy.Decompiler\ICSharpCode.Decompiler\ICSharpCode.Decompiler\ILAst\ILInlining.cs:line 144
at ICSharpCode.Decompiler.ILAst.ILInlining.InlineAllVariables() in D:\a\dnSpy\dnSpy\Extensions\ILSpy.Decompiler\ICSharpCode.Decompiler\ICSharpCode.Decompiler\ILAst\ILInlining.cs:line 108
at ICSharpCode.Decompiler.ILAst.ILAstOptimizer.Optimize(DecompilerContext context, ILBlock method, AutoPropertyProvider autoPropertyProvider, StateMachineKind& stateMachineKind, MethodDef& inlinedMethod, AsyncMethodDebugInfo& asyncInfo, ILAstOptimizationStep abortBeforeStep) in D:\a\dnSpy\dnSpy\Extensions\ILSpy.Decompiler\ICSharpCode.Decompiler\ICSharpCode.Decompiler\ILAst\ILAstOptimizer.cs:line 227
at ICSharpCode.Decompiler.Ast.AstMethodBodyBuilder.CreateMethodBody(IEnumerable`1 parameters, MethodDebugInfoBuilder& builder) in D:\a\dnSpy\dnSpy\Extensions\ILSpy.Decompiler\ICSharpCode.Decompiler\ICSharpCode.Decompiler\Ast\AstMethodBodyBuilder.cs:line 123
at ICSharpCode.Decompiler.Ast.AstMethodBodyBuilder.CreateMethodBody(MethodDef methodDef, DecompilerContext context, AutoPropertyProvider autoPropertyProvider, IEnumerable`1 parameters, Boolean valueParameterIsKeyword, StringBuilder sb, MethodDebugInfoBuilder& stmtsBuilder) in D:\a\dnSpy\dnSpy\Extensions\ILSpy.Decompiler\ICSharpCode.Decompiler\ICSharpCode.Decompiler\Ast\AstMethodBodyBuilder.cs:line 99
--- End of inner exception stack trace ---
at ICSharpCode.Decompiler.Ast.AstMethodBodyBuilder.CreateMethodBody(MethodDef methodDef, DecompilerContext context, AutoPropertyProvider autoPropertyProvider, IEnumerable`1 parameters, Boolean valueParameterIsKeyword, StringBuilder sb, MethodDebugInfoBuilder& stmtsBuilder) in D:\a\dnSpy\dnSpy\Extensions\ILSpy.Decompiler\ICSharpCode.Decompiler\ICSharpCode.Decompiler\Ast\AstMethodBodyBuilder.cs:line 99
at ICSharpCode.Decompiler.Ast.AstBuilder.<>c__DisplayClass90_0.<AddMethodBody>b__0() in D:\a\dnSpy\dnSpy\Extensions\ILSpy.Decompiler\ICSharpCode.Decompiler\ICSharpCode.Decompiler\Ast\AstBuilder.cs:line 1528
*/;
}
Oh I know what happened
I forgot to copy over the variables
aight, I give up, this is insanity

Is there a way to just not use this method
at this point that might just be cleaner
oh, it seems like the only place where it's actually used is just LoadByLabel anyway, lol
which we already were patching previously
so this was kinda unnecessary anyway

dies internally
I mean it's also called by LoadAllRaw, but 1) that actually isn't used anywhere, and 2) it seems to me like that's a bug anyway, the ...Raw methods are supposed to call Addressables methods directly
ao just undo all of what we just did to repatch loadbylabel
Anyways, imma go grab some brekky
and done, patching of all labels now works
It was that simple
a bit more polishing and I think we can make an experimental release
Now I can finally make a test patch to scale up the solar system
Does it still build cache on first run?
yeah
In that case a necessary polish would be splitting every label into its own flow action
and not saving unpatched labels into the cache
That too
Mostly to stop it from "hanging"
yeah, makes sense
alright, for now i merged it all into main, and I gotta go for a bit, but I'll be back later
Gotcha
I just realized a possible issue
Mods can define their own labels
And their bundles are loaded later
Aight, you said its on main branch?
yeah
I haven't done that yet, if you mean the inidividual flow actions for each label
Wait, I forgot to update
We could do it as a per mod flow action, but I think that might run too late? for some things to be patched
Ah, I see whats going on, kinda
for the cache rebuilding
yeah I really don't know what the best time to do the patching is, we don't want to do it too early, before mod catalogs are registered, but also not too late
which is why the lazy patching system was decent, but also not really possible to maintain?
yeah, it's a shame but I don't think we could easily work around the async stuff
Hmm, we have to patch after all addressables are loaded, its kinda just necessary I think, if we want to be able to patch mod stuff
Including modded parts for that matter
as for the not caching everything ... thats simple enough we just need a value in the cache when something is the original value rather than being cached?
but looking at the architecture of this, maybe not, as we are completely overriding the resource locator system
What all is loaded before campaign load?
Actually wait, the simplest way to reduce cache size, is just to remove labels that have zero changes
I'm gonna quickly implement that rq
alright, awesome
just zipping up the current cache so I can inspect it for future purposes
A whole lot smaller
yeah, that's much better
I can't really do sublabel optimization at this point, though I do want to be able to
Also, we were duplicatiing information? Because stuff can have more than one label I think
but thats only in the inventory.json and isn't easily fixable
Do you mind if I don't pretty print the inventory file to save space?
as that does get to almost a megabyte in size w/ everything patched
Ehh, it doesn't matter too much
So @north mist addressables in spacewarp get added at the end of everything :/
so anything that gets loaded beforehand necessarily has to get skipped?
that, that was another thing I needed to optimize
well it's in inventory.json on purpose
because technically someone could request a specific asset both by its own label, and by its group label
although I don't know why that would happen, but it's technically possible
I don't think I get this
essentially, spacewarp loads addressables as late as it can in the progress, which is after everything else has been loaded, meaning that we need to patch after that, but files that are read before then will be missed in that case
we need a balanced patching system of at load time, and at read time for that ... which may be a bit complex
not necessary for an experimental release, but will be later
well for starters we could just patch specifically the things that we know are being read before SpaceWarp registers catalogs
and leave the rest after that happens
Fair
lemme write the stuff for post spacewarp, and then I'll leave you to doing presw stuff
sure
the issue is then, that we still have to read patch files as early as possible, which is fine, thats not the bottleneck
yeah, I'm kinda assuming that we won't be able to make it fully lazy-loaded possibly ever
but we can at least optimize as best as we can
I might have to un-async RebuildAllCache...
Oh gods, patches will still load despite error state in space warp
Alright this is insanity, unities rendering is single-threaded right?
That means then, that I have to generate all the loading actions on the fly :3
I see why itt akes so long munix
@north mist get this, there is an extreme amount of labels, that just iterating through them takes very long
yeah, I've noticed that
I wish we had a way to filter through them somehow
I mean I guess we could add like a blacklist of labels that we don't want to try to patch
if we know they don't contain TextAssets etc
that way we won't have to attempt to load their assets
not sure how much it would shorten the loading time though
And lazy patching ... is impossible
Anyways @north mist
One thing we could do, filter out any labels that look like they are just hash strings
The only useful thing those add are just the privacy policies
Anda also ones that are integers
Ahhh
[Info :Patch Manager] 0 mod libraries loaded!
[Info :Patch Manager] 0 patchers registered!
Alright so its not much faster
but it at least doesn't go through the almost useless stuff
which is more visually appealing
But anyways! it does show the amount of patches
It's just going to hang a bit on the labels it is patching
just had to bump patch manager up to spec 1.3 lol
Shit I broke wwise again accidentally
Idfk how
but I did
what did you do? just added a UITK text?
No, I patched the loadingbar to inject tips into there
I might add it as a spacewarp thing later on
heh fair
yes pls
not in PM scope but what about a timer for how long each loading screen takes? maybe even a debug feature ig
oh yeah, that's looking nice
Except, the way I did it, broke WWISE somehow
oh as always
it happens if you create an instance of a flow action too early
that's why I have the wrapper API for it in Patch Manager
Can't I just harmony patch that away w/ space warp
no clue, but it would be nice
Likely not...
It does mean, I need to pass a state inbetween awake, and initialized though
as the only time you are legally allowed to add space warp general loading actions then is in PreInitialized
And I need to use SW for this
Also, I know what causes the bug
I2Localization kills WWISE
As when you create a flow action, it attempts to access I2Localization, which may not be loaded yet, so through some chain of events WWISE dies
yeah, I'm assuming it has to do with the fact that I2 can also be used to localize audio
so it tries to init wwise
without the correct settings
No wait, I legit can't use general loading actions at all, they are broken in space warp
Aight time to bug fix that
So essentially, this will rely on 1.3.1
You can't create them too early, but once too early is over (in preinitialize), there is no time to create them, as they would already be added
yeah this is the only place where I found them usable
at the end of GameManager.StartBootstrap
They are usable anywhere in gamemanager.startbootstrap
but that'll already have been called by the time I need to do stuff
yeah that makes sense
So, given that API is literally broken, I'm gonna bug fix it
Screw it, I'll release 1.3.1 tonight
as there are 2 things I want to add
@north mist do you mind if I release a spacewarp 1.3.1, it'll at least make semantic versioning line up if anything else
not the complete right place to ask, but it is kinda necessary for me to continue some stuff
yeah, definitely go for it
wait, I can just replace the download on spacedock
But ehh I needed to bump the version anyways
yeah but that's really ew
So anyways munix, the QoL branch has all of what I just did
Some of it is ugly AF but is actually necessary to get the amount of patches to show in the loading tips
awesome, thanks
Alright, what do we need now
A list of labels to patch before everything?
Then a few example patches to show off how to patch
Actually, lemme write my larger solar system patch :3
I'll scream if this actually works
I did not scream
for sound patching there's 2 alternatives
the way that wwise works for sound, is that you provide either an unique name or an unique id, and it will play that sound when triggered
now, i dont know of a reliable way to retrieve that name or id yet at runtime or while datamining, which is a problem
solution A
- just copy the gameobject with the component KSPPartAudio from the target part
solution B - Find the reference somehow and provide somwhere (ie wiki 👀 ) a lookup table of part to wwise sounds names
the patch could be something like this to copy everything
part[MyPartName]
{
@AUDIO = COPY_AUDIO[swivel]
}
or
part[MyPartName]
{
@AUDIO.OnDestroyed = "Swivel_Engine_Destroyed"
@AUDIO.OnEngineStart= 1728947489
}
i dont know scss syntax so 99% my syntax is wrong but you get the idea
and this will be widely used because things like part collision, part destroyed, ambient sounds are all using wwise, so if you want your parts to make noise once destroyed (via collision or heat or wtv) you need to either create your own sounds or use the game's reference
This would require a substantial rewrite of PM
it doesnt need to be literally like this btw
and if it requires that then its better another solution
tho the way that audio is assignes, is via a child gameobject of the prefab
so @upbeat hare , i found a way to get all the sound references from all parts
what could we do on patch manager to patch them in?
w/o much work on your side
I think thats a fundamental misunderstanding of how patch manager works
The sound references aren't stored in a text based format, right @north mist ?
i dont think so ngl
they are stored in the monobehaviour as a Ak.Wwise.Event
there might be some connection from that to a text based format
but again, we can get each sound just with the name of the event
heh sure
tho i remember in the start that there was a talk about custom patches like for LFO etc
i guess the most we could do in that regard is to copy a plume from one place to another
yeah, definitely
we'll need to do a shit ton of custom patching to actually get this to be useful
that's why I tried to make it modular so we can split off those special patch types into separate assemblies and allow people to make their own
then sounds could fit those custom patches? idk i might be misunderstanding patch manager i think
well right now all it does is load all TextAssets from addressable labels, runs all patches on them to transform the JSON, saves it to cache, and then patches the AssetProvider to read assets from the cache if they're in it
so it will need a lot of work to do anything beyond the scope of json files
but I always kinda expected we'll have to do that, anyway
well thats were i come in, for parts, effects, and now sounds
but idek how to start that lmao
have to take a deeper look into patch manager
and into Scss syntax. something like this #1115274490490929172 message would be amazing
but as cheese said it would take a lot of rewriting
which i'd rather avoid
hmm, I think we can parellize the patches on any given label
i think its a good idea, that is, the labels actions are independent
which im 99% sure they are but yeah
whats not working?
iirc for that you need to mess with PQS for collision to work decently
My f-in json ruleset :3
ah lmao
oh btw, we will have an API (or better, an abstract class) to inherit so that other mods can process patches right?
something like
public abstract class PMPatchProcessor{
public abstract void ReceivePatch(string Selector, string target, string bla bla bla bla);
}
Thats what rulesets are for :3
But hmm
gredat my keyboard is fucked 
im adsking because i might make a sound manadger or sound helper wtv and i could process the patches there
but for the time being i'd just have config files to copy sound from parts to other parts
2.5x gravity definitely wobblifies the rockets more
Damn, I'm not sure if I can modify the PQS with patches alone
I might have to write a simple code patch for that
Yup probably not, its not even in the json data i think
everything else I can do in JSON
U know that reminds me of the comment made on the forum post
Can you tell me though where the PQS stuff is stored at runtime
We should show these things to people to solidy the fact that we cant just edit configs and it will work
It requires more than that to modify parta now
yeah but then they'll just say that it's another reason why KSP 2 sucks and why they won't mod it
Why the hell won't my roslyn patch compile, I'll just make a dll mod for this test
Conclusion: I suffer from a deficiency in brain cells
Well fuck them then, they (not all) expect the game to be KSP1 after all
They clearly dont understand that the game was rebuilt in a better way
But yeah ur right 😔😔
By the way I've been thinking about how to best handle the patching of 3rd party mods
It's not a good idea for every mod, even if it only has one config file that could be patched, to have to have it in an addressable bundle, but for now that's the only way Patch Manager would find it
So I'm thinking we should still keep using the addressables and resource location systems but get rid of the bundles part - it could be as simple as adding a special flow action for each mod that would work similarly to the flow action for loading addressables, but it wouldn't add the ResourceLocator from a bundle catalog, but instead a custom ResourceLocator and ResourceProvider used similarly to how Patch Manager already does, to serve JSON files using the Addressables system from the disk instead from bundles
And Patch Manager should then not even need any special treatment for most of those, and would be able to patch any arbitrary mod JSON
Then it you wanted to make a mod that makes use of a library mod, your mod could register its JSON files' ResourceLocator such that they all have a label that the library mod then loads to get all configs from all the mods making use of it/extending it, already patched
What do we need to do for release?
Can I make a request for the filtering capabilities that I can't seem to find in MM for ksp1.
The ability to filter things in an inverted way.
so kinda like
@PART[*]:HAS[@MODULE[ModuleEnginesFX]:HAS[@PROPELLANT[ElectricCharge],!PROPELLANT[*]]]
and that would ONLY find engines that have ElectricCharge but NO secondary propellant.
atm this one doesn't work i'm guessing because its finding the ElectricCharge one as part of the * too.
and while I can make it work by listing EVERY other type of fuel it is VERY annoying to have to do so.
Hmm, there is already a way to do that under what I am working on, but its syntax is completely different, basically you'd have to do a conditional statement inside the block and check that the length of the amount of propellants is 1
@upbeat hare do you still have that language specification document somewhere?
I do yes, I'm gonna retitle it to remove sassy from the name, as I don't want to run afoul of trademarks I might not know about, then I'm going to have to update it quite a bit
Alright, that would be awesome, thanks
What should I use instead of Sassy?
Then we can start putting together some examples of configs and patches in various formats like we were talking about, and do a bit of a survey
I mean we could always just use something like Patch Manager Language as an interim name
I need a few example patches to put in there
do you still have the crew capacity one
I have these two:
:parts {
@if $current["crewCapacity"] > 0 {
crewCapacity: +10;
}
}
:parts {
* > ResourceContainers > * {
capacityUnits: *50;
}
}
but the second one never actually worked
even though I thought I copied it from you
Wait, it didn't work?
not as far as I remember
I've tested it?
how would this look in your one? whats the conversion like? want to make a site that you can create them on.
well it might have changed the JSON but the game could have just used the original values from the prefab or something
there are some places in the game that we'll need to patch
not sure if this is one of them
I think you are missing the initialunits part of the patch
:parts {
* > ResourceContainers > * {
capacityUnits: *5;
initialUnits: *5;
}
}
I had a patch for something similar to that, I just forgot where I stored it, give me a moment
I haven't written a specific adapter for Celestial Bodies yet, so it does require some conditionals for the moment, but I can fix that easily
:json celestial_bodies > data {
@if $current["bodyName"] == "Mun" {
rotationPeriod: 800;
isRotating: true;
isTidallyLocked: false;
}
@if $current["bodyName"] == "Tylo" {
rotationPeriod: 500;
isRotating: true;
isTidallyLocked: false;
}
@if $current["bodyName"] == "Duna" {
rotationPeriod: 400;
isRotating: true;
isTidallyLocked: false;
}
}
Once I do that specific adapter it would look closer to
:bodies {
#Mun {
rotationPeriod: 800;
isRotating: true;
isTidallyLocked: false;
}
#Tylo {
rotationPeriod: 500;
isRotating: true;
isTidallyLocked: false;
}
#Duna {
rotationPeriod: 400;
isRotating: true;
isTidallyLocked: false;
}
}
or
:bodies #Mun {
rotationPeriod: 800;
isRotating: true;
isTidallyLocked: false;
}
:bodies #Tylo {
rotationPeriod: 500;
isRotating: true;
isTidallyLocked: false;
}
:bodies #Duna {
rotationPeriod: 400;
isRotating: true;
isTidallyLocked: false;
}
now I'm interested (although I should probably know this), do we have a way to for example easily copy a body and just change some parameters, like its orbit?
it would probably require some game patches, but I mean more so if the syntax allows it
Orbits are changed in the galaxy definitions which have been giving me trouble
But, I'm trying to think of the syntax for that stuff, it shouldn't be too hard to come up with
@use "builtin:list";
:json GalaxyDefinition_Default > CelestialBodies > OrbitProperties {
semiMajorAxis: $value*2.5;
}
@function ComputeObjectData($localSimObjects) {
@return $localSimObjects:map(
@function($object) {
@if ($object["RelativeTo"] == "") {
@return {
"Name": $object["Name"],
"RelativeTo": "",
"ReferenceFrame": $object["ReferenceFrame"],
"LocalPosition": {
"x": $object["LocalPosition"]["x"]*2.5,
"y": $object["LocalPosition"]["y"]*2.5,
"z": $object["LocalPosition"]["z"]*2.5
},
"LocalRotation": $object["LocalRotation"],
"FixedGuid": $object["FixedGuid"]
};
} @else {
@return $object;
}
}
);
}
:json celestial_bodies > data {
radius: $value*2.5;
MinTerrainHeight: $value*2.5 if $value != null else null;
MaxTerrainHeight: $value*2.5 if $value != null else null;
TerrainHeightScale: $value*2.5 if $value != null else null;
atmosphereDepth: $value*2.5 if $value != null else null;
inverseRotThresholdAltitude: $value*2.5 if $value != null else null;
//gravityASL: $value*2.5 if $value != null else null;
oceanAltitude: $value*2.5 if $value != null else null;
LocalSimObjectsData: ComputeObjectData($value) if $value != null else null;
}
This patch for example is what was giving me a lot of trouble w/ scaled assets and stuff
but thats a very non trivial patch as well lol, using the functional programming stuff
But back to the question
The problem with copying/adding new parts/bodies, is that there is a lot of nontrivial stuff that goes into them
And a lot of information outside of just the json
yeah, absolutely, I mean, that definitely doesn't need to be a part of the initial release, more like something to think about for later
Like youd need to find the parts prefab and copy it ... which needs to be cached somehow
At least planets have their asset keys baked into the json
quite a while back I was talking with Lux in vc and I feel like we figured out pretty well how to patch most of the stuff needed to be able to make a part just with a JSON file, a mesh and textures
though it will require a lot of Harmony patching
At that point our load time issues will be tenfold
Speaking of which, my thoughts are, we have a list of commonly used labels (corresponding to the rulesets we have), then patch files can define any labels they want to patch outside of that list explicitly
yeah, I think that would be a reasonable compromise
that's fine, I mean this thing is not running away anywhere, we've been sitting on it for months and months, so no point in rushing it now 😅
I'll just write it in class, better than going over taylor polynomials ... something I already know
specifically, the idea of this has been hovering around since half of March lmao
from what I could find
@north mist what looks better for defining labels to be loaded
@load 'x', 'y', 'z';
// or
@patch 'x', 'y', 'z';
// or
@labels 'x', 'y', 'z';
I think @patch is the most easily understandable one
alright, @north mist the commit to the label_declarations branch should have that system
by default, since there is only one specific ruleset defined, parts_data is the only label thats patched, but rulesets can declare in the attributes certain labels that should be added to the always patch list, and patches can also declare labels
Once I make a celestial body ruleset as well, then that will be added to the always patch list
Now I need to just quickly test it
Alright, loading time was a lot quicker, though something didn't work ... hmm
alright, that sounds great!
[LOG 11:47:18.069] [System] Patch Manager: parts_data completed in 6.4749s. gosh 7 seconds to go over every single part though
[Info : Console] Testing: resourceContainers against ResourceContainers ahh this was the error
Capitalization
Thats why that patch you were trying to test with didn't work
It might be a bit quicker if we take out a lot of the debug logging I did
[Info : Console] Testing: resourceContainers against resourceContainers -> True
Aight cool, so that part works
[Info : Console] Patch of parts_data:adapter_3v_m3_rightcone_methalox_2v-m3 errored due to: PatchManager.SassyPatching.Exceptions.InvalidVariableReferenceException: C:\Program Files (x86)\Steam\steamapps\common\Kerbal Space Program 2\BepInEx\plugins\PatchManager\patches\test1.patch:3:23: $value does not exist in current scope
at PatchManager.SassyPatching.Nodes.Expressions.Unary.Implicit.Compute (PatchManager.SassyPatching.Execution.Environment environment) [0x00024] in C:\Users\arall\PatchManager\src\PatchManager.SassyPatching\Nodes\Expressions\Unary\Implicit.cs:35
at PatchManager.SassyPatching.Nodes.Statements.SelectionLevel.Field.ExecuteOn (PatchManager.SassyPatching.Execution.Environment environment, PatchManager.SassyPatching.Interfaces.ISelectable selectable, PatchManager.SassyPatching.Interfaces.IModifiable modifiable) [0x000f2] in C:\Users\arall\PatchManager\src\PatchManager.SassyPatching\Nodes\Statements\SelectionLevel\Field.cs:59
at PatchManager.SassyPatching.Nodes.Statements.SelectionBlock.ExecuteOnSingleSelection (PatchManager.SassyPatching.Execution.Environment environment, PatchManager.SassyPatching.Interfaces.ISelectable selection, PatchManager.SassyPatching.DataValue parentDataValue) [0x00077] in C:\Users\arall\PatchManager\src\PatchManager.SassyPatching\Nodes\Statements\SelectionBlock.cs:145
at PatchManager.SassyPatching.Nodes.Statements.SelectionBlock.ExecuteOn (PatchManager.SassyPatching.Execution.Environment environment, PatchManager.SassyPatching.Interfaces.ISelectable selectable, PatchManager.SassyPatching.Interfaces.IModifiable modifiable) [0x000e8] in C:\Users\arall\PatchManager\src\PatchManager.SassyPatching\Nodes\Statements\SelectionBlock.cs:123
at PatchManager.SassyPatching.Nodes.Statements.SelectionBlock.ExecuteFresh (PatchManager.SassyPatching.Execution.Environment snapshot, System.String datasetType, System.String name, System.String& dataset) [0x000af] in C:\Users\arall\PatchManager\src\PatchManager.SassyPatching\Nodes\Statements\SelectionBlock.cs:66
at PatchManager.SassyPatching.Execution.SassyTextPatcher.TryPatch (System.String patchType, System.String name, System.String& patchData) [0x00001] in C:\Users\arall\PatchManager\src\PatchManager.SassyPatching\Execution\SassyTextPatcher.cs:51
at PatchManager.Core.Assets.PatchingManager.PatchJson (System.String label, System.String assetName, System.String text) [0x00034] in C:\Users\arall\PatchManager\src\PatchManager.Core\Assets\PatchingManager.cs:54
[Info : Console] Trying to find capacityUnits in {
"name": "SolidFuel",
"capacityUnits": 2.81,
"initialUnits": 2.81,
"NonStageable": false
}
Okay so why does $value not exist?
no, $value exists ???
Ohhh, I did a dumb
and I also amasked an error
the real error is I forgot to implement Real and Integer implicit multiplication
meaning that just woulda workd if it was *50.0
I still wish we could shunt patching to a completely separate thread altogether and have the game not freeze during it so I can get an actually accurate patch counter
Anyways @north mist I fixed that test patch ... it was a combination of improper capitalization in the patch, and my own dumbassery
ahhh awesome!
So, now that we have solved the load time issue?
release? :O
so do we want to release this as is and then do the survey/discussion about the config/patching formats later?
I mean
the things we wanted to ask about, they can't really experiment with either way
because I don't think that PM in its current state supports creating new configs from scratch
It does not yet ... but im starting to have an idea
But like how do you create like a new mesh if you copy a part config ... or do you just have to have the mesh/prefab in the bundles already
And if you create a part ... you have to add that to the list of parts to be patched at the end
I mean, that should be solved by the dependency order
also, I think that specifically the creation and copying of parts should be a goal for a later point, since that will be one of the most complex things you could possibly do with PM
I meant more so the creation of completely new custom configs, like for example Lux being able to create a config file for LFO with Patch Manager
That should be easier to implement
Thoughts on a syntax like this?
:+lfo("name") {
}
and then other mods should be able to specify they need LFO to be processed first, and then they could patch the LFO default config
as in, create a new asset called "name" with the "lfo" label?
or with a completely custom LFO adapter?
that's another thing I wanted to discuss
Essentially, the semantics are slightly different being what you just said as an adapter
Something like
:+json("label","name") {
}
Would be generic
ah gotcha
And any adapter can specify a list of parameters they take for creation
yeah, I think that could work
although, what's our syntax for deleting?
if we have one
I really sound like I've never seen this project before in my life lmao
I haven't really kept up with the syntax tbh
@delete; in the body of the selection block, though I havent yet added that fully in the engine
I'm just wondering if we should maybe make those two kinda similar, since they mirror each other
One requires a lot more information than the other
also, just to make sure I understand correctly, @delete is only used to delete the top-level object, aka the whole asset, or other parts of the hierarchy as well, such as individual modules?
Can delete anywhere in the hierarchy
and do we have anything for adding things within the hierarchy?
+element_type/name at the end of a selection statement
(hope I'm not being annoying with all these questions, just want to make sure I have the full picture)
(Also not fully handled yet, but the interfaces are there)
alright, then I guess :+adapter makes total sense
We need a way to specify in the cache that an asset has been deleted
we'll need some very extensive docs lmao
you can just do that in the body
lfo:plume{
}
lfo:frost{
}