using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Abstractions.Extractors.Unity.Helpers;
using Abstractions.Extractors.Unity.Services;
namespace Abstractions.Extractors.Unity;
/// Extracts a TextAsset's raw content as .csv/.json/.txt, detected from its shape. This is
/// where CSV/json/plain-text game data (e.g. the *.bytes master tables) comes out.
public sealed class TextAssetExtractor : IAssetExtractor
{
public IReadOnlyCollection HandledTypes { get; } = ["TextAsset"];
public async Task ExtractAsync(ExtractionContext context, CancellationToken cancellationToken = default)
{
var scriptField = context.BaseField.Get("m_Script");
var bytes = scriptField.IsDummy ? [] : scriptField.AsByteArray;
var content = ReflectionHelper.Utf8IgnoreErrors.GetString(bytes);
var trimmed = content.Trim();
var isJson = (trimmed.StartsWith('{') || trimmed.StartsWith('[')) &&
(trimmed.EndsWith('}') || trimmed.EndsWith(']'));
var isCsv = !isJson && LooksLikeCsv(trimmed);
var ext = isJson ? ".json" : isCsv ? ".csv" : ".txt";
await ObjectSerializer.WriteTextAsync(content, context.OutputPathNoExtension + ext, cancellationToken);
var info = new Dictionary
{
["size"] = content.Length,
["type"] = isJson ? "json" : isCsv ? "csv" : "text",
["encoding"] = "utf-8",
};
await ObjectSerializer.WriteJsonAsync(info, context.OutputPathNoExtension + "_info.json", cancellationToken);
return true;
}
/// Header row and first data row have the same (non-zero) comma count. Simple, but
/// avoids false-positiving on ordinary prose that happens to contain a comma somewhere.
private static bool LooksLikeCsv(string trimmed)
{
var lines = trimmed.ReplaceLineEndings("\n").Split('\n', StringSplitOptions.RemoveEmptyEntries);
if (lines.Length < 2) return false;
var headerCommas = lines[0].Count(c => c == ',');
return headerCommas > 0 && headerCommas == lines[1].Count(c => c == ',');
}
}