2025-06-22 11:33:58 +08:00
|
|
|
|
using SharpNBT;
|
|
|
|
|
using SharpNBT.SNBT;
|
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.Globalization;
|
|
|
|
|
using System.IO;
|
|
|
|
|
using System.Numerics;
|
|
|
|
|
using System.Text;
|
|
|
|
|
|
|
|
|
|
namespace MCI18n.Utilities
|
|
|
|
|
{
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// <20>ṩ<EFBFBD><E1B9A9><EFBFBD>ڽ<EFBFBD>CompoundTag<61><67><EFBFBD><EFBFBD>ΪSNBT<42><54>ʽ<EFBFBD>ļ<EFBFBD><C4BC>Ĺ<EFBFBD><C4B9>߷<EFBFBD><DFB7><EFBFBD>
|
|
|
|
|
/// </summary>
|
|
|
|
|
public static class SnbtWriter
|
|
|
|
|
{
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// <20><>CompoundTag<61><67><EFBFBD><EFBFBD>ΪSNBT<42><54>ʽ<EFBFBD>ļ<EFBFBD>
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="tag">Ҫ<><D2AA><EFBFBD><EFBFBD><EFBFBD><EFBFBD>CompoundTag</param>
|
|
|
|
|
/// <param name="filePath"><3E>ļ<EFBFBD>·<EFBFBD><C2B7></param>
|
|
|
|
|
/// <param name="prettyPrint"><3E>Ƿ<EFBFBD><C7B7><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʽ<EFBFBD><CABD><EFBFBD><EFBFBD></param>
|
|
|
|
|
/// <exception cref="ArgumentNullException"><3E><>tagΪnullʱ<6C>׳<EFBFBD></exception>
|
|
|
|
|
/// <exception cref="IOException">д<><D0B4><EFBFBD>ļ<EFBFBD>ʧ<EFBFBD><CAA7>ʱ<EFBFBD>׳<EFBFBD></exception>
|
|
|
|
|
public static void SaveToFile(CompoundTag tag, string filePath, bool prettyPrint = true)
|
|
|
|
|
{
|
|
|
|
|
if (tag == null)
|
|
|
|
|
throw new ArgumentNullException(nameof(tag));
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
string snbt = ConvertToSnbt(tag, prettyPrint);
|
2025-06-24 22:56:20 +08:00
|
|
|
|
File.WriteAllText(filePath, snbt, new UTF8Encoding(false));
|
2025-06-22 11:33:58 +08:00
|
|
|
|
}
|
|
|
|
|
catch (Exception ex) when (ex is IOException or UnauthorizedAccessException or DirectoryNotFoundException)
|
|
|
|
|
{
|
|
|
|
|
throw new IOException($"<22><><EFBFBD><EFBFBD>SNBT<42>ļ<EFBFBD>ʧ<EFBFBD><CAA7> '{filePath}': {ex.Message}", ex);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// <20><>CompoundTagת<67><D7AA>ΪSNBT<42><54>ʽ<EFBFBD>ַ<EFBFBD><D6B7><EFBFBD>
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="tag">Ҫת<D2AA><D7AA><EFBFBD><EFBFBD>CompoundTag</param>
|
|
|
|
|
/// <param name="prettyPrint"><3E>Ƿ<EFBFBD><C7B7><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʽ</param>
|
|
|
|
|
/// <returns>SNBT<42><54>ʽ<EFBFBD>ַ<EFBFBD><D6B7><EFBFBD></returns>
|
|
|
|
|
public static string ConvertToSnbt(CompoundTag tag, bool prettyPrint = true)
|
|
|
|
|
{
|
|
|
|
|
if (prettyPrint)
|
|
|
|
|
return FormatCompound(tag, 0);
|
|
|
|
|
else
|
|
|
|
|
return tag.Stringify(true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static string FormatCompound(CompoundTag tag, int indentLevel)
|
|
|
|
|
{
|
|
|
|
|
if (tag.Count == 0)
|
|
|
|
|
return "{}";
|
|
|
|
|
|
|
|
|
|
var sb = new StringBuilder();
|
|
|
|
|
sb.AppendLine("{");
|
|
|
|
|
|
|
|
|
|
int i = 0;
|
|
|
|
|
foreach (var child in tag)
|
|
|
|
|
{
|
|
|
|
|
sb.Append('\t', indentLevel + 1);
|
|
|
|
|
sb.Append($"{EscapeName(child.Name)}: ");
|
|
|
|
|
sb.Append(FormatTag(child, indentLevel + 1));
|
|
|
|
|
|
|
|
|
|
//if (i < tag.Count - 1)
|
|
|
|
|
// sb.AppendLine(",");
|
|
|
|
|
//else
|
|
|
|
|
// sb.AppendLine();
|
|
|
|
|
sb.AppendLine();
|
|
|
|
|
i++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sb.Append('\t', indentLevel);
|
|
|
|
|
sb.Append("}");
|
|
|
|
|
return sb.ToString();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static string FormatTag(Tag tag, int indentLevel)
|
|
|
|
|
{
|
|
|
|
|
switch (tag)
|
|
|
|
|
{
|
|
|
|
|
case CompoundTag compoundTag:
|
|
|
|
|
return FormatCompound(compoundTag, indentLevel);
|
|
|
|
|
|
|
|
|
|
case ListTag listTag:
|
|
|
|
|
return FormatList(listTag, indentLevel);
|
|
|
|
|
|
|
|
|
|
case ByteArrayTag byteArrayTag:
|
|
|
|
|
return FormatArray(byteArrayTag, 'B', 'b', indentLevel);
|
|
|
|
|
|
|
|
|
|
case IntArrayTag intArrayTag:
|
|
|
|
|
return FormatArray(intArrayTag, 'I', null, indentLevel);
|
|
|
|
|
|
|
|
|
|
case LongArrayTag longArrayTag:
|
|
|
|
|
return FormatArray(longArrayTag, 'L', 'L', indentLevel);
|
|
|
|
|
|
|
|
|
|
case StringTag stringTag:
|
|
|
|
|
return $"\"{EscapeString(stringTag.Value)}\"";
|
|
|
|
|
|
|
|
|
|
case ByteTag byteTag:
|
|
|
|
|
//return $"{byteTag.Value}b";
|
|
|
|
|
return byteTag.Bool ? "true" : "false";
|
|
|
|
|
|
|
|
|
|
case ShortTag shortTag:
|
|
|
|
|
return $"{shortTag.Value}s";
|
|
|
|
|
|
|
|
|
|
case IntTag intTag:
|
|
|
|
|
return intTag.Value.ToString();
|
|
|
|
|
|
|
|
|
|
case LongTag longTag:
|
|
|
|
|
return $"{longTag.Value}L";
|
|
|
|
|
|
|
|
|
|
case FloatTag floatTag:
|
|
|
|
|
return $"{floatTag.Value.ToString("0.#####", CultureInfo.InvariantCulture)}f";
|
|
|
|
|
|
|
|
|
|
case DoubleTag doubleTag:
|
|
|
|
|
return $"{doubleTag.Value.ToString("0.#####", CultureInfo.InvariantCulture)}d";
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
return tag.Stringify(false);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static string FormatList(ListTag listTag, int indentLevel)
|
|
|
|
|
{
|
|
|
|
|
if (listTag.Count == 0)
|
|
|
|
|
return "[]";
|
|
|
|
|
|
|
|
|
|
var sb = new StringBuilder();
|
|
|
|
|
sb.AppendLine("[");
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < listTag.Count; i++)
|
|
|
|
|
{
|
|
|
|
|
sb.Append('\t', indentLevel + 1);
|
|
|
|
|
sb.Append(FormatTag(listTag[i], indentLevel + 1));
|
|
|
|
|
|
|
|
|
|
//if (i < listTag.Count - 1)
|
|
|
|
|
// sb.AppendLine(",");
|
|
|
|
|
//else
|
|
|
|
|
// sb.AppendLine();
|
|
|
|
|
sb.AppendLine();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sb.Append('\t', indentLevel);
|
|
|
|
|
sb.Append("]");
|
|
|
|
|
return sb.ToString();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static string FormatArray<T>(ArrayTag<T> arrayTag, char prefix, char? suffix, int indentLevel)
|
|
|
|
|
where T : unmanaged, INumber<T>
|
|
|
|
|
{
|
|
|
|
|
if (arrayTag.Count == 0)
|
|
|
|
|
return $"[{prefix};]";
|
|
|
|
|
|
|
|
|
|
var sb = new StringBuilder();
|
|
|
|
|
sb.Append($"[{prefix};");
|
|
|
|
|
|
|
|
|
|
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ͣ<EFBFBD><CDA3><EFBFBD><EFBFBD><EFBFBD>ͨ<EFBFBD><CDA8><EFBFBD><EFBFBD>ʹ<EFBFBD>ö<EFBFBD><C3B6>и<EFBFBD>ʽ
|
|
|
|
|
for (int i = 0; i < arrayTag.Count; i++)
|
|
|
|
|
{
|
|
|
|
|
if (i > 0)
|
|
|
|
|
sb.AppendLine();
|
|
|
|
|
//sb.Append("\r\n");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
sb.Append(arrayTag[i]);
|
|
|
|
|
|
|
|
|
|
if (suffix != null)
|
|
|
|
|
sb.Append(suffix.Value);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sb.Append("]");
|
|
|
|
|
return sb.ToString();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static string EscapeName(string name)
|
|
|
|
|
{
|
|
|
|
|
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ư<EFBFBD><C6B0><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ַ<EFBFBD><D6B7><EFBFBD><EFBFBD><EFBFBD>Ҫ<EFBFBD><D2AA><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
|
|
|
|
if (string.IsNullOrEmpty(name) || NeedsQuotes(name))
|
|
|
|
|
return $"\"{EscapeString(name)}\"";
|
|
|
|
|
return name;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static bool NeedsQuotes(string s)
|
|
|
|
|
{
|
|
|
|
|
foreach (char c in s)
|
|
|
|
|
{
|
|
|
|
|
if (!char.IsLetterOrDigit(c) && c != '_' && c != '-' && c != '.')
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static string EscapeString(string str)
|
|
|
|
|
{
|
|
|
|
|
if (string.IsNullOrEmpty(str))
|
|
|
|
|
return str;
|
|
|
|
|
|
|
|
|
|
return str.Replace("\\", "\\\\")
|
|
|
|
|
.Replace("\"", "\\\"")
|
|
|
|
|
.Replace("\n", "\\n")
|
|
|
|
|
.Replace("\r", "\\r")
|
|
|
|
|
.Replace("\t", "\\t");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|