diff --git a/UndertaleModLib/UndertaleChunks.cs b/UndertaleModLib/UndertaleChunks.cs index a411f29e9..dd91d66ea 100644 --- a/UndertaleModLib/UndertaleChunks.cs +++ b/UndertaleModLib/UndertaleChunks.cs @@ -121,6 +121,12 @@ internal override void UnserializeChunk(UndertaleReader reader) while (reader.Position < reader.Length) { lastChunk = reader.ReadChars(4); + if (lastChunk.Contains('\0')) + { + reader.SubmitWarning($"Invalid chunk name found at {reader.Position - 4:x}. " + + "Data file is most likely corrupt. Attempting partial load."); + break; + } reader.AllChunkNames.Add(lastChunk); uint length = reader.ReadUInt32(); reader.Position += length; @@ -129,7 +135,7 @@ internal override void UnserializeChunk(UndertaleReader reader) reader.Position = startPos; // Now, parse the chunks - while (reader.Position < startPos + Length) + foreach (string chunkName in reader.AllChunkNames) { UndertaleChunk chunk = reader.ReadUndertaleChunk(); if (chunk is not null) @@ -162,6 +168,11 @@ internal override uint UnserializeObjectCount(UndertaleReader reader) while (reader.Position < reader.Length) { string chunkName = reader.ReadChars(4); + if (chunkName.Contains('\0')) { + reader.SubmitObjectCountingError(new Exception($"Invalid chunk name found at {reader.Position - 4:x}. " + + "Data file is most likely corrupt. Attempting partial load.")); + break; + } reader.AllChunkNames.Add(chunkName); uint length = reader.ReadUInt32(); reader.Position += length; @@ -180,16 +191,29 @@ internal override uint UnserializeObjectCount(UndertaleReader reader) } // Read object counts for all chunks - while (reader.Position < startPos + Length) + foreach (string chunkName in reader.AllChunkNames) { - (uint count, UndertaleChunk chunk) = reader.CountChunkChildObjects(); - totalCount += count; + long prevPosition = reader.Position; + try + { + (uint count, UndertaleChunk chunk) = reader.CountChunkChildObjects(); + totalCount += count; - // Don't register a new chunk for GEN8 specifically - if (chunk.Name != "GEN8") + // Don't register a new chunk for GEN8 specifically + if (chunk.Name != "GEN8") + { + Chunks.Add(chunk.Name, chunk); + ChunksTypeDict.Add(chunk.GetType(), chunk); + } + } + catch (Exception e) { - Chunks.Add(chunk.Name, chunk); - ChunksTypeDict.Add(chunk.GetType(), chunk); + reader.SubmitObjectCountingError(e); + reader.SwitchReaderType(false); + // Safely jump to next chunk + reader.Position = prevPosition + 4; + uint length = reader.ReadUInt32(); + reader.Position += length; } } diff --git a/UndertaleModLib/UndertaleIO.cs b/UndertaleModLib/UndertaleIO.cs index 109ba99ad..001bae05e 100644 --- a/UndertaleModLib/UndertaleIO.cs +++ b/UndertaleModLib/UndertaleIO.cs @@ -278,21 +278,12 @@ public UndertaleData ReadUndertaleData() uint poolSize = 0; if (!ProcessObjectCountingErrors()) // process an exception from "FillUnserializeCountDictionaries()" { - try + if (!ReadOnlyGEN8) { - if (!ReadOnlyGEN8) - { - poolSize = data.FORM.UnserializeObjectCount(this); - } - } - catch (Exception e) - { - countUnserializeExc = e; - Debug.WriteLine(e); - - SwitchReaderType(false); + poolSize = data.FORM.UnserializeObjectCount(this); } } + ProcessObjectCountingErrors(); ListPtrsPool = null; // Initialize object pools @@ -378,7 +369,7 @@ public override bool ReadBoolean() private Dictionary objectPoolRev; private HashSet unreadObjects = new HashSet(); - private Exception countUnserializeExc = null; + private List countUnserializeExcs = new List(); private readonly Dictionary> unserializeFuncDict = new(); private readonly Dictionary staticObjCountDict = new(); private readonly Dictionary staticObjSizeDict = new(); @@ -389,25 +380,41 @@ private readonly BindingFlags publicStaticFlags private readonly Type delegateType = typeof(Func); private readonly Func blankCountFunc = _ => 0; + /// + /// Submit an error for external logging, intended for object count unserialization. + /// + /// The exception caught. + public void SubmitObjectCountingError(Exception e) + { + countUnserializeExcs.Add(e); + } + private bool ProcessObjectCountingErrors(uint poolSize = 0) { - if (countUnserializeExc is not null) + if (countUnserializeExcs.Count > 0) { try { string fileDir = Path.GetDirectoryName(Environment.ProcessPath); - File.WriteAllText(Path.Combine(fileDir, "unserializeCountError.txt"), - countUnserializeExc + "\n" + string unserializeErrorLog = ""; + foreach (Exception countUnserializeExc in countUnserializeExcs) + { + unserializeErrorLog += countUnserializeExc + "\n" + countUnserializeExc.Message + "\n" - + countUnserializeExc.StackTrace); + + countUnserializeExc.StackTrace + "\n\n"; + } + File.WriteAllText(Path.Combine(fileDir, "unserializeCountError.txt"), + unserializeErrorLog); - SubmitWarning("Warning - there was an error while trying to unserialize total object count.\n" + - "The error log is saved to \"unserializeCountError.txt\"." + + SubmitWarning($"Warning - there { + (countUnserializeExcs.Count == 1 ? "was an error" : $"were {countUnserializeExcs.Count} errors") + } while trying to unserialize total object count.\n" + + "The error log is saved to \"unserializeCountError.txt\". " + "Please report that error to UndertaleModTool GitHub."); } catch { } - countUnserializeExc = null; + countUnserializeExcs.Clear(); return true; } @@ -505,7 +512,7 @@ private void FillUnserializeCountDictionaries() catch (Exception e) { Debug.WriteLine(e); - countUnserializeExc = e; + countUnserializeExcs.Add(e); } } public Func GetUnserializeCountFunc(Type objType) @@ -732,12 +739,21 @@ public void ToHere() uint length = (uint)(endPos - startPos); if (length != expectedLength) { - int diff = (int)expectedLength - (int)length; - reader.SubmitWarning("WARNING: File specified length " + expectedLength + ", but read only " + length + " (" + diff + " padding?)"); - if (diff > 0) - reader.Position += (uint)diff; + if (expectedLength > length) + { + uint diff = expectedLength - length; + reader.SubmitWarning($"WARNING: File specified length {expectedLength}, but read only {length} ({diff} padding?)"); + if (reader.Position + diff >= reader.Length) + reader.Position = reader.Length; + else + reader.Position += diff; + } else - throw new IOException("Read underflow"); + { + uint diff = length - expectedLength; + throw new IOException($"Read underflow: File specified length {expectedLength}, but read {length} ({diff} too great)"); + //reader.Position -= diff; + } } } } diff --git a/UndertaleModLib/UndertaleLists.cs b/UndertaleModLib/UndertaleLists.cs index 4acae40ab..8c8c85720 100644 --- a/UndertaleModLib/UndertaleLists.cs +++ b/UndertaleModLib/UndertaleLists.cs @@ -116,6 +116,11 @@ public void Unserialize(UndertaleReader reader) /// public static uint UnserializeChildObjectCount(UndertaleReader reader) { + if (reader.AbsPosition == 0) + { + reader.SubmitWarning($"Attempted to count child objects of {typeof(T).FullName} given a null pointer. Defaulting to 0."); + return 0; + } // Read base object count; short-circuit if there's no objects uint count = reader.ReadUInt32(); if (count == 0) @@ -294,6 +299,11 @@ public void Unserialize(UndertaleReader reader) /// public static uint UnserializeChildObjectCount(UndertaleReader reader) { + if (reader.AbsPosition == 0) + { + reader.SubmitWarning($"Attempted to count child objects of {typeof(T).FullName} given a null pointer. Defaulting to 0."); + return 0; + } // Read base object count; short-circuit if there's no objects ushort count = reader.ReadUInt16(); if (count == 0) @@ -512,6 +522,11 @@ public void Unserialize(UndertaleReader reader) /// public static uint UnserializeChildObjectCount(UndertaleReader reader) { + if (reader.AbsPosition == 0) + { + reader.SubmitWarning($"Attempted to count child objects of {typeof(T).FullName} given a null pointer. Defaulting to 0."); + return 0; + } // Read base object count; short-circuit if there's no objects uint count = reader.ReadUInt32(); if (count == 0)