diff --git a/src/LibHac/Common/Keys/ExternalKeyReader.cs b/src/LibHac/Common/Keys/ExternalKeyReader.cs index 5267726d..34c14cc6 100644 --- a/src/LibHac/Common/Keys/ExternalKeyReader.cs +++ b/src/LibHac/Common/Keys/ExternalKeyReader.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Runtime.CompilerServices; +using LibHac.Diag; using LibHac.Fs; using LibHac.Spl; using LibHac.Util; @@ -11,6 +12,8 @@ namespace LibHac.Common.Keys { public static class ExternalKeyReader { + private const int ReadBufferSize = 1024; + // Contains info from a specific key being read from a file [DebuggerDisplay("{" + nameof(Name) + "}")] private struct SpecificKeyInfo @@ -165,7 +168,7 @@ namespace LibHac.Common.Keys if (reader == null) return; using var streamReader = new StreamReader(reader); - Span buffer = stackalloc char[1024]; + Span buffer = stackalloc char[ReadBufferSize]; var ctx = new KvPairReaderContext(streamReader, buffer); while (true) @@ -229,7 +232,7 @@ namespace LibHac.Common.Keys if (reader == null) return; using var streamReader = new StreamReader(reader); - Span buffer = stackalloc char[1024]; + Span buffer = stackalloc char[ReadBufferSize]; var ctx = new KvPairReaderContext(streamReader, buffer); // Estimate the number of keys by assuming each line is about 69 bytes. @@ -291,6 +294,8 @@ namespace LibHac.Common.Keys public Span CurrentValue; public int BufferPos; public bool NeedFillBuffer; + public bool HasReadEndOfFile; + public bool SkipNextLine; public KvPairReaderContext(TextReader reader, Span buffer) { @@ -300,6 +305,8 @@ namespace LibHac.Common.Keys CurrentValue = default; BufferPos = buffer.Length; NeedFillBuffer = true; + HasReadEndOfFile = false; + SkipNextLine = false; } } @@ -307,25 +314,36 @@ namespace LibHac.Common.Keys { ReadKey, NoKeyRead, + ReadComment, Finished, + LineTooLong, Error } private enum ReaderState { Initial, + Comment, Key, WhiteSpace1, Delimiter, Value, WhiteSpace2, - End + Success, + CommentSuccess, + Error } private static ReaderStatus GetKeyValuePair(ref KvPairReaderContext reader) { Span buffer = reader.Buffer; + if (reader.BufferPos == buffer.Length && reader.HasReadEndOfFile) + { + // There is no more text to parse. Return that we've finished. + return ReaderStatus.Finished; + } + if (reader.NeedFillBuffer) { // Move unread text to the front of the buffer @@ -333,22 +351,32 @@ namespace LibHac.Common.Keys int charsRead = reader.Reader.ReadBlock(buffer.Slice(buffer.Length - reader.BufferPos)); - if (charsRead == 0) - { - return ReaderStatus.Finished; - } - // ReadBlock will only read less than the buffer size if there's nothing left to read if (charsRead != reader.BufferPos) { buffer = buffer.Slice(0, buffer.Length - reader.BufferPos + charsRead); reader.Buffer = buffer; + reader.HasReadEndOfFile = true; } reader.NeedFillBuffer = false; reader.BufferPos = 0; } + if (reader.SkipNextLine) + { + while (reader.BufferPos < buffer.Length && !IsEndOfLine(buffer[reader.BufferPos])) + { + reader.BufferPos++; + } + + // Stop skipping once we reach a new line + if (reader.BufferPos < buffer.Length) + { + reader.SkipNextLine = false; + } + } + // Skip any empty lines while (reader.BufferPos < buffer.Length && IsEndOfLine(buffer[reader.BufferPos])) { @@ -385,6 +413,14 @@ namespace LibHac.Common.Keys // Decrement so we can process this character the next round through the state machine i--; continue; + case ReaderState.Initial when c == '#': + state = ReaderState.Comment; + keyOffset = i; + continue; + case ReaderState.Initial when IsEndOfLine(c): + // The line was empty. Update the buffer position to indicate a new line + reader.BufferPos = i; + continue; case ReaderState.Key when IsWhiteSpace(c): state = ReaderState.WhiteSpace1; keyLength = i - keyOffset; @@ -412,9 +448,9 @@ namespace LibHac.Common.Keys i--; continue; case ReaderState.Value when IsEndOfLine(c): - state = ReaderState.End; + state = ReaderState.Success; valueLength = i - valueOffset; - continue; + break; case ReaderState.Value when IsWhiteSpace(c): state = ReaderState.WhiteSpace2; valueLength = i - valueOffset; @@ -422,20 +458,89 @@ namespace LibHac.Common.Keys case ReaderState.WhiteSpace2 when IsWhiteSpace(c): continue; case ReaderState.WhiteSpace2 when IsEndOfLine(c): - state = ReaderState.End; - continue; - case ReaderState.End when IsEndOfLine(c): - continue; - case ReaderState.End when !IsEndOfLine(c): + state = ReaderState.Success; break; + case ReaderState.Comment when IsEndOfLine(c): + keyLength = i - keyOffset; + state = ReaderState.CommentSuccess; + break; + case ReaderState.Comment: + continue; + + // If none of the expected characters were found while in these states, the + // line is considered invalid. + case ReaderState.Initial: + case ReaderState.Key: + case ReaderState.WhiteSpace1: + case ReaderState.Delimiter: + state = ReaderState.Error; + continue; + case ReaderState.Error when !IsEndOfLine(c): + continue; } // We've exited the state machine for one reason or another break; } + // First check if hit the end of the buffer or read the entire buffer without seeing a new line + if (i == buffer.Length && !reader.HasReadEndOfFile) + { + reader.NeedFillBuffer = true; + + // If the entire buffer is part of a single long line + if (reader.BufferPos == 0 || reader.SkipNextLine) + { + reader.BufferPos = i; + + // The line might continue past the end of the current buffer, so skip the + // remainder of the line after the buffer is refilled. + reader.SkipNextLine = true; + return ReaderStatus.LineTooLong; + } + + return ReaderStatus.NoKeyRead; + } + + // The only way we should exit the loop in the "Value" or "WhiteSpace2" state is if we reached + // the end of the buffer in that state, meaning i == buffer.Length. + // Running out of buffer when we haven't read the end of the file will have been handled by the + // previous "if" block. If we get to this point in those states, we should be at the very end + // of the file which will be treated as the end of a line. + if (state == ReaderState.Value || state == ReaderState.WhiteSpace2) + { + Assert.AssertTrue(i == buffer.Length); + Assert.AssertTrue(reader.HasReadEndOfFile); + + // WhiteSpace2 will have already set this value + if (state == ReaderState.Value) + valueLength = i - valueOffset; + + state = ReaderState.Success; + } + + // Same situation as the two above states + if (state == ReaderState.Comment) + { + Assert.AssertTrue(i == buffer.Length); + Assert.AssertTrue(reader.HasReadEndOfFile); + + keyLength = i - keyOffset; + state = ReaderState.CommentSuccess; + } + + // Same as the above states except the final line was empty or whitespace. + if (state == ReaderState.Initial) + { + Assert.AssertTrue(i == buffer.Length); + Assert.AssertTrue(reader.HasReadEndOfFile); + + reader.BufferPos = i; + return ReaderStatus.NoKeyRead; + } + // If we successfully read both the key and value - if (state == ReaderState.End || state == ReaderState.WhiteSpace2) + if (state == ReaderState.Success) { reader.CurrentKey = reader.Buffer.Slice(keyOffset, keyLength); reader.CurrentValue = reader.Buffer.Slice(valueOffset, valueLength); @@ -444,40 +549,24 @@ namespace LibHac.Common.Keys return ReaderStatus.ReadKey; } - // We either ran out of buffer or hit an error reading the key-value pair. - // Advance to the end of the line if possible. - while (i < buffer.Length && !IsEndOfLine(buffer[i])) + if (state == ReaderState.CommentSuccess) { - i++; + reader.CurrentKey = reader.Buffer.Slice(keyOffset, keyLength); + reader.BufferPos = i; + + return ReaderStatus.ReadComment; } - // We don't have a complete line. Return that the buffer needs to be refilled. - if (i == buffer.Length) - { - reader.NeedFillBuffer = true; - return ReaderStatus.NoKeyRead; - } - - // If we hit a line with an error, it'll be returned as "CurrentKey" in the reader context - reader.CurrentKey = buffer.Slice(reader.BufferPos, i - reader.BufferPos); + // A bad line was encountered if we're in any of the other states + // Return the line as "CurrentKey" + reader.CurrentKey = reader.Buffer.Slice(reader.BufferPos, i - reader.BufferPos); reader.BufferPos = i; return ReaderStatus.Error; - static bool IsWhiteSpace(char c) - { - return c == ' ' || c == '\t'; - } - - static bool IsDelimiter(char c) - { - return c == '=' || c == ','; - } - - static bool IsEndOfLine(char c) - { - return c == '\0' || c == '\r' || c == '\n'; - } + static bool IsWhiteSpace(char c) => c == ' ' || c == '\t'; + static bool IsDelimiter(char c) => c == '=' || c == ','; + static bool IsEndOfLine(char c) => c == '\0' || c == '\r' || c == '\n'; static void ToLower(ref char c) { diff --git a/src/LibHac/Common/Keys/KeyDerivation.cs b/src/LibHac/Common/Keys/KeyDerivation.cs index d01b8cdc..22c3b77d 100644 --- a/src/LibHac/Common/Keys/KeyDerivation.cs +++ b/src/LibHac/Common/Keys/KeyDerivation.cs @@ -19,7 +19,7 @@ namespace LibHac.Common.Keys PopulateOldMasterKeys(keySet); DerivePerConsoleKeys(keySet); - DerivePerFirmwareKeys(keySet); + DerivePerGenerationKeys(keySet); DeriveNcaHeaderKey(keySet); DeriveSdCardKeys(keySet); } @@ -284,7 +284,7 @@ namespace LibHac.Common.Keys } } - private static void DerivePerFirmwareKeys(KeySet s) + private static void DerivePerGenerationKeys(KeySet s) { bool haveKakSource0 = !s.KeyAreaKeyApplicationSource.IsZeros(); bool haveKakSource1 = !s.KeyAreaKeyOceanSource.IsZeros();