Jump to content

Trying to fix a NullReferenceException in an outdated mod to make it compatible with KSP 1.0.4


Recommended Posts

Hello, I've been trying to update ShipEffects: Dynamic Sound Effects to function properly with KSP 1.0.4, as the Developer has stated that development of the mod has haulted and anyone is welcome to keep it updated.

So the mod (which was compiled for KSP 0.90.0) works perfectly except under very specific and very hard to replicate conditions, in which case it will spam NullReferenceExceptions. So that is the only thing that needs to be fixed in order to call the mod 1.0.4 compatible, however in order to fix the problem I needed to cause the NullReferenceException to fire so that I could locate the source of the problem. This took several attempts and I finally was able to cause the NullReferenceException to fire, and locate the code block that was causing it.

While I was able to locate the code block that was causing the problem, I needed to pinpoint what line of code was causing the NullReferenceException, and so I threw in some try-catch statements so that when it fired, I would know exactly where and why... BUT I can't get the NullReferenceException to fire again, and I've been trying to get it to fire for hours... So here I am with a modified .dll that would tell me the exact location of the problem down to the very line, and I can't use it because I cant get the darn thing to fire.

Now I only have basic, maybe a bit of intermediate experience with C#, and no experience modding KSP at all. So while I would probably be able to come up with some solution to the problem if I could get it to fire with my modified .dll, I suspect that someone more experienced would be able to see the problem by simply looking at the code. This is why I've come here for assistance, because I have the output from the NullReferenceException, and the source code for the plugin, I just need somebody with a bit more experience to help me out.

With all of that being said, here's the error and the source.

NullReferenceException:

NullReferenceException
at (wrapper managed-to-native) UnityEngine.AudioSource:get_isPlaying ()

at ShipEffects.SEMaster.SoundFX (.FXGroup fx, Single volume, Single volCtrl, Single spread, Boolean play) [0x00000] in <filename unknown>:0

at ShipEffects.SEMaster.SoundFX (.FXGroup fx, Boolean play) [0x00000] in <filename unknown>:0

at ShipEffects.SEMaster.Update () [0x00000] in <filename unknown>:0

(Filename: Line: -1)

Source:

using System;
using System.Collections;
using System.Reflection;
using UnityEngine;
using KSP;

