Change most things to new byte strings, introduce new ResourceLoader and Logger fully.

This commit is contained in:
Ottermandias 2022-03-06 16:45:16 +01:00
parent 5d77cd5514
commit f5fccb0235
55 changed files with 2681 additions and 2730 deletions

View file

@ -1,17 +1,21 @@
using System;
using System.IO;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Penumbra.GameData.Util;
namespace Penumbra.GameData.ByteString;
[JsonConverter( typeof( FullPathConverter ) )]
public readonly struct FullPath : IComparable, IEquatable< FullPath >
{
public readonly string FullName;
public readonly Utf8String InternalName;
public readonly ulong Crc64;
public static readonly FullPath Empty = new(string.Empty);
public FullPath( DirectoryInfo baseDir, NewRelPath relPath )
public FullPath( DirectoryInfo baseDir, Utf8RelPath relPath )
: this( Path.Combine( baseDir.FullName, relPath.ToString() ) )
{ }
@ -19,10 +23,11 @@ public readonly struct FullPath : IComparable, IEquatable< FullPath >
: this( file.FullName )
{ }
public FullPath( string s )
{
FullName = s;
InternalName = Utf8String.FromString( FullName, out var name, true ) ? name : Utf8String.Empty;
InternalName = Utf8String.FromString( FullName, out var name, true ) ? name.Replace( ( byte )'\\', ( byte )'/' ) : Utf8String.Empty;
Crc64 = Functions.ComputeCrc64( InternalName.Span );
}
@ -35,9 +40,9 @@ public readonly struct FullPath : IComparable, IEquatable< FullPath >
public string Name
=> Path.GetFileName( FullName );
public bool ToGamePath( DirectoryInfo dir, out NewGamePath path )
public bool ToGamePath( DirectoryInfo dir, out Utf8GamePath path )
{
path = NewGamePath.Empty;
path = Utf8GamePath.Empty;
if( !InternalName.IsAscii || !FullName.StartsWith( dir.FullName ) )
{
return false;
@ -45,13 +50,13 @@ public readonly struct FullPath : IComparable, IEquatable< FullPath >
var substring = InternalName.Substring( dir.FullName.Length + 1 );
path = new NewGamePath( substring.Replace( ( byte )'\\', ( byte )'/' ) );
path = new Utf8GamePath( substring );
return true;
}
public bool ToRelPath( DirectoryInfo dir, out NewRelPath path )
public bool ToRelPath( DirectoryInfo dir, out Utf8RelPath path )
{
path = NewRelPath.Empty;
path = Utf8RelPath.Empty;
if( !FullName.StartsWith( dir.FullName ) )
{
return false;
@ -59,7 +64,7 @@ public readonly struct FullPath : IComparable, IEquatable< FullPath >
var substring = InternalName.Substring( dir.FullName.Length + 1 );
path = new NewRelPath( substring );
path = new Utf8RelPath( substring.Replace( ( byte )'/', ( byte )'\\' ) );
return true;
}
@ -88,9 +93,35 @@ public readonly struct FullPath : IComparable, IEquatable< FullPath >
return InternalName.Equals( other.InternalName );
}
public bool IsRooted
=> new Utf8GamePath( InternalName ).IsRooted();
public override int GetHashCode()
=> InternalName.Crc32;
public override string ToString()
=> FullName;
public class FullPathConverter : JsonConverter
{
public override bool CanConvert( Type objectType )
=> objectType == typeof( FullPath );
public override object ReadJson( JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer )
{
var token = JToken.Load( reader ).ToString();
return new FullPath( token );
}
public override bool CanWrite
=> true;
public override void WriteJson( JsonWriter writer, object? value, JsonSerializer serializer )
{
if( value is FullPath p )
{
serializer.Serialize( writer, p.ToString() );
}
}
}
}

View file

@ -3,20 +3,21 @@ using System.IO;
using Dalamud.Utility;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Penumbra.GameData.Util;
namespace Penumbra.GameData.ByteString;
// NewGamePath wrap some additional validity checking around Utf8String,
// provide some filesystem helpers, and conversion to Json.
[JsonConverter( typeof( NewGamePathConverter ) )]
public readonly struct NewGamePath : IEquatable< NewGamePath >, IComparable< NewGamePath >, IDisposable
[JsonConverter( typeof( Utf8GamePathConverter ) )]
public readonly struct Utf8GamePath : IEquatable< Utf8GamePath >, IComparable< Utf8GamePath >, IDisposable
{
public const int MaxGamePathLength = 256;
public readonly Utf8String Path;
public static readonly NewGamePath Empty = new(Utf8String.Empty);
public readonly Utf8String Path;
public static readonly Utf8GamePath Empty = new(Utf8String.Empty);
internal NewGamePath( Utf8String s )
internal Utf8GamePath( Utf8String s )
=> Path = s;
public int Length
@ -25,16 +26,16 @@ public readonly struct NewGamePath : IEquatable< NewGamePath >, IComparable< New
public bool IsEmpty
=> Path.IsEmpty;
public NewGamePath ToLower()
public Utf8GamePath ToLower()
=> new(Path.AsciiToLower());
public static unsafe bool FromPointer( byte* ptr, out NewGamePath path, bool lower = false )
public static unsafe bool FromPointer( byte* ptr, out Utf8GamePath path, bool lower = false )
{
var utf = new Utf8String( ptr );
return ReturnChecked( utf, out path, lower );
}
public static bool FromSpan( ReadOnlySpan< byte > data, out NewGamePath path, bool lower = false )
public static bool FromSpan( ReadOnlySpan< byte > data, out Utf8GamePath path, bool lower = false )
{
var utf = Utf8String.FromSpanUnsafe( data, false, null, null );
return ReturnChecked( utf, out path, lower );
@ -43,7 +44,7 @@ public readonly struct NewGamePath : IEquatable< NewGamePath >, IComparable< New
// Does not check for Forward/Backslashes due to assuming that SE-strings use the correct one.
// Does not check for initial slashes either, since they are assumed to be by choice.
// Checks for maxlength, ASCII and lowercase.
private static bool ReturnChecked( Utf8String utf, out NewGamePath path, bool lower = false )
private static bool ReturnChecked( Utf8String utf, out Utf8GamePath path, bool lower = false )
{
path = Empty;
if( !utf.IsAscii || utf.Length > MaxGamePathLength )
@ -51,14 +52,17 @@ public readonly struct NewGamePath : IEquatable< NewGamePath >, IComparable< New
return false;
}
path = new NewGamePath( lower ? utf.AsciiToLower() : utf );
path = new Utf8GamePath( lower ? utf.AsciiToLower() : utf );
return true;
}
public NewGamePath Clone()
public Utf8GamePath Clone()
=> new(Path.Clone());
public static bool FromString( string? s, out NewGamePath path, bool toLower = false )
public static explicit operator Utf8GamePath( string s )
=> FromString( s, out var p, true ) ? p : Empty;
public static bool FromString( string? s, out Utf8GamePath path, bool toLower = false )
{
path = Empty;
if( s.IsNullOrEmpty() )
@ -83,11 +87,11 @@ public readonly struct NewGamePath : IEquatable< NewGamePath >, IComparable< New
return false;
}
path = new NewGamePath( ascii );
path = new Utf8GamePath( ascii );
return true;
}
public static bool FromFile( FileInfo file, DirectoryInfo baseDir, out NewGamePath path, bool toLower = false )
public static bool FromFile( FileInfo file, DirectoryInfo baseDir, out Utf8GamePath path, bool toLower = false )
{
path = Empty;
if( !file.FullName.StartsWith( baseDir.FullName ) )
@ -111,13 +115,13 @@ public readonly struct NewGamePath : IEquatable< NewGamePath >, IComparable< New
return idx == -1 ? Utf8String.Empty : Path.Substring( idx );
}
public bool Equals( NewGamePath other )
public bool Equals( Utf8GamePath other )
=> Path.Equals( other.Path );
public override int GetHashCode()
=> Path.GetHashCode();
public int CompareTo( NewGamePath other )
public int CompareTo( Utf8GamePath other )
=> Path.CompareTo( other.Path );
public override string ToString()
@ -132,17 +136,17 @@ public readonly struct NewGamePath : IEquatable< NewGamePath >, IComparable< New
&& ( Path[ 0 ] >= 'A' && Path[ 0 ] <= 'Z' || Path[ 0 ] >= 'a' && Path[ 0 ] <= 'z' )
&& Path[ 1 ] == ':';
private class NewGamePathConverter : JsonConverter
public class Utf8GamePathConverter : JsonConverter
{
public override bool CanConvert( Type objectType )
=> objectType == typeof( NewGamePath );
=> objectType == typeof( Utf8GamePath );
public override object ReadJson( JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer )
{
var token = JToken.Load( reader ).ToString();
return FromString( token, out var p, true )
? p
: throw new JsonException( $"Could not convert \"{token}\" to {nameof( NewGamePath )}." );
: throw new JsonException( $"Could not convert \"{token}\" to {nameof( Utf8GamePath )}." );
}
public override bool CanWrite
@ -150,10 +154,13 @@ public readonly struct NewGamePath : IEquatable< NewGamePath >, IComparable< New
public override void WriteJson( JsonWriter writer, object? value, JsonSerializer serializer )
{
if( value is NewGamePath p )
if( value is Utf8GamePath p )
{
serializer.Serialize( writer, p.ToString() );
}
}
}
public GamePath ToGamePath()
=> GamePath.GenerateUnchecked( ToString() );
}

