mirror of
https://mirror.ghproxy.com/https://github.com/dexyfex/CodeWalker
synced 2026-05-14 16:04:49 +08:00
Cutscene viewer progress, Hash updates
This commit is contained in:
@@ -118,7 +118,7 @@ namespace CodeWalker.GameFiles
|
||||
{ } //just testing
|
||||
else
|
||||
{
|
||||
dates.Add(new CacheFileDate(line));//eg: 2740459947 130680580712018938 8944
|
||||
dates.Add(new CacheFileDate(line));//eg: 2740459947 (hash of: platform:/data/cdimages/scaleform_frontend.rpf) 130680580712018938 8944
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -101,6 +101,11 @@ namespace CodeWalker.GameFiles
|
||||
public CutConcatData[] concatDataList { get; set; } // PsoDataType.Array, 784, 1, (MetaName)2621475),//ARRAYINFO, PsoDataType.Structure, 0, 0, MetaName.rage__cutfCutsceneFile2__SConcatData),
|
||||
public CutHaltFrequency[] discardFrameList { get; set; } // PsoDataType.Array, 5280, 0, (MetaName)37)//ARRAYINFO, PsoDataType.Structure, 0, 0, MetaName.vHaltFrequency),
|
||||
|
||||
|
||||
public Dictionary<int, CutObject> ObjectsDict { get; set; } = new Dictionary<int, CutObject>();
|
||||
|
||||
|
||||
|
||||
public override void ReadXml(XmlNode node)
|
||||
{
|
||||
fTotalDuration = Xml.GetChildFloatAttribute(node, "fTotalDuration", "value");
|
||||
@@ -134,9 +139,98 @@ namespace CodeWalker.GameFiles
|
||||
sectionSplitList = Xml.GetChildRawFloatArray(node, "sectionSplitList");
|
||||
concatDataList = XmlMeta.ReadItemArrayNullable<CutConcatData>(node, "concatDataList");
|
||||
discardFrameList = XmlMeta.ReadItemArrayNullable<CutHaltFrequency>(node, "discardFrameList");
|
||||
|
||||
AssociateObjects();
|
||||
}
|
||||
|
||||
|
||||
public void AssociateObjects()
|
||||
{
|
||||
ObjectsDict.Clear();
|
||||
if (pCutsceneObjects != null)
|
||||
{
|
||||
foreach (var obj in pCutsceneObjects)
|
||||
{
|
||||
if (obj is CutObject cobj)
|
||||
{
|
||||
ObjectsDict[cobj.iObjectId] = cobj;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
CutEventArgs getEventArgs(int i)
|
||||
{
|
||||
if (i < 0) return null;
|
||||
if (i >= pCutsceneEventArgsList?.Length) return null;
|
||||
var args = pCutsceneEventArgsList[i];
|
||||
if (!(args is CutEventArgs))
|
||||
{ }
|
||||
return args as CutEventArgs;
|
||||
}
|
||||
CutObject getObject(int i)
|
||||
{
|
||||
CutObject o = null;
|
||||
ObjectsDict.TryGetValue(i, out o);
|
||||
return o;
|
||||
}
|
||||
|
||||
if (pCutsceneEventArgsList != null)
|
||||
{
|
||||
foreach (var arg in pCutsceneEventArgsList)
|
||||
{
|
||||
if (arg is CutObjectIdEventArgs oarg)
|
||||
{
|
||||
oarg.Object = getObject(oarg.iObjectId);
|
||||
}
|
||||
if (arg is CutObjectIdListEventArgs larg)
|
||||
{
|
||||
var objs = new CutObject[larg.iObjectIdList?.Length ?? 0];
|
||||
for (int i = 0; i < objs.Length; i++)
|
||||
{
|
||||
objs[i] = getObject(larg.iObjectIdList[i]);
|
||||
}
|
||||
larg.ObjectList = objs;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (pCutsceneEventList != null)
|
||||
{
|
||||
foreach (var evt in pCutsceneEventList)
|
||||
{
|
||||
if (evt is CutObjectIdEvent oevt)
|
||||
{
|
||||
oevt.Object = getObject(oevt.iObjectId);
|
||||
}
|
||||
if (evt is CutEvent cevt)
|
||||
{
|
||||
cevt.EventArgs = getEventArgs(cevt.iEventArgsIndex);
|
||||
}
|
||||
else
|
||||
{ }
|
||||
}
|
||||
}
|
||||
if (pCutsceneLoadEventList != null)
|
||||
{
|
||||
foreach (var evt in pCutsceneLoadEventList)
|
||||
{
|
||||
if (evt is CutObjectIdEvent oevt)
|
||||
{
|
||||
oevt.Object = getObject(oevt.iObjectId);
|
||||
}
|
||||
if (evt is CutEvent cevt)
|
||||
{
|
||||
cevt.EventArgs = getEventArgs(cevt.iEventArgsIndex);
|
||||
}
|
||||
else
|
||||
{ }
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static CutBase ConstructObject(string type)
|
||||
{
|
||||
switch (type)
|
||||
@@ -233,6 +327,9 @@ namespace CodeWalker.GameFiles
|
||||
{
|
||||
UserData1 = (byte)Xml.GetChildUIntAttribute(node, "UserData1", "value");
|
||||
UserData2 = (byte)Xml.GetChildUIntAttribute(node, "UserData2", "value");
|
||||
|
||||
if ((UserData1 != 0) || (UserData2 != 0))
|
||||
{ }
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
@@ -248,6 +345,9 @@ namespace CodeWalker.GameFiles
|
||||
public override void ReadXml(XmlNode node)
|
||||
{
|
||||
Items = CutsceneFile2.ReadObjectArray(node, "Items");
|
||||
|
||||
if (Items?.Length > 0)
|
||||
{ }
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
@@ -358,7 +458,7 @@ namespace CodeWalker.GameFiles
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return iObjectId.ToString() + ": " + base.ToString();
|
||||
return iObjectId.ToString() + ": " + base.ToString().Replace("CodeWalker.GameFiles.Cut", "");
|
||||
}
|
||||
}
|
||||
[TC(typeof(EXP))] public class CutAssetManagerObject : CutObject // rage__cutfAssetManagerObject
|
||||
@@ -376,6 +476,11 @@ namespace CodeWalker.GameFiles
|
||||
base.ReadXml(node);
|
||||
cName = XmlMeta.GetHash(Xml.GetChildInnerText(node, "cName"));
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return base.ToString() + ": " + cName.ToString();
|
||||
}
|
||||
}
|
||||
[TC(typeof(EXP))] public class CutCameraObject : CutNamedObject // rage__cutfCameraObject
|
||||
{
|
||||
@@ -726,19 +831,74 @@ namespace CodeWalker.GameFiles
|
||||
}
|
||||
|
||||
|
||||
public enum CutEventType : int
|
||||
{
|
||||
LoadScene = 0,
|
||||
LoadAnimation = 2,
|
||||
LoadAudio = 4,
|
||||
LoadModels = 6,
|
||||
UnloadModels = 7,
|
||||
LoadParticles = 8,
|
||||
LoadOverlays = 10,
|
||||
LoadGxt2 = 12,
|
||||
EnableHideObject = 14,
|
||||
DisableHideObject = 15,
|
||||
EnableFixupModel = 16,
|
||||
EnableBlockBounds = 18,
|
||||
DisableBlockBounds = 19,
|
||||
EnableScreenFade = 20,
|
||||
DisableScreenFade = 21,
|
||||
EnableAnimation = 22,
|
||||
DisableAnimation = 23,
|
||||
EnableParticleEffect = 24,
|
||||
DisableParticleEffect = 25,
|
||||
EnableOverlay = 26,
|
||||
DisableOverlay = 27,
|
||||
EnableAudio = 28,
|
||||
DisableAudio = 29,
|
||||
Subtitle = 30,
|
||||
PedVariation = 34,
|
||||
CameraCut = 43,
|
||||
LoadRayfireDes = 46,
|
||||
UnloadRayfireDes = 47,
|
||||
EnableCamera = 48,
|
||||
CameraUnk1 = 49,
|
||||
CameraUnk2 = 50,
|
||||
DisableCamera = 51,
|
||||
DecalUnk1 = 52,
|
||||
DecalUnk2 = 53,
|
||||
CameraShadowCascade = 54,
|
||||
CameraUnk3 = 55,
|
||||
CameraUnk4 = 59,
|
||||
CameraUnk5 = 63,
|
||||
CameraUnk6 = 64,
|
||||
PropUnk1 = 73,
|
||||
EnableLight = 74,
|
||||
DisableLight = 75,
|
||||
CameraUnk7 = 76,
|
||||
Unk1 = 77,
|
||||
Unk2 = 78,
|
||||
CameraUnk8 = 79,
|
||||
VehicleUnk1 = 258,
|
||||
PedUnk1 = 262,
|
||||
}
|
||||
[TC(typeof(EXP))] public class CutEvent : CutBase // rage__cutfEvent
|
||||
{
|
||||
public float fTime { get; set; } // PsoDataType.Float, 16, 0, 0),
|
||||
public int iEventId { get; set; } // PsoDataType.SInt, 20, 0, 0),
|
||||
public CutEventType iEventId { get; set; } // PsoDataType.SInt, 20, 0, 0),
|
||||
public int iEventArgsIndex { get; set; } // PsoDataType.SInt, 24, 0, 0),
|
||||
public object pChildEvents { get; set; } // PsoDataType.Structure, 32, 3, 0),
|
||||
//public object pChildEvents { get; set; } // PsoDataType.Structure, 32, 3, 0),
|
||||
public uint StickyId { get; set; } // PsoDataType.UInt, 40, 0, 0),
|
||||
public bool IsChild { get; set; } // PsoDataType.Bool, 44, 0, 0)
|
||||
|
||||
public CutEventArgs EventArgs { get; set; }
|
||||
|
||||
|
||||
|
||||
public override void ReadXml(XmlNode node)
|
||||
{
|
||||
fTime = Xml.GetChildFloatAttribute(node, "fTime", "value");
|
||||
iEventId = Xml.GetChildIntAttribute(node, "iEventId", "value");
|
||||
iEventId = (CutEventType)Xml.GetChildIntAttribute(node, "iEventId", "value");
|
||||
iEventArgsIndex = Xml.GetChildIntAttribute(node, "iEventArgsIndex", "value");
|
||||
//pChildEvents = CutsceneFile2.ReadObject(node, "pChildEvents"); //seems never used
|
||||
StickyId = Xml.GetChildUIntAttribute(node, "StickyId", "value");
|
||||
@@ -746,13 +906,20 @@ namespace CodeWalker.GameFiles
|
||||
|
||||
var cNode = node.SelectSingleNode("pChildEvents");
|
||||
if ((cNode?.ChildNodes?.Count > 0) || (cNode?.Attributes?.Count > 0))
|
||||
{ }
|
||||
{ }//nothing gets here?
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return fTime.ToString("0.00") + " : " + iEventId.ToString();
|
||||
}
|
||||
}
|
||||
[TC(typeof(EXP))] public class CutObjectIdEvent : CutEvent // rage__cutfObjectIdEvent
|
||||
{
|
||||
public int iObjectId { get; set; } // PsoDataType.SInt, 48, 0, 0)
|
||||
|
||||
public CutObject Object { get; set; }
|
||||
|
||||
public override void ReadXml(XmlNode node)
|
||||
{
|
||||
base.ReadXml(node);
|
||||
@@ -795,6 +962,8 @@ namespace CodeWalker.GameFiles
|
||||
{
|
||||
public int iObjectId { get; set; } // PsoDataType.SInt, 32, 0, 0)
|
||||
|
||||
public CutObject Object { get; set; }
|
||||
|
||||
public override void ReadXml(XmlNode node)
|
||||
{
|
||||
base.ReadXml(node);
|
||||
@@ -805,6 +974,8 @@ namespace CodeWalker.GameFiles
|
||||
{
|
||||
public int[] iObjectIdList { get; set; } // PsoDataType.Array, 32, 0, (MetaName)2)//ARRAYINFO, PsoDataType.SInt, 0, 0, 0),
|
||||
|
||||
public CutObject[] ObjectList { get; set; }
|
||||
|
||||
public override void ReadXml(XmlNode node)
|
||||
{
|
||||
base.ReadXml(node);
|
||||
|
||||
@@ -16,6 +16,7 @@ namespace CodeWalker.GameFiles
|
||||
|
||||
public Dictionary<MetaHash, ClipMapEntry> ClipMap { get; set; }
|
||||
public Dictionary<MetaHash, AnimationMapEntry> AnimMap { get; set; }
|
||||
public Dictionary<MetaHash, ClipMapEntry> CutsceneMap { get; set; } //used for ycd's that are indexed in cutscenes, since name hashes all appended with -n
|
||||
|
||||
public ClipMapEntry[] ClipMapEntries { get; set; }
|
||||
public AnimationMapEntry[] AnimMapEntries { get; set; }
|
||||
@@ -78,6 +79,29 @@ namespace CodeWalker.GameFiles
|
||||
|
||||
}
|
||||
|
||||
public void BuildCutsceneMap(int cutIndex)
|
||||
{
|
||||
CutsceneMap = new Dictionary<MetaHash, ClipMapEntry>();
|
||||
|
||||
var replstr = "-" + cutIndex.ToString();
|
||||
|
||||
foreach (var cme in ClipMapEntries)
|
||||
{
|
||||
var sn = cme?.Clip?.ShortName ?? "";
|
||||
if (sn.EndsWith(replstr))
|
||||
{
|
||||
sn = sn.Substring(0, sn.Length - replstr.Length);
|
||||
}
|
||||
if (sn.EndsWith("_dual"))
|
||||
{
|
||||
sn = sn.Substring(0, sn.Length - 5);
|
||||
}
|
||||
JenkIndex.Ensure(sn);
|
||||
var h = JenkHash.GenHash(sn);
|
||||
CutsceneMap[h] = cme;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -48,6 +48,7 @@ namespace CodeWalker.GameFiles
|
||||
public Dictionary<uint, RpfFileEntry> YbnDict { get; private set; }
|
||||
public Dictionary<uint, RpfFileEntry> YcdDict { get; private set; }
|
||||
public Dictionary<uint, RpfFileEntry> YnvDict { get; private set; }
|
||||
public Dictionary<uint, RpfFileEntry> Gxt2Dict { get; private set; }
|
||||
|
||||
|
||||
public Dictionary<uint, RpfFileEntry> AllYmapsDict { get; private set; }
|
||||
@@ -1202,7 +1203,7 @@ namespace CodeWalker.GameFiles
|
||||
{
|
||||
var dat = RpfMan.GetFile<CacheDatFile>(dlccachefile);
|
||||
if (dat == null)
|
||||
{ continue; } //update\\x64\\dlcpacks\\mpspecialraces\\dlc.rpf\\x64\\data\\cacheloaderdata_dlc\\mpspecialraces_3336915258_cache_y.dat
|
||||
{ continue; } //update\\x64\\dlcpacks\\mpspecialraces\\dlc.rpf\\x64\\data\\cacheloaderdata_dlc\\mpspecialraces_3336915258_cache_y.dat (hash of: mpspecialraces_interior_additions)
|
||||
AllCacheFiles.Add(dat);
|
||||
foreach (var node in dat.AllMapNodes)
|
||||
{
|
||||
@@ -1402,6 +1403,24 @@ namespace CodeWalker.GameFiles
|
||||
string langstr2 = "americandlc.rpf";
|
||||
string langstr3 = "american.rpf";
|
||||
|
||||
Gxt2Dict = new Dictionary<uint, RpfFileEntry>();
|
||||
foreach (var rpf in AllRpfs)
|
||||
{
|
||||
foreach (var entry in rpf.AllEntries)
|
||||
{
|
||||
if (entry is RpfFileEntry fentry)
|
||||
{
|
||||
var p = entry.Path;
|
||||
if (entry.NameLower.EndsWith(".gxt2") && (p.Contains(langstr) || p.Contains(langstr2) || p.Contains(langstr3)))
|
||||
{
|
||||
Gxt2Dict[entry.ShortNameHash] = fentry;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (!DoFullStringIndex)
|
||||
{
|
||||
string globalgxt2path = "x64b.rpf\\data\\lang\\" + langstr + ".rpf\\global.gxt2";
|
||||
@@ -1419,24 +1438,17 @@ namespace CodeWalker.GameFiles
|
||||
|
||||
|
||||
List<Gxt2File> gxt2files = new List<Gxt2File>();
|
||||
foreach (var rpf in AllRpfs)
|
||||
foreach (var entry in Gxt2Dict.Values)
|
||||
{
|
||||
foreach (var entry in rpf.AllEntries)
|
||||
var gxt2 = RpfMan.GetFile<Gxt2File>(entry);
|
||||
if (gxt2 != null)
|
||||
{
|
||||
var p = entry.Path;
|
||||
if (entry.NameLower.EndsWith(".gxt2") && (p.Contains(langstr)|| p.Contains(langstr2)|| p.Contains(langstr3)))
|
||||
for (int i = 0; i < gxt2.TextEntries.Length; i++)
|
||||
{
|
||||
var gxt2 = RpfMan.GetFile<Gxt2File>(entry);
|
||||
if (gxt2 != null)
|
||||
{
|
||||
for (int i = 0; i < gxt2.TextEntries.Length; i++)
|
||||
{
|
||||
var e = gxt2.TextEntries[i];
|
||||
GlobalText.Ensure(e.Text, e.Hash);
|
||||
}
|
||||
gxt2files.Add(gxt2);
|
||||
}
|
||||
var e = gxt2.TextEntries[i];
|
||||
GlobalText.Ensure(e.Text, e.Hash);
|
||||
}
|
||||
gxt2files.Add(gxt2);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3546,6 +3546,14 @@ namespace CodeWalker.GameFiles
|
||||
@null = 987444055, // how best to handle this? C# doesn't like it
|
||||
|
||||
|
||||
exportcamera = 962998194, //cutscene related stuff
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -16276,7 +16276,7 @@ namespace CodeWalker.GameFiles
|
||||
{
|
||||
public MetaHash Name { get; set; } //0 Name: INT_0Bh: 0
|
||||
public uint Unused0 { get; set; } //4
|
||||
public Array_uint Bounds { get; set; } //8 Bounds//3298223272: Array: 8: 1 {256: INT_0Bh: 0}
|
||||
public Array_uint Bounds { get; set; } //8 Bounds: Array: 8: 1 {256: INT_0Bh: 0}
|
||||
public ushort Flags { get; set; } //24 Flags: SHORT_0Fh: 24: 2097155
|
||||
public ushort Unused1 { get; set; }//26
|
||||
public uint Unused2 { get; set; }//28
|
||||
@@ -16379,7 +16379,7 @@ namespace CodeWalker.GameFiles
|
||||
{
|
||||
public MetaHash Name { get; set; } //0 Name: INT_0Bh: 0
|
||||
public uint Unused0 { get; set; } //4
|
||||
public Array_uint Bounds { get; set; } //8 Bounds//3298223272: Array: 8: 1 {256: INT_0Bh: 0}
|
||||
public Array_uint Bounds { get; set; } //8 Bounds: Array: 8: 1 {256: INT_0Bh: 0}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
|
||||
@@ -876,6 +876,70 @@ namespace CodeWalker.GameFiles
|
||||
|
||||
AssignSequenceBoneIds();
|
||||
}
|
||||
|
||||
|
||||
|
||||
public struct FramePosition
|
||||
{
|
||||
public int Frame0;
|
||||
public int Frame1;
|
||||
public float Alpha0;
|
||||
public float Alpha1;
|
||||
}
|
||||
public FramePosition GetFramePosition(float t)
|
||||
{
|
||||
bool ignoreLastFrame = true;//if last frame is equivalent to the first one, eg rollercoaster small light "globes" don't
|
||||
|
||||
FramePosition p = new FramePosition();
|
||||
var nframes = (ignoreLastFrame) ? (Frames - 1) : Frames;
|
||||
|
||||
var curPos = (t / Duration) * nframes;
|
||||
p.Frame0 = ((ushort)curPos) % Frames;
|
||||
p.Frame1 = (p.Frame0 + 1);// % frames;
|
||||
p.Alpha1 = (float)(curPos - Math.Floor(curPos));
|
||||
p.Alpha0 = 1.0f - p.Alpha1;
|
||||
|
||||
return p;
|
||||
}
|
||||
public Vector4 EvaluateVector4(FramePosition frame, int boneIndex, bool interpolate)
|
||||
{
|
||||
var s = frame.Frame0 / SequenceFrameLimit;
|
||||
int f0 = frame.Frame0 % SequenceFrameLimit;
|
||||
int f1 = f0 + 1;
|
||||
var seq = Sequences.data_items[s];
|
||||
var aseq = seq.Sequences[boneIndex];
|
||||
var v0 = aseq.EvaluateVector(f0);
|
||||
var v1 = aseq.EvaluateVector(f1);
|
||||
var v = interpolate ? (v0 * frame.Alpha0) + (v1 * frame.Alpha1) : v0;
|
||||
return v;
|
||||
}
|
||||
public Quaternion EvaluateQuaternion(FramePosition frame, int boneIndex, bool interpolate)
|
||||
{
|
||||
var s = frame.Frame0 / SequenceFrameLimit;
|
||||
int f0 = frame.Frame0 % SequenceFrameLimit;
|
||||
int f1 = f0 + 1;
|
||||
var seq = Sequences.data_items[s];
|
||||
var aseq = seq.Sequences[boneIndex];
|
||||
var q0 = aseq.EvaluateQuaternion(f0);
|
||||
var q1 = aseq.EvaluateQuaternion(f1);
|
||||
var q = interpolate ? Quaternion.Slerp(q0, q1, frame.Alpha1) : q0;
|
||||
return q;
|
||||
}
|
||||
|
||||
public int FindBoneIndex(ushort boneTag, byte track)
|
||||
{
|
||||
//TODO: make this use a dict??
|
||||
if (BoneIds?.data_items != null)
|
||||
{
|
||||
for (int i = 0; i < BoneIds.data_items.Length; i++)
|
||||
{
|
||||
var b = BoneIds.data_items[i];
|
||||
if ((b.BoneId == boneTag) && (b.Track == track)) return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
}
|
||||
[TypeConverter(typeof(ExpandableObjectConverter))] public struct AnimationBoneId : IMetaXmlItem
|
||||
{
|
||||
|
||||
@@ -119,14 +119,14 @@ namespace CodeWalker.GameFiles
|
||||
|
||||
// structure data
|
||||
public ulong ParametersPointer { get; set; }
|
||||
public MetaHash Name { get; set; } //530103687, 2401522793, 1912906641
|
||||
public MetaHash Name { get; set; } //decal_emissive_only, emissive, spec
|
||||
public uint Unknown_Ch { get; set; } // 0x00000000
|
||||
public byte ParameterCount { get; set; }
|
||||
public byte RenderBucket { get; set; } // 2, 0,
|
||||
public ushort Unknown_12h { get; set; } // 32768 HasComment?
|
||||
public ushort ParameterSize { get; set; } //112, 208, 320 (with 16h) 10485872, 17826000, 26214720
|
||||
public ushort ParameterDataSize { get; set; } //160, 272, 400
|
||||
public MetaHash FileName { get; set; } //2918136469, 2635608835, 2247429097
|
||||
public MetaHash FileName { get; set; } //decal_emissive_only.sps, emissive.sps, spec.sps
|
||||
public uint Unknown_1Ch { get; set; } // 0x00000000
|
||||
public uint RenderBucketMask { get; set; } //65284, 65281 DrawBucketMask? (1<<bucket) | 0xFF00
|
||||
public ushort Unknown_24h { get; set; } //0 Instanced?
|
||||
|
||||
Reference in New Issue
Block a user