namespace ShipEffects
{
[KSPAddon(KSPAddon.Startup.Flight, false)]
public class SEMaster : MonoBehaviour
{
//soundnames
string small_rattles = "ShipEffects/Sounds/small_rattles";
string big_rattles = "ShipEffects/Sounds/big_rattles";
string rumble = "ShipEffects/Sounds/rumble";
string vibrations = "ShipEffects/Sounds/vibrations";
string stress_big = "ShipEffects/Sounds/stress_big";
string atmos_normal = "ShipEffects/Sounds/atmos_normal";

public string thumpLowSound = "ShipEffects/Sounds/thump_low";
public string thumpHeavySound = "ShipEffects/Sounds/thump_heavy";

public string dockedClip = "ShipEffects/Sounds/docked_sound";
public string undockedClip = "ShipEffects/Sounds/undocked_sound";

//groups
FXGroup smallRattlesGroup = new FXGroup("smallRattlesFXGroup");
FXGroup bigRattlesGroup = new FXGroup("bigRattlesFXGroup");
FXGroup rumbleGroup = new FXGroup("rumbleFXGroup");
FXGroup vibrationsGroup = new FXGroup("vibrationsFXGroup");
FXGroup atmosphereGroup = new FXGroup("atmosphereFXGroup");
FXGroup stressBigGroup = new FXGroup("stressBigFXGroup");

FXGroup thumpLowGroup = new FXGroup("ThumpLowFXGroup");
FXGroup thumpHeavyGroup = new FXGroup("ThumpHeavyFXGroup");

FXGroup dockedGroup = new FXGroup("DockFXGroup");
FXGroup undockedGroup = new FXGroup("DockFXGroup");

//Settings
public float masterVolume = 1f;
public float rVolCtrl = 1f;
public float vVolCtrl = 1f;
public float rmVolCtrl = 1f;
public float sVolCtrl = 1f;
public float aVolCtrl = 0.7f;
public float tVolCtrl = 0.7f;

public float resistMultiplier = 1.0f;
public float reEntryMultiplier = 8.0f;

//sets
bool rumbleSet;
bool smallRattlesSet;
bool bigRattlesSet;
bool vibrationsSet;
bool atmosphereSet;
bool stressBigSet;

bool thumpLowSet;
bool thumpHeavySet;

bool dockedSet;
bool undockedSet;

bool gamePaused = false;

Vessel vessel;
AerodynamicsFX aeroFx;

float vesselMass;
float vesselAcceleration;
float vesselRot;
float surfSpeed;
float atmDensity;
float engineThrust;
float vResist;
float engineAccel;
float engineMicro;
float burnDownTime = 0;
float[] atmBurnDowntimes = new float[] { 1.0f };

float counter = 0;

bool doEngineThrust;
bool onlyIVA = true;


void Start()
{
vessel = FlightGlobals.ActiveVessel;
if (vessel == null || !HighLogic.LoadedSceneIsFlight)
return;

smallRattlesSet = createGroup(smallRattlesGroup, vessel, small_rattles, true, true);
bigRattlesSet = createGroup(bigRattlesGroup, vessel, big_rattles, true, true);
rumbleSet = createGroup(rumbleGroup, vessel, rumble, true, true);
vibrationsSet = createGroup(vibrationsGroup, vessel, vibrations, true, true);
stressBigSet = createGroup(stressBigGroup, vessel, stress_big, true, true);
atmosphereSet = createGroup(atmosphereGroup, vessel, atmos_normal, true, true);

thumpLowSet = createGroup(thumpLowGroup, vessel, thumpLowSound, false, false);
thumpHeavySet = createGroup(thumpHeavyGroup, vessel, thumpHeavySound, false, false);

dockedSet = createGroup(dockedGroup, vessel, dockedClip, false, true);
undockedSet = createGroup(undockedGroup, vessel, undockedClip, false, true);

LoadSettings();

GameEvents.onPartCouple.Add(this.onVesselDock);
GameEvents.onPartUndock.Add(this.onVesselUndock);

GameEvents.onGamePause.Add(this.onGamePaused);
GameEvents.onGameUnpause.Add(this.onGameUnpaused);
}


void LoadSettings()
{
foreach (ConfigNode node in GameDatabase.Instance.GetConfigNodes("SHIPEFFECTS_SETTINGS"))
{
if (node.HasValue("OnlyInIVA"))
bool.TryParse(node.GetValue("OnlyInIVA"), out onlyIVA);

if (node.HasValue("masterVolume"))
float.TryParse(node.GetValue("masterVolume"), out masterVolume);
if (node.HasValue("rattleVolume"))
float.TryParse(node.GetValue("rattleVolume"), out rVolCtrl);
if (node.HasValue("vibrationVolume"))
float.TryParse(node.GetValue("vibrationVolume"), out vVolCtrl);
if (node.HasValue("rumbleVolume"))
float.TryParse(node.GetValue("rumbleVolume"), out rmVolCtrl);
if (node.HasValue("thumpVolume"))
float.TryParse(node.GetValue("thumpVolume"), out tVolCtrl);
if (node.HasValue("stressVolume"))
float.TryParse(node.GetValue("stressVolume"), out sVolCtrl);
if (node.HasValue("atmosphereVolume"))
float.TryParse(node.GetValue("atmosphereVolume"), out aVolCtrl);

if (node.HasValue("ResistMultiplier"))
float.TryParse(node.GetValue("ResistMultiplier"), out resistMultiplier);
if (node.HasValue("ReEntryMultiplier"))
float.TryParse(node.GetValue("ReEntryMultiplier"), out reEntryMultiplier);

Debug.Log("ShipEffects Volume Settings loaded");
break;
}
}

public bool createGroup(FXGroup group, Vessel vessel, string clip, bool loop, bool fxBypass)
{
group.audio = vessel.gameObject.AddComponent<AudioSource>();
group.audio.clip = GameDatabase.Instance.GetAudioClip(clip);
group.audio.Stop();
group.audio.loop = loop;
group.audio.rolloffMode = AudioRolloffMode.Logarithmic;
group.audio.playOnAwake = false;
group.audio.maxDistance = 100f;
group.audio.panLevel = 1f;
group.audio.dopplerLevel = 0f;

group.audio.bypassEffects = fxBypass;

Debug.Log("ShipEffects: " + group.name + " is Created");

return true;
}

void SoundFX(FXGroup fx, float volume, float volCtrl, float spread, bool play)
{
if (!gamePaused && !fx.audio.isPlaying && play == true)
{
fx.audio.Play();
fx.audio.volume = (Mathf.Clamp(volume, 0, 1f) * volCtrl) * masterVolume;
}
else
{
fx.audio.volume = Mathf.Clamp(volume, 0, 1f);
}
if (play == false)
{
fx.audio.Stop();
}
fx.audio.spread = spread;
}

void SoundFX(FXGroup fx, bool play)
{
SoundFX(fx, 0, 0, 0, play);
}

float dockTime = 0;
float[] dockTimes = new float[] { 0.25f };
bool ifUndocking = false;
public void onVesselDock(GameEvents.FromToAction<Part, Part> action)
{

dockTime = dockTimes[0];
ifUndocking = false;
}

public void onVesselUndock(Part action)
{
dockTime = dockTimes[0];
ifUndocking = true;

}


void Update()
{
vessel = FlightGlobals.ActiveVessel;

if (vessel == null || vessel.isEVA || !HighLogic.LoadedSceneIsFlight)
return;

//grab the data we need
surfSpeed = (float)vessel.srfSpeed;
atmDensity = (float)vessel.atmDensity;
vesselMass = vessel.GetTotalMass();
vesselRot = (float)Math.Sqrt(Math.Pow(vessel.angularVelocity.x, 2) + Math.Pow(vessel.angularVelocity.y, 2) + Math.Pow(vessel.angularVelocity.z, 2));
vesselAcceleration = (float)Math.Sqrt(Math.Pow(vessel.acceleration.x, 2) + Math.Pow(vessel.acceleration.y, 2) + Math.Pow(vessel.acceleration.z, 2)) * 0.1f;
vResist = (atmDensity * surfSpeed * 0.01f) * vesselAcceleration;



if (aeroFx == null)
{
GameObject fx = GameObject.Find("FXLogic");
if (fx != null)
{
aeroFx = fx.GetComponent<AerodynamicsFX>();
}
}

if ((aeroFx != null) && (aeroFx.FxScalar > 0.01))
{
if (aeroFx.fxLight.color.b < 0.12f)
{

burnDownTime = atmBurnDowntimes[0];

}

if (aeroFx.fxLight.color.b > 0.20f)
{

if (aeroFx.FxScalar > 0.1)
vResist += (aeroFx.FxScalar * 2);
}
}

if (burnDownTime > 0)
{
vResist += (aeroFx.FxScalar * burnDownTime * reEntryMultiplier);

burnDownTime -= Time.deltaTime;
}

//thickeratmosphericsound in thicker atmospheres
if (atmDensity > 0)
{
float airPressure = 1f / atmDensity;
atmosphereGroup.audio.pitch = Mathf.Clamp(airPressure, 0, 1.5f);
}

foreach (Part part in vessel.parts)
{
foreach (PartModule module in part.Modules)
{
if (module.moduleName.Contains("ModuleEnginesFX"))
{
ModuleEnginesFX e = module as ModuleEnginesFX;
if (e.isOperational)
{

engineThrust += (e.finalThrust);
}
if (engineThrust > 0)
doEngineThrust = true;
}
else if (module.moduleName.Contains("ModuleEngine"))
{
ModuleEngines e = module as ModuleEngines;
if (e.isOperational)
{

engineThrust += (e.finalThrust);
}

if (engineThrust > 0)
doEngineThrust = true;
}

}
}

if (engineThrust > 0 && doEngineThrust == true)
{
engineMicro = engineThrust / 1000f;
engineAccel = Mathf.Abs(((engineThrust / vesselMass) * 0.1f) - vesselAcceleration) + engineMicro;

}
else if (engineThrust <= 0)
{
doEngineThrust = false;
}

vResist += engineAccel;

vResist *= resistMultiplier;

bool isCrewed = false;

foreach (Part part in vessel.parts)
{
if (part.protoModuleCrew.Count >= 1)
{
isCrewed = true;
}
}

if (!gamePaused)
{

if (isCrewed && !MapView.MapIsEnabled && (onlyIVA == false || InternalCamera.Instance.isActive))
{
//wind and pressure?
if (surfSpeed > 10 || vesselRot > 1.5f)
{
SoundFX(atmosphereGroup, ((atmDensity * surfSpeed - 10f) / 80f) + ((vesselRot - 1.5f) / 7.0f * atmDensity), aVolCtrl, 90f, true);
}
else
{
SoundFX(atmosphereGroup, false);
}

//dynamics
if (vResist > 0.5)
{
SoundFX(smallRattlesGroup, (vResist - 0.5f) / 4f, rVolCtrl, 90f, true);
}
else
{
SoundFX(smallRattlesGroup, false);
}
if (vResist > 0.8 || vesselRot > 1.5f)
{
SoundFX(vibrationsGroup, ((vResist - 0.8f) / 2f) + ((vesselRot - 1.5f) / 6f), vVolCtrl, 35f, true);
}
else
{
SoundFX(vibrationsGroup, false);
}
if (vResist > 1 || vesselRot > 2.0f)
{
SoundFX(rumbleGroup, ((vResist - 1f) / 2f) + ((vesselRot - 2.0f) / 6f), rmVolCtrl, 180f, true);
}
else
{
SoundFX(rumbleGroup, false);
}
if (vResist > 4.0)
{
SoundFX(bigRattlesGroup, (vResist - 5f) / 4f, rVolCtrl, 90f, true);
}
else
{
SoundFX(bigRattlesGroup, false);
}
if (vResist > 8.0)
{
SoundFX(stressBigGroup, (vResist - 6f) / 6f, sVolCtrl, 90f, true);
}
else
{
SoundFX(stressBigGroup, false);
}

if (dockTime > 0)
{
if (ifUndocking)
SoundFX(undockedGroup, 1f, 1f, 45f, true);
if (!ifUndocking)
SoundFX(dockedGroup, 1f, 1f, 45f, true);

dockTime -= Time.deltaTime;
}

if (vResist > 1.8)
{
counter += Time.deltaTime;
if (counter > 0.26f)
counter = 1;
if (counter < 0.25f)
{
if (vResist > 2.0f && vResist < 5.0)
{
SoundFX(thumpLowGroup, (vResist - 2.0f) / 3f, tVolCtrl, 180f, true);
}
if (vResist > 4.0f && vResist < 8.0)
{
SoundFX(thumpHeavyGroup, (vResist - 4.0f) / 4f, tVolCtrl, 180f, true);
}
}
}
else
{
counter = 0;
}

}
else
{
SoundFX(atmosphereGroup, false);
SoundFX(smallRattlesGroup, false);
SoundFX(vibrationsGroup, false);
SoundFX(bigRattlesGroup, false);
SoundFX(rumbleGroup, false);
SoundFX(stressBigGroup, false);
}


}
else
{
SoundFX(atmosphereGroup, false);
SoundFX(smallRattlesGroup, false);
SoundFX(vibrationsGroup, false);
SoundFX(bigRattlesGroup, false);
SoundFX(rumbleGroup, false);
SoundFX(stressBigGroup, false);
}

engineMicro = 0;
engineThrust = 0;
engineAccel = 0;

}

public void onGamePaused()
{
gamePaused = true;
}

public void onGameUnpaused()
{
gamePaused = false;
}

public void OnDestroy()
{
GameEvents.onGamePause.Remove(new EventVoid.OnEvent(onGamePaused));
GameEvents.onGameUnpause.Remove(new EventVoid.OnEvent(onGameUnpaused));
GameEvents.onPartCouple.Remove(this.onVesselDock);
GameEvents.onPartUndock.Remove(this.onVesselUndock);
}
}
}

Oh and for legal reasons... The source code is the intellectual property of ensouensou licensed under a CC BY-NC-SA 4.0

Edited by CoriW
Link to comment
Share on other sites

While it's not certain, I would look at this:

void SoundFX(FXGroup fx, float volume, float volCtrl, float spread, bool play)
{
if (!gamePaused && !fx.audio.isPlaying && play == true)
{

Specifically the !fx.audio.isPlaying check.

This is from the error line of UnityEngine.AudioSource:get_isPlaying().

My assumption is that somehow the fx.audio object does not exist, therefore when the mod tries to see if fx.audio is playing, it can't find the fx.audio object and throws a nullRef.

You can prove this by adding a Debug.Log("1"); line just before and Debug.Log("2"); line after.

If it is in fact that line, there will be a 1 in the log just before the null ref error and the 2 will not log.

If the 1 does not log the error is earlier, if the 2 does log, the error is later in the code.

D.

Link to comment
Share on other sites

While it's not certain, I would look at this:

void SoundFX(FXGroup fx, float volume, float volCtrl, float spread, bool play)
{
if (!gamePaused && !fx.audio.isPlaying && play == true)
{

Specifically the !fx.audio.isPlaying check.

This is from the error line of UnityEngine.AudioSource:get_isPlaying().

My assumption is that somehow the fx.audio object does not exist, therefore when the mod tries to see if fx.audio is playing, it can't find the fx.audio object and throws a nullRef.

You can prove this by adding a Debug.Log("1"); line just before and Debug.Log("2"); line after.

If it is in fact that line, there will be a 1 in the log just before the null ref error and the 2 will not log.

If the 1 does not log the error is earlier, if the 2 does log, the error is later in the code.

D.

Hey Diazo, thanks for the reply.

The biggest problem that I'm having right now is that I can't debug it properly because I can't get it to throw the NullRef, I even tried something similar with Debug.Log to what you suggested before I came here for help, and just tried exactly what you suggested a few times before posting this. As I said in the OP the NullRef seems to be very hard to replicate. It seems to be exceedingly rare to happen and I can't figure out why it happens because I've been doing the same thing that caused it to happen the first time and it simply won't happen for me again. Meanwhile in the ShipEffects thread Enceos claims to get the NullRef frequently, where as I've only been able to get it once in a number of hours.

That being said I'm stuck between a rock and a hard place, I can't properly debug due to not being able to trigger the NullRef, even though I know the block of code that causes it... It's actually quite frustrating. Would you by chance have any idea's on how to fix something without being able to properly debug it as mentioned above?

Also too I'm not sure that this would be related to my problem but I've noticed that the original .dll is 24kb whereas every time I re-compile it, it comes out to be only 14kb while maintaining it's functionality...

EDIT: I just made a big step in the right direction. So I decided that I was getting nowhere and to start from scratch and thus I went back to the original .dll, and after some experimenting and tinkering I was able to find a way to reliably replicate the NullRef! Now what I'm going to do it re-compile it again and try what I did to replicate it, because I now have a sneaking suspicion that the reason I couldn't replicate it before was simply because the old .dll was compiled for 0.90.0 where as mile is compiled for 1.0.4... If that's what is really going on here then the solution to update the mod is as simple as a re-compile! :)

EDIT: Alright... So it wasn't as simple as a re-compile, however I still have found a way to reliably re-produce the NullRef, and so now I can actually finally pinpoint the problem.

EDIT: Okay so I finally have a definitive answer to the cause of the NullRef. @Diazo you were completely right, for some reason the fx.audio object does not exist when the mod tried to access it, now that I know that for sure all I have to do is code a solution.

Edited by CoriW
Link to comment
Share on other sites

You need to be able to reproduce the error in order to fix it. :/

In order to narrow it down, I would compile a version of the .dll that spammed Debug.Log's all over the place so Enceos can generate a log for you.

There's always a reason things behave differently, you just have to find it.

In this case, I would add Debug.Logs in the Start() method making sure the effects loaded correctly and the Debug.Logs in the SoundFX methods to narrow down exactly which line of code is throwing the nullRef.

D.

Link to comment
Share on other sites

Well... It took a lot of frustration but once I finally figured out how to replicate the problem I was able to very quickly find a solution. I now officially have the mod in a state in which it appears to be fully and completely functional in KSP 1.0.4! :)

Basically what was happening was that the fx.audio object was being removed for some reason which was throwing the NullRef, and my solution was to simply add a check to the part of the code that accessed the fx.audio object in order to check if it was null or not before trying to access it. However it didn't end there because after the fx.audio object was removed, while it wouldn't throw NullRef's anymore, the mod would stop functioning completely, so I had to add an else that said if fx.audio was null, to then re-create the fx.audio object so that the rest of the code could continue functioning normally.

Link to comment
Share on other sites

This thread is quite old. Please consider starting a new thread rather than reviving this one.

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...