View file

@ -1,23 +1,28 @@
using System;
using System.IO;
using Dalamud.Utility;
using Microsoft.VisualBasic.CompilerServices;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace Penumbra.GameData.ByteString;
[JsonConverter( typeof( NewRelPathConverter ) )]
public readonly struct NewRelPath : IEquatable< NewRelPath >, IComparable< NewRelPath >, IDisposable
[JsonConverter( typeof( Utf8RelPathConverter ) )]
public readonly struct Utf8RelPath : IEquatable< Utf8RelPath >, IComparable< Utf8RelPath >, IDisposable
{
public const int MaxRelPathLength = 250;
public readonly Utf8String Path;
public static readonly NewRelPath Empty = new(Utf8String.Empty);
public readonly Utf8String Path;
public static readonly Utf8RelPath Empty = new(Utf8String.Empty);
internal NewRelPath( Utf8String path )
internal Utf8RelPath( Utf8String path )
=> Path = path;
public static bool FromString( string? s, out NewRelPath path )
public static explicit operator Utf8RelPath( string s )
=> FromString( s, out var p ) ? p : Empty;
public static bool FromString( string? s, out Utf8RelPath path )
{
path = Empty;
if( s.IsNullOrEmpty() )
@ -42,11 +47,11 @@ public readonly struct NewRelPath : IEquatable< NewRelPath >, IComparable< NewRe
return false;
}
path = new NewRelPath( ascii );
path = new Utf8RelPath( ascii );
return true;
}
public static bool FromFile( FileInfo file, DirectoryInfo baseDir, out NewRelPath path )
public static bool FromFile( FileInfo file, DirectoryInfo baseDir, out Utf8RelPath path )
{
path = Empty;
if( !file.FullName.StartsWith( baseDir.FullName ) )
@ -58,7 +63,7 @@ public readonly struct NewRelPath : IEquatable< NewRelPath >, IComparable< NewRe
return FromString( substring, out path );
}
public static bool FromFile( FullPath file, DirectoryInfo baseDir, out NewRelPath path )
public static bool FromFile( FullPath file, DirectoryInfo baseDir, out Utf8RelPath path )
{
path = Empty;
if( !file.FullName.StartsWith( baseDir.FullName ) )
@ -70,10 +75,10 @@ public readonly struct NewRelPath : IEquatable< NewRelPath >, IComparable< NewRe
return FromString( substring, out path );
}
public NewRelPath( NewGamePath gamePath )
public Utf8RelPath( Utf8GamePath gamePath )
=> Path = gamePath.Path.Replace( ( byte )'/', ( byte )'\\' );
public unsafe NewGamePath ToGamePath( int skipFolders = 0 )
public unsafe Utf8GamePath ToGamePath( int skipFolders = 0 )
{
var idx = 0;
while( skipFolders > 0 )
@ -82,7 +87,7 @@ public readonly struct NewRelPath : IEquatable< NewRelPath >, IComparable< NewRe
--skipFolders;
if( idx <= 0 )
{
return NewGamePath.Empty;
return Utf8GamePath.Empty;
}
}
@ -91,13 +96,13 @@ public readonly struct NewRelPath : IEquatable< NewRelPath >, IComparable< NewRe
ByteStringFunctions.Replace( ptr, length, ( byte )'\\', ( byte )'/' );
ByteStringFunctions.AsciiToLowerInPlace( ptr, length );
var utf = new Utf8String().Setup( ptr, length, null, true, true, true, true );
return new NewGamePath( utf );
return new Utf8GamePath( utf );
}
public int CompareTo( NewRelPath rhs )
public int CompareTo( Utf8RelPath rhs )
=> Path.CompareTo( rhs.Path );
public bool Equals( NewRelPath other )
public bool Equals( Utf8RelPath other )
=> Path.Equals( other.Path );
public override string ToString()
@ -106,17 +111,17 @@ public readonly struct NewRelPath : IEquatable< NewRelPath >, IComparable< NewRe
public void Dispose()
=> Path.Dispose();
private class NewRelPathConverter : JsonConverter
public class Utf8RelPathConverter : JsonConverter
{
public override bool CanConvert( Type objectType )
=> objectType == typeof( NewRelPath );
=> objectType == typeof( Utf8RelPath );
public override object ReadJson( JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer )
{
var token = JToken.Load( reader ).ToString();
return FromString( token, out var p )
? p
: throw new JsonException( $"Could not convert \"{token}\" to {nameof( NewRelPath )}." );
: throw new JsonException( $"Could not convert \"{token}\" to {nameof( Utf8RelPath )}." );
}
public override bool CanWrite
@ -124,7 +129,7 @@ public readonly struct NewRelPath : IEquatable< NewRelPath >, IComparable< NewRe
public override void WriteJson( JsonWriter writer, object? value, JsonSerializer serializer )
{
if( value is NewRelPath p )
if( value is Utf8RelPath p )
{
serializer.Serialize( writer, p.ToString() );
}

View file

@ -75,6 +75,19 @@ public sealed unsafe partial class Utf8String : IEquatable< Utf8String >, ICompa
return ByteStringFunctions.AsciiCaselessCompare( _path, Length, other._path, other.Length );
}
public bool StartsWith( Utf8String other )
{
var otherLength = other.Length;
return otherLength <= Length && ByteStringFunctions.Equals( other.Path, otherLength, Path, otherLength );
}
public bool EndsWith( Utf8String other )
{
var otherLength = other.Length;
var offset = Length - otherLength;
return offset >= 0 && ByteStringFunctions.Equals( other.Path, otherLength, Path + offset, otherLength );
}
public bool StartsWith( params char[] chars )
{
if( chars.Length > Length )