2020-05-31 17:07:22 +00:00
|
|
|
|
using SanAndreasUnity.Importing.Items.Placements;
|
|
|
|
|
using SanAndreasUnity.Utilities;
|
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.Globalization;
|
|
|
|
|
using System.IO;
|
|
|
|
|
using System.Linq;
|
|
|
|
|
#if !ENABLE_IL2CPP
|
|
|
|
|
using System.Linq.Expressions;
|
|
|
|
|
#endif
|
|
|
|
|
using System.Reflection;
|
2021-01-31 00:34:23 +00:00
|
|
|
|
using UnityEngine;
|
2020-05-31 17:07:22 +00:00
|
|
|
|
|
|
|
|
|
namespace SanAndreasUnity.Importing.Items
|
|
|
|
|
{
|
|
|
|
|
public class SectionAttribute : Attribute
|
|
|
|
|
{
|
|
|
|
|
public readonly string Section;
|
|
|
|
|
|
|
|
|
|
public SectionAttribute(string section)
|
|
|
|
|
{
|
|
|
|
|
Section = section;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public abstract class ItemBase
|
|
|
|
|
{
|
|
|
|
|
private readonly string[] _parts;
|
|
|
|
|
|
|
|
|
|
public int Parts { get { return _parts.Length; } }
|
|
|
|
|
|
|
|
|
|
protected ItemBase(string line, bool commaSeparated = true)
|
|
|
|
|
{
|
|
|
|
|
var ws = new[] { ' ', '\t' };
|
|
|
|
|
|
|
|
|
|
if (commaSeparated)
|
|
|
|
|
{
|
|
|
|
|
_parts = line.Split(',')
|
|
|
|
|
.SelectMany(x => x.Split(ws, StringSplitOptions.RemoveEmptyEntries))
|
|
|
|
|
.Select(x => x.Trim())
|
|
|
|
|
.ToArray();
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
_parts = line.Split(ws, StringSplitOptions.RemoveEmptyEntries)
|
|
|
|
|
.Select(x => x.Trim())
|
|
|
|
|
.Where(x => x.Length > 0)
|
|
|
|
|
.ToArray();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected ItemBase(BinaryReader reader)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public string GetString(int index)
|
|
|
|
|
{
|
|
|
|
|
return _parts[index];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public byte GetByte(int index)
|
|
|
|
|
{
|
|
|
|
|
return byte.Parse(_parts[index], CultureInfo.InvariantCulture);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public int GetInt(int index)
|
|
|
|
|
{
|
|
|
|
|
return int.Parse(_parts[index], CultureInfo.InvariantCulture);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public int GetInt(int index, NumberStyles numberStyles)
|
|
|
|
|
{
|
|
|
|
|
return int.Parse(_parts[index], numberStyles);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public float GetSingle(int index)
|
|
|
|
|
{
|
|
|
|
|
return float.Parse(_parts[index], CultureInfo.InvariantCulture);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public double GetDouble(int index)
|
|
|
|
|
{
|
|
|
|
|
return double.Parse(_parts[index], CultureInfo.InvariantCulture);
|
|
|
|
|
}
|
2019-10-20 23:54:52 +00:00
|
|
|
|
|
|
|
|
|
public UnityEngine.Vector3 GetUnityVec3(ref int index, bool invertYAndZ)
|
|
|
|
|
{
|
|
|
|
|
float x = GetSingle(index++);
|
|
|
|
|
float y = GetSingle(index++);
|
|
|
|
|
float z = GetSingle(index++);
|
|
|
|
|
if (invertYAndZ)
|
|
|
|
|
return new UnityEngine.Vector3(x, z, y);
|
|
|
|
|
else
|
|
|
|
|
return new UnityEngine.Vector3(x, y, z);
|
|
|
|
|
}
|
2020-05-31 17:07:22 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public abstract class Definition : ItemBase
|
|
|
|
|
{
|
|
|
|
|
protected Definition(string line, bool commaSeparated = true)
|
|
|
|
|
: base(line, commaSeparated) { }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public interface IObjectDefinition
|
|
|
|
|
{
|
|
|
|
|
int Id { get; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public abstract class Placement : ItemBase
|
|
|
|
|
{
|
|
|
|
|
protected Placement(string line, bool commaSeparated = true)
|
|
|
|
|
: base(line, commaSeparated) { }
|
|
|
|
|
|
|
|
|
|
protected Placement(BinaryReader reader)
|
|
|
|
|
: base(reader) { }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public class ItemFile<TType>
|
|
|
|
|
where TType : ItemBase
|
|
|
|
|
{
|
|
|
|
|
private delegate TType ItemCtor(string line);
|
|
|
|
|
|
|
|
|
|
private static readonly Dictionary<string, ItemCtor> _sCtors;
|
|
|
|
|
|
|
|
|
|
static ItemFile()
|
|
|
|
|
{
|
|
|
|
|
_sCtors = new Dictionary<string, ItemCtor>();
|
|
|
|
|
|
|
|
|
|
foreach (var type in Assembly.GetExecutingAssembly().GetTypes())
|
|
|
|
|
{
|
|
|
|
|
var attrib = (SectionAttribute)type.GetCustomAttributes(typeof(SectionAttribute), false).FirstOrDefault();
|
|
|
|
|
if (attrib == null) continue;
|
|
|
|
|
|
|
|
|
|
if (!typeof(TType).IsAssignableFrom(type)) continue;
|
|
|
|
|
|
|
|
|
|
var ctor = type.GetConstructor(new[] { typeof(string) });
|
|
|
|
|
|
|
|
|
|
#if !ENABLE_IL2CPP
|
|
|
|
|
var line = Expression.Parameter(typeof(string), "line");
|
|
|
|
|
var call = Expression.New(ctor, line);
|
|
|
|
|
var cast = Expression.Convert(call, typeof(TType));
|
|
|
|
|
var lamb = Expression.Lambda<ItemCtor>(cast, line);
|
|
|
|
|
|
|
|
|
|
_sCtors.Add(attrib.Section, lamb.Compile());
|
|
|
|
|
#else
|
|
|
|
|
_sCtors.Add(attrib.Section, line => (TType) ctor.Invoke(new object[] { line }));
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private readonly Dictionary<string, List<TType>> _sections
|
|
|
|
|
= new Dictionary<string, List<TType>>();
|
|
|
|
|
|
|
|
|
|
public ItemFile(string path)
|
|
|
|
|
{
|
2021-01-31 00:34:23 +00:00
|
|
|
|
string fileName = Path.GetFileName(path);
|
|
|
|
|
|
|
|
|
|
if (!Archive.ArchiveManager.FileExists(fileName))
|
|
|
|
|
{
|
|
|
|
|
Debug.LogError($"Item file not found: {path}");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
using (var stream = Archive.ArchiveManager.ReadFile(fileName))
|
2019-10-08 15:59:10 +00:00
|
|
|
|
{
|
2021-01-31 00:34:23 +00:00
|
|
|
|
using (var reader = new StreamReader(stream))
|
|
|
|
|
{
|
|
|
|
|
Load(reader);
|
|
|
|
|
}
|
2019-10-08 15:59:10 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public ItemFile(StreamReader reader)
|
|
|
|
|
{
|
|
|
|
|
Load(reader);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Load(StreamReader reader)
|
|
|
|
|
{
|
|
|
|
|
|
2020-05-31 17:07:22 +00:00
|
|
|
|
List<TType> curSection = null;
|
|
|
|
|
ItemCtor curCtor = null;
|
|
|
|
|
|
2019-10-08 15:59:10 +00:00
|
|
|
|
string line;
|
|
|
|
|
while ((line = reader.ReadLine()) != null)
|
2020-05-31 17:07:22 +00:00
|
|
|
|
{
|
2019-10-08 15:59:10 +00:00
|
|
|
|
var hashIndex = line.IndexOf('#');
|
|
|
|
|
if (hashIndex != -1)
|
2020-05-31 17:07:22 +00:00
|
|
|
|
{
|
2019-10-08 15:59:10 +00:00
|
|
|
|
line = line.Substring(0, hashIndex);
|
|
|
|
|
}
|
2020-05-31 17:07:22 +00:00
|
|
|
|
|
2019-10-08 15:59:10 +00:00
|
|
|
|
line = line.Trim();
|
2020-05-31 17:07:22 +00:00
|
|
|
|
|
2019-10-08 15:59:10 +00:00
|
|
|
|
if (line.Length == 0) continue;
|
2020-05-31 17:07:22 +00:00
|
|
|
|
|
2019-10-08 15:59:10 +00:00
|
|
|
|
if (curSection == null)
|
|
|
|
|
{
|
|
|
|
|
line = line.ToLower();
|
|
|
|
|
|
|
|
|
|
if (_sections.ContainsKey(line))
|
|
|
|
|
{
|
|
|
|
|
curSection = _sections[line];
|
|
|
|
|
}
|
|
|
|
|
else
|
2020-05-31 17:07:22 +00:00
|
|
|
|
{
|
2019-10-08 15:59:10 +00:00
|
|
|
|
curSection = new List<TType>();
|
|
|
|
|
_sections.Add(line, curSection);
|
2020-05-31 17:07:22 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-10-08 15:59:10 +00:00
|
|
|
|
if (_sCtors.ContainsKey(line))
|
2020-05-31 17:07:22 +00:00
|
|
|
|
{
|
2019-10-08 15:59:10 +00:00
|
|
|
|
curCtor = _sCtors[line];
|
2020-05-31 17:07:22 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-10-08 15:59:10 +00:00
|
|
|
|
continue;
|
|
|
|
|
}
|
2020-05-31 17:07:22 +00:00
|
|
|
|
|
2019-10-08 15:59:10 +00:00
|
|
|
|
if (line.Equals("end"))
|
|
|
|
|
{
|
|
|
|
|
curSection = null;
|
|
|
|
|
curCtor = null;
|
|
|
|
|
continue;
|
2020-05-31 17:07:22 +00:00
|
|
|
|
}
|
2019-10-08 15:59:10 +00:00
|
|
|
|
|
|
|
|
|
if (curCtor == null) continue;
|
|
|
|
|
|
|
|
|
|
curSection.Add(curCtor(line));
|
2020-05-31 17:07:22 +00:00
|
|
|
|
}
|
2019-10-08 15:59:10 +00:00
|
|
|
|
|
2020-05-31 17:07:22 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public ItemFile(Stream stream)
|
|
|
|
|
{
|
|
|
|
|
var reader = new BinaryReader(stream);
|
|
|
|
|
|
|
|
|
|
if (reader.ReadString(4) != "bnry") throw new Exception("Not a binary IPL file.");
|
|
|
|
|
|
|
|
|
|
var instCount = reader.ReadInt32();
|
|
|
|
|
stream.Seek(12, SeekOrigin.Current);
|
|
|
|
|
var carsCount = reader.ReadInt32();
|
|
|
|
|
stream.Seek(4, SeekOrigin.Current);
|
|
|
|
|
var instOffset = reader.ReadInt32();
|
|
|
|
|
stream.Seek(28, SeekOrigin.Current);
|
|
|
|
|
var carsOffset = reader.ReadInt32();
|
|
|
|
|
|
|
|
|
|
var insts = new List<TType>();
|
|
|
|
|
_sections.Add("inst", insts);
|
|
|
|
|
|
|
|
|
|
stream.Seek(instOffset, SeekOrigin.Begin);
|
|
|
|
|
for (var j = 0; j < instCount; ++j)
|
|
|
|
|
{
|
|
|
|
|
insts.Add((TType)(ItemBase)new Instance(reader));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var cars = new List<TType>();
|
|
|
|
|
_sections.Add("cars", cars);
|
|
|
|
|
|
|
|
|
|
stream.Seek(carsOffset, SeekOrigin.Begin);
|
|
|
|
|
for (var j = 0; j < carsCount; ++j)
|
|
|
|
|
{
|
|
|
|
|
cars.Add((TType)(ItemBase)new ParkedVehicle(reader));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public IEnumerable<TItem> GetSection<TItem>(string name)
|
|
|
|
|
where TItem : TType
|
|
|
|
|
{
|
|
|
|
|
name = name.ToLower();
|
|
|
|
|
|
|
|
|
|
return !_sections.ContainsKey(name)
|
|
|
|
|
? Enumerable.Empty<TItem>()
|
|
|
|
|
: _sections[name].Cast<TItem>();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public IEnumerable<TItem> GetItems<TItem>()
|
|
|
|
|
where TItem : TType
|
|
|
|
|
{
|
|
|
|
|
return _sections.SelectMany(x => x.Value.OfType<TItem>());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|