#Patch Manager

1 messages · Page 2 of 1

upbeat hare
#

just "modify" the text asset and it gets cached in a zip file

north mist
#

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

upbeat hare
#

True

upbeat hare
#

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

north mist
#

yeah that makes sense

upbeat hare
#

Damn, I'm actually writing a whole lotta shit to make my system work

#

I mean I am basically running a programming language so

upbeat hare
#

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

upbeat hare
#

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

north mist
#

damn

upbeat hare
#

@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

north mist
#

amazing stuff

upbeat hare
#

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

upbeat hare
#

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

north mist
upbeat hare
#

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

north mist
#

Am in bed but not asleep yet

#

Let's gooooo

#

That's amazing

upbeat hare
#

Technically I ran it on all these json files :3

north mist
#

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

upbeat hare
#

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

upbeat hare
#

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

upbeat hare
#

Sorry that its another >100 file change

upbeat hare
#

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

upbeat hare
#

insane in the best possible way

north mist
upbeat hare
#

I should write an architecture breakdown of the engine

north mist
#

That would be very helpful

upbeat hare
#

Aight time to pull out google slides

#

Me realizing I made a slight mistake that doesnt require too much to change

upbeat hare
#

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

north mist
#

pinning just in case

#

but it sounds straightforward enough

upbeat hare
#

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

north mist
#

now we need a custom VS Code language server extension for this munley

#

for full intellisense

upbeat hare
#

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

upbeat hare
#

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)

north mist
#

I don't think we need that lmao

upbeat hare
#

Ehh ... scss has higher order functions which is what this enables

north mist
upbeat hare
north mist
#

yeah

upbeat hare
#
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

north mist
#

I mean can't we just do something similar to meta.get-function() and meta.call()?

upbeat hare
#

I am basically doing that yes, but this is more so on the interop side of things I'm talking about

north mist
#

gotcha

upbeat hare
#

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

north mist
#

that's pretty cool

upbeat hare
#

It also allows for an easy map function that can be used like this

$list:map(@function($x) { @return x*2; });
upbeat hare
#

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

north mist
#

functional sassy patches programming, here we go

upbeat hare
#

That's the point

north mist
#

we should just allow Sassy Patches as a language for standalone SpaceWarp mods

#

lmao

upbeat hare
#

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 {
  //...
}
north mist
#

idk, I think the @each $val in $list syntax might be more readable for non-programmers

#

but that's just my opinion

upbeat hare
#

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

north mist
#

i guess

upbeat hare
#

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

north mist
#

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

upbeat hare
#

No I get that, ehh, I can do each/for
A minor different in control flow structures though

@else if
// is now
@else-if
north mist
#

eh that seems fine

upbeat hare
#

Do I add interpolation to selection blocks, w/ the caveat that it makes incremental patching nigh on impossible in the future

north mist
#

hm

#

that's a tough one

upbeat hare
#
@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"

upbeat hare
#

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

upbeat hare
#

Well imma sleep

upbeat hare
#

Munix, any update on your side of things so I can look at integration?

north mist
#

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

upbeat hare
#

Gotcha

north mist
#

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

upbeat hare
#

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

upbeat hare
#

But patch files actually all run at the start and register the patch functions which are the ITextPatchers

north mist
#

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

north mist
#

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!

upbeat hare
#

Did you load the parts assembly

north mist
#

aaaand I think I know why lmao

#

yeah exactly, I didn't

#

only took me typing it to realize

upbeat hare
#

I ran into that same issue w/ my manual testing

north mist
#

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

upbeat hare
#

F

#

cache invalidation is a fun problem

north mist
#

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
north mist
#

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

upbeat hare
#

We can attempt to check the disabled plugins file using the patch manager patcher

north mist
#

Yeah, we really should

upbeat hare
#

Just check if patchmanagers guid is in the list and return early if it is

upbeat hare
#

@north mist do you have the latest changes you made pushed?

north mist
#

