mirror of
https://github.com/kwsch/pk3DS.git
synced 2026-08-02 01:34:17 -05:00
826 lines
33 KiB
C#
826 lines
33 KiB
C#
using pk3DS.Core;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Drawing;
|
|
using System.Drawing.Imaging;
|
|
using System.Globalization;
|
|
using System.IO;
|
|
using System.Linq;
|
|
|
|
namespace pk3DS.WinForms;
|
|
|
|
#region Game Related Classes
|
|
public class MapMatrix
|
|
{
|
|
public ushort u0;
|
|
public ushort u1;
|
|
public ushort uL;
|
|
public ushort Width, Height;
|
|
private readonly int Area;
|
|
public ushort[] EntryList;
|
|
public Entry[] Entries;
|
|
public Unknown[] Unknowns;
|
|
|
|
public byte[] UnkData;
|
|
|
|
public MapMatrix(byte[][] data)
|
|
{
|
|
using (var br = new BinaryReader(new MemoryStream(data[0])))
|
|
{
|
|
u0 = br.ReadUInt16();
|
|
u1 = br.ReadUInt16();
|
|
Width = br.ReadUInt16();
|
|
Height = br.ReadUInt16();
|
|
|
|
Area = Width * Height;
|
|
Entries = new Entry[Area];
|
|
EntryList = new ushort[Area];
|
|
for (int i = 0; i < Area; i++)
|
|
EntryList[i] = br.ReadUInt16();
|
|
|
|
if (br.BaseStream.Position != br.BaseStream.Length)
|
|
uL = br.ReadUInt16();
|
|
}
|
|
if (data.Length > 1)
|
|
ParseUnk(UnkData = data[1]);
|
|
}
|
|
|
|
public byte[] Write()
|
|
{
|
|
using var ms = new MemoryStream();
|
|
using var bw = new BinaryWriter(ms);
|
|
bw.Write(u0);
|
|
bw.Write(Width);
|
|
bw.Write(Height);
|
|
foreach (ushort Entry in EntryList)
|
|
{
|
|
bw.Write(Entry);
|
|
}
|
|
|
|
bw.Write(uL);
|
|
return ms.ToArray();
|
|
}
|
|
|
|
public Bitmap Preview(int Scale, int ColorShift)
|
|
{
|
|
// Require the entries to be defined in order to continue.
|
|
if (Entries.Any(entry => entry == null))
|
|
{
|
|
// Do nothing; images are instead created with the standard dimensions and returned.
|
|
}
|
|
|
|
// Fetch Singular Images first
|
|
Bitmap[] EntryImages = new Bitmap[Area];
|
|
for (int i = 0; i < Area; i++)
|
|
{
|
|
EntryImages[i] = Entries[i] == null
|
|
? new Bitmap(40 * Scale, 40 * Scale)
|
|
: Entries[i].Preview(Scale, ColorShift);
|
|
}
|
|
|
|
// Combine all images into one.
|
|
var img = new Bitmap(EntryImages[0].Width * Width, EntryImages[0].Height * Height);
|
|
|
|
using var g = Graphics.FromImage(img);
|
|
for (int i = 0; i < Area; i++)
|
|
g.DrawImage(EntryImages[i], new Point(i * EntryImages[0].Width % img.Width, EntryImages[0].Height * (i / Width)));
|
|
|
|
return img;
|
|
}
|
|
|
|
public class Entry
|
|
{
|
|
public Collision coll;
|
|
|
|
public ushort Width, Height;
|
|
private readonly int Area;
|
|
public uint[] Tiles; // Certain bits?
|
|
|
|
public Entry(byte[] data)
|
|
{
|
|
using var br = new BinaryReader(new MemoryStream(data));
|
|
Width = br.ReadUInt16();
|
|
Height = br.ReadUInt16();
|
|
Area = Width * Height;
|
|
Tiles = new uint[Area];
|
|
for (int i = 0; i < Area; i++)
|
|
{
|
|
Tiles[i] = br.ReadUInt32();
|
|
}
|
|
}
|
|
|
|
public byte[] Write()
|
|
{
|
|
using var ms = new MemoryStream();
|
|
using var bw = new BinaryWriter(ms);
|
|
bw.Write(Width);
|
|
bw.Write(Height);
|
|
foreach (uint Tile in Tiles)
|
|
{
|
|
bw.Write(Tile);
|
|
}
|
|
|
|
return ms.ToArray();
|
|
}
|
|
|
|
public Bitmap Preview(int s, int ColorShift)
|
|
{
|
|
byte[] bmpData = BytePreview(s, ColorShift);
|
|
var b = new Bitmap(Width * s, Height * s, PixelFormat.Format32bppArgb);
|
|
var bData = b.LockBits(new Rectangle(0, 0, Width * s, Height * s), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
|
|
System.Runtime.InteropServices.Marshal.Copy(bmpData, 0, bData.Scan0, bmpData.Length);
|
|
b.UnlockBits(bData);
|
|
|
|
return b;
|
|
}
|
|
|
|
public byte[] BytePreview(int s, int ColorShift)
|
|
{
|
|
byte[] bmpData = new byte[4 * Width * Height * s * s];
|
|
for (int i = 0; i < Area; i++)
|
|
{
|
|
int X = i % 40;
|
|
int Y = i / 40;
|
|
uint colorValue = Tiles[i] == 0x01000021
|
|
? 0xFF000000
|
|
: LCRNG32.Advance(Tiles[i], ColorShift) | 0xFF000000;
|
|
|
|
byte[] pixel = BitConverter.GetBytes(colorValue);
|
|
for (int x = 0; x < s * s; x++)
|
|
{
|
|
pixel.CopyTo(bmpData, 4 * ((((Y * s) + (x / s)) * Width * s) + (X * s) + (x % s)));
|
|
}
|
|
}
|
|
return bmpData;
|
|
}
|
|
}
|
|
|
|
public class Collision
|
|
{
|
|
public string Magic;
|
|
public int termOffset;
|
|
public int U5D8;
|
|
public byte[] UnknownBytes;
|
|
public CollisionObject[] Map40;
|
|
public int[] MapInts;
|
|
public CollisionObject[] MapMisc;
|
|
public string termMagic;
|
|
public byte[] termData;
|
|
|
|
public Collision(byte[] data)
|
|
{
|
|
using var br = new BinaryReader(new MemoryStream(data));
|
|
Magic = new string(br.ReadChars(4)); // Magic
|
|
if (Magic != "coll")
|
|
{
|
|
return; // all other properties are null
|
|
}
|
|
|
|
termOffset = br.ReadInt32();
|
|
U5D8 = br.ReadInt32();
|
|
UnknownBytes = br.ReadBytes(0x14);
|
|
|
|
// Read 40 collision rectangles
|
|
Map40 = new CollisionObject[40];
|
|
for (int i = 0; i < Map40.Length; i++)
|
|
{
|
|
Map40[i] = new CollisionObject(br.ReadBytes(0x10));
|
|
}
|
|
|
|
// Read 32 Int32s
|
|
MapInts = new int[0x20];
|
|
for (int i = 0; i < MapInts.Length; i++)
|
|
{
|
|
MapInts[i] = br.ReadInt32();
|
|
}
|
|
|
|
// Read misc collision rectangles
|
|
int ct = termOffset - (int)br.BaseStream.Position + 0x10;
|
|
MapMisc = new CollisionObject[ct / 0x10];
|
|
for (int i = 0; i < MapMisc.Length; i++)
|
|
{
|
|
MapMisc[i] = new CollisionObject(br.ReadBytes(0x10));
|
|
}
|
|
|
|
// Read Term
|
|
termMagic = new string(br.ReadChars(4));
|
|
if (termMagic != "term")
|
|
{
|
|
return; // all other properties are null
|
|
}
|
|
|
|
// Read the rest of the data....
|
|
termData = br.ReadBytes((int)(br.BaseStream.Length - br.BaseStream.Position));
|
|
}
|
|
|
|
public class CollisionObject(byte[] data)
|
|
{
|
|
private readonly float _0 = BitConverter.ToSingle(data, 0x0);
|
|
private readonly float _1 = BitConverter.ToSingle(data, 0x4);
|
|
private readonly float _2 = BitConverter.ToSingle(data, 0x8);
|
|
private readonly float _3 = BitConverter.ToSingle(data, 0xC); // rarely used
|
|
|
|
// I don't even know...
|
|
public float F1 => _0 / 2;
|
|
public float F2 => _1 * 80;
|
|
public float F3 => _2 / 2;
|
|
public float F4 => _3 / 1;
|
|
|
|
public override string ToString()
|
|
{
|
|
return string.Join(", ",
|
|
F1.ToString(CultureInfo.InvariantCulture),
|
|
F2.ToString(CultureInfo.InvariantCulture),
|
|
F3.ToString(CultureInfo.InvariantCulture),
|
|
F4.ToString(CultureInfo.InvariantCulture));
|
|
}
|
|
}
|
|
}
|
|
|
|
public string Unk2String()
|
|
{
|
|
return Unknowns.Aggregate("", (current, l) => current + $"{l.Direction}: {l.P1,3} {l.P2,3} {l.P3,3} {l.P4,3}{Environment.NewLine,3}");
|
|
}
|
|
|
|
private void ParseUnk(byte[] data)
|
|
{
|
|
List<Unknown> unk = [];
|
|
using var ms = new MemoryStream(data);
|
|
using var br = new BinaryReader(ms);
|
|
do
|
|
{
|
|
var obj = new Unknown
|
|
{
|
|
Direction = br.ReadUInt32(),
|
|
_1 = br.ReadSingle(),
|
|
_2 = br.ReadSingle(),
|
|
_3 = br.ReadSingle(),
|
|
_4 = br.ReadSingle(),
|
|
};
|
|
unk.Add(obj);
|
|
} while (unk[^1].Direction != 0);
|
|
|
|
unk.RemoveAt(unk.Count - 1);
|
|
Unknowns = [.. unk];
|
|
}
|
|
|
|
public class Unknown
|
|
{
|
|
public uint Direction;
|
|
public float _1;
|
|
public float _2;
|
|
public float _3;
|
|
public float _4;
|
|
|
|
public int P1 => (int)_1 / 18;
|
|
public int P2 => (int)_2 / 18;
|
|
public int P3 => (int)_3 / 18;
|
|
public int P4 => (int)_4 / 18;
|
|
}
|
|
}
|
|
|
|
public class ZoneData
|
|
{
|
|
internal const int Size = 0x38;
|
|
public readonly byte[] Data;
|
|
|
|
public byte MapType { get => Data[0]; set => Data[0] = value; }
|
|
public byte MapMove { get => Data[1]; set => Data[1] = value; }
|
|
public int MapArea { get => BitConverter.ToUInt16(Data, 0x02); set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0x02); }
|
|
public int MapMatrix { get => BitConverter.ToUInt16(Data, 0x04); set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0x04); }
|
|
public int TextFile { get => BitConverter.ToUInt16(Data, 0x06); set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0x06); }
|
|
|
|
public uint BGMSpring { get => BitConverter.ToUInt32(Data, 0x08); set => BitConverter.GetBytes(value).CopyTo(Data, 0x08); }
|
|
public uint BGMSummer { get => BitConverter.ToUInt32(Data, 0x0C); set => BitConverter.GetBytes(value).CopyTo(Data, 0x0C); }
|
|
public uint BGMAutumn { get => BitConverter.ToUInt32(Data, 0x10); set => BitConverter.GetBytes(value).CopyTo(Data, 0x10); }
|
|
public uint BGMWinter { get => BitConverter.ToUInt32(Data, 0x14); set => BitConverter.GetBytes(value).CopyTo(Data, 0x14); }
|
|
|
|
public int ScriptFile { get => BitConverter.ToUInt16(Data, 0x18); set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0x18); }
|
|
public int TownMapGroup { get => BitConverter.ToUInt16(Data, 0x1A); set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0x1A); }
|
|
|
|
public int ParentMap // 0x1C - low 10 bits
|
|
{
|
|
get => BitConverter.ToUInt16(Data, 0x1C) & 0x3FF;
|
|
set => BitConverter.GetBytes((ushort)(value | (BitConverter.ToUInt16(Data, 0x1C) & ~0x3FF))).CopyTo(Data, 0x1C);
|
|
}
|
|
|
|
public int OLValue // 0x1C - high 6 bits
|
|
{
|
|
get => BitConverter.ToUInt16(Data, 0x1C) >> 10;
|
|
set => BitConverter.GetBytes((ushort)((value << 10) | ParentMap)).CopyTo(Data, 0x1C);
|
|
}
|
|
|
|
// 5,1,1,7
|
|
public int Weather // 0x1E, low 5
|
|
{
|
|
get => BitConverter.ToUInt16(Data, 0x1E) & 0x1F;
|
|
set => BitConverter.GetBytes((ushort)(value | (BitConverter.ToUInt16(Data, 0x1E) & ~0x1F))).CopyTo(Data, 0x1E);
|
|
}
|
|
|
|
public bool IsSkyBoxEnable { get => ((Data[0x1E] >> 5) & 1) == 1; set => Data[0x1E] = (byte)((Data[0x1E] & ~0x20) | (value ? 1 << 5 : 0)); }
|
|
public bool IsRollerSkateEnable { get => ((Data[0x1E] >> 6) & 1) == 1; set => Data[0x1E] = (byte)((Data[0x1E] & ~0x40) | (value ? 1 << 6 : 0)); }
|
|
|
|
public int BattleBackground // 7-13
|
|
{
|
|
get => (BitConverter.ToUInt16(Data, 0x1E) >> 7) & 0x7F;
|
|
set => BitConverter.GetBytes((ushort)(((value & 0x7F) << 7) | (BitConverter.ToUInt16(Data, 0x1E) & ~0x3F80))).CopyTo(Data, 0x1E);
|
|
}
|
|
|
|
private uint Unk_20 { get => BitConverter.ToUInt32(Data, 0x20); set => BitConverter.GetBytes(value).CopyTo(Data, 0x20); }
|
|
public uint MapChange { get => Unk_20 & 0x1Fu; set => Unk_20 = (Unk_20 & ~0x1Fu) | value; }
|
|
// ??? 5
|
|
public bool IsBicycleEnable { get => ((Unk_20 >> 10) & 1) == 1; set => Unk_20 = (Unk_20 & ~(1u << 10)) | (value ? 1u << 10 : 0); }
|
|
public bool IsRunEnable { get => ((Unk_20 >> 11) & 1) == 1; set => Unk_20 = (Unk_20 & ~(1u << 11)) | (value ? 1u << 11 : 0); }
|
|
public bool IsEscapeRopeEnable { get => ((Unk_20 >> 12) & 1) == 1; set => Unk_20 = (Unk_20 & ~(1u << 12)) | (value ? 1u << 12 : 0); }
|
|
public bool IsFlyEnable { get => ((Unk_20 >> 13) & 1) == 1; set => Unk_20 = (Unk_20 & ~(1u << 13)) | (value ? 1u << 13 : 0); }
|
|
public bool IsBGM { get => ((Unk_20 >> 14) & 1) == 1; set => Unk_20 = (Unk_20 & ~(1u << 14)) | (value ? 1u << 14 : 0); }
|
|
public bool IsUnk { get => ((Unk_20 >> 15) & 1) == 1; set => Unk_20 = (Unk_20 & ~(1u << 15)) | (value ? 1u << 15 : 0); }
|
|
|
|
// unused - camera
|
|
public ushort Camera1 { get => BitConverter.ToUInt16(Data, 0x22); set => BitConverter.GetBytes(value).CopyTo(Data, 0x22); }
|
|
|
|
public ushort Camera2 { get => BitConverter.ToUInt16(Data, 0x24); set => BitConverter.GetBytes(value).CopyTo(Data, 0x24); }
|
|
public uint CameraFlags { get => BitConverter.ToUInt32(Data, 0x26); set => BitConverter.GetBytes((ushort)value).CopyTo(Data, 0x26); }
|
|
|
|
private short X { get => BitConverter.ToInt16(Data, 0x2C); set => BitConverter.GetBytes(value).CopyTo(Data, 0x2C); }
|
|
public short Z { get => BitConverter.ToInt16(Data, 0x2E); set => BitConverter.GetBytes(value).CopyTo(Data, 0x2E); }
|
|
private short Y { get => BitConverter.ToInt16(Data, 0x30); set => BitConverter.GetBytes(value).CopyTo(Data, 0x30); }
|
|
private short X2 { get => BitConverter.ToInt16(Data, 0x32); set => BitConverter.GetBytes(value).CopyTo(Data, 0x32); }
|
|
public short Z2 { get => BitConverter.ToInt16(Data, 0x34); set => BitConverter.GetBytes(value).CopyTo(Data, 0x34); }
|
|
private short Y2 { get => BitConverter.ToInt16(Data, 0x36); set => BitConverter.GetBytes(value).CopyTo(Data, 0x36); }
|
|
|
|
public float PX { get => (float)X / 18; set => X = (short)(18 * value); }
|
|
public float PY { get => (float)Y / 18; set => Y = (short)(18 * value); }
|
|
public float PX2 { get => (float)X2 / 18; set => X2 = (short)(18 * value); }
|
|
public float PY2 { get => (float)Y2 / 18; set => Y2 = (short)(18 * value); }
|
|
|
|
public ZoneData(byte[] data)
|
|
{
|
|
if (data.Length != Size)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Data = data;
|
|
}
|
|
|
|
public byte[] Write()
|
|
{
|
|
return Data;
|
|
}
|
|
}
|
|
|
|
public class Zone
|
|
{
|
|
public readonly ZoneData ZD;
|
|
public ZoneEntities Entities;
|
|
public readonly ZoneScript MapScript;
|
|
public readonly ZoneEncounters Encounters;
|
|
public readonly ZoneUnknown File5;
|
|
|
|
public Zone(byte[][] Zone)
|
|
{
|
|
// A ZO is comprised of 4-5 files.
|
|
|
|
// Array 0 is [Map Info]
|
|
ZD = new ZoneData(Zone[0]);
|
|
// Array 1 is [Overworld Entities & their Scripts]
|
|
Entities = new ZoneEntities(Zone[1]);
|
|
// Array 2 is [Map Script]
|
|
MapScript = new ZoneScript(Zone[2]);
|
|
// Array 3 is [Wild Encounters]
|
|
Encounters = new ZoneEncounters(Zone[3]);
|
|
// Array 4 is [???] - May not be present in all.
|
|
if (Zone.Length <= 4)
|
|
{
|
|
return;
|
|
}
|
|
|
|
File5 = new ZoneUnknown(Zone[4]);
|
|
}
|
|
|
|
public byte[][] Write()
|
|
{
|
|
byte[][] Zone = new byte[File5 != null ? 5 : 4][];
|
|
Zone[0] = ZD.Data;
|
|
Zone[1] = Entities.Write();
|
|
Zone[2] = MapScript.Write();
|
|
Zone[3] = Encounters.Write();
|
|
if (Zone.Length <= 4)
|
|
{
|
|
return Zone;
|
|
}
|
|
|
|
Zone[4] = File5.Write();
|
|
return Zone;
|
|
}
|
|
|
|
public class ZoneEntities
|
|
{
|
|
public byte[] Data;
|
|
|
|
public int Length;
|
|
public int FurnitureCount, NPCCount, WarpCount, TriggerCount, UnknownCount;
|
|
public EntityFurniture[] Furniture;
|
|
public EntityNPC[] NPCs;
|
|
public EntityWarp[] Warps;
|
|
public EntityTrigger1[] Triggers1;
|
|
public EntityTrigger2[] Triggers2;
|
|
|
|
public Script Script;
|
|
|
|
public ZoneEntities(byte[] data)
|
|
{
|
|
Data = data;
|
|
|
|
using var br = new BinaryReader(new MemoryStream(data));
|
|
// Load Header
|
|
Length = br.ReadInt32();
|
|
Furniture = new EntityFurniture[FurnitureCount = br.ReadByte()];
|
|
NPCs = new EntityNPC[NPCCount = br.ReadByte()];
|
|
Warps = new EntityWarp[WarpCount = br.ReadByte()];
|
|
Triggers1 = new EntityTrigger1[TriggerCount = br.ReadByte()];
|
|
Triggers2 = new EntityTrigger2[UnknownCount = br.ReadInt32()]; // not sure if there's other types or if the remaining 3 bytes are padding.
|
|
|
|
// Load Entitites
|
|
for (int i = 0; i < FurnitureCount; i++)
|
|
{
|
|
Furniture[i] = new EntityFurniture(br.ReadBytes(EntityFurniture.Size));
|
|
}
|
|
|
|
for (int i = 0; i < NPCCount; i++)
|
|
{
|
|
NPCs[i] = new EntityNPC(br.ReadBytes(EntityNPC.Size));
|
|
}
|
|
|
|
for (int i = 0; i < WarpCount; i++)
|
|
{
|
|
Warps[i] = new EntityWarp(br.ReadBytes(EntityWarp.Size));
|
|
}
|
|
|
|
for (int i = 0; i < TriggerCount; i++)
|
|
{
|
|
Triggers1[i] = new EntityTrigger1(br.ReadBytes(EntityTrigger1.Size));
|
|
}
|
|
|
|
for (int i = 0; i < UnknownCount; i++)
|
|
{
|
|
Triggers2[i] = new EntityTrigger2(br.ReadBytes(EntityTrigger2.Size));
|
|
}
|
|
|
|
// Load Script Data
|
|
int len = br.ReadInt32();
|
|
br.BaseStream.Position -= 4;
|
|
Script = new Script(br.ReadBytes(len));
|
|
}
|
|
|
|
public byte[] Write()
|
|
{
|
|
byte[] F = new byte[Furniture.Length * EntityFurniture.Size];
|
|
for (int i = 0; i < Furniture.Length; i++)
|
|
{
|
|
Furniture[i].Write().CopyTo(F, i * EntityFurniture.Size);
|
|
}
|
|
|
|
byte[] N = new byte[NPCs.Length * EntityNPC.Size];
|
|
for (int i = 0; i < NPCs.Length; i++)
|
|
{
|
|
NPCs[i].Write().CopyTo(N, i * EntityNPC.Size);
|
|
}
|
|
|
|
byte[] W = new byte[Warps.Length * EntityWarp.Size];
|
|
for (int i = 0; i < Warps.Length; i++)
|
|
{
|
|
Warps[i].Write().CopyTo(W, i * EntityWarp.Size);
|
|
}
|
|
|
|
byte[] T = new byte[Triggers1.Length * EntityTrigger1.Size];
|
|
for (int i = 0; i < Triggers1.Length; i++)
|
|
{
|
|
Triggers1[i].Write().CopyTo(T, i * EntityTrigger1.Size);
|
|
}
|
|
|
|
byte[] U = new byte[Triggers2.Length * EntityTrigger2.Size];
|
|
for (int i = 0; i < Triggers2.Length; i++)
|
|
{
|
|
Triggers2[i].Write().CopyTo(U, i * EntityTrigger2.Size);
|
|
}
|
|
|
|
// Assemble entity information
|
|
byte[] OWEntities = [.. F, .. N, .. W, .. T, .. U];
|
|
byte[] EntityLength = BitConverter.GetBytes(8 + OWEntities.Length);
|
|
byte[] EntityCounts = [(byte)Furniture.Length, (byte)NPCs.Length, (byte)Warps.Length, (byte)Triggers1.Length, (byte)Triggers2.Length, 0, 0, 0,
|
|
];
|
|
|
|
// Reassemble NPC portion
|
|
byte[] OWEntityData = [.. EntityLength, .. EntityCounts, .. OWEntities];
|
|
|
|
// Reassemble Script portion
|
|
byte[] OWScriptData = Script.Write();
|
|
|
|
byte[] finalData = [.. OWEntityData, .. OWScriptData];
|
|
|
|
// Add padding zeroes if required (yield size % 4 == 0)
|
|
if (finalData.Length % 4 != 0)
|
|
{
|
|
Array.Resize(ref finalData, finalData.Length + 4 - (finalData.Length % 4));
|
|
}
|
|
|
|
return finalData;
|
|
}
|
|
|
|
// Entity Classes
|
|
public class EntityFurniture
|
|
{
|
|
// Usable Attributes
|
|
public int Script { get => BitConverter.ToUInt16(Raw, 0x00); set => BitConverter.GetBytes((ushort)value).CopyTo(Raw, 0x00); }
|
|
|
|
public int U2 { get => BitConverter.ToUInt16(Raw, 0x02); set => BitConverter.GetBytes((ushort)value).CopyTo(Raw, 0x02); }
|
|
public int U4 { get => BitConverter.ToUInt16(Raw, 0x04); set => BitConverter.GetBytes((ushort)value).CopyTo(Raw, 0x04); }
|
|
public int U6 { get => BitConverter.ToUInt16(Raw, 0x06); set => BitConverter.GetBytes((ushort)value).CopyTo(Raw, 0x06); }
|
|
|
|
// Coordinates have some upper-bit usage it seems...
|
|
public int X { get => BitConverter.ToUInt16(Raw, 0x08); set => BitConverter.GetBytes((ushort)value).CopyTo(Raw, 0x08); }
|
|
public int Y { get => BitConverter.ToUInt16(Raw, 0x0A); set => BitConverter.GetBytes((ushort)value).CopyTo(Raw, 0x0A); }
|
|
// Next two bytes should be dealing with furniture width?
|
|
public int WX { get => BitConverter.ToInt16(Raw, 0x0C); set => BitConverter.GetBytes((short)value).CopyTo(Raw, 0x0C); }
|
|
public int WY { get => BitConverter.ToInt16(Raw, 0x0E); set => BitConverter.GetBytes((short)value).CopyTo(Raw, 0x0E); }
|
|
|
|
public int U10 { get => BitConverter.ToInt32(Raw, 0x10); set => BitConverter.GetBytes(value).CopyTo(Raw, 0x10); }
|
|
|
|
public byte[] Raw;
|
|
public byte[] OriginalData;
|
|
internal const byte Size = 0x14;
|
|
|
|
public EntityFurniture(byte[] data = null)
|
|
{
|
|
Raw = data ?? new byte[Size];
|
|
OriginalData = (byte[])Raw.Clone();
|
|
}
|
|
|
|
public byte[] Write()
|
|
{
|
|
return Raw;
|
|
}
|
|
}
|
|
|
|
public class EntityNPC
|
|
{
|
|
// Usable Attributes
|
|
public int ID { get => BitConverter.ToUInt16(Raw, 0x00); set => BitConverter.GetBytes((ushort)value).CopyTo(Raw, 0x00); }
|
|
public int Model { get => BitConverter.ToUInt16(Raw, 0x02); set => BitConverter.GetBytes((ushort)value).CopyTo(Raw, 0x02); }
|
|
public int MovePermissions { get => BitConverter.ToUInt16(Raw, 0x04); set => BitConverter.GetBytes((ushort)value).CopyTo(Raw, 0x04); }
|
|
public int MovePermissions2 { get => BitConverter.ToUInt16(Raw, 0x06); set => BitConverter.GetBytes((ushort)value).CopyTo(Raw, 0x06); }
|
|
public int SpawnFlag { get => BitConverter.ToUInt16(Raw, 0x08); set => BitConverter.GetBytes((ushort)value).CopyTo(Raw, 0x08); }
|
|
public int Script { get => BitConverter.ToUInt16(Raw, 0x0A); set => BitConverter.GetBytes((ushort)value).CopyTo(Raw, 0x0A); }
|
|
public int FaceDirection { get => BitConverter.ToUInt16(Raw, 0x0C); set => BitConverter.GetBytes((ushort)value).CopyTo(Raw, 0x0C); }
|
|
public int SightRange { get => BitConverter.ToUInt16(Raw, 0x0E); set => BitConverter.GetBytes((ushort)value).CopyTo(Raw, 0x0E); }
|
|
|
|
// XY Only
|
|
public int U10 { get => BitConverter.ToUInt16(Raw, 0x10); set => BitConverter.GetBytes((ushort)value).CopyTo(Raw, 0x10); }
|
|
public int U12 { get => BitConverter.ToUInt16(Raw, 0x12); set => BitConverter.GetBytes((ushort)value).CopyTo(Raw, 0x12); }
|
|
|
|
// Shorts
|
|
public int U14 { get => BitConverter.ToInt16(Raw, 0x14); set => BitConverter.GetBytes((short)value).CopyTo(Raw, 0x14); }
|
|
public int U16 { get => BitConverter.ToInt16(Raw, 0x16); set => BitConverter.GetBytes((short)value).CopyTo(Raw, 0x16); }
|
|
// Negative only in X/Y... seeing behind them? Might be projection of an interaction area.
|
|
public int U18 { get => BitConverter.ToInt16(Raw, 0x18); set => BitConverter.GetBytes((short)value).CopyTo(Raw, 0x18); }
|
|
public int U1A { get => BitConverter.ToInt16(Raw, 0x1A); set => BitConverter.GetBytes((short)value).CopyTo(Raw, 0x1A); }
|
|
|
|
// WalkArea Leashes (?): If these are for NPCs that walk in an area, I'm not sure if there's a direction specified.
|
|
// Set L# to -1 to turn off.
|
|
public int L1 { get => BitConverter.ToInt16(Raw, 0x1C); set => BitConverter.GetBytes((short)value).CopyTo(Raw, 0x1C); }
|
|
public int L2 { get => BitConverter.ToInt16(Raw, 0x1E); set => BitConverter.GetBytes((short)value).CopyTo(Raw, 0x1E); }
|
|
public int L3 { get => BitConverter.ToInt16(Raw, 0x20); set => BitConverter.GetBytes((short)value).CopyTo(Raw, 0x20); }
|
|
// Leash Direction? Only used when an area is specified.
|
|
public int LDir { get => BitConverter.ToUInt16(Raw, 0x22); set => BitConverter.GetBytes((ushort)value).CopyTo(Raw, 0x22); }
|
|
|
|
// 0x24-0x25 is Unused in OR/AS, rarely 1 in XY
|
|
public int U24 { get => BitConverter.ToUInt16(Raw, 0x24); set => BitConverter.GetBytes((ushort)value).CopyTo(Raw, 0x24); }
|
|
// 0x26-0x27 is Unused in OR/AS
|
|
|
|
// Highest bits for X/Y seem to be fractions of a coordinate?
|
|
public int X { get => BitConverter.ToUInt16(Raw, 0x28); set => BitConverter.GetBytes((ushort)value).CopyTo(Raw, 0x28); }
|
|
public int Y { get => BitConverter.ToUInt16(Raw, 0x2A); set => BitConverter.GetBytes((ushort)value).CopyTo(Raw, 0x2A); }
|
|
|
|
// -360, 360 ????
|
|
public float Degrees { get => BitConverter.ToSingle(Raw, 0x2C); set => BitConverter.GetBytes(value).CopyTo(Raw, 0x2C); }
|
|
public float Deg18 => Degrees / 18;
|
|
|
|
public byte[] Raw;
|
|
public byte[] OriginalData;
|
|
internal const byte Size = 0x30;
|
|
|
|
public EntityNPC(byte[] data = null)
|
|
{
|
|
Raw = data ?? new byte[Size];
|
|
OriginalData = (byte[])Raw.Clone();
|
|
}
|
|
|
|
public byte[] Write()
|
|
{
|
|
return Raw;
|
|
}
|
|
}
|
|
|
|
public class EntityWarp
|
|
{
|
|
// Usable Attributes
|
|
public int DestinationMap { get => BitConverter.ToUInt16(Raw, 0x00); set => BitConverter.GetBytes((ushort)value).CopyTo(Raw, 0x00); }
|
|
public int DestinationTileIndex { get => BitConverter.ToUInt16(Raw, 0x02); set => BitConverter.GetBytes((ushort)value).CopyTo(Raw, 0x02); }
|
|
|
|
// Not sure if these are widths or face direction
|
|
public byte FaceDirection { get => Raw[0x04]; set => Raw[0x4] = value; }
|
|
public byte SceneTransitionEffect { get => Raw[0x05]; set => Raw[0x5] = value; }
|
|
|
|
// Either 0 or 1, only in X/Y
|
|
// 0: use x,z,y
|
|
// 1: use x,y,{unused}
|
|
public ushort CoordinateType { get => BitConverter.ToUInt16(Raw, 0x06); set => BitConverter.GetBytes(value).CopyTo(Raw, 0x06); }
|
|
// Coordinates have some upper-bit usage it seems...
|
|
public short X { get => BitConverter.ToInt16(Raw, 0x08); set => BitConverter.GetBytes(value).CopyTo(Raw, 0x08); }
|
|
public short Z { get => BitConverter.ToInt16(Raw, 0x0A); set => BitConverter.GetBytes(value).CopyTo(Raw, 0x0A); }
|
|
public short Y { get => BitConverter.ToInt16(Raw, 0x0C); set => BitConverter.GetBytes(value).CopyTo(Raw, 0x0C); }
|
|
|
|
public decimal PX => (decimal)X / 18;
|
|
public decimal PY => (decimal)Y / 18;
|
|
|
|
// Stretches RIGHT
|
|
public short Width { get => BitConverter.ToInt16(Raw, 0x0E); set => BitConverter.GetBytes(value).CopyTo(Raw, 0x0E); }
|
|
// Stretches DOWN
|
|
public short Height { get => BitConverter.ToInt16(Raw, 0x10); set => BitConverter.GetBytes(value).CopyTo(Raw, 0x10); }
|
|
// 0-bidirectional
|
|
// 1-send only
|
|
// 2-receive only
|
|
public ushort Directionality { get => BitConverter.ToUInt16(Raw, 0x12); set => BitConverter.GetBytes((short)value).CopyTo(Raw, 0x12); }
|
|
|
|
// 0x14-0x15 Unused
|
|
// 0x16-0x17 Unused
|
|
|
|
public byte[] Raw;
|
|
public byte[] OriginalData;
|
|
internal const byte Size = 0x18;
|
|
|
|
public EntityWarp(byte[] data = null)
|
|
{
|
|
Raw = data ?? new byte[Size];
|
|
OriginalData = (byte[])Raw.Clone();
|
|
}
|
|
|
|
public byte[] Write()
|
|
{
|
|
return Raw;
|
|
}
|
|
}
|
|
|
|
public class EntityTrigger1
|
|
{
|
|
// Usable Attributes
|
|
public int Script { get => BitConverter.ToUInt16(Raw, 0x00); set => BitConverter.GetBytes((ushort)value).CopyTo(Raw, 0x00); }
|
|
public int U2 { get => BitConverter.ToUInt16(Raw, 0x02); set => BitConverter.GetBytes((ushort)value).CopyTo(Raw, 0x02); }
|
|
public int Constant { get => BitConverter.ToUInt16(Raw, 0x04); set => BitConverter.GetBytes((ushort)value).CopyTo(Raw, 0x04); }
|
|
|
|
// 0 or 1 for type2, 0/5-8 for type1
|
|
public int U6 { get => BitConverter.ToUInt16(Raw, 0x06); set => BitConverter.GetBytes((ushort)value).CopyTo(Raw, 0x06); }
|
|
// 0 or 1, always 0 in ORAS
|
|
public int U8 { get => BitConverter.ToUInt16(Raw, 0x08); set => BitConverter.GetBytes((ushort)value).CopyTo(Raw, 0x08); }
|
|
|
|
// 0x0A-0x0B unused
|
|
|
|
public int X { get => BitConverter.ToUInt16(Raw, 0x0C); set => BitConverter.GetBytes((ushort)value).CopyTo(Raw, 0x0C); }
|
|
public int Y { get => BitConverter.ToUInt16(Raw, 0x0E); set => BitConverter.GetBytes((ushort)value).CopyTo(Raw, 0x0E); }
|
|
|
|
public int Width { get => BitConverter.ToInt16(Raw, 0x10); set => BitConverter.GetBytes((short)value).CopyTo(Raw, 0x10); }
|
|
public int Height { get => BitConverter.ToInt16(Raw, 0x12); set => BitConverter.GetBytes((short)value).CopyTo(Raw, 0x12); }
|
|
public int U14 { get => BitConverter.ToInt16(Raw, 0x14); set => BitConverter.GetBytes((short)value).CopyTo(Raw, 0x14); }
|
|
public int U16 { get => BitConverter.ToInt16(Raw, 0x16); set => BitConverter.GetBytes((short)value).CopyTo(Raw, 0x16); }
|
|
|
|
public byte[] Raw;
|
|
public byte[] OriginalData;
|
|
internal const byte Size = 0x18;
|
|
|
|
public EntityTrigger1(byte[] data = null)
|
|
{
|
|
Raw = data ?? new byte[Size];
|
|
OriginalData = (byte[])Raw.Clone();
|
|
}
|
|
|
|
public byte[] Write()
|
|
{
|
|
return Raw;
|
|
}
|
|
}
|
|
|
|
public class EntityTrigger2
|
|
{
|
|
// Usable Attributes
|
|
public int Script { get => BitConverter.ToUInt16(Raw, 0x00); set => BitConverter.GetBytes((ushort)value).CopyTo(Raw, 0x00); }
|
|
public int U2 { get => BitConverter.ToUInt16(Raw, 0x02); set => BitConverter.GetBytes((ushort)value).CopyTo(Raw, 0x02); }
|
|
public int Constant { get => BitConverter.ToUInt16(Raw, 0x04); set => BitConverter.GetBytes((ushort)value).CopyTo(Raw, 0x04); }
|
|
|
|
// 0 or 1 for type2, 0/5-8 for type1
|
|
public int U6 { get => BitConverter.ToUInt16(Raw, 0x06); set => BitConverter.GetBytes((ushort)value).CopyTo(Raw, 0x06); }
|
|
// 0 or 1, always 0 in ORAS
|
|
public int U8 { get => BitConverter.ToUInt16(Raw, 0x08); set => BitConverter.GetBytes((ushort)value).CopyTo(Raw, 0x08); }
|
|
|
|
// 0x0A-0x0B unused
|
|
|
|
public int X { get => BitConverter.ToUInt16(Raw, 0x0C); set => BitConverter.GetBytes((ushort)value).CopyTo(Raw, 0x0C); }
|
|
public int Y { get => BitConverter.ToUInt16(Raw, 0x0E); set => BitConverter.GetBytes((ushort)value).CopyTo(Raw, 0x0E); }
|
|
|
|
public int Width { get => BitConverter.ToInt16(Raw, 0x10); set => BitConverter.GetBytes((short)value).CopyTo(Raw, 0x10); }
|
|
public int Height { get => BitConverter.ToInt16(Raw, 0x12); set => BitConverter.GetBytes((short)value).CopyTo(Raw, 0x12); }
|
|
public int U14 { get => BitConverter.ToInt16(Raw, 0x14); set => BitConverter.GetBytes((short)value).CopyTo(Raw, 0x14); }
|
|
public int U16 { get => BitConverter.ToInt16(Raw, 0x16); set => BitConverter.GetBytes((short)value).CopyTo(Raw, 0x16); }
|
|
|
|
public byte[] Raw;
|
|
public byte[] OriginalData;
|
|
internal const byte Size = 0x18;
|
|
|
|
public EntityTrigger2(byte[] data = null)
|
|
{
|
|
Raw = data ?? new byte[Size];
|
|
OriginalData = (byte[])Raw.Clone();
|
|
}
|
|
|
|
public byte[] Write() => Raw;
|
|
}
|
|
}
|
|
|
|
public class ZoneScript(byte[] data)
|
|
{
|
|
public byte[] Data = data; // File details unknown.
|
|
public Script Script = new(data);
|
|
|
|
public byte[] Write()
|
|
{
|
|
Data = Script.Write();
|
|
return Data;
|
|
}
|
|
}
|
|
|
|
public class ZoneEncounters
|
|
{
|
|
public byte[] Data; // File details unknown.
|
|
public byte[] Header;
|
|
public EncounterSet[] Encounters;
|
|
|
|
public ZoneEncounters(byte[] data)
|
|
{
|
|
Data = data;
|
|
|
|
using var br = new BinaryReader(new MemoryStream(data));
|
|
Header = br.ReadBytes(0x10);
|
|
Encounters = new EncounterSet[(int)(br.BaseStream.Length - br.BaseStream.Position) / 4];
|
|
for (int i = 0; i < Encounters.Length; i++)
|
|
{
|
|
Encounters[i] = new EncounterSet(br.ReadBytes(4));
|
|
}
|
|
}
|
|
|
|
public byte[] Write()
|
|
{
|
|
byte[] data = Header; // Start with the header data, then concat every encounter in afterwards.
|
|
return Encounters.Aggregate(data, (current, t) => [.. current, .. t.Write()]);
|
|
}
|
|
|
|
public class EncounterSet
|
|
{
|
|
public int Species;
|
|
public int Form;
|
|
public byte LevelMin, LevelMax;
|
|
|
|
public EncounterSet(byte[] data)
|
|
{
|
|
using var br = new BinaryReader(new MemoryStream(data));
|
|
ushort SpecForm = br.ReadUInt16();
|
|
Species = SpecForm & 0x7FF;
|
|
Form = SpecForm >> 11;
|
|
LevelMin = br.ReadByte();
|
|
LevelMax = br.ReadByte();
|
|
}
|
|
|
|
public byte[] Write()
|
|
{
|
|
using var ms = new MemoryStream();
|
|
using var bw = new BinaryWriter(ms);
|
|
bw.Write((ushort)(Species | (Form << 11)));
|
|
bw.Write(LevelMin);
|
|
bw.Write(LevelMax);
|
|
return ms.ToArray();
|
|
}
|
|
}
|
|
}
|
|
|
|
public class ZoneUnknown(byte[] data)
|
|
{
|
|
public byte[] FileData = data; // File details unknown.
|
|
|
|
public byte[] Write()
|
|
{
|
|
return FileData;
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion |