/// <summary> /// Localizes the PLib strings. /// </summary> /// <param name="locale">The locale to use.</param> internal static void LocalizeItself(Localization.Locale locale) { if (locale == null) { throw new ArgumentNullException(nameof(locale)); } Localization.RegisterForTranslation(typeof(PLibStrings)); var assembly = Assembly.GetExecutingAssembly(); string locCode = locale.Code; if (string.IsNullOrEmpty(locCode)) { locCode = Localization.GetCurrentLanguageCode(); } try { using (var stream = assembly.GetManifestResourceStream( TRANSLATIONS_RES_PATH + locCode + TRANSLATIONS_EXT)) { if (stream != null) { // File.ReadAllLines does not work on streams unfortunately var lines = new List <string>(128); using (var reader = new StreamReader(stream, Encoding.UTF8)) { string line; while ((line = reader.ReadLine()) != null) { lines.Add(line); } } Localization.OverloadStrings(Localization.ExtractTranslatedStrings( lines.ToArray())); #if DEBUG PUtil.LogDebug("Localizing PLib Core to locale {0}".F(locCode)); #endif } } } catch (Exception e) { PUtil.LogWarning("Failed to load {0} localization for PLib Core:".F(locCode)); PUtil.LogExcWarn(e); } }
/// <summary> /// Retrieves a type using its full name (including namespace). However, the assembly /// name is optional, as this method searches all assemblies in the current /// AppDomain if it is null or empty. /// </summary> /// <param name="name">The type name to retrieve.</param> /// <param name="assemblyName">If specified, the name of the assembly that contains /// the type. No other assembly name will be searched if this parameter is not null /// or empty. The assembly name might not match the DLL name, use a decompiler to /// make sure.</param> /// <returns>The type, or null if the type is not found or cannot be loaded.</returns> public static Type GetTypeSafe(string name, string assemblyName = null) { Type type = null; if (string.IsNullOrEmpty(assemblyName)) { foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) { try { type = assembly.GetType(name, false); } catch (System.IO.IOException) { // The common parent of exceptions when the type requires another type // that cannot be loaded } catch (BadImageFormatException) { } if (type != null) { break; } } } else { try { type = Type.GetType(name + ", " + assemblyName, false); } catch (TargetInvocationException e) { PUtil.LogWarning("Unable to load type {0} from assembly {1}:".F(name, assemblyName)); PUtil.LogExcWarn(e); } catch (ArgumentException e) { // A generic type is loaded with bad arguments PUtil.LogWarning("Unable to load type {0} from assembly {1}:".F(name, assemblyName)); PUtil.LogExcWarn(e); } catch (System.IO.IOException) { // The common parent of exceptions when the type requires another type that // cannot be loaded } catch (BadImageFormatException) { } } return(type); }
/// <summary> /// Goes through the forwarded components, and picks the latest version of each to /// instantiate. /// </summary> public void Instantiate() { foreach (var pair in forwardedComponents) { // Sort value by version var versions = pair.Value.Components; int n = versions.Count; if (n > 0) { string id = pair.Key; versions.Sort(); var component = versions[n - 1]; latestComponents.GetOrAdd(id, component); #if DEBUG PRegistry.LogPatchDebug("Instantiating component {0} using version {1} from assembly {2}".F( id, component.Version, component.GetOwningAssembly().FullName)); #endif try { instantiatedComponents.GetOrAdd(id, component?.DoInitialize( PLibInstance)); } catch (Exception e) { PRegistry.LogPatchWarning("Error when instantiating component " + id + ":"); PUtil.LogException(e); } } } // Post initialize for component compatibility foreach (var pair in latestComponents) { try { pair.Value.PostInitialize(PLibInstance); } catch (Exception e) { PRegistry.LogPatchWarning("Error when instantiating component " + pair.Key + ":"); PUtil.LogException(e); } } }
/// <summary> /// Copies the sounds from one animation to another animation. /// </summary> /// <param name="dstAnim">The destination anim file name.</param> /// <param name="srcAnim">The source anim file name.</param> public static void CopySoundsToAnim(string dstAnim, string srcAnim) { if (string.IsNullOrEmpty(dstAnim)) { throw new ArgumentNullException(nameof(dstAnim)); } if (string.IsNullOrEmpty(srcAnim)) { throw new ArgumentNullException(nameof(srcAnim)); } var anim = Assets.GetAnim(dstAnim); if (anim != null) { var audioSheet = GameAudioSheets.Get(); var animData = anim.GetData(); // For each anim in the kanim, look for existing sound events under the old // anim's file name for (int i = 0; i < animData.animCount; i++) { string animName = animData.GetAnim(i)?.name ?? ""; var events = audioSheet.GetEvents(srcAnim + "." + animName); if (events != null) { #if DEBUG PUtil.LogDebug("Adding {0:D} audio event(s) to anim {1}.{2}".F(events. Count, dstAnim, animName)); #endif audioSheet.events[dstAnim + "." + animName] = events; } } } else { PUtil.LogWarning("Destination animation \"{0}\" not found!".F(dstAnim)); } }
/// <summary> /// Gets the shared data between components with this ID, serialized to the specified /// type. The shared data is retrieved, serialized with JSON, and reconstituted as type /// T in the memory space of the caller. /// /// The target type must exist and be a [JsonObject] in both this assembly and the /// target component's assembly. /// /// This method is somewhat slow and memory intensive, and should be used sparingly. /// </summary> /// <typeparam name="T">The data type to retrieve and into which to convert.</typeparam> /// <param name="defValue">The default value if the shared data is unset.</param> /// <returns>The data, or defValue if the shared data has not been set or cannot be serialized.</returns> public T GetSharedDataSerialized <T>(T defValue = default) { var remoteData = PRegistry.Instance.GetSharedData(ID); T result = defValue; using (var buffer = new MemoryStream(1024)) { try { SerializationSettings.Serialize(new StreamWriter(buffer, Encoding.UTF8), remoteData); buffer.Position = 0L; if (SerializationSettings.Deserialize(new StreamReader(buffer, Encoding.UTF8), typeof(T)) is T decoded) { result = decoded; } } catch (JsonException e) { PUtil.LogError("Unable to serialize shared data for component " + ID + ":"); PUtil.LogException(e); result = defValue; } } return(result); }
/// <summary> /// Patches a constructor manually. /// </summary> /// <param name="instance">The Harmony instance.</param> /// <param name="type">The class to modify.</param> /// <param name="arguments">The constructor's argument types.</param> /// <param name="prefix">The prefix to apply, or null if none.</param> /// <param name="postfix">The postfix to apply, or null if none.</param> public static void PatchConstructor(this Harmony instance, Type type, Type[] arguments, HarmonyMethod prefix = null, HarmonyMethod postfix = null) { if (type == null) { throw new ArgumentNullException(nameof(type)); } // Fetch the constructor try { var cons = type.GetConstructor(PPatchTools.BASE_FLAGS | BindingFlags.Static | BindingFlags.Instance, null, arguments, null); if (cons != null) { instance.Patch(cons, prefix, postfix); } else { PUtil.LogWarning("Unable to find constructor on type {0}".F(type. FullName)); } } catch (ArgumentException e) { PUtil.LogException(e); } }
/// <summary> /// Transpiles a method to replace calls to the specified victim methods with /// replacement methods, altering the call type if necessary. /// /// Each key to value pair must meet the criteria defined in /// ReplaceMethodCall(TranspiledMethod, MethodInfo, MethodInfo). /// </summary> /// <param name="method">The method to patch.</param> /// <param name="translation">A mapping from the old method calls to replace, to the /// new method calls to use instead.</param> /// <returns>A transpiled version of that method that replaces or removes all calls /// to the specified methods.</returns> /// <exception cref="ArgumentException">If any of the new methods' argument types do /// not exactly match the old methods' argument types.</exception> public static TranspiledMethod ReplaceMethodCall(TranspiledMethod method, IDictionary <MethodInfo, MethodInfo> translation) { if (method == null) { throw new ArgumentNullException(nameof(method)); } if (translation == null) { throw new ArgumentNullException(nameof(translation)); } // Sanity check arguments int replaced = 0; foreach (var pair in translation) { var victim = pair.Key; var newMethod = pair.Value; if (victim == null) { throw new ArgumentNullException(nameof(victim)); } if (newMethod != null) { PTranspilerTools.CompareMethodParams(victim, victim.GetParameterTypes(), newMethod); } else if (victim.ReturnType != typeof(void)) { throw new ArgumentException("Cannot remove method {0} with a return value". F(victim.Name)); } } foreach (var instruction in method) { var opcode = instruction.opcode; MethodInfo target; if ((opcode == OpCodes.Call || opcode == OpCodes.Calli || opcode == OpCodes. Callvirt) && translation.TryGetValue(target = instruction.operand as MethodInfo, out MethodInfo newMethod)) { if (newMethod != null) { // Replace with new method instruction.opcode = newMethod.IsStatic ? OpCodes.Call : OpCodes.Callvirt; instruction.operand = newMethod; yield return(instruction); } else { // Pop "this" if needed int n = target.GetParameters().Length; if (!target.IsStatic) { n++; } // Pop the arguments off the stack instruction.opcode = (n == 0) ? OpCodes.Nop : OpCodes.Pop; instruction.operand = null; yield return(instruction); for (int i = 0; i < n - 1; i++) { yield return(new CodeInstruction(OpCodes.Pop)); } } replaced++; } else { yield return(instruction); } } #if DEBUG if (replaced == 0) { if (translation.Count == 1) { // Diagnose the method that could not be replaced var items = new KeyValuePair <MethodInfo, MethodInfo> [1]; translation.CopyTo(items, 0); MethodInfo from = items[0].Key, to = items[0].Value; PUtil.LogWarning("No method calls replaced: {0}.{1} to {2}.{3}".F( from.DeclaringType.FullName, from.Name, to.DeclaringType.FullName, to.Name)); } else { PUtil.LogWarning("No method calls replaced (multiple replacements)"); } } #endif }
public static void LogAllFailedAsserts() { PUtil.LogWarning("PLib in mod " + Assembly.GetCallingAssembly().GetName()?.Name + " is logging ALL failed assertions!"); PTranspilerTools.LogAllFailedAsserts(); }
public static void LogAllExceptions() { PUtil.LogWarning("PLib in mod " + Assembly.GetCallingAssembly().GetName()?.Name + " is logging ALL unhandled exceptions!"); PTranspilerTools.LogAllExceptions(); }