Nope lemme quickly do that

#

pushed

upbeat hare
north mist
#

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

upbeat hare
#

This is an issue?

north mist
#

try deleting your build folder and rebuilding

#

doesn't happen to me

upbeat hare
#

Fixed

#

why do I keep having these build folder issues aaaaa

north mist
#

eh it's probably because I changed some stuff in the project files

upbeat hare
#

Version check working lol
But yeah imma test some stuff

#

Damn thats a lot of labels

north mist
#

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

upbeat hare
#

How early do you get these errors

north mist
#

when loading a campaign (after launching the game with the cache already existing)

upbeat hare
#

Interesting error, but thats expected (I really want copy clicking errors)

north mist
#

oh yeah, we need to just filter out stuff that isn't JSON

upbeat hare
#

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

north mist
#

yeah, sure

upbeat hare
#

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)

north mist
#

nope, I tested it

#

it will

upbeat hare
#

Actually no I might just be being dumb

#

strings are immutable

north mist
#

yeah

upbeat hare
#

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)

north mist
#

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

upbeat hare
#

You can manually edit the assembly?

north mist
#

wdym?

upbeat hare
#

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

north mist
#

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

upbeat hare
#

Thats literally what I was suggesting but done manually

north mist
#

yeah

#

this would be a bit easier I was assuming

#

since we're already doing it

#

but just in memory

upbeat hare
#

I guess ... editing assemblies in dnSpy is very easy though

north mist
#

I wouldn't know, never tried 😆

upbeat hare
#

Literallly just press edit method C#

north mist
#

heh

upbeat hare
#

And you get a code editor

north mist
#

cool

upbeat hare
#

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?

north mist
#

I'm pretty sure you do

upbeat hare
#

Done

#

as if

#

@north mist I found the problem, something is causing the keey in here to be an empty string

north mist
#

huh

upbeat hare
#

Thats meant to be "KERBALATTRIBUTES_EVA"

#

So something w/ those

north mist
#

OH

upbeat hare
#

OHHH

north mist
#

lmao

upbeat hare
#

So somehow the name isn't being passed through to the delegate?

north mist
#

name corresponds to PrimaryKey

#

so it should start with KerbalAttributes_

#

even if it ends with .zip

upbeat hare
#

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

north mist
#

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

upbeat hare
#

Yeah, then just do that lol

#

I mean you have the asset name here lol

#

Seems not impossible

north mist
#

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

upbeat hare
#

And there, easy enough
(Even if it was readonly ... reflection)

#

Test it?

upbeat hare
#

Aight, push it!

north mist
#

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

upbeat hare
#

oh?

north mist
#

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

upbeat hare
#

Anyway to make it not async?

north mist
#

I can just add a callback argument to RebuildCache which will contain the call, instead of putting it below

upbeat hare
#

Gotcha

#

Anyways I'm gonna test a quick patch

#

(That being my crew capacity patch

north mist
#

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)

upbeat hare
#

Its the most obvious one

#

I hate the obnoxious debug console from ksp

north mist
#

yeah lmao

upbeat hare
#

They should just steal space warps

north mist
#

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

upbeat hare
#

I think I just need to add a generic json patcher and we got something worthy of a testing release

north mist
#

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

upbeat hare
north mist
#

I can tell lmao

#

you didn't cease to amaze me with the speed with which you were writing this stuff

upbeat hare
#

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

north mist
#

apparently Newtonsoft.Json supports both single- and multi-line comments

#

so I could even use them in the swinfo.json like I wanted

upbeat hare
#

Why don't we?

north mist
#

I don't know, I just never looked up if it's implemented in the Newtonsoft library I guess lmao

upbeat hare
#

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

copper forum
upbeat hare
#

that much is obvious

north mist
#

alright, loading of patched assets when cache is invalidated is fixed

upbeat hare
#

Cool, I'm gonna have to pull that into my branch'

north mist
#

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

upbeat hare
#

