wabbajack/Compression.BSA/Utils.cs

218 lines
6.8 KiB
C#
Raw Normal View History

using System;
2019-07-28 23:04:23 +00:00
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading.Tasks;
2020-03-23 12:57:18 +00:00
using Wabbajack.Common;
2019-08-28 03:22:57 +00:00
using Path = Alphaleonis.Win32.Filesystem.Path;
2019-07-28 23:04:23 +00:00
2021-01-29 04:02:26 +00:00
// Yeah, we know, but BSAs use UTF7, that's how old they are
#pragma warning disable 618
namespace Compression.BSA
2019-07-28 23:04:23 +00:00
{
public static class BSAUtils
2019-07-28 23:04:23 +00:00
{
2020-01-10 13:16:41 +00:00
private static readonly Encoding Windows1252;
2019-09-14 04:35:42 +00:00
static BSAUtils()
2020-01-10 13:16:41 +00:00
{
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
Windows1252 = Encoding.GetEncoding(1252);
}
2019-09-08 03:34:18 +00:00
private static Encoding GetEncoding(VersionType version)
{
2021-01-29 04:02:26 +00:00
return version switch
{
VersionType.TES3 => Encoding.ASCII,
VersionType.SSE => Windows1252,
_ => Encoding.UTF7
};
2019-09-08 03:34:18 +00:00
}
2019-09-08 03:34:18 +00:00
public static string ReadStringLen(this BinaryReader rdr, VersionType version)
2019-07-28 23:04:23 +00:00
{
var len = rdr.ReadByte();
if (len == 0) return string.Empty;
2019-09-08 22:44:15 +00:00
2019-07-28 23:04:23 +00:00
var bytes = rdr.ReadBytes(len - 1);
rdr.ReadByte();
2019-09-08 03:34:18 +00:00
return GetEncoding(version).GetString(bytes);
2019-07-28 23:04:23 +00:00
}
2019-09-08 03:34:18 +00:00
public static string ReadStringLenNoTerm(this BinaryReader rdr, VersionType version)
{
var len = rdr.ReadByte();
var bytes = rdr.ReadBytes(len);
2019-09-08 03:34:18 +00:00
return GetEncoding(version).GetString(bytes);
}
2019-09-08 03:34:18 +00:00
public static string ReadStringTerm(this BinaryReader rdr, VersionType version)
2019-07-28 23:04:23 +00:00
{
2019-09-14 04:35:42 +00:00
var acc = new List<byte>();
2019-07-28 23:04:23 +00:00
while (true)
{
var c = rdr.ReadByte();
if (c == '\0') break;
acc.Add(c);
}
2019-09-14 04:35:42 +00:00
2019-09-08 03:34:18 +00:00
return GetEncoding(version).GetString(acc.ToArray());
2019-07-28 23:04:23 +00:00
}
public static string ReadStringLenTerm(this ReadOnlyMemorySlice<byte> bytes, VersionType version)
{
if (bytes.Length <= 1) return string.Empty;
return GetEncoding(version).GetString(bytes.Slice(1, bytes[0]));
}
public static string ReadStringTerm(this ReadOnlyMemorySlice<byte> bytes, VersionType version)
{
if (bytes.Length <= 1) return string.Empty;
return GetEncoding(version).GetString(bytes[0..^1]);
}
2019-07-28 23:04:23 +00:00
/// <summary>
2021-01-29 04:02:26 +00:00
/// Returns \0 terminated bytes for a string encoded with a given BSA version's encoding format
2019-07-28 23:04:23 +00:00
/// </summary>
/// <param name="val"></param>
2021-01-29 04:02:26 +00:00
/// <param name="version"></param>
2019-07-28 23:04:23 +00:00
/// <returns></returns>
2020-03-23 12:57:18 +00:00
public static byte[] ToBZString(this RelativePath val, VersionType version)
2019-07-28 23:04:23 +00:00
{
2020-03-23 12:57:18 +00:00
var b = GetEncoding(version).GetBytes((string)val);
2019-07-28 23:04:23 +00:00
var b2 = new byte[b.Length + 2];
b.CopyTo(b2, 1);
2019-09-14 04:35:42 +00:00
b2[0] = (byte) (b.Length + 1);
return b2;
}
/// <summary>
2019-09-14 04:35:42 +00:00
/// Returns bytes for unterminated string with a count at the start
/// </summary>
/// <param name="val"></param>
/// <returns></returns>
2020-03-23 12:57:18 +00:00
public static byte[] ToBSString(this RelativePath val)
{
2020-03-23 12:57:18 +00:00
var b = Encoding.ASCII.GetBytes((string)val);
var b2 = new byte[b.Length + 1];
b.CopyTo(b2, 1);
2019-09-14 04:35:42 +00:00
b2[0] = (byte) b.Length;
2019-07-28 23:04:23 +00:00
return b2;
}
/// <summary>
2021-01-29 04:02:26 +00:00
/// Returns bytes for a string with a length prefix, version is the BSA version
2019-07-28 23:04:23 +00:00
/// </summary>
/// <param name="val"></param>
2021-01-29 04:02:26 +00:00
/// <param name="version"></param>
2019-07-28 23:04:23 +00:00
/// <returns></returns>
2019-09-08 03:34:18 +00:00
public static byte[] ToTermString(this string val, VersionType version)
2019-07-28 23:04:23 +00:00
{
2019-09-08 03:34:18 +00:00
var b = GetEncoding(version).GetBytes(val);
2019-07-28 23:04:23 +00:00
var b2 = new byte[b.Length + 1];
b.CopyTo(b2, 0);
2019-09-14 04:35:42 +00:00
b[0] = (byte) b.Length;
2019-07-28 23:04:23 +00:00
return b2;
}
2020-03-23 12:57:18 +00:00
public static byte[] ToTermString(this RelativePath val, VersionType version)
{
return ((string)val).ToTermString(version);
}
2019-07-28 23:04:23 +00:00
public static ulong GetBSAHash(this string name)
{
name = name.Replace('/', '\\');
return GetBSAHash(Path.ChangeExtension(name, null), Path.GetExtension(name));
}
2020-03-23 12:57:18 +00:00
public static ulong GetBSAHash(this RelativePath name)
{
return ((string)name).GetBSAHash();
}
public static ulong GetFolderBSAHash(this RelativePath name)
{
return GetBSAHash((string)name, "");
}
2019-07-28 23:04:23 +00:00
public static ulong GetBSAHash(this string name, string ext)
2019-07-28 23:04:23 +00:00
{
name = name.ToLowerInvariant();
ext = ext.ToLowerInvariant();
2019-09-08 22:44:15 +00:00
if (string.IsNullOrEmpty(name))
return 0;
var hashBytes = new[]
2019-07-28 23:04:23 +00:00
{
2019-09-14 04:35:42 +00:00
(byte) (name.Length == 0 ? '\0' : name[name.Length - 1]),
(byte) (name.Length < 3 ? '\0' : name[name.Length - 2]),
(byte) name.Length,
(byte) name[0]
2019-07-28 23:04:23 +00:00
};
var hash1 = BitConverter.ToUInt32(hashBytes, 0);
switch (ext)
{
case ".kf":
hash1 |= 0x80;
break;
case ".nif":
hash1 |= 0x8000;
break;
case ".dds":
hash1 |= 0x8080;
break;
case ".wav":
hash1 |= 0x80000000;
break;
}
uint hash2 = 0;
2019-09-14 04:35:42 +00:00
for (var i = 1; i < name.Length - 2; i++) hash2 = hash2 * 0x1003f + (byte) name[i];
2019-07-28 23:04:23 +00:00
uint hash3 = 0;
2019-09-14 04:35:42 +00:00
for (var i = 0; i < ext.Length; i++) hash3 = hash3 * 0x1003f + (byte) ext[i];
2019-07-28 23:04:23 +00:00
2019-09-14 04:35:42 +00:00
return ((ulong) (hash2 + hash3) << 32) + hash1;
2019-07-28 23:04:23 +00:00
}
public static void CopyToLimit(this Stream frm, Stream tw, int limit)
{
2019-09-14 04:35:42 +00:00
var buff = new byte[1024];
while (limit > 0)
{
2019-09-14 04:35:42 +00:00
var to_read = Math.Min(buff.Length, limit);
var read = frm.Read(buff, 0, to_read);
if (read == 0)
throw new Exception("End of stream before end of limit");
tw.Write(buff, 0, read);
limit -= read;
}
2019-09-14 04:35:42 +00:00
tw.Flush();
}
public static async Task CopyToLimitAsync(this Stream frm, Stream tw, int limit)
{
var buff = new byte[1024];
while (limit > 0)
{
var to_read = Math.Min(buff.Length, limit);
var read = await frm.ReadAsync(buff, 0, to_read);
if (read == 0)
throw new Exception("End of stream before end of limit");
await tw.WriteAsync(buff, 0, read);
limit -= read;
}
await tw.FlushAsync();
}
2019-07-28 23:04:23 +00:00
}
2020-01-10 13:16:41 +00:00
}