my side is pushed, now time to try a different type of patch though

#

I ... had 50 gb in my recycle bin wtf

upbeat hare
north mist
#

now that's a good test

upbeat hare
#

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

north mist
#

where are you looking for the orbital parameters?

upbeat hare
#

in the bodies json

north mist
#

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

upbeat hare
north mist
#

the key is GalaxyDefinition_Default

upbeat hare
#

Which doesn't show up in the cache

north mist
#

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

upbeat hare
#

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

upbeat hare
north mist
#

sure, I'll look into it

upbeat hare
#

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

#

I think we did good

upbeat hare
#

Anyways @north mist I think I am done for the next hour or 2

north mist
#

yeah same, I'm gonna go watch a show with my brother

north mist
#

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

upbeat hare
north mist
#

Awesome

upbeat hare
#

Any work on patching the other load functions?

#

Cuz I think thats all we need more

north mist
#

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

north mist
#

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

north mist
#

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?

upbeat hare
#

Why do you need to call the async functions

#

And why cant you just await them

north mist
#

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)

upbeat hare
north mist
#

how?

#

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

upbeat hare
#

you can do asynccontext.runtask the inner task?

north mist
#

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

upbeat hare
north mist
#

I just get stuck on this

#

when debugging the code, it gets to the line with .Result and hangs there

upbeat hare
#

Hmm, im not sure, are we sure we can't just call the synchronous versions of the methods, what do those return?

north mist
#

there aren't any

north mist
#

and that throws an error

upbeat hare
#

hmm .. idk then

upbeat hare
#

We cant really know every label before load nor do we want to load everything that soon do we?

north mist
#

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

north mist
#

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

maiden wraith
#

do u have a patch on minmus?

north mist
#

nope

#

but the way it works right now, the cache contains all text assets, not just patched ones

north mist
#

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

maiden wraith
#

wait 1m13s w/o mods?

#

and whats the difference when the cache is built?

north mist
#

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

upbeat hare
upbeat hare
#

Im thinking we should release this relatively soon, is there anything i should do?

north mist
#

I mean, I can push what I have now, if you feel like having a look at those errors

upbeat hare
#

Ill take a crack at it tomorrow

upbeat hare
#

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

north mist
#

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

upbeat hare
#

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

upbeat hare
#

Did you push the latest stuff munix?

north mist
#

Yep, it's in the cache branch again

upbeat hare
#

Aight, gonna check it out now

upbeat hare
north mist
#

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

upbeat hare
#

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

north mist
#

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

upbeat hare
#

you also never set __result to false at any point, but I don't think thats the issue here

maiden wraith
#

it defaults as false no?

north mist
#

if we don't

upbeat hare
#

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

north mist
#

Ahh, yeah, that makes sense

upbeat hare
#

screaming

north mist
#

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

upbeat hare
#

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

maiden wraith
#

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?

north mist
#

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

upbeat hare
#

The issue is also that this makes stuff like deletion of planets impossible through this method

#

because it will always load internal data

maiden wraith
#

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

north mist
#

It won't if we go back to the previous approach

#

At least it shouldn't

north mist
#

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

upbeat hare
#

No that was from w/in assetprovider

north mist
#

Oh well then that's fine

upbeat hare
#

So we have to basically rewrite every dang method in asset provider then?

north mist
#

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

maiden wraith
#

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

upbeat hare
north mist
#

And it won't need to be every method, either, I think we should be able to just patch this one specifically causing issues

upbeat hare
#

did you get the rewrite done?

#

and is there more I need to do?

north mist
#

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

upbeat hare
#

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

north mist
#

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

upbeat hare
#

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

north mist
#

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?

upbeat hare
north mist
#

it has three arguments as well

#

so that should be fine

upbeat hare
#

including self?

north mist
#

oh yeah, that's a good point lmao

upbeat hare
#

tho actually

#

I skipped self there

#

that'd be ldarg 0

#

so your good

north mist
#

how does this Ldarg_X stuff even work?

#

if there's more than 3

upbeat hare
#

Instruction.Create(OpCodes.Ldarg, 4);

north mist
#

ah, makes sense

#

alright, so I'm kinda getting somewhere

#

the patched method ran

#

but it didn't fix the issue

#

lmao

upbeat hare
#

Oof

upbeat hare
#

As assetprovider is all but a static class

north mist
#

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

upbeat hare
#

Easy fix .Replace("/",SOMETHING_ELSE) and vice versa

north mist
#

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

upbeat hare
#

Can I see what you wrote

#

This load function for referencfe

#

@north mist

#

this is the one its calling

#

The object keyObject one

north mist
#

yeah, that's what I thought, and that in turn calls LoadAssetAsync

#

so I don't see how that could duplicate the data

upbeat hare
#

Log the stack every time that is called

#

Is it pushed?

north mist
#

not yet

#

lemme just make this change

upbeat hare
#

Well I have to dip for the rest of the night, so I'll get to it tomorrow

north mist
#

gotcha, I'll try to debug it and fix it

north mist
#

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

maiden wraith
#

you're passin IL_0000?

#

that doesnt seem right

#

what r u doing to check if youre on the right il?

north mist
#

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));
maiden wraith
#

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

north mist
#

hm, that's weird, this is the signature of the method

#

and there's only one, no overloads

maiden wraith
#

iirc async methods need other opcodes too

north mist
#

it's not async

upbeat hare
north mist
#

it returns an AsyncOperationHandle

#

but not actually marked "async"

maiden wraith
#

yeah i mean IL_0000 is always valid no?

maiden wraith
upbeat hare
north mist
#

if it helps in any way, this is the method in AssetProviderPatch that I'm trying to call

upbeat hare
#

whats the stack size

#

tho it shouldnt matter

north mist
upbeat hare
#

I meant the IL stack size, but it defaults to 8 so it dont matter

north mist
#

oh lmao

upbeat hare
#

Push the entire code and ill figure it out tomorrow

north mist
#

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

upbeat hare
#

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

maiden wraith
#

how?

upbeat hare
#
        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

maiden wraith
#

lmao

upbeat hare
#

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

north mist
#

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

north mist
#

wtf

north mist
#

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

north mist
#

aaahhhh this is so frustrating

upbeat hare
#

I was able to compile and run it

#

I wouldve gotten a preload error otherwise?

#

Wait it literally doesnt like the opcode??

north mist
#

Yeah I have no clue, neither this version nor the one with _1 and _2 run for me

upbeat hare
#

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????

upbeat hare
#

targetMethod.Parameters[1]

north mist
#

wtf

upbeat hare
#

I was using that

north mist
#

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

upbeat hare
#

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

north mist
#

ah

#

by the way I also tried changing the extractedMethod to not be static, and added Ldarg_0, but no bueno

upbeat hare
#

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)
north mist
#

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

upbeat hare
#

And its so damn odd that the base starts w/ a ldarg just fine??

#

Lemme try something else

upbeat hare
#

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

upbeat hare
#

aight, I give up, this is insanity

north mist
upbeat hare
#

Is there a way to just not use this method

north mist
#

well we can patch every other method where it's used

#

lmao

upbeat hare
#

at this point that might just be cleaner

north mist
#

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

upbeat hare
#

dies internally

north mist
#

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

upbeat hare
#

ao just undo all of what we just did to repatch loadbylabel

#

Anyways, imma go grab some brekky

north mist
#

and done, patching of all labels now works

upbeat hare
#

It was that simple

north mist
#

a bit more polishing and I think we can make an experimental release

upbeat hare
#

Now I can finally make a test patch to scale up the solar system

#

Does it still build cache on first run?

north mist
#

yeah

upbeat hare
#

In that case a necessary polish would be splitting every label into its own flow action

north mist
#

and not saving unpatched labels into the cache

upbeat hare
#

That too

upbeat hare
north mist
#

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

upbeat hare
#

Gotcha

#

I just realized a possible issue

#

Mods can define their own labels

#

And their bundles are loaded later

upbeat hare
#

Aight, you said its on main branch?

north mist
#

yeah

upbeat hare
#

Aight got it

#

Wait, I see no actions being registered at all

north mist
#

I haven't done that yet, if you mean the inidividual flow actions for each label

upbeat hare
#

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

north mist
#

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

upbeat hare
#

which is why the lazy patching system was decent, but also not really possible to maintain?

north mist
#

yeah, it's a shame but I don't think we could easily work around the async stuff

upbeat hare
#

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

north mist
#

alright, awesome

upbeat hare
#

just zipping up the current cache so I can inspect it for future purposes

#

A whole lot smaller

north mist
#

yeah, that's much better

upbeat hare
#

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?

north mist
#

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

north mist
upbeat hare
#

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

north mist
#

yeah

#

sounds like it might be useful to split the patching process after all

upbeat hare
#

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

north mist
#

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

upbeat hare
#

Fair

#

lemme write the stuff for post spacewarp, and then I'll leave you to doing presw stuff

north mist
#

sure

upbeat hare
#

the issue is then, that we still have to read patch files as early as possible, which is fine, thats not the bottleneck

north mist
#

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

upbeat hare
#

I might have to un-async RebuildAllCache...

#

Oh gods, patches will still load despite error state in space warp

upbeat hare
#

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

upbeat hare
#

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

north mist
#

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

upbeat hare
#

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

maiden wraith
upbeat hare
#

No, I patched the loadingbar to inject tips into there

#

I might add it as a spacewarp thing later on

maiden wraith
#

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

north mist
upbeat hare
#

Except, the way I did it, broke WWISE somehow

maiden wraith
#

oh as always

north mist
#

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

upbeat hare
#

Can't I just harmony patch that away w/ space warp

north mist
#

no clue, but it would be nice

upbeat hare
#

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

north mist
#

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

upbeat hare
#

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

upbeat hare
north mist
#

yeah this is the only place where I found them usable

#

at the end of GameManager.StartBootstrap

upbeat hare
#

They are usable anywhere in gamemanager.startbootstrap

#

but that'll already have been called by the time I need to do stuff

north mist
#

yeah that makes sense

upbeat hare
#

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

north mist
#

yeah, definitely go for it

upbeat hare
#

wait, I can just replace the download on spacedock

#

But ehh I needed to bump the version anyways

north mist
upbeat hare
#

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

north mist
#

awesome, thanks

upbeat hare
#

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

upbeat hare
#

I'll scream if this actually works

maiden wraith
#

did you scream?

#

i dont think you screamed

upbeat hare
#

I did not scream

maiden wraith
#

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

upbeat hare
#

This would require a substantial rewrite of PM

maiden wraith
#

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

maiden wraith
#

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

upbeat hare
#

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 ?

north mist
#

I have no clue about that tbh

#

I need to look into the json files

maiden wraith
#

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

upbeat hare
#

That is definitely not the aim of patch manager

#

Stored in the monobehaviour

maiden wraith
#

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

north mist
#

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

maiden wraith
#

then sounds could fit those custom patches? idk i might be misunderstanding patch manager i think

north mist
#

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

maiden wraith
#

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

upbeat hare
#

hmm, I think we can parellize the patches on any given label

maiden wraith
#

i think its a good idea, that is, the labels actions are independent

#

which im 99% sure they are but yeah

upbeat hare
#

Am tryna get patch manager to work reeee

#

Tryna do the 2.5 scale solar system

maiden wraith
#

whats not working?

#

iirc for that you need to mess with PQS for collision to work decently

upbeat hare
maiden wraith
#

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);

}
upbeat hare
#

Thats what rulesets are for :3
But hmm

maiden wraith
#

gredat my keyboard is fucked noo_why

#

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

upbeat hare
#

2.5x gravity definitely wobblifies the rockets more

upbeat hare
#

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

maiden wraith
#

Yup probably not, its not even in the json data i think

upbeat hare
#

everything else I can do in JSON

maiden wraith
#

U know that reminds me of the comment made on the forum post

upbeat hare
#

Can you tell me though where the PQS stuff is stored at runtime

maiden wraith
#

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

north mist
upbeat hare
#

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

maiden wraith
#

They clearly dont understand that the game was rebuilt in a better way

#

But yeah ur right 😔😔

north mist
#

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

upbeat hare
#

What do we need to do for release?

rapid parrot
#

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.

upbeat hare
#

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

rapid parrot
#

ahh nice. then all good

#

🙂

north mist
#

@upbeat hare do you still have that language specification document somewhere?

upbeat hare
#

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

north mist
#

Alright, that would be awesome, thanks

upbeat hare
north mist
#

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

upbeat hare
#

I need a few example patches to put in there

#

do you still have the crew capacity one

north mist
#

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

upbeat hare
#

Wait, it didn't work?

north mist
#

not as far as I remember

upbeat hare
#

I've tested it?

copper forum
#

how would this look in your one? whats the conversion like? want to make a site that you can create them on.

north mist
# upbeat hare I've tested it?

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

upbeat hare
#
:parts {
    * > ResourceContainers > * {
        capacityUnits: *5;
        initialUnits: *5;
    }
}
upbeat hare
upbeat hare
# copper forum how would this look in your one? whats the conversion like? want to make a site ...

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;
}
north mist
#

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

upbeat hare
#

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

north mist
#

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

upbeat hare
#

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

north mist
#

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

upbeat hare
#

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

north mist
#

yeah, I think that would be a reasonable compromise

upbeat hare
#

I can easily write something like that :3

#

But I have class soon

north mist
#

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 😅

upbeat hare
#

I'll just write it in class, better than going over taylor polynomials ... something I already know

north mist
#

specifically, the idea of this has been hovering around since half of March lmao

#

from what I could find

upbeat hare
#

@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';
north mist
#

I think @patch is the most easily understandable one

upbeat hare
#

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

upbeat hare
#

[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

north mist
#

ahhh awesome!

upbeat hare
#

So, now that we have solved the load time issue?

maiden wraith
#

release? :O

north mist
#

so do we want to release this as is and then do the survey/discussion about the config/patching formats later?

maiden wraith
#

this way people can experiment with it

#

and not just have an unexperienced opinion

north mist
#

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

upbeat hare
#

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

north mist
#

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

upbeat hare
#

That should be easier to implement

#

Thoughts on a syntax like this?

:+lfo("name") {
}
north mist
#

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

north mist
#

or with a completely custom LFO adapter?

#

that's another thing I wanted to discuss

upbeat hare
#

Something like

:+json("label","name") {
}

Would be generic

north mist
#

ah gotcha

upbeat hare
#

And any adapter can specify a list of parameters they take for creation

north mist
#

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

upbeat hare
#

@delete; in the body of the selection block, though I havent yet added that fully in the engine

north mist
#

I'm just wondering if we should maybe make those two kinda similar, since they mirror each other

upbeat hare
#

One requires a lot more information than the other

north mist
#

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?

upbeat hare
#

Can delete anywhere in the hierarchy

north mist
#

and do we have anything for adding things within the hierarchy?

upbeat hare
#

+element_type/name at the end of a selection statement

north mist
#

(hope I'm not being annoying with all these questions, just want to make sure I have the full picture)

upbeat hare
north mist
#

alright, then I guess :+adapter makes total sense

upbeat hare
#

We need a way to specify in the cache that an asset has been deleted

north mist
#

we'll need some very extensive docs lmao

maiden wraith
#

i'd love something more

#

complex? i think

#

like

#
lfo:action
north mist
#

you can just do that in the body

maiden wraith
#
lfo:plume{
}
lfo:frost{
}