diff --git a/Directory.Build.props b/Directory.Build.props
new file mode 100644
index 0000000..a71affb
--- /dev/null
+++ b/Directory.Build.props
@@ -0,0 +1,13 @@
+
+
+
+ latest
+ Copyright © Pilzinsel64 2018 - 2024
+ Pilzinsel64
+
+
+
+ true
+
+
+
\ No newline at end of file
diff --git a/SM64Lib.LIBMIO0/MIO0.cs b/SM64Lib.LIBMIO0/MIO0.cs
new file mode 100644
index 0000000..085db25
--- /dev/null
+++ b/SM64Lib.LIBMIO0/MIO0.cs
@@ -0,0 +1,151 @@
+using System;
+
+namespace SM64Lib.LIBMIO0
+{
+ public struct MIO0_Header
+ {
+ public uint dest_size;
+ public uint comp_offset;
+ public uint uncomp_offset;
+ public bool big_endian;
+ };
+
+ public class MIO0
+ {
+
+ private const int MIO0_HEADER_LENGTH = 16;
+
+ private static int GET_BIT(byte[] buf, int offset, int bit)
+ {
+ return buf[(bit / 8) + offset] & (1 << (7 - (bit % 8)));
+ }
+
+ private static bool compareByteArrays(byte[] buf1, byte[] buf2, int length)
+ {
+ for (int i = 0; i < length; ++i)
+ if (buf1[i] != buf2[i]) return false;
+ return true;
+ }
+
+ private static uint read_u32_be(byte[] buf, int off)
+ {
+ return (uint)(((buf)[off + 0] << 24) + ((buf)[off + 1] << 16) +
+ ((buf)[off + 2] << 8) + ((buf)[off + 3]));
+ }
+
+ private static uint read_u32_le(byte[] buf, int off)
+ {
+ return (uint)(((buf)[off + 1] << 24) + ((buf)[off + 0] << 16) +
+ ((buf)[off + 3] << 8) + ((buf)[off + 2]));
+ }
+
+ private static void write_u32_be(byte[] buf, uint val, int off)
+ {
+ buf[off + 0] = (byte)((val >> 24) & 0xFF);
+ buf[off + 1] = (byte)((val >> 16) & 0xFF);
+ buf[off + 2] = (byte)((val >> 8) & 0xFF);
+ buf[off + 3] = (byte)(val & 0xFF);
+ }
+
+ ///
+ /// decode MIO0 header
+ /// returns true if valid header, false otherwise
+ ///
+ public static bool decode_header(byte[] buf, ref MIO0_Header head)
+ {
+ byte[] mio0_ascii_be = new byte[] { 0x4D, 0x49, 0x4F, 0x30 };
+ byte[] mio0_ascii_le = new byte[] { 0x49, 0x4D, 0x30, 0x4F };
+
+ if (compareByteArrays(buf, mio0_ascii_be, 4))
+ {
+ head.dest_size = read_u32_be(buf, 4);
+ head.comp_offset = read_u32_be(buf, 8);
+ head.uncomp_offset = read_u32_be(buf, 12);
+ head.big_endian = true;
+ return true;
+ }
+ else if (compareByteArrays(buf, mio0_ascii_le, 4))
+ {
+ head.dest_size = read_u32_le(buf, 4);
+ head.comp_offset = read_u32_le(buf, 8);
+ head.uncomp_offset = read_u32_le(buf, 12);
+ head.big_endian = false;
+ return true;
+ }
+
+ return false;
+ }
+
+ ///
+ /// encode MIO0 header from struct
+ ///
+ public static void encode_header(byte[] buf, ref MIO0_Header head)
+ {
+ write_u32_be(buf, 0x4D494F30, 0); // write "MIO0" at start of buffer
+ write_u32_be(buf, head.dest_size, 4);
+ write_u32_be(buf, head.comp_offset, 8);
+ write_u32_be(buf, head.uncomp_offset, 12);
+ }
+
+ ///
+ /// decode MIO0 data
+ /// mio0_buf: buffer containing MIO0 data
+ /// returns the raw data as a byte array
+ ///
+ public static byte[] mio0_decode(byte[] mio0_buf)
+ {
+
+ MIO0_Header head = new MIO0_Header();
+ uint bytes_written = 0;
+ int bit_idx = 0;
+ int comp_idx = 0;
+ int uncomp_idx = 0;
+ bool valid;
+
+ // extract header
+ valid = decode_header(mio0_buf, ref head);
+ // verify MIO0 header
+ if (!valid)
+ {
+ return null;
+ }
+
+ if (!head.big_endian)
+ {
+ return null;
+ }
+
+ byte[] decoded = new byte[head.dest_size];
+
+ // decode data
+ while (bytes_written < head.dest_size)
+ {
+ if (GET_BIT(mio0_buf, MIO0_HEADER_LENGTH, bit_idx) > 0)
+ {
+ // 1 - pull uncompressed data
+ decoded[bytes_written] = mio0_buf[head.uncomp_offset + uncomp_idx];
+ bytes_written++;
+ uncomp_idx++;
+ }
+ else
+ {
+ // 0 - read compressed data
+ byte a = mio0_buf[head.comp_offset + comp_idx + 0];
+ byte b = mio0_buf[head.comp_offset + comp_idx + 1];
+ comp_idx += 2;
+ int length = ((a & 0xF0) >> 4) + 3;
+ int idx = ((a & 0x0F) << 8) + b + 1;
+ for (int i = 0; i < length; i++)
+ {
+ decoded[bytes_written] = decoded[bytes_written - idx];
+ bytes_written++;
+ }
+
+ }
+ bit_idx++;
+ }
+ return decoded;
+ }
+
+ }
+}
diff --git a/SM64Lib.LIBMIO0/Properties/AssemblyInfo.cs b/SM64Lib.LIBMIO0/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..0b48e7b
--- /dev/null
+++ b/SM64Lib.LIBMIO0/Properties/AssemblyInfo.cs
@@ -0,0 +1,11 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// Durch Festlegen von ComVisible auf FALSE werden die Typen in dieser Assembly
+// für COM-Komponenten unsichtbar. Wenn Sie auf einen Typ in dieser Assembly von
+// COM aus zugreifen müssen, sollten Sie das ComVisible-Attribut für diesen Typ auf "True" festlegen.
+[assembly: ComVisible(false)]
+
+// Die folgende GUID bestimmt die ID der Typbibliothek, wenn dieses Projekt für COM verfügbar gemacht wird
+[assembly: Guid("e4cec3b1-5778-4d63-8c97-c95153cdc052")]
diff --git a/SM64Lib.LIBMIO0/SM64Lib.LIBMIO0.csproj b/SM64Lib.LIBMIO0/SM64Lib.LIBMIO0.csproj
new file mode 100644
index 0000000..e37140e
--- /dev/null
+++ b/SM64Lib.LIBMIO0/SM64Lib.LIBMIO0.csproj
@@ -0,0 +1,21 @@
+
+
+ net8.0
+ LIBMIO0
+ Quad64
+ Copyright © David 2018
+ AnyCPU
+ Debug;Release;ReleaseBundle;ReleaseStandalone
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SM64Lib.N64Graphics/N64Graphics.cs b/SM64Lib.N64Graphics/N64Graphics.cs
new file mode 100644
index 0000000..342eb6b
--- /dev/null
+++ b/SM64Lib.N64Graphics/N64Graphics.cs
@@ -0,0 +1,524 @@
+using System.Drawing;
+
+namespace SM64Lib.N64Graphics
+{
+ public enum N64Codec { RGBA16, RGBA32, IA16, IA8, IA4, I8, I4, CI8, CI4, ONEBPP };
+ public enum N64IMode { AlphaCopyIntensity, AlphaBinary, AlphaOne };
+
+ public class N64Graphics
+ {
+ private static int SCALE_5_8(int val)
+ {
+ return (val * 0xFF) / 0x1F;
+ }
+
+ private static byte SCALE_8_5(byte val)
+ {
+ return (byte)((((val) + 4) * 0x1F) / 0xFF);
+ }
+
+ private static byte SCALE_8_4(byte val)
+ {
+ return (byte)(val / 0x11);
+ }
+
+ private static int SCALE_3_8(byte val)
+ {
+ return (val * 0xFF) / 0x7;
+ }
+
+ private static byte SCALE_8_3(byte val)
+ {
+ return (byte)(val / 0x24);
+ }
+
+ public static Color RGBA16Color(byte c0, byte c1)
+ {
+ int r = SCALE_5_8((c0 & 0xF8) >> 3);
+ int g = SCALE_5_8(((c0 & 0x07) << 2) | ((c1 & 0xC0) >> 6));
+ int b = SCALE_5_8((c1 & 0x3E) >> 1);
+ int a = ((c1 & 0x1) > 0) ? 255 : 0;
+ return Color.FromArgb(a, r, g, b);
+ }
+
+ public static void ColorRGBA16(Color col, out byte c0, out byte c1)
+ {
+ byte r, g, b;
+ r = SCALE_8_5(col.R);
+ g = SCALE_8_5(col.G);
+ b = SCALE_8_5(col.B);
+ c0 = (byte)((r << 3) | (g >> 2));
+ c1 = (byte)(((g & 0x3) << 6) | (b << 1) | ((col.A > 0) ? 1 : 0));
+ }
+
+ public static Color RGBA16Color(byte[] data, int pixOffset)
+ {
+ byte c0 = data[pixOffset];
+ byte c1 = data[pixOffset + 1];
+
+ return RGBA16Color(c0, c1);
+ }
+
+ public static Color RGBA32Color(byte[] data, int pixOffset)
+ {
+ int r, g, b, a;
+ r = data[pixOffset];
+ g = data[pixOffset + 1];
+ b = data[pixOffset + 2];
+ a = data[pixOffset + 3];
+ return Color.FromArgb(a, r, g, b);
+ }
+
+ public static Color IA16Color(byte[] data, int pixOffset)
+ {
+ int i = data[pixOffset];
+ int a = data[pixOffset + 1];
+ return Color.FromArgb(a, i, i, i);
+ }
+
+ public static Color IA8Color(byte[] data, int pixOffset)
+ {
+ int i, a;
+ byte c = data[pixOffset];
+ i = (c >> 4) * 0x11;
+ a = (c & 0xF) * 0x11;
+ return Color.FromArgb(a, i, i, i);
+ }
+
+ public static Color IA4Color(byte[] data, int pixOffset, int nibble)
+ {
+ int shift = (1 - nibble) * 4;
+ int i, a;
+ int val = (data[pixOffset] >> shift) & 0xF;
+ i = SCALE_3_8((byte)(val >> 1));
+ a = (val & 0x1) > 0 ? 0xFF : 0x00;
+ return Color.FromArgb(a, i, i, i);
+ }
+
+ public static Color I8Color(byte[] data, int pixOffset, N64IMode mode = N64IMode.AlphaCopyIntensity)
+ {
+ int i = data[pixOffset];
+ int a = i;
+ switch (mode)
+ {
+ case N64IMode.AlphaBinary: a = (i == 0) ? 0 : 0xFF; break;
+ case N64IMode.AlphaCopyIntensity: a = i; break;
+ case N64IMode.AlphaOne: a = 0xFF; break;
+ }
+ return Color.FromArgb(a, i, i, i);
+ }
+
+ public static Color I4Color(byte[] data, int pixOffset, int nibble, N64IMode mode = N64IMode.AlphaCopyIntensity)
+ {
+ int shift = (1 - nibble) * 4;
+ int i = (data[pixOffset] >> shift) & 0xF;
+ i *= 0x11;
+ int a = i;
+ switch (mode)
+ {
+ case N64IMode.AlphaBinary: a = (i == 0) ? 0 : 0xFF; break;
+ case N64IMode.AlphaCopyIntensity: a = i; break;
+ case N64IMode.AlphaOne: a = 0xFF; break;
+ }
+ return Color.FromArgb(a, i, i, i);
+ }
+
+ public static Color CI8Color(byte[] data, byte[] palette, int pixOffset)
+ {
+ byte c0, c1;
+ int palOffset = 2 * data[pixOffset];
+ c0 = palette[palOffset];
+ c1 = palette[palOffset + 1];
+
+ return RGBA16Color(c0, c1);
+ }
+
+ public static Color CI4Color(byte[] data, byte[] palette, int pixOffset, int nibble)
+ {
+ byte c0, c1;
+ int shift = (1 - nibble) * 4;
+ int palOffset = 2 * ((data[pixOffset] >> shift) & 0xF);
+ c0 = palette[palOffset];
+ c1 = palette[palOffset + 1];
+
+ return RGBA16Color(c0, c1);
+ }
+
+ public static Color BPPColor(byte[] data, int pixOffset, int bit)
+ {
+ int i, a;
+ int val = (data[pixOffset] >> (7 - bit)) & 0x1;
+ i = a = val == 0 ? 0x00 : 0xFF;
+ return Color.FromArgb(a, i, i, i);
+ }
+
+ // return number of bytes needed to encode numPixels using codec
+ public static int PixelsToBytes(N64Codec codec, int numPixels)
+ {
+ int numBytes = 0;
+ switch (codec)
+ {
+ case N64Codec.RGBA16: numBytes = numPixels * 2; break;
+ case N64Codec.RGBA32: numBytes = numPixels * 4; break;
+ case N64Codec.IA16: numBytes = numPixels * 2; break;
+ case N64Codec.IA8: numBytes = numPixels; break;
+ case N64Codec.IA4: numBytes = numPixels / 2; break;
+ case N64Codec.I8: numBytes = numPixels; break;
+ case N64Codec.I4: numBytes = numPixels / 2; break;
+ case N64Codec.CI8: numBytes = numPixels; break;
+ case N64Codec.CI4: numBytes = numPixels / 2; break;
+ case N64Codec.ONEBPP: numBytes = numPixels / 8; break;
+ }
+ return numBytes;
+ }
+
+ public static string CodecString(N64Codec codec)
+ {
+ switch (codec)
+ {
+ case N64Codec.RGBA16: return "rgba16";
+ case N64Codec.RGBA32: return "rgba32";
+ case N64Codec.IA16: return "ia16";
+ case N64Codec.IA8: return "ia8";
+ case N64Codec.IA4: return "ia4";
+ case N64Codec.I8: return "i8";
+ case N64Codec.I4: return "i4";
+ case N64Codec.CI8: return "ci8";
+ case N64Codec.CI4: return "ci4";
+ case N64Codec.ONEBPP: return "1bpp";
+ }
+ return "unk";
+ }
+
+ public static N64Codec StringCodec(string str)
+ {
+ switch (str.ToLower())
+ {
+ default:
+ case "rgba16": return N64Codec.RGBA16;
+ case "rgba32": return N64Codec.RGBA32;
+ case "ia16": return N64Codec.IA16;
+ case "ia8": return N64Codec.IA8;
+ case "ia4": return N64Codec.IA4;
+ case "i8": return N64Codec.I8;
+ case "i4": return N64Codec.I4;
+ case "ci8": return N64Codec.CI8;
+ case "ci4": return N64Codec.CI4;
+ case "1bpp": return N64Codec.ONEBPP;
+ }
+ }
+
+ public static Color MakeColor(byte[] data, byte[] palette, int offset, int select, N64Codec codec, N64IMode mode)
+ {
+ Color color;
+ switch (codec)
+ {
+ case N64Codec.RGBA16:
+ color = RGBA16Color(data, offset);
+ break;
+ case N64Codec.RGBA32:
+ color = RGBA32Color(data, offset);
+ break;
+ case N64Codec.IA16:
+ color = IA16Color(data, offset);
+ break;
+ case N64Codec.IA8:
+ color = IA8Color(data, offset);
+ break;
+ case N64Codec.IA4:
+ color = IA4Color(data, offset, select);
+ break;
+ case N64Codec.I8:
+ color = I8Color(data, offset, mode);
+ break;
+ case N64Codec.I4:
+ color = I4Color(data, offset, select, mode);
+ break;
+ case N64Codec.CI8:
+ color = CI8Color(data, palette, offset);
+ break;
+ case N64Codec.CI4:
+ color = CI4Color(data, palette, offset, select);
+ break;
+ case N64Codec.ONEBPP:
+ color = BPPColor(data, offset, select);
+ break;
+ default:
+ color = RGBA16Color(data, offset);
+ break;
+ }
+ return color;
+ }
+
+ public static void RenderTexture(Graphics g, byte[] data, byte[] palette, int offset, int width, int height, int scale, N64Codec codec, N64IMode mode)
+ {
+ Brush brush;
+ for (int h = 0; h < height; h++)
+ {
+ for (int w = 0; w < width; w++)
+ {
+ int pixOffset = (h * width + w);
+ int bytesPerPix = 1;
+ int select = 0;
+ switch (codec)
+ {
+ case N64Codec.RGBA16: bytesPerPix = 2; pixOffset *= bytesPerPix; break;
+ case N64Codec.RGBA32: bytesPerPix = 4; pixOffset *= bytesPerPix; break;
+ case N64Codec.IA16: bytesPerPix = 2; pixOffset *= bytesPerPix; break;
+ case N64Codec.IA8: break;
+ case N64Codec.IA4:
+ select = pixOffset & 0x1;
+ pixOffset /= 2;
+ break;
+ case N64Codec.I8: break;
+ case N64Codec.I4:
+ case N64Codec.CI4:
+ select = pixOffset & 0x1;
+ pixOffset /= 2;
+ break;
+ case N64Codec.CI8: break;
+ case N64Codec.ONEBPP:
+ select = pixOffset & 0x7;
+ pixOffset /= 8;
+ break;
+ }
+ pixOffset += offset;
+ if (data.Length > pixOffset + bytesPerPix - 1)
+ {
+ brush = new SolidBrush(MakeColor(data, palette, pixOffset, select, codec, mode));
+ g.FillRectangle(brush, w * scale, h * scale, scale, scale);
+ }
+ }
+ }
+ }
+
+ // return palette index of matching c0/c1 16-bit dataset, or -1 if not found
+ private static int paletteIndex(byte[] pal, int palCount, byte c0, byte c1)
+ {
+ for (int i = 0; i < palCount; i++)
+ {
+ if (pal[2 * i] == c0 && pal[2 * i + 1] == c1)
+ {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ public static void Convert(ref byte[] imageData, ref byte[] paletteData, N64Codec codec, Bitmap bm)
+ {
+ int numPixels = bm.Width * bm.Height;
+ imageData = new byte[PixelsToBytes(codec, numPixels)];
+ int palCount = 0;
+ switch (codec)
+ {
+ case N64Codec.RGBA16:
+ for (int y = 0; y < bm.Height; y++)
+ {
+ for (int x = 0; x < bm.Width; x++)
+ {
+ Color col = bm.GetPixel(x, y);
+ byte c0;
+ byte c1;
+ ColorRGBA16(col, out c0, out c1);
+ int idx = 2 * (y * bm.Width + x);
+ imageData[idx + 0] = c0;
+ imageData[idx + 1] = c1;
+ }
+ }
+ break;
+ case N64Codec.RGBA32:
+ for (int y = 0; y < bm.Height; y++)
+ {
+ for (int x = 0; x < bm.Width; x++)
+ {
+ Color col = bm.GetPixel(x, y);
+ int idx = 4 * (y * bm.Width + x);
+ imageData[idx + 0] = col.R;
+ imageData[idx + 1] = col.G;
+ imageData[idx + 2] = col.B;
+ imageData[idx + 3] = col.A;
+ }
+ }
+ break;
+ case N64Codec.IA16:
+ for (int y = 0; y < bm.Height; y++)
+ {
+ for (int x = 0; x < bm.Width; x++)
+ {
+ Color col = bm.GetPixel(x, y);
+ int sum = col.R + col.G + col.B;
+ byte intensity = (byte)(sum / 3);
+ byte alpha = col.A;
+ int idx = 2 * (y * bm.Width + x);
+ imageData[idx + 0] = intensity;
+ imageData[idx + 1] = alpha;
+ }
+ }
+ break;
+ case N64Codec.IA8:
+ for (int y = 0; y < bm.Height; y++)
+ {
+ for (int x = 0; x < bm.Width; x++)
+ {
+ Color col = bm.GetPixel(x, y);
+ int sum = col.R + col.G + col.B;
+ byte intensity = SCALE_8_4((byte)(sum / 3));
+ byte alpha = SCALE_8_4(col.A);
+ int idx = y * bm.Width + x;
+ imageData[idx] = (byte)((intensity << 4) | alpha);
+ }
+ }
+ break;
+ case N64Codec.IA4:
+ for (int y = 0; y < bm.Height; y++)
+ {
+ for (int x = 0; x < bm.Width; x++)
+ {
+ Color col = bm.GetPixel(x, y);
+ int sum = col.R + col.G + col.B;
+ byte intensity = SCALE_8_3((byte)(sum / 3));
+ byte alpha = (byte)(col.A > 0 ? 1 : 0);
+ int idx = y * bm.Width + x;
+ byte old = imageData[idx / 2];
+ if ((idx % 2) > 0)
+ {
+ imageData[idx / 2] = (byte)((old & 0xF0) | (intensity << 1) | alpha);
+ }
+ else
+ {
+ imageData[idx / 2] = (byte)((old & 0x0F) | (((intensity << 1) | alpha) << 4));
+ }
+ }
+ }
+ break;
+ case N64Codec.I8:
+ for (int y = 0; y < bm.Height; y++)
+ {
+ for (int x = 0; x < bm.Width; x++)
+ {
+ Color col = bm.GetPixel(x, y);
+ int sum = col.R + col.G + col.B;
+ byte intensity = (byte)(sum / 3);
+ int idx = y * bm.Width + x;
+ imageData[idx] = intensity;
+ }
+ }
+ break;
+ case N64Codec.I4:
+ for (int y = 0; y < bm.Height; y++)
+ {
+ for (int x = 0; x < bm.Width; x++)
+ {
+ Color col = bm.GetPixel(x, y);
+ int sum = col.R + col.G + col.B;
+ byte intensity = SCALE_8_4((byte)(sum / 3));
+ int idx = y * bm.Width + x;
+ byte old = imageData[idx / 2];
+ if ((idx % 2) > 0)
+ {
+ imageData[idx / 2] = (byte)((old & 0xF0) | intensity);
+ }
+ else
+ {
+ imageData[idx / 2] = (byte)((old & 0x0F) | (intensity << 4));
+ }
+ }
+ }
+ break;
+ case N64Codec.CI4:
+ paletteData = new byte[16 * 2];
+ for (int y = 0; y < bm.Height; y++)
+ {
+ for (int x = 0; x < bm.Width; x++)
+ {
+ Color col = bm.GetPixel(x, y);
+ byte r, g, b;
+ r = SCALE_8_5(col.R);
+ g = SCALE_8_5(col.G);
+ b = SCALE_8_5(col.B);
+ byte c0 = (byte)((r << 3) | (g >> 2));
+ byte c1 = (byte)(((g & 0x3) << 6) | (b << 1) | ((col.A > 0) ? 1 : 0));
+ int idx = y * bm.Width + x;
+ int palIdx = paletteIndex(paletteData, palCount, c0, c1);
+ if (palIdx < 0)
+ {
+ if (palCount < paletteData.Length / 2)
+ {
+ palIdx = palCount;
+ paletteData[2 * palCount] = c0;
+ paletteData[2 * palCount + 1] = c1;
+ palCount++;
+ }
+ else
+ {
+ palIdx = 0;
+ // TODO: out of palette entries. error or pick closest?
+ }
+ }
+ byte old = imageData[idx / 2];
+ if ((idx % 2) > 0)
+ {
+ imageData[idx / 2] = (byte)((old & 0xF0) | (byte)palIdx);
+ }
+ else
+ {
+ imageData[idx / 2] = (byte)((old & 0x0F) | ((byte)palIdx << 4));
+ }
+ }
+ }
+ break;
+ case N64Codec.CI8:
+ paletteData = new byte[256 * 2];
+ for (int y = 0; y < bm.Height; y++)
+ {
+ for (int x = 0; x < bm.Width; x++)
+ {
+ Color col = bm.GetPixel(x, y);
+ byte r, g, b;
+ r = SCALE_8_5(col.R);
+ g = SCALE_8_5(col.G);
+ b = SCALE_8_5(col.B);
+ byte c0 = (byte)((r << 3) | (g >> 2));
+ byte c1 = (byte)(((g & 0x3) << 6) | (b << 1) | ((col.A > 0) ? 1 : 0));
+ int idx = y * bm.Width + x;
+ int palIdx = paletteIndex(paletteData, palCount, c0, c1);
+ if (palIdx < 0)
+ {
+ if (palCount < paletteData.Length / 2)
+ {
+ palIdx = palCount;
+ paletteData[2 * palCount] = c0;
+ paletteData[2 * palCount + 1] = c1;
+ palCount++;
+ }
+ else
+ {
+ palIdx = 0;
+ // TODO: out of palette entries. error or pick closest?
+ }
+ }
+ imageData[idx] = (byte)palIdx;
+ }
+ }
+ break;
+ case N64Codec.ONEBPP:
+ for (int y = 0; y < bm.Height; y++)
+ {
+ for (int x = 0; x < bm.Width; x++)
+ {
+ Color col = bm.GetPixel(x, y);
+ int sum = col.R + col.G + col.B;
+ byte intensity = (sum > 0) ? (byte)1 : (byte)0;
+ int idx = y * bm.Width + x;
+ byte old = imageData[idx / 8];
+ int bit = idx % 8;
+ int mask = ~(1 << bit);
+ imageData[idx / 8] = (byte)((old & mask) | (intensity << bit));
+ }
+ }
+ break;
+ }
+ }
+ }
+}
diff --git a/SM64Lib.N64Graphics/Properties/AssemblyInfo.cs b/SM64Lib.N64Graphics/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..8903d48
--- /dev/null
+++ b/SM64Lib.N64Graphics/Properties/AssemblyInfo.cs
@@ -0,0 +1,11 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// Durch Festlegen von ComVisible auf "false" werden die Typen in dieser Assembly unsichtbar
+// für COM-Komponenten. Wenn Sie auf einen Typ in dieser Assembly von
+// COM aus zugreifen müssen, sollten Sie das ComVisible-Attribut für diesen Typ auf "True" festlegen.
+[assembly: ComVisible(false)]
+
+// Die folgende GUID bestimmt die ID der Typbibliothek, wenn dieses Projekt für COM verfügbar gemacht wird
+[assembly: Guid("d9258483-950c-46d1-a333-31f32492cd7e")]
diff --git a/SM64Lib.N64Graphics/SM64Lib.N64Graphics.csproj b/SM64Lib.N64Graphics/SM64Lib.N64Graphics.csproj
new file mode 100644
index 0000000..a762083
--- /dev/null
+++ b/SM64Lib.N64Graphics/SM64Lib.N64Graphics.csproj
@@ -0,0 +1,23 @@
+
+
+ net8.0-windows
+ N64Graphics
+ Pilzinsel64
+ N64Graphics
+ Copyright © Pilzinsel64 2017 - 2018
+ true
+ AnyCPU
+ Debug;Release;ReleaseBundle;ReleaseStandalone
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SM64Lib.N64Graphics/TextureFormat.cs b/SM64Lib.N64Graphics/TextureFormat.cs
new file mode 100644
index 0000000..d067855
--- /dev/null
+++ b/SM64Lib.N64Graphics/TextureFormat.cs
@@ -0,0 +1,598 @@
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+using System.Drawing.Imaging;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SM64Lib.N64Graphics
+{
+ public class TextureFormats
+ {
+ public static Bitmap createColorTexture(Color color)
+ {
+ Bitmap tex = new Bitmap(1, 1);
+ Graphics.FromImage(tex).Clear(color);
+ return tex;
+ }
+
+ public static string ConvertFormatToString(byte formatColorType, byte formatByteSize)
+ {
+ string formatStr = "";
+ switch (formatColorType & 7)
+ {
+ case 0: formatStr = "RGBA"; break;
+ case 1: formatStr = "YUV"; break;
+ case 2: formatStr = "CI"; break;
+ case 3: formatStr = "IA"; break;
+ case 4: formatStr = "I"; break;
+ default: formatStr = "UNKNOWN"; break;
+ }
+ switch (formatByteSize & 3)
+ {
+ case 0: formatStr += "4"; break;
+ case 1: formatStr += "8"; break;
+ case 2: formatStr += "16"; break;
+ case 3: formatStr += "32"; break;
+ }
+ return formatStr;
+ }
+
+ public static byte ConvertStringToFormat(string str)
+ {
+ str = str.ToLower();
+ if (str.Equals("rgba16"))
+ return 0x10;
+ else if (str.Equals("rgba32"))
+ return 0x18;
+ else if (str.Equals("ci4"))
+ return 0x40;
+ else if (str.Equals("ci8"))
+ return 0x48;
+ else if (str.Equals("ia4"))
+ return 0x60;
+ else if (str.Equals("ia8"))
+ return 0x68;
+ else if (str.Equals("ia16"))
+ return 0x70;
+ else if (str.Equals("i4"))
+ return 0x80;
+ else if (str.Equals("i8"))
+ return 0x88;
+ else if (str.Equals("1bpp")) // Not a real F3D format.
+ return 0x00;
+
+ return 0x10;
+ }
+
+ public static int getNumberOfBitsForFormat(byte format)
+ {
+ switch (format)
+ {
+ case 0x00: // Note: "1 bit per pixel" is not a Fast3D format.
+ return 1;
+ case 0x40:
+ case 0x60:
+ case 0x80:
+ return 4;
+ case 0x48:
+ case 0x68:
+ case 0x88:
+ return 8;
+ case 0x10:
+ case 0x70:
+ case 0x90:
+ default:
+ return 16;
+ case 0x18:
+ return 32;
+ }
+ }
+
+ public static byte[] encodeTexture(N64Codec format, Bitmap texture)
+ {
+ switch (format)
+ {
+ default:
+ case N64Codec.ONEBPP: // Note: "1 bit per pixel" is not a Fast3D format.
+ return encode1BPP(texture);
+ case N64Codec.RGBA16:
+ return encodeRGBA16(texture);
+ case N64Codec.RGBA32:
+ return encodeRGBA32(texture);
+ case N64Codec.IA4:
+ return encodeIA4(texture);
+ case N64Codec.IA8:
+ return encodeIA8(texture);
+ case N64Codec.IA16:
+ return encodeIA16(texture);
+ case N64Codec.I4:
+ return encodeI4(texture);
+ case N64Codec.I8:
+ return encodeI4(texture);
+ case N64Codec.CI4:
+ case N64Codec.CI8:
+ throw new ArgumentException("CI texture encoding is not currently supported in this version.");
+ }
+ }
+
+ public static byte getBit(int color, int bit)
+ {
+ return (byte)(((color >> 24) & 0xFF) > 0 ? (1 << bit) : 0);
+ }
+
+ public static byte[] encode1BPP(Bitmap texture)
+ {
+ int data_size = (texture.Width * texture.Height) / 8;
+ byte[] data = new byte[data_size];
+ for (int i = 0; i < data_size; i++)
+ {
+ int x = (i * 8) % texture.Width;
+ int y = (i * 8) / texture.Width;
+
+ data[i] = (byte)(
+ getBit(texture.GetPixel(x + 0, y).ToArgb(), 7) |
+ getBit(texture.GetPixel(x + 1, y).ToArgb(), 6) |
+ getBit(texture.GetPixel(x + 2, y).ToArgb(), 5) |
+ getBit(texture.GetPixel(x + 3, y).ToArgb(), 4) |
+ getBit(texture.GetPixel(x + 4, y).ToArgb(), 3) |
+ getBit(texture.GetPixel(x + 5, y).ToArgb(), 2) |
+ getBit(texture.GetPixel(x + 6, y).ToArgb(), 1) |
+ getBit(texture.GetPixel(x + 7, y).ToArgb(), 0)
+ );
+ }
+ return data;
+ }
+
+ public static byte[] encodeRGBA16(Bitmap texture)
+ {
+ int data_size = (texture.Width * texture.Height) * 2;
+ byte[] data = new byte[data_size];
+ for (int i = 0; i < data_size / 2; i++)
+ {
+ int x = i % texture.Width;
+ int y = i / texture.Width;
+ Color pix = texture.GetPixel(x, y);
+ byte red = (byte)((pix.R / 256.0f) * 32.0f);
+ byte green = (byte)((pix.G / 256.0f) * 32.0f);
+ byte blue = (byte)((pix.B / 256.0f) * 32.0f);
+ byte alpha = (byte)(pix.A >= 128.0f ? 1 : 0);
+
+ data[i * 2] = (byte)((red << 3) | (green >> 2));
+ data[(i * 2) + 1] = (byte)(((green & 3) << 6) | (blue << 1) | alpha);
+ }
+ return data;
+ }
+
+ public static byte[] encodeRGBA32(Bitmap texture)
+ {
+ int data_size = (texture.Width * texture.Height) * 4;
+ byte[] data = new byte[data_size];
+ for (int i = 0; i < data_size / 4; i++)
+ {
+ int x = i % texture.Width;
+ int y = i / texture.Width;
+ Color pix = texture.GetPixel(x, y);
+
+ data[(i * 4) + 0] = pix.R;
+ data[(i * 4) + 1] = pix.G;
+ data[(i * 4) + 2] = pix.B;
+ data[(i * 4) + 3] = pix.A;
+ }
+ return data;
+ }
+
+ public static byte[] encodeIA4(Bitmap texture)
+ {
+ int data_size = (texture.Width * texture.Height) / 2;
+ byte[] data = new byte[data_size];
+ for (int i = 0; i < data_size; i++)
+ {
+ int x = (i * 2) % texture.Width;
+ int y = (i * 2) / texture.Width;
+
+ Color pix1 = texture.GetPixel(x, y);
+ byte pix1_avg = (byte)((((pix1.R + pix1.G + pix1.B) / 3) / 255.0f) * 8.0f);
+ byte upper = (byte)((pix1_avg << 1) | (pix1.A < 255 ? 0 : 1));
+
+ Color pix2 = texture.GetPixel(x + 1, y);
+ byte pix2_avg = (byte)((((pix2.R + pix2.G + pix2.B) / 3) / 255.0f) * 8.0f);
+ byte lower = (byte)((pix2_avg << 1) | (pix2.A < 255 ? 0 : 1));
+
+ data[i] = (byte)(((upper & 0xF) << 4) | (lower & 0xF));
+ }
+ return data;
+ }
+
+ public static byte[] encodeIA8(Bitmap texture)
+ {
+ int data_size = texture.Width * texture.Height;
+ byte[] data = new byte[data_size];
+ for (int i = 0; i < data_size; i++)
+ {
+ int x = i % texture.Width;
+ int y = i / texture.Width;
+
+ Color pix = texture.GetPixel(x, y);
+ byte pix_avg = (byte)((((pix.R + pix.G + pix.B) / 3) / 255.0f) * 16.0f);
+ byte pix_alpha = (byte)((pix.A / 255.0f) * 16.0f);
+
+ data[i] = (byte)(((pix_avg & 0xF) << 4) | (pix_alpha & 0xF));
+ }
+ return data;
+ }
+
+ public static byte[] encodeIA16(Bitmap texture)
+ {
+ int data_size = texture.Width * texture.Height * 2;
+ byte[] data = new byte[data_size];
+ for (int i = 0; i < data_size / 2; i++)
+ {
+ int x = i % texture.Width;
+ int y = i / texture.Width;
+
+ Color pix = texture.GetPixel(x, y);
+ byte pix_avg = (byte)((pix.R + pix.G + pix.B) / 3);
+
+ data[i * 2] = pix_avg;
+ data[(i * 2) + 1] = pix.A;
+ }
+ return data;
+ }
+
+ public static byte[] encodeI4(Bitmap texture)
+ {
+ int data_size = (texture.Width * texture.Height) / 2;
+ byte[] data = new byte[data_size];
+ for (int i = 0; i < data_size; i++)
+ {
+ int x = (i * 2) % texture.Width;
+ int y = (i * 2) / texture.Width;
+
+ Color pix1 = texture.GetPixel(x, y);
+ byte upper = (byte)((((pix1.R + pix1.G + pix1.B) / 3) / 255.0f) * 16.0f);
+
+ Color pix2 = texture.GetPixel(x + 1, y);
+ byte lower = (byte)((((pix2.R + pix2.G + pix2.B) / 3) / 255.0f) * 16.0f);
+
+ data[i] = (byte)(((upper & 0xF) << 4) | (lower & 0xF));
+ }
+ return data;
+ }
+
+
+ public static byte[] encodeI8(Bitmap texture)
+ {
+ int data_size = texture.Width * texture.Height;
+ byte[] data = new byte[data_size];
+ for (int i = 0; i < data_size; i++)
+ {
+ int x = i % texture.Width;
+ int y = i / texture.Width;
+
+ Color pix = texture.GetPixel(x, y);
+
+ data[i] = (byte)((pix.R + pix.G + pix.B) / 3);
+ }
+ return data;
+ }
+
+ public static Bitmap decodeTexture(N64Codec format, byte[] data, int width, int height, ushort[] palette)
+ {
+ switch (format)
+ {
+ default:
+ case N64Codec.ONEBPP: // Note: "1 bit per pixel" is not a Fast3D format.
+ return decode1BPP(data, width, height);
+ case N64Codec.RGBA16:
+ return decodeRGBA16(data, width, height);
+ case N64Codec.RGBA32:
+ return decodeRGBA32(data, width, height);
+ case N64Codec.CI4:
+ return decodeCI4(data, width, height, palette);
+ case N64Codec.CI8:
+ return decodeCI8(data, width, height, palette);
+ case N64Codec.IA4:
+ return decodeIA4(data, width, height);
+ case N64Codec.IA8:
+ return decodeIA8(data, width, height);
+ case N64Codec.IA16:
+ return decodeIA16(data, width, height);
+ case N64Codec.I4:
+ return decodeI4(data, width, height);
+ case N64Codec.I8:
+ return decodeI8(data, width, height);
+ }
+ }
+
+
+ public static Bitmap decode1BPP(byte[] data, int width, int height)
+ {
+ Bitmap tex = new Bitmap(width, height);
+ if (data.Length >= (width * height) / 8) // Sanity Check
+ {
+ int len = (width * height) / 8;
+ for (int i = 0; i < len; ++i)
+ {
+ for (int x = 0; x < 8; x++)
+ {
+ byte intensity = (byte)((data[i] >> (7 - x)) & 1);
+ if (intensity > 0)
+ intensity = 0xFF;
+ int alpha = intensity;
+ int pos = (i * 8) + x;
+ tex.SetPixel(pos % width, pos / width, Color.FromArgb(alpha, intensity, intensity, intensity));
+ }
+ }
+ }
+ tex.Tag = new string[] { "Format: 1BPP", "Width: " + width,
+ "Height: " + height };
+ return tex;
+ }
+
+ public static Bitmap decodeRGBA32(byte[] data, int width, int height)
+ {
+ Console.WriteLine("Texture size = (" + width + "x" + height + ")");
+ Console.WriteLine("data.Length = (" + data.Length + ")");
+ Bitmap tex = new Bitmap(width, height);
+
+ if (data.Length >= width * height * 4) // Sanity Check
+ {
+ BitmapData bitmapData = tex.LockBits(new Rectangle(0, 0, width, height),
+ ImageLockMode.ReadWrite, tex.PixelFormat);
+
+ int len = width * height;
+ for (int i = 0; i < len; i++)
+ {
+ // Swap red and blue values
+ byte temp_red = data[(i * 4) + 0];
+ data[(i * 4) + 0] = data[(i * 4) + 2];
+ data[(i * 4) + 2] = temp_red;
+ }
+ Marshal.Copy(data, 0, bitmapData.Scan0, data.Length);
+ tex.UnlockBits(bitmapData);
+
+ }
+ tex.Tag = new string[] { "Format: RGBA32", "Width: " + width,
+ "Height: " + height };
+ return tex;
+ }
+
+ public static Bitmap decodeRGBA16(byte[] data, int width, int height)
+ {
+ Bitmap tex = new Bitmap(width, height);
+ if (data.Length >= width * height * 2) // Sanity Check
+ {
+ BitmapData bitmapData = tex.LockBits(new Rectangle(0, 0, width, height),
+ ImageLockMode.ReadWrite, tex.PixelFormat);
+ byte[] pixels = new byte[width * height * 4];
+
+ int len = width * height;
+ for (int i = 0; i < len; i++)
+ {
+ ushort pixel = (ushort)((data[i * 2] << 8) | data[i * 2 + 1]);
+ pixels[(i * 4) + 2] = (byte)(((pixel >> 11) & 0x1F) * 8); // Red
+ pixels[(i * 4) + 1] = (byte)(((pixel >> 6) & 0x1F) * 8); // Green
+ pixels[(i * 4) + 0] = (byte)(((pixel >> 1) & 0x1F) * 8); // Blue
+ pixels[(i * 4) + 3] = (pixel & 1) > 0 ? (byte)0xFF : (byte)0x00; // (Transparency)
+ }
+ Marshal.Copy(pixels, 0, bitmapData.Scan0, pixels.Length);
+ tex.UnlockBits(bitmapData);
+ }
+
+ tex.Tag = new string[] { "Format: RGBA16", "Width: " + width,
+ "Height: " + height };
+ return tex;
+ }
+
+ public static Bitmap decodeIA16(byte[] data, int width, int height)
+ {
+ Bitmap tex = new Bitmap(width, height);
+ if (data.Length >= width * height * 2) // Sanity Check
+ {
+ BitmapData bitmapData = tex.LockBits(new Rectangle(0, 0, width, height),
+ ImageLockMode.ReadWrite, tex.PixelFormat);
+ byte[] pixels = new byte[width * height * 4];
+
+ int len = width * height;
+ for (int i = 0; i < len; i++)
+ {
+ pixels[(i * 4) + 2] = data[i * 2]; // Red
+ pixels[(i * 4) + 1] = data[i * 2]; // Green
+ pixels[(i * 4) + 0] = data[i * 2]; // Blue
+ pixels[(i * 4) + 3] = data[(i * 2) + 1]; // Alpha
+ }
+ Marshal.Copy(pixels, 0, bitmapData.Scan0, pixels.Length);
+ tex.UnlockBits(bitmapData);
+ }
+ tex.Tag = new string[] { "Format: IA16", "Width: " + width,
+ "Height: " + height};
+ return tex;
+ }
+
+ public static Bitmap decodeIA8(byte[] data, int width, int height)
+ {
+ Bitmap tex = new Bitmap(width, height);
+ if (data.Length >= width * height) // Sanity Check
+ {
+ BitmapData bitmapData = tex.LockBits(new Rectangle(0, 0, width, height),
+ ImageLockMode.ReadWrite, tex.PixelFormat);
+ byte[] pixels = new byte[width * height * 4];
+
+ int len = width * height;
+ for (int i = 0; i < len; i++)
+ {
+ byte intensity = (byte)(((data[i] >> 4) & 0xF) * 16);
+ pixels[(i * 4) + 2] = intensity; // Red
+ pixels[(i * 4) + 1] = intensity; // Green
+ pixels[(i * 4) + 0] = intensity; // Blue
+ pixels[(i * 4) + 3] = (byte)((data[i] & 0xF) * 16); // Alpha
+ }
+ Marshal.Copy(pixels, 0, bitmapData.Scan0, pixels.Length);
+ tex.UnlockBits(bitmapData);
+ }
+ tex.Tag = new string[] { "Format: IA8", "Width: " + width,
+ "Height: " + height };
+ return tex;
+ }
+ public static Bitmap decodeIA4(byte[] data, int width, int height)
+ {
+ Bitmap tex = new Bitmap(width, height);
+
+ if (data.Length >= (width * height) / 2) // Sanity Check
+ {
+ BitmapData bitmapData = tex.LockBits(new Rectangle(0, 0, width, height),
+ ImageLockMode.ReadWrite, tex.PixelFormat);
+ byte[] pixels = new byte[width * height * 4];
+
+ int len = (width * height) / 2;
+ for (int i = 0; i < len; i++)
+ {
+ byte twoPixels = data[i];
+
+ byte intensity = (byte)((twoPixels >> 5) * 32);
+ pixels[(i * 8) + 2] = intensity; // Red
+ pixels[(i * 8) + 1] = intensity; // Green
+ pixels[(i * 8) + 0] = intensity; // Blue
+ pixels[(i * 8) + 3] = (byte)(((twoPixels >> 4) & 0x1) * 255); // Alpha
+
+ intensity = (byte)(((twoPixels >> 1) & 0x7) * 32);
+ pixels[(i * 8) + 6] = intensity; // Red
+ pixels[(i * 8) + 5] = intensity; // Green
+ pixels[(i * 8) + 4] = intensity; // Blue
+ pixels[(i * 8) + 7] = (byte)((twoPixels & 0x1) * 255); // Alpha
+ }
+ Marshal.Copy(pixels, 0, bitmapData.Scan0, pixels.Length);
+ tex.UnlockBits(bitmapData);
+ tex.Tag = new string[] { "Format: IA4", "Width: " + width,
+ "Height: " + height };
+ }
+ return tex;
+ }
+ public static Bitmap decodeI8(byte[] data, int width, int height)
+ {
+ Bitmap tex = new Bitmap(width, height);
+
+ if (data.Length >= width * height) // Sanity Check
+ {
+ BitmapData bitmapData = tex.LockBits(new Rectangle(0, 0, width, height),
+ ImageLockMode.ReadWrite, tex.PixelFormat);
+ byte[] pixels = new byte[width * height * 4];
+
+ int len = width * height;
+ for (int i = 0; i < len; i++)
+ {
+ byte intensity = data[i];
+ pixels[(i * 4) + 2] = intensity; // Red
+ pixels[(i * 4) + 1] = intensity; // Green
+ pixels[(i * 4) + 0] = intensity; // Blue
+ pixels[(i * 4) + 3] = 0xFF; // Alpha
+ }
+ Marshal.Copy(pixels, 0, bitmapData.Scan0, pixels.Length);
+ tex.UnlockBits(bitmapData);
+
+ tex.Tag = new string[] { "Format: I8", "Width: " + width,
+ "Height: " + height };
+ }
+ return tex;
+ }
+ public static Bitmap decodeI4(byte[] data, int width, int height)
+ {
+ Bitmap tex = new Bitmap(width, height);
+
+ if (data.Length >= (width * height) / 2) // Sanity Check
+ {
+ BitmapData bitmapData = tex.LockBits(new Rectangle(0, 0, width, height),
+ ImageLockMode.ReadWrite, tex.PixelFormat);
+ byte[] pixels = new byte[width * height * 4];
+
+ int len = (width * height) / 2;
+ for (int i = 0; i < len; i++)
+ {
+ byte twoPixels = data[i];
+
+ byte intensity = (byte)((twoPixels >> 4) * 16);
+ pixels[(i * 8) + 2] = intensity; // Red
+ pixels[(i * 8) + 1] = intensity; // Green
+ pixels[(i * 8) + 0] = intensity; // Blue
+ pixels[(i * 8) + 3] = 0xFF; // Alpha
+
+ intensity = (byte)((twoPixels & 0xF) * 16);
+ pixels[(i * 8) + 6] = intensity; // Red
+ pixels[(i * 8) + 5] = intensity; // Green
+ pixels[(i * 8) + 4] = intensity; // Blue
+ pixels[(i * 8) + 7] = 0xFF; // Alpha
+ }
+ Marshal.Copy(pixels, 0, bitmapData.Scan0, pixels.Length);
+ tex.UnlockBits(bitmapData);
+ }
+ tex.Tag = new string[] { "Format: I4", "Width: " + width,
+ "Height: " + height };
+ return tex;
+ }
+
+ public static Bitmap decodeCI4(byte[] data, int width, int height, ushort[] palette)
+ {
+ Bitmap tex = new Bitmap(width, height);
+
+ if (data.Length >= (width * height) / 2) // Sanity Check
+ {
+ BitmapData bitmapData = tex.LockBits(new Rectangle(0, 0, width, height),
+ ImageLockMode.ReadWrite, tex.PixelFormat);
+ byte[] pixels = new byte[width * height * 4];
+
+ int len = (width * height) / 2;
+ for (int i = 0; i < len; i++)
+ {
+ ushort pixel = palette[(data[i] >> 4) & 0xF];
+ pixels[(i * 8) + 2] = (byte)(((pixel >> 11) & 0x1F) * 8); // Red
+ pixels[(i * 8) + 1] = (byte)(((pixel >> 6) & 0x1F) * 8); // Green
+ pixels[(i * 8) + 0] = (byte)(((pixel >> 1) & 0x1F) * 8); // Blue
+ pixels[(i * 8) + 3] = (pixel & 1) > 0 ? (byte)0xFF : (byte)0x00; // Alpha
+
+ pixel = palette[(data[i]) & 0xF];
+ pixels[(i * 8) + 6] = (byte)(((pixel >> 11) & 0x1F) * 8); // Red
+ pixels[(i * 8) + 5] = (byte)(((pixel >> 6) & 0x1F) * 8); // Green
+ pixels[(i * 8) + 4] = (byte)(((pixel >> 1) & 0x1F) * 8); // Blue
+ pixels[(i * 8) + 7] = (pixel & 1) > 0 ? (byte)0xFF : (byte)0x00; // Alpha
+
+ }
+ Marshal.Copy(pixels, 0, bitmapData.Scan0, pixels.Length);
+ tex.UnlockBits(bitmapData);
+ }
+ tex.Tag = new string[] { "Format: CI4", "Width: " + width,
+ "Height: " + height };
+ return tex;
+ }
+
+ public static Bitmap decodeCI8(byte[] data, int width, int height, ushort[] palette)
+ {
+ Bitmap tex = new Bitmap(width, height);
+
+ if (data.Length >= width * height) // Sanity Check
+ {
+ BitmapData bitmapData = tex.LockBits(new Rectangle(0, 0, width, height),
+ ImageLockMode.ReadWrite, tex.PixelFormat);
+ byte[] pixels = new byte[width * height * 4];
+
+ int len = width * height;
+ for (int i = 0; i < len; i++)
+ {
+ ushort pixel = palette[data[i]];
+ pixels[(i * 4) + 2] = (byte)(((pixel >> 11) & 0x1F) * 8); // Red
+ pixels[(i * 4) + 1] = (byte)(((pixel >> 6) & 0x1F) * 8); // Green
+ pixels[(i * 4) + 0] = (byte)(((pixel >> 1) & 0x1F) * 8); // Blue
+ pixels[(i * 4) + 3] = (pixel & 1) > 0 ? (byte)0xFF : (byte)0x00; // (Transparency)
+ //tex.SetPixel(i % width, i / width, Color.FromArgb(alpha, red, green, blue));
+ }
+ Marshal.Copy(pixels, 0, bitmapData.Scan0, pixels.Length);
+ tex.UnlockBits(bitmapData);
+ }
+ tex.Tag = new string[] { "Format: CI8", "Width: " + width,
+ "Height: " + height };
+ return tex;
+ }
+ }
+}
diff --git a/SM64Lib.Text.Exporters/ExcelExporter.cs b/SM64Lib.Text.Exporters/ExcelExporter.cs
new file mode 100644
index 0000000..bd71057
--- /dev/null
+++ b/SM64Lib.Text.Exporters/ExcelExporter.cs
@@ -0,0 +1,102 @@
+using OfficeOpenXml;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SM64Lib.Text.Exporters
+{
+ public class ExcelExporter
+ {
+ public ExcelExporter()
+ {
+ ExcelPackage.LicenseContext = LicenseContext.NonCommercial;
+ }
+
+ public async Task Export(string destFilePath, TextGroup[] groups)
+ {
+ var pkg = new ExcelPackage();
+
+ foreach (var tg in groups)
+ {
+ var ws = pkg.Workbook.Worksheets.Add(tg.TextGroupInfo.Name);
+ var hasDialogCells = false;
+
+ ws.Cells.Style.VerticalAlignment = OfficeOpenXml.Style.ExcelVerticalAlignment.Top;
+ ws.Cells.Style.HorizontalAlignment = OfficeOpenXml.Style.ExcelHorizontalAlignment.Left;
+ ws.Row(1).Style.Font.Bold = true;
+ ws.Cells[1, 1].Value = "#";
+ ws.Cells[1, 2].Value = "Text";
+
+ for (int i = 0; i < tg.Count; i++)
+ {
+ var ti = tg[i];
+ var ri = i + 2;
+
+ ws.Cells[ri, 1].Value = i;
+ ws.Cells[ri, 2].Value = ti.Text;
+
+ if (ti is TextTableDialogItem)
+ hasDialogCells = true;
+ }
+
+
+ for (int ri = 1; ri <= ws.Cells.Rows; ri++)
+ {
+ var r = ws.Row(ri);
+ r.CustomHeight = false;
+ }
+
+ if (hasDialogCells)
+ {
+ var c = ws.Column(2);
+ c.Style.WrapText = true;
+ c.Width = 30;
+ }
+
+ for (int ci = 1; ci <= 2; ci++)
+ {
+ var c = ws.Column(ci);
+ if (!c.Style.WrapText)
+ c.AutoFit();
+ }
+ }
+
+ await pkg.SaveAsAsync(new FileInfo(destFilePath));
+ pkg.Dispose();
+ }
+
+ public async Task Import(string filePath, TextGroup[] groups)
+ {
+ try
+ {
+ var pkg = new ExcelPackage();
+ await pkg.LoadAsync(new FileInfo(filePath));
+
+ foreach (var tg in groups)
+ {
+ var ws = pkg.Workbook.Worksheets[tg.TextGroupInfo.Name];
+ if (ws is not null)
+ {
+ for (int iti = 0; iti < tg.Count; iti++)
+ {
+ var ti = tg[iti];
+ var ri = iti + 2;
+ var c = ws.Cells[ri, 2];
+
+ ti.Text = ((string)c.Value).Replace("\r\n", "\n").Replace("\n", "\r\n");
+ tg.NeedToSave = true;
+ }
+ }
+ }
+
+ pkg.Dispose();
+ }
+ catch (Exception)
+ {
+ }
+ }
+ }
+}
diff --git a/SM64Lib.Text.Exporters/Properties/AssemblyInfo.cs b/SM64Lib.Text.Exporters/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..0a38d8c
--- /dev/null
+++ b/SM64Lib.Text.Exporters/Properties/AssemblyInfo.cs
@@ -0,0 +1,11 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// Durch Festlegen von ComVisible auf FALSE werden die Typen in dieser Assembly
+// für COM-Komponenten unsichtbar. Wenn Sie auf einen Typ in dieser Assembly von
+// COM aus zugreifen müssen, sollten Sie das ComVisible-Attribut für diesen Typ auf "True" festlegen.
+[assembly: ComVisible(false)]
+
+// Die folgende GUID bestimmt die ID der Typbibliothek, wenn dieses Projekt für COM verfügbar gemacht wird
+[assembly: Guid("e6305a1d-e5da-4bb6-992e-3a341bc99a7b")]
diff --git a/SM64Lib.Text.Exporters/SM64Lib.Text.Exporters.csproj b/SM64Lib.Text.Exporters/SM64Lib.Text.Exporters.csproj
new file mode 100644
index 0000000..30e6244
--- /dev/null
+++ b/SM64Lib.Text.Exporters/SM64Lib.Text.Exporters.csproj
@@ -0,0 +1,23 @@
+
+
+ net8.0-windows
+ SM64Lib.Text.Exporters
+ SM64Lib.Text.Exporters
+ Copyright © 2020
+ AnyCPU
+ Debug;Release;ReleaseBundle;ReleaseStandalone
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SM64Lib.Text.Exporters/TxtExporter.cs b/SM64Lib.Text.Exporters/TxtExporter.cs
new file mode 100644
index 0000000..da66691
--- /dev/null
+++ b/SM64Lib.Text.Exporters/TxtExporter.cs
@@ -0,0 +1,48 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SM64Lib.Text.Exporters
+{
+ public class TxtExporter
+ {
+ public async Task Export(string destFilePath, TextGroup[] groups)
+ {
+ var sw = new StreamWriter(destFilePath);
+
+ for (int itg = 0; itg < groups.Length; itg++)
+ {
+ var tg = groups[itg];
+
+ if (itg != 0)
+ await sw.WriteLineAsync("------------------------------\n");
+ await sw.WriteLineAsync($"Text Group - {tg.TextGroupInfo.Name}\n");
+ await sw.WriteLineAsync("------------------------------\n");
+
+ for (int iti = 0; iti < tg.Count; iti++)
+ {
+ var ti = tg[iti];
+
+ if (ti is TextTableDialogItem)
+ {
+ await sw.WriteLineAsync($"Dialog #{iti}\n");
+ await sw.WriteLineAsync(ti.Text);
+ await sw.WriteLineAsync("\n\n");
+ }
+ else
+ {
+ await sw.WriteLineAsync($"Text Item #{iti}");
+ await sw.WriteLineAsync(ti.Text);
+ await sw.WriteLineAsync();
+ }
+ }
+ }
+
+ await sw.FlushAsync();
+ sw.Close();
+ }
+ }
+}
diff --git a/SM64Lib.TextValueConverter/Properties/AssemblyInfo.cs b/SM64Lib.TextValueConverter/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..021c7af
--- /dev/null
+++ b/SM64Lib.TextValueConverter/Properties/AssemblyInfo.cs
@@ -0,0 +1,8 @@
+using global::System;
+using global::System.Reflection;
+using global::System.Runtime.InteropServices;
+[assembly: ComVisible(false)]
+
+// Die folgende GUID bestimmt die ID der Typbibliothek, wenn dieses Projekt für COM verfügbar gemacht wird.
+[assembly: Guid("245f4730-1df1-43f7-8bef-9626648c4204")]
+
diff --git a/SM64Lib.TextValueConverter/SM64Lib.TextValueConverter.csproj b/SM64Lib.TextValueConverter/SM64Lib.TextValueConverter.csproj
new file mode 100644
index 0000000..eed1d50
--- /dev/null
+++ b/SM64Lib.TextValueConverter/SM64Lib.TextValueConverter.csproj
@@ -0,0 +1,62 @@
+
+
+ Windows
+ net8.0
+ $(DefaultItemExcludes);$(ProjectDir)**\*.vb
+ latest
+ TextValueConverter
+ DRSN
+ TextValueConverter
+ Copyright © Pilzinsel64 2018 - 2020
+ SM64Lib.TextValueConverter.xml
+ true
+ 42016,41999,42017,42018,42019,42032,42036,42020,42021,42022,CS1591,CS0168
+ AnyCPU
+ Debug;Release;ReleaseBundle;ReleaseStandalone
+
+
+ true
+
+
+ false
+
+
+ false
+
+
+ false
+
+
+ On
+
+
+ Binary
+
+
+ Off
+
+
+ On
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SM64Lib.TextValueConverter/SM64Lib.TextValueConverter.xml b/SM64Lib.TextValueConverter/SM64Lib.TextValueConverter.xml
new file mode 100644
index 0000000..8172c5b
--- /dev/null
+++ b/SM64Lib.TextValueConverter/SM64Lib.TextValueConverter.xml
@@ -0,0 +1,8 @@
+
+
+
+ SM64Lib.TextValueConverter
+
+
+
+
diff --git a/SM64Lib.TextValueConverter/TextValueConverter.cs b/SM64Lib.TextValueConverter/TextValueConverter.cs
new file mode 100644
index 0000000..170a6f1
--- /dev/null
+++ b/SM64Lib.TextValueConverter/TextValueConverter.cs
@@ -0,0 +1,89 @@
+using System;
+using Microsoft.VisualBasic.CompilerServices;
+
+namespace SM64Lib.TextValueConverter
+{
+ public static class TextValueConverter
+ {
+ public static event WantIntegerValueModeEventHandler WantIntegerValueMode;
+ public delegate void WantIntegerValueModeEventHandler(WantIntegerValueModeEventArgs e);
+
+ public static int ValueFromText(string Text, int DefaultValue = 0, int useIVM = -1)
+ {
+ return Convert.ToInt32(LongFromText(Text, DefaultValue, useIVM));
+ }
+
+ public static long LongFromText(string Text, long DefaultValue = 0, int useIVM = -1)
+ {
+ try
+ {
+ int IVM = useIVM > -1 ? useIVM : GetIntegerValueMode();
+ Text = Text?.ToLower()?.Trim();
+
+ if (string.IsNullOrWhiteSpace(Text))
+ return 0;
+
+ switch (true)
+ {
+ case object _ when Text.StartsWith("0x"):
+ case object _ when Text.StartsWith("&h"):
+ return Convert.ToInt32(Text.Substring(2), 16);
+ case object _ when Text.StartsWith("$"):
+ return Convert.ToInt32(Text.Substring(1), 16);
+ case object _ when Text.StartsWith("0b"):
+ case object _ when Text.StartsWith("&b"):
+ return Convert.ToInt32(Text.Substring(2), 2);
+ default:
+ return Convert.ToInt32(Text);
+ }
+ }
+ catch (Exception)
+ {
+ return DefaultValue;
+ }
+ }
+
+ public static string TextFromValue(long Value, int IVM = -1, int charCount = 0)
+ {
+ if (IVM == -1)
+ {
+ IVM = GetIntegerValueMode();
+ }
+
+ switch (IVM)
+ {
+ case 0:
+ return Value.ToString(GetCharCountAsZeroString(charCount));
+ case 1:
+ return "0x" + Value.ToString("X" + (charCount > 0 ? charCount.ToString() : ""));
+ case 2:
+ return "&H" + Value.ToString("X" + (charCount > 0 ? charCount.ToString() : ""));
+ case 3:
+ return "$" + Value.ToString("X" + (charCount > 0 ? charCount.ToString() : ""));
+ default:
+ return string.Empty;
+ }
+ }
+
+ private static string GetCharCountAsZeroString(int charCount)
+ {
+ string GetCharCountAsZeroStringRet = default;
+ GetCharCountAsZeroStringRet = "";
+ while (GetCharCountAsZeroStringRet.Length < charCount)
+ GetCharCountAsZeroStringRet += "0";
+ return GetCharCountAsZeroStringRet;
+ }
+
+ private static int GetIntegerValueMode()
+ {
+ var e = new WantIntegerValueModeEventArgs();
+ WantIntegerValueMode?.Invoke(e);
+ return e.IntegerValueMode;
+ }
+ }
+
+ public class WantIntegerValueModeEventArgs : EventArgs
+ {
+ public int IntegerValueMode { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/SM64Lib/ASM/CustomAsmArea.cs b/SM64Lib/ASM/CustomAsmArea.cs
new file mode 100644
index 0000000..e34272b
--- /dev/null
+++ b/SM64Lib/ASM/CustomAsmArea.cs
@@ -0,0 +1,83 @@
+using Newtonsoft.Json;
+using SM64Lib.Data;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SM64Lib.ASM
+{
+ public class CustomAsmArea
+ {
+ public byte[] AreaBytes { get; set; }
+
+ [JsonProperty(nameof(Config))]
+ private CustomAsmAreaConfig config;
+
+ [JsonIgnore]
+ public CustomAsmAreaConfig Config
+ {
+ get
+ {
+ if (config == null)
+ config = new CustomAsmAreaConfig();
+ return config;
+ }
+ }
+
+ [JsonConstructor]
+ private CustomAsmArea(JsonConstructorAttribute emptyObject)
+ {
+ }
+
+ public CustomAsmArea()
+ : this(true)
+ {
+ }
+
+ private CustomAsmArea(bool createEmptyAreaBytesArray)
+ : this(null, createEmptyAreaBytesArray)
+ {
+ }
+
+ public CustomAsmArea(CustomAsmAreaConfig config)
+ : this(config, false)
+ {
+ }
+
+ private CustomAsmArea(CustomAsmAreaConfig config, bool createEmptyAreaBytesArray)
+ {
+ this.config = config;
+
+ if (createEmptyAreaBytesArray)
+ AreaBytes = new byte[] { };
+ }
+
+ public void Load(BinaryData data, CustomAsmBankConfig bankConfig)
+ {
+ if (Config.RomAddress != -1 && Config.Length > 0)
+ {
+ data.Position = Config.RomAddress;
+ AreaBytes = data.Read(Config.Length);
+ }
+ else
+ AreaBytes = new byte[] { };
+ }
+
+ public int Save(BinaryData data, int address, CustomAsmBankConfig bankConfig)
+ {
+ data.Position = address;
+ data.Write(AreaBytes);
+ return UpdateAddresses(address, bankConfig);
+ }
+
+ internal int UpdateAddresses(int address, CustomAsmBankConfig bankConfig)
+ {
+ Config.RomAddress = address;
+ Config.Length = AreaBytes.Length;
+ Config.RamAddress = address - bankConfig.GetRomStartAddress() + bankConfig.GetRamStartAddress();
+ return Config.Length;
+ }
+ }
+}
diff --git a/SM64Lib/ASM/CustomAsmAreaConfig.cs b/SM64Lib/ASM/CustomAsmAreaConfig.cs
new file mode 100644
index 0000000..dff55ca
--- /dev/null
+++ b/SM64Lib/ASM/CustomAsmAreaConfig.cs
@@ -0,0 +1,39 @@
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Pilz.Cryptography;
+using Pilz.Json.Converters;
+
+namespace SM64Lib.ASM
+{
+ public class CustomAsmAreaConfig
+ {
+ internal delegate void RequestCustomAsmAreaEventHandler(CustomAsmAreaConfig config, RequestCustomAsmAreaEventArgs request);
+ internal static event RequestCustomAsmAreaEventHandler RequestCustomAsmArea;
+
+ [JsonConverter(typeof(UniquieIDStringJsonConverter))]
+ public UniquieID ID { get; set; } = new();
+ public string Name { get; set; }
+ [JsonProperty]
+ public int RamAddress { get; internal set; } = -1;
+ [JsonProperty]
+ public int RomAddress { get; internal set; } = -1;
+ [JsonProperty]
+ public int Length { get; internal set; } = 0;
+
+ public CustomAsmArea FindCustomAsmArea()
+ {
+ var args = new RequestCustomAsmAreaEventArgs();
+ RequestCustomAsmArea?.Invoke(this, args);
+ return args.CustomAsmArea;
+ }
+
+ internal class RequestCustomAsmAreaEventArgs
+ {
+ public CustomAsmArea CustomAsmArea { get; set; }
+ }
+ }
+}
diff --git a/SM64Lib/ASM/CustomAsmBank.cs b/SM64Lib/ASM/CustomAsmBank.cs
new file mode 100644
index 0000000..0013409
--- /dev/null
+++ b/SM64Lib/ASM/CustomAsmBank.cs
@@ -0,0 +1,85 @@
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SM64Lib.ASM
+{
+ public class CustomAsmBank
+ {
+ public CustomAsmBankConfig Config { get; }
+ [JsonIgnore]
+ public List Areas { get; } = new List();
+
+ public CustomAsmBank() : this(new CustomAsmBankConfig())
+ {
+ }
+
+ public CustomAsmBank(CustomAsmBankConfig config)
+ {
+ Config = config;
+ }
+
+ public void Load(RomManager romManager)
+ {
+ Areas.Clear();
+
+ bool canLoadArea(CustomAsmAreaConfig areaConfig) =>
+ areaConfig.RomAddress != -1;
+
+ if (Config.Areas.Where(n => canLoadArea(n)).Any())
+ {
+ var data = romManager.GetBinaryRom(System.IO.FileAccess.Read);
+
+ foreach (var areaConfig in Config.Areas)
+ {
+ var area = new CustomAsmArea(areaConfig);
+ area.Config.ID.GenerateIfNull();
+ area.Load(data, Config);
+ Areas.Add(area);
+ }
+
+ data.Close();
+ }
+ }
+
+ public void Save(RomManager romManager)
+ {
+ var startAddr = Config.GetRomStartAddress();
+ var curRomAddr = startAddr;
+
+ if (Areas.Any())
+ {
+ var data = romManager.GetBinaryRom(System.IO.FileAccess.ReadWrite);
+
+ foreach (var area in Areas)
+ curRomAddr += area.Save(data, curRomAddr, Config);
+
+ data.Close();
+ }
+
+ UpdateAreaConfigCollection();
+ Config.Length = curRomAddr - startAddr;
+ }
+
+ public void UpdateAddresses()
+ {
+ if (Areas.Any())
+ {
+ var startAddr = Config.GetRomStartAddress();
+ var curRomAddr = startAddr;
+ foreach (var area in Areas)
+ curRomAddr += area.UpdateAddresses(curRomAddr, Config);
+ Config.Length = curRomAddr - startAddr;
+ }
+ }
+
+ public void UpdateAreaConfigCollection()
+ {
+ Config.Areas.Clear();
+ Config.Areas.AddRange(Areas.Select(n => n.Config));
+ }
+ }
+}
diff --git a/SM64Lib/ASM/CustomAsmBankConfig.cs b/SM64Lib/ASM/CustomAsmBankConfig.cs
new file mode 100644
index 0000000..14646bb
--- /dev/null
+++ b/SM64Lib/ASM/CustomAsmBankConfig.cs
@@ -0,0 +1,50 @@
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SM64Lib.ASM
+{
+ public class CustomAsmBankConfig
+ {
+ public static int DefaultMaxLength { get; set; } = 0xA000;
+ public static int DefaultRomStartAddress { get; internal set; } = 0x1206000;
+ public static int DefaultRamStartAddress { get; internal set; } = 0x406000;
+
+ public List Areas { get; } = new List();
+ public int MaxLength { get; set; } = -1;
+ [JsonProperty]
+ public int Length { get; internal set; } = -1;
+ public int RomStartAddress { get; set; } = -1;
+ public int RamStartAddress { get; set; } = -1;
+
+ public int GetRomStartAddress()
+ => GetRomStartAddressAdv().address;
+
+ public (int address, bool isDefault) GetRomStartAddressAdv()
+ {
+ bool isDefault = RomStartAddress == -1;
+ return (isDefault ? DefaultRomStartAddress : RomStartAddress, isDefault);
+ }
+
+ public int GetRamStartAddress()
+ => GetRamStartAddressAdv().address;
+
+ public (int address, bool isDefault) GetRamStartAddressAdv()
+ {
+ bool isDefault = RamStartAddress == -1;
+ return (isDefault ? DefaultRamStartAddress : RamStartAddress, isDefault);
+ }
+
+ public int GetMaxLength()
+ => GetMaxLengthAdv().length;
+
+ public (int length, bool isDefault) GetMaxLengthAdv()
+ {
+ bool isDefault = RamStartAddress == -1;
+ return (isDefault ? DefaultMaxLength : MaxLength, isDefault);
+ }
+ }
+}
diff --git a/SM64Lib/BaseTweakScriptInfo.cs b/SM64Lib/BaseTweakScriptInfo.cs
new file mode 100644
index 0000000..55a1b66
--- /dev/null
+++ b/SM64Lib/BaseTweakScriptInfo.cs
@@ -0,0 +1,30 @@
+using SM64Lib.Patching;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SM64Lib
+{
+ public class BaseTweakScriptInfo
+ {
+ public PatchScript Script { get; private set; }
+ public int Priority { get; private set; }
+ public bool IsReadOnly { get; private set; }
+ public string Name { get; private set; }
+ public bool EnabledByDefault { get; private set; }
+ public bool Enabled { get; set; }
+
+ public BaseTweakScriptInfo(PatchScript script)
+ {
+ var infos = script.Name.Split('#');
+ Priority = Convert.ToInt32(infos[0]);
+ IsReadOnly = infos[1] == "w" ? true : false;
+ EnabledByDefault = infos[2] == "y" ? true : false;
+ Enabled = EnabledByDefault;
+ Name = infos[3];
+ Script = script;
+ }
+ }
+}
diff --git a/SM64Lib/Behaviors/Behavior.cs b/SM64Lib/Behaviors/Behavior.cs
new file mode 100644
index 0000000..a2dd0f1
--- /dev/null
+++ b/SM64Lib/Behaviors/Behavior.cs
@@ -0,0 +1,241 @@
+using Newtonsoft.Json;
+using SM64Lib.ASM;
+using SM64Lib.Behaviors.Script;
+using SM64Lib.Data;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Z.Collections.Extensions;
+using static SM64Lib.Extensions.ObjectExtensions;
+
+namespace SM64Lib.Behaviors
+{
+ public class Behavior
+ {
+ private readonly Dictionary knownCustomAsmCommands = new Dictionary();
+
+ [JsonProperty]
+ public BehaviorConfig Config { get; private set; }
+ [JsonProperty]
+ public Behaviorscript Script { get; private set; }
+ public int CollisionPointer { get; set; }
+ public bool EnableCollisionPointer { get; set; }
+ public List BehaviorAddressDestinations { get; set; } = new List();
+
+ [JsonIgnore]
+ public long Length
+ {
+ get
+ {
+ if (Config.FixedLength == -1)
+ return Script.Length;
+ else
+ return Config.FixedLength;
+ }
+ }
+
+ public Behavior() : this(new BehaviorConfig())
+ {
+ }
+
+ public Behavior(BehaviorConfig config)
+ {
+ CreateNewBehaviorscript();
+ Config = config;
+ }
+
+ public Behavior(BehaviorCreationTypes behaviorCreationType) : this()
+ {
+ Create(behaviorCreationType);
+ }
+
+ public void Create(BehaviorCreationTypes type)
+ {
+ CreateNewBehaviorscript();
+
+ switch (type)
+ {
+ case BehaviorCreationTypes.SolidObject:
+ Script.Add(new BehaviorscriptCommand("00 09 00 00"));
+ Script.Add(new BehaviorscriptCommand("11 01 00 01"));
+ Script.Add(new BehaviorscriptCommand("2a 00 00 00 00 00 00 00"));
+ Script.Add(new BehaviorscriptCommand("08 00 00 00"));
+ Script.Add(new BehaviorscriptCommand("0c 00 00 00 80 38 39 cc"));
+ Script.Add(new BehaviorscriptCommand("09 00 00 00"));
+ break;
+ }
+
+ ParseScript();
+ }
+
+ private void CreateNewBehaviorscript()
+ {
+ if (Script != null)
+ Script.Close();
+ Script = new Behaviorscript();
+ }
+
+ public bool Read(BinaryData data, int address)
+ {
+ CreateNewBehaviorscript();
+ var success = Script.Read(data, address, Config.IsVanilla, Config.ExpectedLength != -1, Config.ExpectedLength);
+ if (Config.IsVanilla)
+ Config.FixedLength = (int)Script.Length;
+ Config.ExpectedLength = (int)Script.Length;
+ ParseScript();
+ return success;
+ }
+
+ public void Write(BinaryData data, int address)
+ {
+ //if (!Config.IsVanilla)
+ TakeoverSettingsToScript();
+
+ var length = Script.Write(data, address);
+
+ if (Config.FixedLength != -1 && length != Config.FixedLength)
+ data.Position -= length - Config.FixedLength;
+ Config.ExpectedLength = length;
+ }
+
+ public void ParseScript()
+ {
+ var dicCustomAsmFuncs = new Dictionary();
+ EnableCollisionPointer = false;
+ knownCustomAsmCommands.Clear();
+
+ foreach (var link in Config.CustomAsmLinks)
+ dicCustomAsmFuncs.AddOrUpdate(link.CustomAsmAreaConfig.RamAddress | unchecked((int)0x80000000), link);
+
+ foreach (BehaviorscriptCommand cmd in Script)
+ {
+ switch (cmd.CommandType)
+ {
+ case BehaviorscriptCommandTypes.x2A_SetCollision:
+ CollisionPointer = BehaviorscriptCommandFunctions.X2A.GetCollisionPointer(cmd);
+ EnableCollisionPointer = true;
+ break;
+ case BehaviorscriptCommandTypes.x0C_CallFunction:
+ var ptr = BehaviorscriptCommandFunctions.X0C.GetAsmPointer(cmd);
+ if (dicCustomAsmFuncs.ContainsKey(ptr))
+ knownCustomAsmCommands.AddOrUpdate(dicCustomAsmFuncs[ptr].CustomAsmAreaConfig, cmd);
+ break;
+ }
+ }
+ }
+
+ public void TakeoverSettingsToScript()
+ {
+ // Update collision pointer
+ AddUpdateRemoveCmd(
+ BehaviorscriptCommandTypes.x2A_SetCollision,
+ EnableCollisionPointer,
+ () => BehaviorscriptCommandFactory.Build_x2A(CollisionPointer),
+ (cmd) => BehaviorscriptCommandFunctions.X2A.SetCollisionPointer(cmd, CollisionPointer));
+
+ // Insert Custom Asm Links
+ {
+ foreach (var link in Config.CustomAsmLinks)
+ {
+ var asmPointer = link.CustomAsmAreaConfig.RamAddress | unchecked((int)0x80000000);
+ var cmdStartLoop = Script.FirstOfType(BehaviorscriptCommandTypes.x08_LoopStart);
+ var cmdStartLoopIndex = Script.IndexOf(cmdStartLoop);
+ int iInsert;
+
+ if (link.Loop)
+ {
+ if (cmdStartLoop is not null)
+ iInsert = cmdStartLoopIndex + 1;
+ else
+ iInsert = -1;
+ }
+ else
+ {
+ if (cmdStartLoop is not null)
+ iInsert = cmdStartLoopIndex;
+ else
+ iInsert = (int)Script.Count - 2;
+ }
+
+ if (knownCustomAsmCommands.ContainsKey(link.CustomAsmAreaConfig))
+ {
+ var cmd = knownCustomAsmCommands[link.CustomAsmAreaConfig];
+ var cmdIndex = Script.IndexOf(cmd);
+ BehaviorscriptCommandFunctions.X0C.SetAsmPointer(cmd, asmPointer);
+
+ if (cmdIndex != -1)
+ {
+ var reinsert = false;
+
+ if (link.Loop && cmdIndex < cmdStartLoopIndex)
+ {
+ reinsert = true;
+ iInsert -= 1;
+ }
+ else if (!link.Loop && cmdIndex > cmdStartLoopIndex)
+ reinsert = true;
+
+ if (reinsert)
+ {
+ Script.Remove(cmd);
+ Script.Insert(iInsert, cmd);
+ }
+ }
+ }
+ else if (iInsert != -1)
+ {
+ var cmd = BehaviorscriptCommandFactory.Build_x0C(asmPointer);
+ Script.Insert(iInsert, cmd);
+ knownCustomAsmCommands.Add(link.CustomAsmAreaConfig, cmd);
+ }
+ }
+
+ foreach (var kvp in knownCustomAsmCommands.ToArray())
+ {
+ if (!Config.CustomAsmLinks.Where(n => n.CustomAsmAreaConfig == kvp.Key).Any())
+ {
+ knownCustomAsmCommands.Remove(kvp.Key);
+ Script.RemoveIfContains(kvp.Value);
+ kvp.Value.Close();
+ }
+ }
+ }
+ }
+
+ private void AddUpdateRemoveCmd(BehaviorscriptCommandTypes cmdType, bool conditionAddUpdate, Func createCmd, Action updateCmd)
+ {
+ var cmd = Script.FirstOfType(cmdType);
+ if (cmd is not null)
+ {
+ if (conditionAddUpdate)
+ updateCmd(cmd);
+ else
+ {
+ Script.Remove(cmd);
+ cmd.Close();
+ }
+ }
+ else if (conditionAddUpdate)
+ {
+ cmd = createCmd();
+ Script.Insert(1, cmd);
+ }
+ }
+
+ public void CopyPropertiesTo(Behavior dest)
+ {
+ // Copy Script
+ TakeoverSettingsToScript();
+ dest.Script.Close();
+ dest.Script.AddRange(Script);
+ dest.ParseScript();
+
+ // Copy Configs
+ dest.Config.Name = Config.Name;
+ Config.ParamsInfo.CloneTo(dest.Config.ParamsInfo);
+ }
+
+ }
+}
diff --git a/SM64Lib/Behaviors/BehaviorBank.cs b/SM64Lib/Behaviors/BehaviorBank.cs
new file mode 100644
index 0000000..c0ab209
--- /dev/null
+++ b/SM64Lib/Behaviors/BehaviorBank.cs
@@ -0,0 +1,220 @@
+using Newtonsoft.Json;
+using SM64Lib.Data;
+using SM64Lib.SegmentedBanking;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Z.Collections.Extensions;
+
+namespace SM64Lib.Behaviors
+{
+ public class BehaviorBank
+ {
+ public BehaviorBankConfig Config { get; private set; }
+ public List Behaviors { get; } = new List();
+
+ [JsonIgnore]
+ public long Length
+ {
+ get => Behaviors.Sum(n => n.Length);
+ }
+
+ public BehaviorBank(BehaviorBankConfig config)
+ {
+ Config = config;
+ }
+
+ public void ReadBank(SegmentedBank seg, int offset)
+ {
+ ReadBank(seg, offset, false);
+ }
+
+ public void ReadVanillaBank(SegmentedBank seg)
+ {
+ ReadBank(seg, 0, true);
+ }
+
+ private void ReadBank(SegmentedBank seg, int offset, bool isVanilla)
+ {
+ var data = new BinaryStreamData(seg.Data);
+ data.Position = offset;
+ ReadBank(data, isVanilla, seg.Length, (sbyte)seg.BankID);
+ }
+
+ public void ReadBank(BinaryData data)
+ {
+ ReadBank(data, false, -1, -1);
+ }
+
+ private void ReadBank(BinaryData data, bool isVanilla, int endAddress, sbyte bankID)
+ {
+ // Clear current list
+ Behaviors.Clear();
+
+ if (isVanilla)
+ {
+ while (data.Position <= endAddress - 0x10)
+ {
+ var conf = new BehaviorConfig();
+ Config.BehaviorConfigs.Add(conf);
+ readBehavior(conf);
+ }
+ }
+ else
+ {
+ foreach (var config in Config.BehaviorConfigs.OrderBy(n => n.BankAddress))
+ readBehavior(config);
+ }
+
+ void readBehavior(BehaviorConfig config)
+ {
+ int bankOffset;
+ Behavior behav = new Behavior(config);
+
+ if (isVanilla)
+ {
+ bankOffset = (int)data.Position;
+ config.BankAddress = (bankID << 24) | bankOffset;
+ behav.Config.IsVanilla = true;
+ }
+ else
+ {
+ bankOffset = config.BankAddress & 0xffffff;
+ if (!behav.Config.IsVanilla && !config.ID.HasID) config.ID.Generate();
+ }
+
+ var lastBehav = Behaviors.LastOrDefault();
+
+ if (behav.Read(data, bankOffset))
+ {
+ if (isVanilla && lastBehav is not null && lastBehav.Config.IsVanilla && lastBehav.Config.FixedLength != -1)
+ lastBehav.Config.FixedLength = bankOffset - (lastBehav.Config.BankAddress & 0xffffff);
+
+ Behaviors.Add(behav);
+ }
+ }
+ }
+
+ public SegmentedBank WriteToSeg(byte bankID, int offset, RomManager rommgr)
+ {
+ var segStream = new MemoryStream();
+ var seg = new SegmentedBank(bankID, segStream);
+ int lastPos = WriteToSeg(seg, offset, rommgr);
+ seg.Length = General.HexRoundUp1(lastPos);
+ return seg;
+ }
+
+ public int WriteToSeg(SegmentedBank seg, int offset, RomManager rommgr)
+ {
+ var addressUpdates = new Dictionary();
+ var segStartAddress = seg.BankAddress | offset;
+ var data = new BinaryStreamData(seg.Data);
+ data.Position = offset;
+
+ if (Behaviors.Any())
+ {
+ // Save Behaviors to ROM
+ foreach (var behav in Behaviors)
+ {
+ var address = (int)data.Position;
+ var newBankAddress = (int)data.Position - offset + segStartAddress;
+ if (newBankAddress != behav.Config.BankAddress)
+ {
+ if (behav.Config.BankAddress != -1)
+ addressUpdates.AddOrUpdate(behav.Config.BankAddress, newBankAddress);
+ behav.Config.BankAddress = newBankAddress;
+ }
+ behav.Write(data, (int)data.Position);
+ }
+ }
+
+ // Delete unused configs / Add new configs
+ Config.BehaviorConfigs.Clear();
+ Config.BehaviorConfigs.AddRange(Behaviors.Select(n => n.Config));
+
+ // Update addresses
+ UpdateBehaviorAddresses(rommgr, addressUpdates);
+
+ return (int)data.Position;
+ }
+
+ private void UpdateBehaviorAddresses(RomManager rommgr, Dictionary addressUpdates)
+ {
+ if (Behaviors.Any() && rommgr is not null)
+ {
+ foreach (var lvl in rommgr.Levels)
+ {
+ foreach (var area in lvl.Areas)
+ {
+ foreach (var obj in area.Objects)
+ {
+ var behavAddr = Levels.Script.Commands.clNormal3DObject.GetSegBehaviorAddr(obj);
+ foreach (var kvp in addressUpdates)
+ {
+ if (behavAddr == kvp.Key)
+ Levels.Script.Commands.clNormal3DObject.SetSegBehaviorAddr(obj, (uint)kvp.Value);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ public void WriteBehaviorAddresss(RomManager rommgr)
+ {
+ var data = rommgr.GetBinaryRom(FileAccess.ReadWrite);
+ WriteBehaviorAddresss(data);
+ data.Close();
+ }
+
+ public void WriteBehaviorAddresss(BinaryData data)
+ {
+ if (Behaviors.Any())
+ {
+ foreach (var behav in Behaviors)
+ {
+ foreach (var dest in behav.BehaviorAddressDestinations)
+ {
+ data.Position = dest;
+ data.Write(behav.Config.BankAddress);
+ }
+ }
+ }
+ }
+
+ public Behavior GetBehaviorByID(string id)
+ {
+ return Behaviors.FirstOrDefault(n => n.Config.ID == id);
+ }
+
+ public Behavior GetBehaviorByBankAddress(int bankAddress)
+ {
+ return Behaviors.FirstOrDefault(n => n.Config.BankAddress == bankAddress);
+ }
+
+ public void CalculateBehaviorBankAddresses(int bankStartAddress, RomManager rommgr)
+ {
+ var length = 0;
+ var addressUpdates = new Dictionary();
+
+ foreach (var behav in Behaviors)
+ {
+ if (behav.Config.IsVanilla)
+ length += behav.Config.FixedLength;
+ else
+ {
+ var newBankAddress = bankStartAddress + length;
+ addressUpdates.AddIfNotContainsKey(behav.Config.BankAddress, newBankAddress);
+ behav.Config.BankAddress = newBankAddress;
+ length += (int)behav.Script.Length;
+ }
+ }
+
+ // Update addresses
+ UpdateBehaviorAddresses(rommgr, addressUpdates);
+ }
+ }
+}
diff --git a/SM64Lib/Behaviors/BehaviorBankConfig.cs b/SM64Lib/Behaviors/BehaviorBankConfig.cs
new file mode 100644
index 0000000..48fef6f
--- /dev/null
+++ b/SM64Lib/Behaviors/BehaviorBankConfig.cs
@@ -0,0 +1,36 @@
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SM64Lib.Behaviors
+{
+ public class BehaviorBankConfig
+ {
+ public bool IsVanilla { get; set; } = true;
+ public List BehaviorConfigs { get; } = new List();
+
+ [JsonProperty(nameof(Enabled))]
+ private bool enabled = false;
+ public void Enable() => enabled = true;
+ internal void Disable()
+ {
+ enabled = false;
+ IsVanilla = true;
+ BehaviorConfigs.Clear();
+ }
+
+ [JsonIgnore]
+ public bool Enabled
+ {
+ get
+ {
+ if (!enabled && !IsVanilla)
+ enabled = true;
+ return enabled;
+ }
+ }
+ }
+}
diff --git a/SM64Lib/Behaviors/BehaviorConfig.cs b/SM64Lib/Behaviors/BehaviorConfig.cs
new file mode 100644
index 0000000..53e2cc2
--- /dev/null
+++ b/SM64Lib/Behaviors/BehaviorConfig.cs
@@ -0,0 +1,39 @@
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Pilz.Cryptography;
+using Pilz.Json.Converters;
+
+namespace SM64Lib.Behaviors
+{
+ public class BehaviorConfig
+ {
+ internal delegate void RequestModelEventHandler(BehaviorConfig config, RequestBehaviorEventArgs request);
+ internal static event RequestModelEventHandler RequestBehavior;
+
+ [JsonConverter(typeof(UniquieIDStringJsonConverter))]
+ public UniquieID ID { get; set; } = new();
+ public int BankAddress { get; set; } = -1;
+ public bool IsVanilla { get; set; } = false;
+ public string Name { get; set; } = string.Empty;
+ public int FixedLength { get; set; } = -1;
+ public int ExpectedLength { get; set; } = -1;
+ public List CustomAsmLinks { get; } = new List();
+ public BehaviorParamsInfo ParamsInfo { get; } = new BehaviorParamsInfo();
+
+ public Behavior FindBehavior()
+ {
+ var args = new RequestBehaviorEventArgs();
+ RequestBehavior?.Invoke(this, args);
+ return args.Behavior;
+ }
+
+ internal class RequestBehaviorEventArgs
+ {
+ public Behavior Behavior { get; set; }
+ }
+ }
+}
diff --git a/SM64Lib/Behaviors/BehaviorCreationTypes.cs b/SM64Lib/Behaviors/BehaviorCreationTypes.cs
new file mode 100644
index 0000000..a626d09
--- /dev/null
+++ b/SM64Lib/Behaviors/BehaviorCreationTypes.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SM64Lib.Behaviors
+{
+ public enum BehaviorCreationTypes
+ {
+ SolidObject
+ }
+}
diff --git a/SM64Lib/Behaviors/BehaviorParamInfo.cs b/SM64Lib/Behaviors/BehaviorParamInfo.cs
new file mode 100644
index 0000000..8bd9927
--- /dev/null
+++ b/SM64Lib/Behaviors/BehaviorParamInfo.cs
@@ -0,0 +1,15 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SM64Lib.Behaviors
+{
+ public class BehaviorParamInfo
+ {
+ public string Name { get; set; } = string.Empty;
+ public string Description { get; set; } = string.Empty;
+ public List Values { get; } = new List();
+ }
+}
diff --git a/SM64Lib/Behaviors/BehaviorParamValue.cs b/SM64Lib/Behaviors/BehaviorParamValue.cs
new file mode 100644
index 0000000..e76106b
--- /dev/null
+++ b/SM64Lib/Behaviors/BehaviorParamValue.cs
@@ -0,0 +1,14 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SM64Lib.Behaviors
+{
+ public class BehaviorParamValue
+ {
+ public string Name { get; set; } = "";
+ public byte Value { get; set; } = 0;
+ }
+}
diff --git a/SM64Lib/Behaviors/BehaviorParamsInfo.cs b/SM64Lib/Behaviors/BehaviorParamsInfo.cs
new file mode 100644
index 0000000..fe682f3
--- /dev/null
+++ b/SM64Lib/Behaviors/BehaviorParamsInfo.cs
@@ -0,0 +1,16 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SM64Lib.Behaviors
+{
+ public class BehaviorParamsInfo
+ {
+ public BehaviorParamInfo Param1 { get; } = new();
+ public BehaviorParamInfo Param2 { get; } = new();
+ public BehaviorParamInfo Param3 { get; } = new();
+ public BehaviorParamInfo Param4 { get; } = new();
+ }
+}
diff --git a/SM64Lib/Behaviors/CustomAsmAreaLinkOptions.cs b/SM64Lib/Behaviors/CustomAsmAreaLinkOptions.cs
new file mode 100644
index 0000000..1720f61
--- /dev/null
+++ b/SM64Lib/Behaviors/CustomAsmAreaLinkOptions.cs
@@ -0,0 +1,25 @@
+using Newtonsoft.Json;
+using SM64Lib.ASM;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SM64Lib.Behaviors
+{
+ public class CustomAsmAreaLinkOptions
+ {
+ // Temporary fix - Remove custom property code and make it auto property in v1.?
+ private CustomAsmAreaConfig customAsmAreaConfig;
+ public CustomAsmAreaConfig CustomAsmAreaConfig
+ {
+ get => customAsmAreaConfig ?? CustomAsm?.Config;
+ set => customAsmAreaConfig = value;
+ }
+ // Temporary fix for old files - Remove property in v1.?
+ [JsonProperty]
+ public CustomAsmArea CustomAsm { get; private set; }
+ public bool Loop { get; set; }
+ }
+}
diff --git a/SM64Lib/Behaviors/Script/Behaviorscript.cs b/SM64Lib/Behaviors/Script/Behaviorscript.cs
new file mode 100644
index 0000000..57216a4
--- /dev/null
+++ b/SM64Lib/Behaviors/Script/Behaviorscript.cs
@@ -0,0 +1,106 @@
+using SM64Lib.Data;
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace SM64Lib.Behaviors.Script
+{
+ public class Behaviorscript : BehaviorscriptCommandCollection
+ {
+ public bool Read(BinaryData data, int address, bool detectJumpsAsEnd = false, bool ignoreEndCmds = false, int expectedLength = -1)
+ {
+ bool ende = false;
+ bool success = true;
+ var newCmds = new List();
+
+ data.Position = address;
+
+ if (expectedLength < 0)
+ expectedLength = (int)data.Length;
+ else
+ expectedLength += address;
+
+ while (!ende)
+ {
+ if (expectedLength - data.Position >= 4)
+ {
+ // Get command infos
+ var cmdType = (BehaviorscriptCommandTypes)data.ReadByte();
+ int cmdLength = BehaviorscriptCommand.GetCommandLength(cmdType);
+ var unknownCmd = cmdLength == -1;
+ bool isEndCmd = ignoreEndCmds ? false : (unknownCmd || BehaviorscriptCommand.IsEndCommand(cmdType, detectJumpsAsEnd));
+
+ if (!unknownCmd)
+ {
+ // Reset position
+ data.Position -= 1;
+
+ if (data.Position + cmdLength <= expectedLength)
+ {
+ // Read full command
+ byte[] buf = new byte[cmdLength];
+ data.Read(buf);
+
+ // Create & add command
+ try
+ {
+ newCmds.Add(new BehaviorscriptCommand(buf));
+ }
+ catch (Exception)
+ {
+ success = false;
+ }
+
+ ende = isEndCmd;
+ }
+ else
+ ende = true;
+ }
+ else
+ {
+ data.Position += 3;
+ ende = isEndCmd;
+ }
+ }
+ else
+ ende = true;
+ }
+
+ // Add new Cmds
+ if (success && newCmds.Any())
+ {
+ Close();
+ AddRange(newCmds.ToArray());
+ }
+
+ return success;
+ }
+
+ public int Write(BinaryData data, int address)
+ {
+ data.Position = address;
+
+ foreach (BehaviorscriptCommand command in this)
+ {
+ var cmdLength = BehaviorscriptCommand.GetCommandLength(command.CommandType);
+ if (command.Length != cmdLength)
+ command.SetLength(cmdLength);
+ data.Write(command.ToArray());
+ }
+
+ return (int)data.Position - address;
+ }
+
+ public int IndexOfType(BehaviorscriptCommandTypes cmdType)
+ {
+ return IndexOf(this.FirstOrDefault(n => n.CommandType == cmdType));
+ }
+
+ public BehaviorscriptCommand FirstOfType(BehaviorscriptCommandTypes cmdType)
+ {
+ return (BehaviorscriptCommand)this.FirstOrDefault(n => n.CommandType == cmdType);
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/SM64Lib/Behaviors/Script/BehaviorscriptCommand.cs b/SM64Lib/Behaviors/Script/BehaviorscriptCommand.cs
new file mode 100644
index 0000000..9e42486
--- /dev/null
+++ b/SM64Lib/Behaviors/Script/BehaviorscriptCommand.cs
@@ -0,0 +1,120 @@
+
+using Newtonsoft.Json;
+using SM64Lib.Json;
+
+namespace SM64Lib.Behaviors.Script
+{
+ [JsonConverter(typeof(BehaviorscriptCommandJsonConverter))]
+ public class BehaviorscriptCommand : SM64Lib.Script.BaseCommand
+ {
+
+ public override BehaviorscriptCommandTypes CommandType
+ {
+ get
+ {
+ Position = 0;
+ return (BehaviorscriptCommandTypes)ReadByte();
+ }
+
+ set
+ {
+ Position = 0;
+ WriteByte((byte)value);
+ }
+ }
+
+ public BehaviorscriptCommand() : base()
+ {
+ }
+
+ public BehaviorscriptCommand(byte[] cmd) : base(cmd)
+ {
+ }
+
+ public BehaviorscriptCommand(string cmd) : base(cmd, true)
+ {
+ }
+
+ public static int GetCommandLength(BehaviorscriptCommandTypes type)
+ {
+ switch (type)
+ {
+ case BehaviorscriptCommandTypes.x00_Start: return 4;
+ case BehaviorscriptCommandTypes.x01_StateLoop: return 4;
+ case BehaviorscriptCommandTypes.x02_JumpandLink: return 8;
+ case BehaviorscriptCommandTypes.x03_Return: return 4;
+ case BehaviorscriptCommandTypes.x04_Jump: return 8;
+ case BehaviorscriptCommandTypes.x05_LoopN: return 4;
+ case BehaviorscriptCommandTypes.x06_EndLoopN: return 4;
+ case BehaviorscriptCommandTypes.x07_InfiniteLoop: return 4;
+ case BehaviorscriptCommandTypes.x08_LoopStart: return 4;
+ case BehaviorscriptCommandTypes.x09_LoopEnd: return 4;
+ case BehaviorscriptCommandTypes.x0A_EndBehaviorscript: return 4;
+ case BehaviorscriptCommandTypes.x0B_EndBehaviorscript_Unused: return 4;
+ case BehaviorscriptCommandTypes.x0C_CallFunction: return 8;
+ case BehaviorscriptCommandTypes.x0D_OffsetbyFloat: return 4;
+ case BehaviorscriptCommandTypes.x0E_SettoFloat: return 4;
+ case BehaviorscriptCommandTypes.x0F_OffsetbyInteger: return 4;
+ case BehaviorscriptCommandTypes.x10_SettoInteger: return 4;
+ case BehaviorscriptCommandTypes.x11_BitSet: return 4;
+ case BehaviorscriptCommandTypes.x12_BitClear: return 4;
+ case BehaviorscriptCommandTypes.x13_AddRNG: return 8;
+ case BehaviorscriptCommandTypes.x14_ObjectType: return 8;
+ case BehaviorscriptCommandTypes.x15_FloatMultiply: return 8;
+ case BehaviorscriptCommandTypes.x16_FloatAdd: return 8;
+ case BehaviorscriptCommandTypes.x17_AddRNG2: return 8;
+ case BehaviorscriptCommandTypes.x18_NoOperation: return 4;
+ case BehaviorscriptCommandTypes.x19_NoOperation: return 4;
+ case BehaviorscriptCommandTypes.x1A_NoOperation: return 4;
+ case BehaviorscriptCommandTypes.x1B_ChangeModelID: return 4;
+ case BehaviorscriptCommandTypes.x1C_LoadChildObject: return 0xC;
+ case BehaviorscriptCommandTypes.x1D_Deactivate: return 4;
+ case BehaviorscriptCommandTypes.x1E_DroptoGround: return 4;
+ case BehaviorscriptCommandTypes.x1F_UsedforWaves: return 4;
+ case BehaviorscriptCommandTypes.x20_Unused: return 4;
+ case BehaviorscriptCommandTypes.x21_SetBillboarding: return 4;
+ case BehaviorscriptCommandTypes.x22_SetRenderInvisibleflag: return 4;
+ case BehaviorscriptCommandTypes.x23_Collisioncylindersize: return 8;
+ case BehaviorscriptCommandTypes.x24_Nothing: return 4;
+ case BehaviorscriptCommandTypes.x25_StateCycle: return 4;
+ case BehaviorscriptCommandTypes.x26_Loop: return 4;
+ case BehaviorscriptCommandTypes.x27_SetWord: return 8;
+ case BehaviorscriptCommandTypes.x28_Animates: return 4;
+ case BehaviorscriptCommandTypes.x29_LoadChildObject: return 0xC;
+ case BehaviorscriptCommandTypes.x2A_SetCollision: return 8;
+ case BehaviorscriptCommandTypes.x2B_SetCollisionSphere: return 0xC;
+ case BehaviorscriptCommandTypes.x2C_SpawnObject: return 0xC;
+ case BehaviorscriptCommandTypes.x2D_SetInitPosition: return 4;
+ case BehaviorscriptCommandTypes.x2E_SetHurtbox: return 8;
+ case BehaviorscriptCommandTypes.x2F_SetInteraction: return 8;
+ case BehaviorscriptCommandTypes.x30_SetGravity: return 0x14;
+ case BehaviorscriptCommandTypes.x31_SetInteractionSubType: return 8;
+ case BehaviorscriptCommandTypes.x32_ScaleObject: return 4;
+ case BehaviorscriptCommandTypes.x33_ChildObjectChange: return 8;
+ case BehaviorscriptCommandTypes.x34_TextureAnimateRate: return 4;
+ case BehaviorscriptCommandTypes.x35_ClearGraphFlag: return 4;
+ case BehaviorscriptCommandTypes.x36_SetValue: return 8;
+ case BehaviorscriptCommandTypes.x37_SpawnSomething: return 8;
+ default: return -1; //throw new System.Exception("Command type not found!");
+ }
+ }
+
+ public static bool IsEndCommand(BehaviorscriptCommandTypes type, bool detectJumpsAsEnd = true)
+ {
+ switch (type)
+ {
+ case BehaviorscriptCommandTypes.x02_JumpandLink:
+ case BehaviorscriptCommandTypes.x04_Jump:
+ return detectJumpsAsEnd;
+ case BehaviorscriptCommandTypes.x06_EndLoopN:
+ case BehaviorscriptCommandTypes.x0A_EndBehaviorscript:
+ case BehaviorscriptCommandTypes.x0B_EndBehaviorscript_Unused:
+ case BehaviorscriptCommandTypes.x09_LoopEnd:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/SM64Lib/Behaviors/Script/BehaviorscriptCommandCollection.cs b/SM64Lib/Behaviors/Script/BehaviorscriptCommandCollection.cs
new file mode 100644
index 0000000..f2a4c84
--- /dev/null
+++ b/SM64Lib/Behaviors/Script/BehaviorscriptCommandCollection.cs
@@ -0,0 +1,7 @@
+
+namespace SM64Lib.Behaviors.Script
+{
+ public class BehaviorscriptCommandCollection : SM64Lib.Script.BaseCommandCollection
+ {
+ }
+}
\ No newline at end of file
diff --git a/SM64Lib/Behaviors/Script/BehaviorscriptCommandFactory.cs b/SM64Lib/Behaviors/Script/BehaviorscriptCommandFactory.cs
new file mode 100644
index 0000000..6383b0a
--- /dev/null
+++ b/SM64Lib/Behaviors/Script/BehaviorscriptCommandFactory.cs
@@ -0,0 +1,32 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SM64Lib.Behaviors.Script
+{
+ public static class BehaviorscriptCommandFactory
+ {
+ public static BehaviorscriptCommand Build_x2A(int collisionPointer = -1)
+ {
+ var cmd = new BehaviorscriptCommand("2a 00 00 00 00 00 00 00");
+ if (collisionPointer != -1)
+ BehaviorscriptCommandFunctions.X2A.SetCollisionPointer(cmd, collisionPointer);
+ return cmd;
+ }
+
+ public static BehaviorscriptCommand Build_x08()
+ {
+ return new BehaviorscriptCommand("08 00 00 00");
+ }
+
+ public static BehaviorscriptCommand Build_x0C(int asmPointer = -1)
+ {
+ var cmd = new BehaviorscriptCommand("0C 00 00 00 00 00 00 00");
+ if (asmPointer != -1)
+ BehaviorscriptCommandFunctions.X2A.SetCollisionPointer(cmd, asmPointer);
+ return cmd;
+ }
+ }
+}
diff --git a/SM64Lib/Behaviors/Script/BehaviorscriptCommandFunctions.cs b/SM64Lib/Behaviors/Script/BehaviorscriptCommandFunctions.cs
new file mode 100644
index 0000000..5265762
--- /dev/null
+++ b/SM64Lib/Behaviors/Script/BehaviorscriptCommandFunctions.cs
@@ -0,0 +1,39 @@
+using SM64Lib.Data;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SM64Lib.Behaviors.Script
+{
+ public class BehaviorscriptCommandFunctions
+ {
+ public class X2A
+ {
+ public static int GetCollisionPointer(BehaviorscriptCommand cmd)
+ {
+ cmd.Position = 4;
+ return cmd.ReadInt32();
+ }
+ public static void SetCollisionPointer(BehaviorscriptCommand cmd, int pointer)
+ {
+ cmd.Position = 4;
+ cmd.Write(pointer);
+ }
+ }
+ public class X0C
+ {
+ public static int GetAsmPointer(BehaviorscriptCommand cmd)
+ {
+ cmd.Position = 4;
+ return cmd.ReadInt32();
+ }
+ public static void SetAsmPointer(BehaviorscriptCommand cmd, int pointer)
+ {
+ cmd.Position = 4;
+ cmd.Write(pointer);
+ }
+ }
+ }
+}
diff --git a/SM64Lib/Behaviors/Script/BehaviorscriptCommandTypes.cs b/SM64Lib/Behaviors/Script/BehaviorscriptCommandTypes.cs
new file mode 100644
index 0000000..9bcb9fd
--- /dev/null
+++ b/SM64Lib/Behaviors/Script/BehaviorscriptCommandTypes.cs
@@ -0,0 +1,62 @@
+namespace SM64Lib.Behaviors.Script
+{
+ public enum BehaviorscriptCommandTypes
+ {
+ x00_Start,
+ x01_StateLoop,
+ x02_JumpandLink,
+ x03_Return,
+ x04_Jump,
+ x05_LoopN,
+ x06_EndLoopN,
+ x07_InfiniteLoop,
+ x08_LoopStart,
+ x09_LoopEnd,
+ x0A_EndBehaviorscript,
+ x0B_EndBehaviorscript_Unused,
+ x0C_CallFunction,
+ x0D_OffsetbyFloat,
+ x0E_SettoFloat,
+ x0F_OffsetbyInteger,
+ x10_SettoInteger,
+ x11_BitSet,
+ x12_BitClear,
+ x13_AddRNG,
+ x14_ObjectType,
+ x15_FloatMultiply,
+ x16_FloatAdd,
+ x17_AddRNG2,
+ x18_NoOperation,
+ x19_NoOperation,
+ x1A_NoOperation,
+ x1B_ChangeModelID,
+ x1C_LoadChildObject,
+ x1D_Deactivate,
+ x1E_DroptoGround,
+ x1F_UsedforWaves,
+ x20_Unused,
+ x21_SetBillboarding,
+ x22_SetRenderInvisibleflag,
+ x23_Collisioncylindersize,
+ x24_Nothing,
+ x25_StateCycle,
+ x26_Loop,
+ x27_SetWord,
+ x28_Animates,
+ x29_LoadChildObject,
+ x2A_SetCollision,
+ x2B_SetCollisionSphere,
+ x2C_SpawnObject,
+ x2D_SetInitPosition,
+ x2E_SetHurtbox,
+ x2F_SetInteraction,
+ x30_SetGravity,
+ x31_SetInteractionSubType,
+ x32_ScaleObject,
+ x33_ChildObjectChange,
+ x34_TextureAnimateRate,
+ x35_ClearGraphFlag,
+ x36_SetValue,
+ x37_SpawnSomething
+ }
+}
\ No newline at end of file
diff --git a/SM64Lib/Configuration/CollisionBasicConfig.cs b/SM64Lib/Configuration/CollisionBasicConfig.cs
new file mode 100644
index 0000000..b21fded
--- /dev/null
+++ b/SM64Lib/Configuration/CollisionBasicConfig.cs
@@ -0,0 +1,22 @@
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SM64Lib.Configuration
+{
+ public class CollisionBasicConfig
+ {
+ private List collisionTypesWithParams = new List()
+ { 4, 14, 44, 36, 37, 39, 45 };
+
+ [JsonProperty("CollisionTypesWithParamsV2", ObjectCreationHandling = ObjectCreationHandling.Replace)]
+ public List CollisionTypesWithParams
+ {
+ get => collisionTypesWithParams;
+ private set => collisionTypesWithParams = value.Distinct().ToList();
+ }
+ }
+}
diff --git a/SM64Lib/Configuration/CustomModelConfig.cs b/SM64Lib/Configuration/CustomModelConfig.cs
new file mode 100644
index 0000000..38067a7
--- /dev/null
+++ b/SM64Lib/Configuration/CustomModelConfig.cs
@@ -0,0 +1,31 @@
+using Newtonsoft.Json;
+using SM64Lib.Objects.ModelBanks;
+using System.Collections.Generic;
+using Pilz.Cryptography;
+using Pilz.Json.Converters;
+
+namespace SM64Lib.Configuration
+{
+ public class CustomModelConfig
+ {
+ internal delegate void RequestModelEventHandler(CustomModelConfig config, RequestModelEventArgs request);
+ internal static event RequestModelEventHandler RequestModel;
+
+ [JsonConverter(typeof(UniquieIDStringJsonConverter))]
+ public UniquieID ID { get; set; } = new();
+ public string Name { get; set; } = string.Empty;
+ public List CollisionPointerDestinations { get; private set; } = new List();
+
+ public CustomModel FindModel()
+ {
+ var args = new RequestModelEventArgs();
+ RequestModel?.Invoke(this, args);
+ return args.Model;
+ }
+
+ internal class RequestModelEventArgs
+ {
+ public CustomModel Model { get; set; }
+ }
+ }
+}
\ No newline at end of file
diff --git a/SM64Lib/Configuration/LevelAreaConfig.cs b/SM64Lib/Configuration/LevelAreaConfig.cs
new file mode 100644
index 0000000..ac99b0f
--- /dev/null
+++ b/SM64Lib/Configuration/LevelAreaConfig.cs
@@ -0,0 +1,10 @@
+using System.Collections.Generic;
+
+namespace SM64Lib.Configuration
+{
+ public class LevelAreaConfig
+ {
+ public string AreaName { get; set; }
+ public Dictionary ScrollingNames { get; set; } = new Dictionary();
+ }
+}
\ No newline at end of file
diff --git a/SM64Lib/Configuration/LevelConfig.cs b/SM64Lib/Configuration/LevelConfig.cs
new file mode 100644
index 0000000..d761a2b
--- /dev/null
+++ b/SM64Lib/Configuration/LevelConfig.cs
@@ -0,0 +1,26 @@
+using System.Collections.Generic;
+
+namespace SM64Lib.Configuration
+{
+ public class LevelConfig
+ {
+ public string LevelName { get; set; }
+ public bool EnableLocalObjectBank { get; set; } = false;
+ public ObjectModelConfig LocalObjectBank { get; set; } = new ObjectModelConfig();
+ public Dictionary AreaConfigs { get; private set; } = new Dictionary();
+
+ public LevelAreaConfig GetLevelAreaConfig(byte areaID)
+ {
+ if (AreaConfigs.ContainsKey(areaID))
+ {
+ return AreaConfigs[areaID];
+ }
+ else
+ {
+ var conf = new LevelAreaConfig();
+ AreaConfigs.Add(areaID, conf);
+ return conf;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/SM64Lib/Configuration/MusicConfiguration.cs b/SM64Lib/Configuration/MusicConfiguration.cs
new file mode 100644
index 0000000..be25ccd
--- /dev/null
+++ b/SM64Lib/Configuration/MusicConfiguration.cs
@@ -0,0 +1,9 @@
+using System.Collections.Generic;
+
+namespace SM64Lib.Configuration
+{
+ public class MusicConfiguration
+ {
+ public List SqeuenceNames { get; private set; } = new List();
+ }
+}
\ No newline at end of file
diff --git a/SM64Lib/Configuration/NPCConfig.cs b/SM64Lib/Configuration/NPCConfig.cs
new file mode 100644
index 0000000..d14d6d6
--- /dev/null
+++ b/SM64Lib/Configuration/NPCConfig.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SM64Lib.Configuration
+{
+ public class NPCConfig
+ {
+ public bool Enabled3DCoins { get; set; } = false;
+ }
+}
diff --git a/SM64Lib/Configuration/ObjectModelConfig.cs b/SM64Lib/Configuration/ObjectModelConfig.cs
new file mode 100644
index 0000000..d28d25e
--- /dev/null
+++ b/SM64Lib/Configuration/ObjectModelConfig.cs
@@ -0,0 +1,20 @@
+using System.Collections.Generic;
+
+namespace SM64Lib.Configuration
+{
+ public class ObjectModelConfig
+ {
+ public Dictionary CustomObjectConfigs { get; private set; } = new Dictionary();
+
+ public CustomModelConfig GetCustomObjectConfig(int id)
+ {
+ CustomModelConfig conf = null;
+ if (!CustomObjectConfigs.TryGetValue(id, out conf))
+ {
+ conf = new CustomModelConfig();
+ }
+
+ return conf;
+ }
+ }
+}
\ No newline at end of file
diff --git a/SM64Lib/Configuration/PatchingConfig.cs b/SM64Lib/Configuration/PatchingConfig.cs
new file mode 100644
index 0000000..78b30b4
--- /dev/null
+++ b/SM64Lib/Configuration/PatchingConfig.cs
@@ -0,0 +1,15 @@
+using Pilz.IO;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SM64Lib.Configuration
+{
+ public class PatchingConfig
+ {
+ public EmbeddedFilesContainer TweakBackups { get; } = new EmbeddedFilesContainer();
+ public bool Patched2DCamera { get; set; } = false;
+ }
+}
diff --git a/SM64Lib/Configuration/RomConfig.cs b/SM64Lib/Configuration/RomConfig.cs
new file mode 100644
index 0000000..6fc300e
--- /dev/null
+++ b/SM64Lib/Configuration/RomConfig.cs
@@ -0,0 +1,78 @@
+using System.Collections.Generic;
+using global::System.IO;
+using Microsoft.VisualBasic.CompilerServices;
+using global::Newtonsoft.Json.Linq;
+using Newtonsoft.Json;
+using SM64Lib.Behaviors;
+using SM64Lib.Objects.ObjectBanks;
+using SM64Lib.ASM;
+using SM64Lib.Objects.ObjectBanks.Data;
+using System;
+
+namespace SM64Lib.Configuration
+{
+ public class RomConfig
+ {
+ // Levels
+ public Dictionary LevelConfigs { get; } = new Dictionary();
+
+ // Global Banks
+ [JsonProperty("GlobalObjectBankConfig")]
+ public ObjectModelConfig GlobalModelBank { get; } = new ObjectModelConfig();
+ public CustomAsmBankConfig GlobalCustomAsmBank { get; } = new CustomAsmBankConfig();
+ public BehaviorBankConfig GlobalBehaviorBank { get; } = new BehaviorBankConfig();
+ public CustomObjectCollection GlobalObjectBank { get; } = new CustomObjectCollection();
+
+ // Music
+ public MusicConfiguration MusicConfig { get; } = new MusicConfiguration();
+
+ // Texts
+ public Text.Profiles.TextProfileInfo TextProfileInfo { get; set; } = null;
+
+ // Patching
+ public PatchingConfig PatchingConfig { get; set; } = new PatchingConfig();
+
+ // Other
+ public ScrollTexConfig ScrollTexConfig { get; set; } = new ScrollTexConfig();
+ public ObjectBankDataListCollection ObjectBankInfoData { get; } = new ObjectBankDataListCollection();
+ public NPCConfig NPCConfig { get; } = new NPCConfig();
+ public CollisionBasicConfig CollisionBaseConfig { get; } = new CollisionBasicConfig();
+
+ ///
+ /// Contains custom configuration that isn't used by SM64Lib. E.g. Extra Object Combos.
+ ///
+ public Dictionary CustomConfigs { get; } = new Dictionary();
+
+ // F e a t u r e s
+
+ public LevelConfig GetLevelConfig(ushort levelID)
+ {
+ if (LevelConfigs.ContainsKey(Convert.ToByte(levelID)))
+ {
+ return LevelConfigs[Convert.ToByte(levelID)];
+ }
+ else
+ {
+ var conf = new LevelConfig();
+ LevelConfigs.Add(Convert.ToByte(levelID), conf);
+ return conf;
+ }
+ }
+
+ // L o a d / S a v e
+
+ public static RomConfig Load(string filePath)
+ {
+ var serializer = JsonSerializer.CreateDefault();
+ serializer.PreserveReferencesHandling = PreserveReferencesHandling.All;
+ return JObject.Parse(File.ReadAllText(filePath)).ToObject(serializer);
+ }
+
+ public void Save(string filePath)
+ {
+ var serializer = JsonSerializer.CreateDefault();
+ serializer.PreserveReferencesHandling = PreserveReferencesHandling.All;
+ File.WriteAllText(filePath, JObject.FromObject(this, serializer).ToString());
+ }
+ }
+}
\ No newline at end of file
diff --git a/SM64Lib/Configuration/ScrollTexConfig.cs b/SM64Lib/Configuration/ScrollTexConfig.cs
new file mode 100644
index 0000000..898d160
--- /dev/null
+++ b/SM64Lib/Configuration/ScrollTexConfig.cs
@@ -0,0 +1,14 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SM64Lib.Configuration
+{
+ public class ScrollTexConfig
+ {
+ public bool UseCustomBehavior { get; set; } = false;
+ public int CustomBehaviorAddress { get; set; } = -1;
+ }
+}
diff --git a/SM64Lib/Data/BinaryArrayData.cs b/SM64Lib/Data/BinaryArrayData.cs
new file mode 100644
index 0000000..8cfd2d8
--- /dev/null
+++ b/SM64Lib/Data/BinaryArrayData.cs
@@ -0,0 +1,28 @@
+using global::System.IO;
+
+namespace SM64Lib.Data
+{
+ public class BinaryArrayData : BinaryStreamData
+ {
+ public FileAccess RomAccess { get; private set; } = FileAccess.Read;
+
+ public BinaryArrayData(byte[] buffer) : base(new MemoryStream(buffer))
+ {
+ }
+
+ // Inherits BinaryData
+
+ // Private ReadOnly myBaseStream As Stream
+
+ // Public ReadOnly Property RomAccess As FileAccess = FileAccess.Read
+
+ // Public Sub New(buffer As Byte())
+ // myBaseStream = New MemoryStream(buffer)
+ // End Sub
+
+ // Protected Overrides Function GetBaseStream() As Stream
+ // Return myBaseStream
+ // End Function
+
+ }
+}
\ No newline at end of file
diff --git a/SM64Lib/Data/BinaryData.cs b/SM64Lib/Data/BinaryData.cs
new file mode 100644
index 0000000..6fd8ff1
--- /dev/null
+++ b/SM64Lib/Data/BinaryData.cs
@@ -0,0 +1,280 @@
+using global::System.IO;
+using SM64Lib.Data.System;
+using System;
+
+namespace SM64Lib.Data
+{
+ public abstract class BinaryData : IDisposable
+ {
+ public delegate void AnyBinaryDataEventHandler(BinaryData data);
+
+ public static event AnyBinaryDataEventHandler AnyBinaryDataOpened;
+ public static event AnyBinaryDataEventHandler AnyBinaryDataClosing;
+ public static event AnyBinaryDataEventHandler AnyBinaryDataClosed;
+ public static event AnyBinaryDataDisposedEventHandler AnyBinaryDataDisposed;
+
+ public delegate void AnyBinaryDataDisposedEventHandler();
+
+ private Stream _BaseStream = null;
+ private BinaryWriter _writer = null;
+ private BinaryReader _reader = null;
+
+ public RomManager RomManager { get; private set; }
+
+ protected abstract Stream GetBaseStream();
+
+ protected BinaryWriter Writer
+ {
+ get
+ {
+ if (_writer is null)
+ {
+ _writer = new BinaryWriter(BaseStream);
+ }
+
+ return _writer;
+ }
+ }
+
+ protected BinaryReader Reader
+ {
+ get
+ {
+ if (_reader is null)
+ {
+ _reader = new BinaryReader(BaseStream);
+ }
+
+ return _reader;
+ }
+ }
+
+ public long Position
+ {
+ get
+ {
+ return BaseStream.Position;
+ }
+
+ set
+ {
+ BaseStream.Position = value;
+ }
+ }
+
+ public long Length
+ {
+ get
+ {
+ return BaseStream.Length;
+ }
+ }
+
+ public Stream BaseStream
+ {
+ get
+ {
+ if (_BaseStream is null)
+ {
+ _BaseStream = GetBaseStream();
+ RaiseAnyBinaryDataOpened(this);
+ }
+
+ return _BaseStream;
+ }
+ }
+
+ ~BinaryData()
+ {
+ AnyBinaryDataDisposed?.Invoke();
+ }
+
+ public void RoundUpPosition()
+ {
+ Position = General.HexRoundUp1(Position);
+ }
+
+ public void RoundUpPosition(int add)
+ {
+ Position = General.HexRoundUp1(Position + add);
+ }
+
+ protected void SetRomManager(RomManager rommgr)
+ {
+ RomManager = rommgr;
+ }
+
+ public void Write(sbyte value)
+ {
+ Writer.Write(value);
+ }
+
+ public void Write(byte value)
+ {
+ Writer.Write(value);
+ }
+
+ public void WriteByte(byte value)
+ {
+ Write(value);
+ }
+
+ public void Write(short value)
+ {
+ Writer.Write(SwapInts.SwapInt16(value));
+ }
+
+ public void Write(ushort value)
+ {
+ Writer.Write(SwapInts.SwapUInt16(value));
+ }
+
+ public void Write(int value)
+ {
+ Writer.Write(SwapInts.SwapInt32(value));
+ }
+
+ public void Write(uint value)
+ {
+ Writer.Write(SwapInts.SwapUInt32(value));
+ }
+
+ public void Write(long value)
+ {
+ Writer.Write(SwapInts.SwapInt64(value));
+ }
+
+ public void Write(ulong value)
+ {
+ Writer.Write(SwapInts.SwapUInt64(value));
+ }
+
+ public void Write(float value, bool isHalf = false)
+ {
+ if (isHalf)
+ Writer.Write(SwapInts.SwapHalf(value));
+ else
+ Writer.Write(SwapInts.SwapFloat32(value));
+ }
+
+ public void Write(string value)
+ {
+ Writer.Write(value);
+ }
+
+ public void Write(byte[] buffer)
+ {
+ Write(buffer, 0, buffer.Length);
+ }
+
+ public void Write(byte[] buffer, int index, int count)
+ {
+ Writer.Write(buffer, index, count);
+ }
+
+ public byte ReadByte()
+ {
+ return Reader.ReadByte();
+ }
+
+ public sbyte ReadSByte()
+ {
+ return Reader.ReadSByte();
+ }
+
+ public short ReadInt16()
+ {
+ return SwapInts.SwapInt16(Reader.ReadInt16());
+ }
+
+ public ushort ReadUInt16()
+ {
+ return SwapInts.SwapUInt16(Reader.ReadUInt16());
+ }
+
+ public int ReadInt32()
+ {
+ return SwapInts.SwapInt32(Reader.ReadInt32());
+ }
+
+ public uint ReadUInt32()
+ {
+ return SwapInts.SwapUInt32(Reader.ReadUInt32());
+ }
+
+ public long ReadInt64()
+ {
+ return SwapInts.SwapInt64(Reader.ReadInt64());
+ }
+
+ public ulong ReadUInt64()
+ {
+ return SwapInts.SwapUInt64(Reader.ReadUInt64());
+ }
+
+ public float ReadSingle()
+ {
+ return SwapInts.SwapFloat32(Reader.ReadSingle());
+ }
+
+ public string ReadString()
+ {
+ return Reader.ReadString();
+ }
+
+ public void Read(byte[] buffer)
+ {
+ Read(buffer, 0, buffer.Length);
+ }
+
+ public void Read(byte[] buffer, int index, int count)
+ {
+ Reader.Read(buffer, index, count);
+ }
+
+ public byte[] Read(int count)
+ {
+ var buf = new byte[count];
+ Read(buf, 0, count);
+ return buf;
+ }
+
+ public void Close()
+ {
+ AnyBinaryDataClosing?.Invoke(this);
+ BaseStream.Close();
+ AnyBinaryDataClosed?.Invoke(this);
+ }
+
+ public bool CanRead
+ {
+ get
+ {
+ return BaseStream.CanRead;
+ }
+ }
+
+ public bool CanWrite
+ {
+ get
+ {
+ return BaseStream.CanWrite;
+ }
+ }
+
+ public void SetLength(long length)
+ {
+ BaseStream.SetLength(length);
+ }
+
+ internal static void RaiseAnyBinaryDataOpened(BinaryData data)
+ {
+ AnyBinaryDataOpened?.Invoke(data);
+ }
+
+ public void Dispose()
+ {
+ Close();
+ }
+ }
+}
\ No newline at end of file
diff --git a/SM64Lib/Data/BinaryFile.cs b/SM64Lib/Data/BinaryFile.cs
new file mode 100644
index 0000000..3e2c31a
--- /dev/null
+++ b/SM64Lib/Data/BinaryFile.cs
@@ -0,0 +1,11 @@
+using global::System.IO;
+
+namespace SM64Lib.Data
+{
+ public class BinaryFile : BinaryStreamData
+ {
+ public BinaryFile(string filePath, FileMode fileMode, FileAccess fileAccess) : base(new FileStream(filePath, fileMode, fileAccess))
+ {
+ }
+ }
+}
\ No newline at end of file
diff --git a/SM64Lib/Data/BinaryRom.cs b/SM64Lib/Data/BinaryRom.cs
new file mode 100644
index 0000000..9784dec
--- /dev/null
+++ b/SM64Lib/Data/BinaryRom.cs
@@ -0,0 +1,26 @@
+using global::System.IO;
+
+namespace SM64Lib.Data
+{
+ public class BinaryRom : BinaryFile
+ {
+ public BinaryRom(RomManager rommgr, FileAccess romAccess) : base(rommgr.RomFile, FileMode.Open, romAccess)
+ {
+ SetRomManager(rommgr);
+ }
+
+ // Inherits BinaryData
+
+ // Public ReadOnly Property RomAccess As FileAccess = FileAccess.Read
+
+ // Public Sub New(rommgr As RomManager, romAccess As FileAccess)
+ // SetRomManager(rommgr)
+ // Me.RomAccess = romAccess
+ // End Sub
+
+ // Protected Overrides Function GetBaseStream() As Stream
+ // Return RomManager.GetStream(RomAccess)
+ // End Function
+
+ }
+}
\ No newline at end of file
diff --git a/SM64Lib/Data/BinarySegBank.cs b/SM64Lib/Data/BinarySegBank.cs
new file mode 100644
index 0000000..8ca6d83
--- /dev/null
+++ b/SM64Lib/Data/BinarySegBank.cs
@@ -0,0 +1,33 @@
+using global::System.IO;
+using global::SM64Lib.SegmentedBanking;
+
+namespace SM64Lib.Data
+{
+ public class BinarySegBank : BinaryData
+ {
+ public SegmentedBank SegBank { get; private set; }
+
+ public BinarySegBank(SegmentedBank segBank, RomManager rommgr)
+ {
+ SegBank = segBank;
+ SetRomManager(rommgr);
+ }
+
+ public BinarySegBank(SegmentedBank segBank)
+ {
+ SegBank = segBank;
+ }
+
+ protected override Stream GetBaseStream()
+ {
+ if (RomManager is not null)
+ {
+ var s = RomManager.GetBinaryRom(FileAccess.Read);
+ SegBank.ReadDataIfNull(s.BaseStream);
+ s.Close();
+ }
+
+ return SegBank.Data;
+ }
+ }
+}
\ No newline at end of file
diff --git a/SM64Lib/Data/BinaryStreamData.cs b/SM64Lib/Data/BinaryStreamData.cs
new file mode 100644
index 0000000..dd9c568
--- /dev/null
+++ b/SM64Lib/Data/BinaryStreamData.cs
@@ -0,0 +1,20 @@
+using global::System.IO;
+
+namespace SM64Lib.Data
+{
+ public class BinaryStreamData : BinaryData
+ {
+ protected readonly Stream myBaseStream;
+
+ public BinaryStreamData(Stream stream)
+ {
+ myBaseStream = stream;
+ //RaiseAnyBinaryDataOpened(this);
+ }
+
+ protected override Stream GetBaseStream()
+ {
+ return myBaseStream;
+ }
+ }
+}
\ No newline at end of file
diff --git a/SM64Lib/Data/System/Bits.cs b/SM64Lib/Data/System/Bits.cs
new file mode 100644
index 0000000..33b9727
--- /dev/null
+++ b/SM64Lib/Data/System/Bits.cs
@@ -0,0 +1,98 @@
+using System;
+using System.Collections;
+using System.Linq;
+using Microsoft.VisualBasic.CompilerServices;
+
+namespace SM64Lib.Data.System
+{
+ public class Bits
+ {
+ public static byte[] ByteToBitArray(byte b)
+ {
+ var bitarray = new BitArray(new[] { b });
+ var temp = new byte[bitarray.Length];
+ for (int i = 0, loopTo = bitarray.Length - 1; i <= loopTo; i++)
+ temp[i] = Convert.ToByte(bitarray[i]);
+ int tindex = 0;
+ var temp2 = new byte[(temp.Count())];
+ for (int i = temp.Length - 1; i >= 0; i += -1)
+ {
+ temp2[tindex] = temp[i];
+ tindex += 1;
+ }
+
+ return temp2;
+ }
+
+ public static bool[] ByteToBoolArray(byte b)
+ {
+ var bitarray = new BitArray(new[] { b });
+ var temp = new bool[bitarray.Length];
+ for (int i = 0, loopTo = bitarray.Length - 1; i <= loopTo; i++)
+ temp[i] = Convert.ToBoolean(bitarray[i]);
+ int tindex = 0;
+ var temp2 = new bool[(temp.Count())];
+ for (int i = temp.Length - 1; i >= 0; i += -1)
+ {
+ temp2[tindex] = temp[i];
+ tindex += 1;
+ }
+
+ return temp2;
+ }
+
+ public static byte ArrayToByte(byte[] ba)
+ {
+ // ODER: BitArray.ToByte()
+
+ byte endval = 0;
+ int index = ba.Count() - 1;
+ foreach (var bit in ba)
+ {
+ endval += (byte)(bit * Math.Pow(2, index));
+ index -= 1;
+ }
+
+ return endval;
+ }
+
+ public static byte ArrayToByte(bool[] ba)
+ {
+ // ODER: BitArray.ToByte()
+
+ byte endval = 0;
+ int index = ba.Count() - 1;
+ foreach (var bit in ba)
+ {
+ endval += (byte)((bit ? 1 : 0) * Math.Pow(2, index));
+ index -= 1;
+ }
+
+ return endval;
+ }
+
+ public static byte SetInByte(byte b, int index, byte value)
+ {
+ var temp = ByteToBitArray(b);
+ temp[index] = value;
+ return ArrayToByte(temp);
+ }
+
+ public static byte SetInByte(byte b, int index, bool value)
+ {
+ var temp = ByteToBoolArray(b);
+ temp[index] = value;
+ return ArrayToByte(temp);
+ }
+
+ public static byte GetBitOfByte(byte b, int index)
+ {
+ return ByteToBitArray(b)[index];
+ }
+
+ public static bool GetBoolOfByte(byte b, int index)
+ {
+ return ByteToBoolArray(b)[index];
+ }
+ }
+}
\ No newline at end of file
diff --git a/SM64Lib/Data/System/SwapInts.cs b/SM64Lib/Data/System/SwapInts.cs
new file mode 100644
index 0000000..ac0d32a
--- /dev/null
+++ b/SM64Lib/Data/System/SwapInts.cs
@@ -0,0 +1,62 @@
+using System;
+
+namespace SM64Lib.Data.System
+{
+ internal static class SwapInts
+ {
+ public static short SwapInt16(short value)
+ {
+ var bytes = BitConverter.GetBytes(value);
+ Array.Reverse(bytes);
+ return BitConverter.ToInt16(bytes, 0);
+ }
+
+ public static ushort SwapUInt16(ushort value)
+ {
+ var bytes = BitConverter.GetBytes(value);
+ Array.Reverse(bytes);
+ return BitConverter.ToUInt16(bytes, 0);
+ }
+
+ public static int SwapInt32(int value)
+ {
+ var bytes = BitConverter.GetBytes(value);
+ Array.Reverse(bytes);
+ return BitConverter.ToInt32(bytes, 0);
+ }
+
+ public static uint SwapUInt32(uint value)
+ {
+ var bytes = BitConverter.GetBytes(value);
+ Array.Reverse(bytes);
+ return BitConverter.ToUInt32(bytes, 0);
+ }
+
+ public static long SwapInt64(long value)
+ {
+ var bytes = BitConverter.GetBytes(value);
+ Array.Reverse(bytes);
+ return BitConverter.ToInt64(bytes, 0);
+ }
+
+ public static ulong SwapUInt64(ulong value)
+ {
+ var bytes = BitConverter.GetBytes(value);
+ Array.Reverse(bytes);
+ return BitConverter.ToUInt64(bytes, 0);
+ }
+
+ public static short SwapHalf(float value)
+ {
+ byte[] bytes = BitConverter.GetBytes(value);
+ return (short)(BitConverter.ToInt32(bytes, 0) >> 16);
+ }
+
+ public static float SwapFloat32(float value)
+ {
+ byte[] bytes = BitConverter.GetBytes(value);
+ Array.Reverse(bytes);
+ return BitConverter.ToSingle(bytes, 0);
+ }
+ }
+}
\ No newline at end of file
diff --git a/SM64Lib/Datatypecastes.cs b/SM64Lib/Datatypecastes.cs
new file mode 100644
index 0000000..fc61fc5
--- /dev/null
+++ b/SM64Lib/Datatypecastes.cs
@@ -0,0 +1,39 @@
+using global::System.Runtime.InteropServices;
+
+namespace SM64Lib
+{
+ static class Datatypecastes
+ {
+ public static short LongToInt16(long value)
+ {
+ var cast = default(CasterLongInt16);
+ cast.LongValue = value;
+ return cast.Int16Value;
+ }
+
+ [StructLayout(LayoutKind.Explicit)]
+ private struct CasterLongInt16
+ {
+ [FieldOffset(0)]
+ public long LongValue;
+ [FieldOffset(0)]
+ public short Int16Value;
+ }
+
+ public static byte LongToByte(long value)
+ {
+ var cast = default(CasterLongByte);
+ cast.LongValue = value;
+ return cast.ByteValue;
+ }
+
+ [StructLayout(LayoutKind.Explicit)]
+ private struct CasterLongByte
+ {
+ [FieldOffset(0)]
+ public long LongValue;
+ [FieldOffset(0)]
+ public byte ByteValue;
+ }
+ }
+}
\ No newline at end of file
diff --git a/SM64Lib/EventArguments/GetTextProfileInfoEventArgs.cs b/SM64Lib/EventArguments/GetTextProfileInfoEventArgs.cs
new file mode 100644
index 0000000..0766d46
--- /dev/null
+++ b/SM64Lib/EventArguments/GetTextProfileInfoEventArgs.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SM64Lib.EventArguments
+{
+ public class GetTextProfileInfoEventArgs : EventArgs
+ {
+ public Text.Profiles.TextProfileInfo ProfileInfo { get; set; }
+ }
+}
diff --git a/SM64Lib/EventArguments/PrepairingRomEventArgs.cs b/SM64Lib/EventArguments/PrepairingRomEventArgs.cs
new file mode 100644
index 0000000..6f519a6
--- /dev/null
+++ b/SM64Lib/EventArguments/PrepairingRomEventArgs.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SM64Lib.EventArguments
+{
+ public class PrepairingRomEventArgs : EventArgs
+ {
+ public IEnumerable ScriptInfos { get; set; }
+ }
+}
diff --git a/SM64Lib/EventArguments/RomVersionEventArgs.cs b/SM64Lib/EventArguments/RomVersionEventArgs.cs
new file mode 100644
index 0000000..121f8f8
--- /dev/null
+++ b/SM64Lib/EventArguments/RomVersionEventArgs.cs
@@ -0,0 +1,14 @@
+using System;
+
+namespace SM64Lib.EventArguments
+{
+ public class RomVersionEventArgs : EventArgs
+ {
+ public RomVersion RomVersion { get; set; }
+
+ internal RomVersionEventArgs(RomVersion romVersion)
+ {
+ RomVersion = romVersion;
+ }
+ }
+}
\ No newline at end of file
diff --git a/SM64Lib/Exceptions/InvalidMD5HashException.cs b/SM64Lib/Exceptions/InvalidMD5HashException.cs
new file mode 100644
index 0000000..2788c67
--- /dev/null
+++ b/SM64Lib/Exceptions/InvalidMD5HashException.cs
@@ -0,0 +1,8 @@
+using System;
+
+namespace SM64Lib.Exceptions
+{
+ public class InvalidMD5HashException : Exception
+ {
+ }
+}
\ No newline at end of file
diff --git a/SM64Lib/Exceptions/RomCompatiblityException.cs b/SM64Lib/Exceptions/RomCompatiblityException.cs
new file mode 100644
index 0000000..b41d182
--- /dev/null
+++ b/SM64Lib/Exceptions/RomCompatiblityException.cs
@@ -0,0 +1,11 @@
+using System;
+
+namespace SM64Lib.Exceptions
+{
+ public class RomCompatiblityException : Exception
+ {
+ public RomCompatiblityException(string msg) : base(msg)
+ {
+ }
+ }
+}
\ No newline at end of file
diff --git a/SM64Lib/ExtensionMethods.cs b/SM64Lib/ExtensionMethods.cs
new file mode 100644
index 0000000..a2fa437
--- /dev/null
+++ b/SM64Lib/ExtensionMethods.cs
@@ -0,0 +1,33 @@
+
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+
+namespace SM64Lib.Extensions
+{
+ public static class Int16Extensions
+ {
+ public static bool IsInRange(this short value, short min, short max)
+ {
+ return value >= min && value <= max;
+ }
+ }
+
+ public static class ObjectExtensions
+ {
+ public static void CloneTo(this T source, T destination) where T : class
+ {
+ var json = JsonSerializer.CreateDefault();
+ var jobj = JObject.FromObject(source);
+ jobj.Populate(destination);
+ }
+ }
+
+ public static class JsonExtensions
+ {
+ public static void Populate(this JToken value, object target)
+ {
+ using (var sr = value.CreateReader())
+ JsonSerializer.CreateDefault().Populate(sr, target);
+ }
+ }
+}
\ No newline at end of file
diff --git a/SM64Lib/FilePathsConfiguration.cs b/SM64Lib/FilePathsConfiguration.cs
new file mode 100644
index 0000000..48386fc
--- /dev/null
+++ b/SM64Lib/FilePathsConfiguration.cs
@@ -0,0 +1,49 @@
+using System.Collections.Generic;
+
+namespace SM64Lib
+{
+ public class FilePathsConfiguration
+ {
+
+ // S T A T I C M E M B E R S
+
+ public static FilePathsConfiguration DefaultConfiguration { get; private set; } = new FilePathsConfiguration();
+
+ public static string[] AllFileKeys
+ {
+ get
+ {
+ return new[] { "rn64crc.exe","ApplyPPF3.exe", "Level Tabel.json", "Update-Patches.json", "Update Patches Folder", "Text Profiles.json", "BaseTweak", "sm64extend.exe", "Original Level Pointers.bin", "armips.exe", "Flips.exe", "nconvert.exe" };
+ }
+ }
+
+ // F I E L D S
+
+ private readonly Dictionary dic = new Dictionary();
+
+ // P R O P E R T I E S
+
+ public IReadOnlyDictionary Files
+ {
+ get
+ {
+ return dic;
+ }
+ }
+
+ // C O N S T R U C T O R
+
+ public FilePathsConfiguration()
+ {
+ foreach (string key in AllFileKeys)
+ dic.Add(key, string.Empty);
+ }
+
+ // M E T H O D S
+
+ public void SetFilePath(string key, string path)
+ {
+ dic[key] = path;
+ }
+ }
+}
\ No newline at end of file
diff --git a/SM64Lib/General.cs b/SM64Lib/General.cs
new file mode 100644
index 0000000..78ac61b
--- /dev/null
+++ b/SM64Lib/General.cs
@@ -0,0 +1,606 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using global::System.Drawing;
+using System.Linq;
+using Microsoft.VisualBasic.CompilerServices;
+using global::SM64Lib.Patching;
+using System.IO;
+using System.IO.Compression;
+using System.Security.Cryptography;
+using System.Management;
+using System.Text;
+
+namespace SM64Lib
+{
+ public static class General
+ {
+ public static byte[] DisplayListCommandsWithPointerList { get; private set; } = new byte[] { 0x1, 0x3, 0x4, 0x6, 0xFD };
+ public static bool[] ActSelectorDefaultValues { get; private set; } = new bool[] { false, false, false, true, true, false, true, true, true, true, true, true, true, true, true, false, false, false, false, false, false, true, true, true, false, false, false, false, false, false, false, false, false, false, false };
+ public static PatchClass PatchClass { get; private set; } = new PatchClass();
+
+ public static bool IsDebugging
+ {
+ get
+ {
+ return Debugger.IsAttached;
+ }
+ }
+
+ public static short KeepInInt16Range(double value)
+ {
+ if (value > short.MaxValue)
+ {
+ value = short.MaxValue;
+ }
+ else if (value < short.MinValue)
+ {
+ value = short.MinValue;
+ }
+
+ return Convert.ToInt16(value);
+ }
+
+ internal static IReadOnlyDictionary MyFilePaths
+ {
+ get
+ {
+ return FilePathsConfiguration.DefaultConfiguration.Files;
+ }
+ }
+
+ public static string ComputeMD5Hash(string filePath)
+ {
+ var md5 = MD5.Create();
+ string res;
+ FileStream fs;
+
+ try
+ {
+ fs = new FileStream(filePath, FileMode.Open, FileAccess.Read);
+ res = BitConverter.ToString(md5.ComputeHash(fs)).Replace("-", string.Empty).ToLowerInvariant();
+ }
+ catch (Exception)
+ {
+ fs = null;
+ res = string.Empty;
+ }
+
+ fs?.Close();
+ md5.Dispose();
+ return res;
+ }
+
+ public static void CopyBitmap(Bitmap src, Bitmap dest)
+ {
+ for (int y = 0, loopTo = src.Height - 1; y <= loopTo; y++)
+ {
+ for (int x = 0, loopTo1 = src.Width - 1; x <= loopTo1; x++)
+ dest.SetPixel(x, y, src.GetPixel(x, y));
+ }
+ }
+
+ public static Bitmap CopyBitmap(Bitmap src)
+ {
+ return CopyBitmap(src, src.PixelFormat);
+ }
+
+ public static Bitmap CopyBitmap(Bitmap src, System.Drawing.Imaging.PixelFormat pixelFormat)
+ {
+ var dest = new Bitmap(src.Width, src.Height, pixelFormat);
+ CopyBitmap(src, dest);
+ return dest;
+ }
+
+ public static double Round(double d)
+ {
+ return Math.Round(d, MidpointRounding.AwayFromZero);
+ }
+
+ public static bool CompareTwoByteArrays(byte[] arr1, byte[] arr2, uint size = 0)
+ {
+ if (arr2.Length != arr1.Length) return false;
+
+ int loopTo = size != 0 ? (int)size : arr1.Length;
+
+ for (int i = 0; i < loopTo; i++)
+ {
+ if (arr1[i] != arr2[i])
+ return false;
+ }
+
+ return true;
+ }
+
+ public static byte GetLevelIndexFromID(byte LevelID)
+ {
+ switch (LevelID)
+ {
+ case 0x4:
+ {
+ return 0;
+ }
+
+ case 0x5:
+ {
+ return 1;
+ }
+
+ case 0x6:
+ {
+ return 2;
+ }
+
+ case 0x7:
+ {
+ return 3;
+ }
+
+ case 0x8:
+ {
+ return 4;
+ }
+
+ case 0x9:
+ {
+ return 5;
+ }
+
+ case 0xA:
+ {
+ return 6;
+ }
+
+ case 0xB:
+ {
+ return 7;
+ }
+
+ case 0xC:
+ {
+ return 8;
+ }
+
+ case 0xD:
+ {
+ return 9;
+ }
+
+ case 0xE:
+ {
+ return 10;
+ }
+
+ case 0xF:
+ {
+ return 11;
+ }
+
+ case 0x10:
+ {
+ return 12;
+ }
+
+ case 0x11:
+ {
+ return 13;
+ }
+
+ case 0x12:
+ {
+ return 14;
+ }
+
+ case 0x13:
+ {
+ return 15;
+ }
+
+ case 0x14:
+ {
+ return 16;
+ }
+
+ case 0x15:
+ {
+ return 17;
+ }
+
+ case 0x16:
+ {
+ return 18;
+ }
+
+ case 0x17:
+ {
+ return 19;
+ }
+
+ case 0x18:
+ {
+ return 20;
+ }
+
+ case 0x19:
+ {
+ return 21;
+ }
+
+ case 0x1A:
+ {
+ return 22;
+ }
+
+ case 0x1B:
+ {
+ return 23;
+ }
+
+ case 0x1C:
+ {
+ return 24;
+ }
+
+ case 0x1D:
+ {
+ return 25;
+ }
+
+ case 0x1E:
+ {
+ return 26;
+ }
+
+ case 0x1F:
+ {
+ return 27;
+ }
+
+ case 0x21:
+ {
+ return 28;
+ }
+
+ case 0x22:
+ {
+ return 29;
+ }
+
+ case 0x24:
+ {
+ return 30;
+ }
+
+ default:
+ {
+ return 5;
+ }
+ }
+ }
+
+ public static byte GetLevelIDFromIndex(byte LevelID)
+ {
+ switch (LevelID)
+ {
+ case 0:
+ {
+ return 0x4;
+ }
+
+ case 1:
+ {
+ return 0x5;
+ }
+
+ case 2:
+ {
+ return 0x6;
+ }
+
+ case 3:
+ {
+ return 0x7;
+ }
+
+ case 4:
+ {
+ return 0x8;
+ }
+
+ case 5:
+ {
+ return 0x9;
+ }
+
+ case 6:
+ {
+ return 0xA;
+ }
+
+ case 7:
+ {
+ return 0xB;
+ }
+
+ case 8:
+ {
+ return 0xC;
+ }
+
+ case 9:
+ {
+ return 0xD;
+ }
+
+ case 10:
+ {
+ return 0xE;
+ }
+
+ case 11:
+ {
+ return 0xF;
+ }
+
+ case 12:
+ {
+ return 0x10;
+ }
+
+ case 13:
+ {
+ return 0x11;
+ }
+
+ case 14:
+ {
+ return 0x12;
+ }
+
+ case 15:
+ {
+ return 0x13;
+ }
+
+ case 16:
+ {
+ return 0x14;
+ }
+
+ case 17:
+ {
+ return 0x15;
+ }
+
+ case 18:
+ {
+ return 0x16;
+ }
+
+ case 19:
+ {
+ return 0x17;
+ }
+
+ case 20:
+ {
+ return 0x18;
+ }
+
+ case 21:
+ {
+ return 0x19;
+ }
+
+ case 22:
+ {
+ return 0x1A;
+ }
+
+ case 23:
+ {
+ return 0x1B;
+ }
+
+ case 24:
+ {
+ return 0x1C;
+ }
+
+ case 25:
+ {
+ return 0x1D;
+ }
+
+ case 26:
+ {
+ return 0x1E;
+ }
+
+ case 27:
+ {
+ return 0x1F;
+ }
+
+ case 28:
+ {
+ return 0x21;
+ }
+
+ case 29:
+ {
+ return 0x22;
+ }
+
+ case 30:
+ {
+ return 0x24;
+ }
+
+ default:
+ {
+ return 5;
+ }
+ }
+ }
+
+ public static int HexRoundUp1(int value)
+ {
+ while (value % 0x10 != 0)
+ value += 1;
+ return value;
+ }
+
+ public static long HexRoundUp1(long value)
+ {
+ while (value % 0x10 != 0)
+ value += 1;
+ return value;
+ }
+
+ public static void HexRoundUp2(ref long value)
+ {
+ while (value % 0x10 != 0)
+ value += 1;
+ }
+
+ public static void HexRoundUp2(ref uint value)
+ {
+ while (value % 0x10 != 0)
+ value += 1;
+ }
+
+ public static void HexRoundUp2(ref int value)
+ {
+ while (value % 0x10 != 0)
+ value += 1;
+ }
+
+ public static string FillStrToCharCount(string str, int charCount, string fillVal = "0")
+ {
+ if (fillVal.Count() == 0)
+ return str;
+ if (charCount == 0)
+ return str;
+ while (str.Count() < charCount)
+ str = fillVal + str;
+ return str;
+ }
+
+ public static Geolayout.BackgroundPointers GetBackgroundAddressOfID(Geolayout.BackgroundIDs ID, bool EndAddr)
+ {
+ switch (ID)
+ {
+ case Geolayout.BackgroundIDs.AboveClouds:
+ {
+ if (EndAddr)
+ return Geolayout.BackgroundPointers.AboveCloudsEnd;
+ return Geolayout.BackgroundPointers.AboveCloudsStart;
+ }
+
+ case Geolayout.BackgroundIDs.BelowClouds:
+ {
+ if (EndAddr)
+ return Geolayout.BackgroundPointers.BelowCloudsEnd;
+ return Geolayout.BackgroundPointers.BelowCloudsStart;
+ }
+
+ case Geolayout.BackgroundIDs.Cavern:
+ {
+ if (EndAddr)
+ return Geolayout.BackgroundPointers.CavernEnd;
+ return Geolayout.BackgroundPointers.CavernStart;
+ }
+
+ case Geolayout.BackgroundIDs.Desert:
+ {
+ if (EndAddr)
+ return Geolayout.BackgroundPointers.DesertEnd;
+ return Geolayout.BackgroundPointers.DesertStart;
+ }
+
+ case Geolayout.BackgroundIDs.FlamingSky:
+ {
+ if (EndAddr)
+ return Geolayout.BackgroundPointers.FlamingSkyEnd;
+ return Geolayout.BackgroundPointers.FlamingSkyStart;
+ }
+
+ case Geolayout.BackgroundIDs.HauntedForest:
+ {
+ if (EndAddr)
+ return Geolayout.BackgroundPointers.HauntedForestEnd;
+ return Geolayout.BackgroundPointers.HauntedForestStart;
+ }
+
+ case Geolayout.BackgroundIDs.Ocean:
+ {
+ if (EndAddr)
+ return Geolayout.BackgroundPointers.OceanEnd;
+ return Geolayout.BackgroundPointers.OceanStart;
+ }
+
+ case Geolayout.BackgroundIDs.PurpleClouds:
+ {
+ if (EndAddr)
+ return Geolayout.BackgroundPointers.PurpleCloudsEnd;
+ return Geolayout.BackgroundPointers.PurpleCloudsStart;
+ }
+
+ case Geolayout.BackgroundIDs.SnowyMountains:
+ {
+ if (EndAddr)
+ return Geolayout.BackgroundPointers.SnowyMountainsEnd;
+ return Geolayout.BackgroundPointers.SnowyMountainsStart;
+ }
+
+ case Geolayout.BackgroundIDs.UnderwaterCity:
+ {
+ if (EndAddr)
+ return Geolayout.BackgroundPointers.UnderwaterCityEnd;
+ return Geolayout.BackgroundPointers.UnderwaterCityStart;
+ }
+ }
+
+ return EndAddr ? Geolayout.BackgroundPointers.OceanEnd : Geolayout.BackgroundPointers.OceanStart;
+ }
+
+ public static string CommandByteArrayToString(byte[] bytes)
+ {
+ string res = string.Empty;
+ foreach (byte b in bytes)
+ {
+ if (!string.IsNullOrEmpty(res))
+ {
+ res += " ";
+ }
+
+ res += b.ToString("X2");
+ }
+
+ return res;
+ }
+
+ internal static byte[] CompressData(Stream input, CompressionLevel compressionLevel)
+ {
+ var output = new MemoryStream();
+ input.Position = 0;
+
+ using (var compressor = new DeflateStream(output, compressionLevel, true))
+ input.CopyTo(compressor);
+
+ var res = output.ToArray();
+ output.Close();
+ return res;
+ }
+
+ internal static void DecompressData(byte[] input, Stream output)
+ {
+ using (var sInput = new MemoryStream(input))
+ {
+ using (var decompressor = new DeflateStream(sInput, CompressionMode.Decompress, true))
+ decompressor.CopyTo(output);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/SM64Lib/Geolayout/BackgroundIDs.cs b/SM64Lib/Geolayout/BackgroundIDs.cs
new file mode 100644
index 0000000..f4560ce
--- /dev/null
+++ b/SM64Lib/Geolayout/BackgroundIDs.cs
@@ -0,0 +1,18 @@
+
+namespace SM64Lib.Geolayout
+{
+ public enum BackgroundIDs : byte
+ {
+ HauntedForest = 0x6,
+ SnowyMountains = 0x4,
+ Desert = 0x5,
+ Ocean = 0x0,
+ UnderwaterCity = 0x2,
+ BelowClouds = 0x8,
+ AboveClouds = 0x3,
+ Cavern = 0x7,
+ FlamingSky = 0x1,
+ PurpleClouds = 0x9,
+ Custom = 0xA
+ }
+}
\ No newline at end of file
diff --git a/SM64Lib/Geolayout/BackgroundPointers.cs b/SM64Lib/Geolayout/BackgroundPointers.cs
new file mode 100644
index 0000000..3dd546c
--- /dev/null
+++ b/SM64Lib/Geolayout/BackgroundPointers.cs
@@ -0,0 +1,27 @@
+
+namespace SM64Lib.Geolayout
+{
+ public enum BackgroundPointers
+ {
+ HauntedForestStart = 0xC3B030, // &HC3AFD5
+ HauntedForestEnd = 0xC4F970, // &HC4F915
+ SnowyMountainsStart = 0xB5D8B0, // &HB5D855
+ SnowyMountainsEnd = 0xB7D9F0, // &HB7D995
+ DesertStart = 0xC12EF0, // &HC12E95
+ DesertEnd = 0xC33030, // &HC32FD5
+ OceanStart = 0xB35770, // &HB35715
+ OceanEnd = 0xB558B0, // &HB55855
+ UnderwaterCityStart = 0xBC2C70, // &HBC2C15
+ UnderwaterCityEnd = 0xBE2DB0, // &HBE2D55
+ BelowCloudsStart = 0xB859F0, // &HB85995
+ BelowCloudsEnd = 0xB9A330, // &HB9A2D5
+ AboveCloudsStart = 0xBEADB0, // &HBEAD55
+ AboveCloudsEnd = 0xC0AEF0, // &HC0AE95
+ CavernStart = 0xC57970, // &HC57915
+ CavernEnd = 0xC77AB0, // &HC77A55
+ FlamingSkyStart = 0xBA2330, // &HBA22D5
+ FlamingSkyEnd = 0xBBAC70, // &HBBAC15
+ PurpleCloudsStart = 0xC7FAB0, // &HC7FA55
+ PurpleCloudsEnd = 0xC9FBF0 // &HC9FB95
+ }
+}
\ No newline at end of file
diff --git a/SM64Lib/Geolayout/CameraFrustrum.cs b/SM64Lib/Geolayout/CameraFrustrum.cs
new file mode 100644
index 0000000..78738d5
--- /dev/null
+++ b/SM64Lib/Geolayout/CameraFrustrum.cs
@@ -0,0 +1,20 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SM64Lib.Geolayout
+{
+ public class CameraFrustrum
+ {
+ ///
+ /// This is essentially render distance of geometry.
+ ///
+ public short CameraFar { get; set; } = 0x7530;
+ ///
+ /// Defines how close to the camera geometry will render.
+ ///
+ public short CameraNear { get; set; } = 0x64;
+ }
+}
diff --git a/SM64Lib/Geolayout/CameraPresets.cs b/SM64Lib/Geolayout/CameraPresets.cs
new file mode 100644
index 0000000..3ffd72d
--- /dev/null
+++ b/SM64Lib/Geolayout/CameraPresets.cs
@@ -0,0 +1,23 @@
+
+namespace SM64Lib.Geolayout
+{
+ public enum CameraPresets
+ {
+ OpenCamera = 0x1,
+ TickTockClock,
+ SecretAquarium,
+ InsideCastle,
+ Unmoving,
+ MarioFacingObject,
+ Unmoving1,
+ UnderwaterCamera,
+ PeachSlideCamera,
+ InsideCannon,
+ BowserFightsDistant,
+ CloseCameraCrashes,
+ FixedReferencePoint,
+ PlattfromLevels,
+ Unmoving2,
+ FreeRoamCamera
+ }
+}
\ No newline at end of file
diff --git a/SM64Lib/Geolayout/DefaultGeolayers.cs b/SM64Lib/Geolayout/DefaultGeolayers.cs
new file mode 100644
index 0000000..f6c6797
--- /dev/null
+++ b/SM64Lib/Geolayout/DefaultGeolayers.cs
@@ -0,0 +1,15 @@
+
+namespace SM64Lib.Geolayout
+{
+ public enum DefaultGeolayers
+ {
+ SolidNoAntiAlias,
+ Solid,
+ SolidDecal,
+ TranslucentDecal,
+ Alpha,
+ Translucent,
+ TranslucentDecal2,
+ TranslucentDecal3
+ }
+}
\ No newline at end of file
diff --git a/SM64Lib/Geolayout/EnvironmentEffects.cs b/SM64Lib/Geolayout/EnvironmentEffects.cs
new file mode 100644
index 0000000..9504db9
--- /dev/null
+++ b/SM64Lib/Geolayout/EnvironmentEffects.cs
@@ -0,0 +1,14 @@
+
+namespace SM64Lib.Geolayout
+{
+ public enum EnvironmentEffects
+ {
+ NoEffect = 0x0,
+ Snow = 0x1,
+ Bllizard = 0x3,
+ BetaFlower = 0xB,
+ Lava = 0xC,
+ WaterRelated1 = 0xD,
+ WaterRelated2 = 0x2
+ }
+}
\ No newline at end of file
diff --git a/SM64Lib/Geolayout/GeoAsmPointer.cs b/SM64Lib/Geolayout/GeoAsmPointer.cs
new file mode 100644
index 0000000..8ae265c
--- /dev/null
+++ b/SM64Lib/Geolayout/GeoAsmPointer.cs
@@ -0,0 +1,10 @@
+
+namespace SM64Lib.Geolayout
+{
+ public enum GeoAsmPointer : uint
+ {
+ EnvironmentEffect = 0x802761D0,
+ Water = 0x802D104C,
+ Unknown = 0x802CD1E8
+ }
+}
\ No newline at end of file
diff --git a/SM64Lib/Geolayout/Geolayout.cs b/SM64Lib/Geolayout/Geolayout.cs
new file mode 100644
index 0000000..d2751da
--- /dev/null
+++ b/SM64Lib/Geolayout/Geolayout.cs
@@ -0,0 +1,380 @@
+using System.Collections.Generic;
+using global::System.IO;
+using Microsoft.VisualBasic;
+using Microsoft.VisualBasic.CompilerServices;
+using global::SM64Lib.Geolayout.Script;
+using global::SM64Lib.Geolayout.Script.Commands;
+using Newtonsoft.Json;
+using System;
+
+namespace SM64Lib.Geolayout
+{
+ public class Geolayout
+ {
+ [JsonProperty]
+ private int IndexForGeopointers = -1;
+
+ public Geolayoutscript Geolayoutscript { get; set; } = new Geolayoutscript();
+ public CameraPresets CameraPreset { get; set; } = CameraPresets.OpenCamera;
+ public EnvironmentEffects EnvironmentEffect { get; set; } = default;
+ public List GeopointerOffsets { get; set; } = new List();
+ public List Geopointers { get; set; } = new List();
+ public int NewGeoOffset { get; set; } = 0;
+ public bool Closed { get; set; } = false;
+ public ObjectShadow ObjectShadow { get; set; } = new ObjectShadow();
+ public CameraFrustrum CameraFrustrum { get; set; } = new CameraFrustrum();
+
+ public int Length
+ {
+ get
+ {
+ int tLength = Geopointers.Count * 8;
+ foreach (var c in Geolayoutscript)
+ tLength += (int)c.Length;
+ return tLength;
+ }
+ }
+
+ private void RemoveOldGeopointerCommands()
+ {
+ var ToRemove = new List();
+ foreach (GeolayoutCommand c in Geolayoutscript)
+ {
+ if (c.CommandType == GeolayoutCommandTypes.LoadDisplaylist)
+ {
+ ToRemove.Add(c);
+ }
+ }
+
+ foreach (GeolayoutCommand cmd in ToRemove)
+ {
+ Geolayoutscript.Remove(cmd);
+ cmd.Close();
+ }
+ }
+
+ private List GetGeopointersFromGeolayoutScript(Geolayoutscript script)
+ {
+ List geopointers = new List();
+ int geolayoutCommandIndex = 0;
+ var curMdlScale = System.Numerics.Vector3.One;
+ var curMdlOffset = System.Numerics.Vector3.Zero;
+ foreach (GeolayoutCommand geolayoutCommand in script)
+ {
+ var referenceCommand = geolayoutCommand;
+ if (geolayoutCommand.CommandType == GeolayoutCommandTypes.LoadDisplaylist)
+ {
+ geopointers.Add(new Geopointer(
+ cgLoadDisplayList.GetDrawingLayer(ref referenceCommand),
+ cgLoadDisplayList.GetSegGeopointer(ref referenceCommand),
+ curMdlScale,
+ curMdlOffset,
+ geolayoutCommandIndex
+ ));
+ }
+ geolayoutCommandIndex += 1;
+ }
+ return geopointers;
+ }
+
+ public Geolayout(NewScriptCreationMode mode)
+ {
+ switch (mode)
+ {
+ case NewScriptCreationMode.Level:
+ {
+ {
+ var withBlock = Geolayoutscript;
+ withBlock.Add(new GeolayoutCommand(new byte[] { 0x8, 0x0, 0x0, 0xA, 0x0, 0xA0, 0x0, 0x78, 0x0, 0xA0, 0x0, 0x78 }));
+ withBlock.Add(new GeolayoutCommand(new byte[] { 0x4, 0x0, 0x0, 0x0 }));
+ withBlock.Add(new GeolayoutCommand(new byte[] { 0xC, 0x0, 0x0, 0x0 }));
+ withBlock.Add(new GeolayoutCommand(new byte[] { 0x4, 0x0, 0x0, 0x0 }));
+ withBlock.Add(new GeolayoutCommand(new byte[] { 0x9, 0x0, 0x0, 0x64 }));
+ withBlock.Add(new GeolayoutCommand(new byte[] { 0x4, 0x0, 0x0, 0x0 }));
+ withBlock.Add(new GeolayoutCommand(new byte[] { 0x19, 0x0, 0x0, 0x0, 0x80, 0x27, 0x63, 0xD4 }));
+ withBlock.Add(new GeolayoutCommand(new byte[] { 0x5, 0x0, 0x0, 0x0 }));
+ withBlock.Add(new GeolayoutCommand(new byte[] { 0x5, 0x0, 0x0, 0x0 }));
+ withBlock.Add(new GeolayoutCommand(new byte[] { 0xC, 0x1, 0x0, 0x0 }));
+ withBlock.Add(new GeolayoutCommand(new byte[] { 0x4, 0x0, 0x0, 0x0 }));
+ withBlock.Add(new GeolayoutCommand(new byte[] { 0xA, 0x1, 0x0, 0x2D, 0x0, 0x64, 0x75, 0x30, 0x80, 0x29, 0xAA, 0x3C }));
+ withBlock.Add(new GeolayoutCommand(new byte[] { 0x4, 0x0, 0x0, 0x0 }));
+ withBlock.Add(new GeolayoutCommand("0F 00 00 01 00 00 00 00 00 00 00 00 00 00 00 00 80 28 7D 30"));
+ withBlock.Add(new GeolayoutCommand(new byte[] { 0x4, 0x0, 0x0, 0x0 }));
+ IndexForGeopointers = 15;
+ // .Add(New GeolayoutCommand({&H15, &H1, &H0, &H0, &H0, &H0, &H0, &H0}))
+ withBlock.Add(new GeolayoutCommand(new byte[] { 0x17, 0x0, 0x0, 0x0 }));
+ withBlock.Add(new GeolayoutCommand(new byte[] { 0x18, 0x0, 0x0, 0x0, 0x80, 0x27, 0x61, 0xD0 }));
+ withBlock.Add(new GeolayoutCommand(new byte[] { 0x18, 0x0, 0x50, 0x0, 0x80, 0x2D, 0x10, 0x4C })); // Water
+ withBlock.Add(new GeolayoutCommand(new byte[] { 0x18, 0x0, 0x50, 0x1, 0x80, 0x2D, 0x10, 0x4C })); // Toxic Haze
+ withBlock.Add(new GeolayoutCommand(new byte[] { 0x18, 0x0, 0x50, 0x2, 0x80, 0x2D, 0x10, 0x4C })); // Mist
+ withBlock.Add(new GeolayoutCommand(new byte[] { 0x5, 0x0, 0x0, 0x0 }));
+ withBlock.Add(new GeolayoutCommand(new byte[] { 0x5, 0x0, 0x0, 0x0 }));
+ withBlock.Add(new GeolayoutCommand(new byte[] { 0x5, 0x0, 0x0, 0x0 }));
+ withBlock.Add(new GeolayoutCommand(new byte[] { 0xC, 0x0, 0x0, 0x0 }));
+ withBlock.Add(new GeolayoutCommand(new byte[] { 0x4, 0x0, 0x0, 0x0 }));
+ withBlock.Add(new GeolayoutCommand(new byte[] { 0x18, 0x0, 0x0, 0x0, 0x80, 0x2C, 0xD1, 0xE8 }));
+ withBlock.Add(new GeolayoutCommand(new byte[] { 0x5, 0x0, 0x0, 0x0 }));
+ withBlock.Add(new GeolayoutCommand(new byte[] { 0x5, 0x0, 0x0, 0x0 }));
+ withBlock.Add(new GeolayoutCommand(new byte[] { 0x1, 0x0, 0x0, 0x0 }));
+ }
+
+ break;
+ }
+
+ case NewScriptCreationMode.Object:
+ {
+ {
+ var withBlock1 = Geolayoutscript;
+ withBlock1.Add(new GeolayoutCommand("20 00 0F A0"));
+ withBlock1.Add(new GeolayoutCommand("04 00 00 00"));
+ IndexForGeopointers = 2;
+ withBlock1.Add(new GeolayoutCommand("05 00 00 00"));
+ withBlock1.Add(new GeolayoutCommand("01 00 00 00"));
+ }
+
+ break;
+ }
+ }
+ }
+
+ public void Read(byte[] data, byte bankID)
+ {
+ RenewGeolayout();
+ Geolayoutscript.Read(data, bankID);
+ ParseGeolayout();
+ }
+
+ public void Read(RomManager rommgr, int segAddress)
+ {
+ RenewGeolayout();
+ Geolayoutscript.Read(rommgr, segAddress);
+ ParseGeolayout();
+ }
+
+ private void RenewGeolayout()
+ {
+ if (!Closed) Close();
+ Closed = false;
+ Geolayoutscript.Clear();
+ Geopointers.Clear();
+ GeopointerOffsets.Clear();
+ Geolayoutscript = new Geolayoutscript();
+ ObjectShadow = new ObjectShadow();
+ CameraFrustrum = new CameraFrustrum();
+ }
+
+ public void ParseGeolayout()
+ {
+ var ToRemove = new List();
+ int cIndex = 0;
+ var curMdlScale = System.Numerics.Vector3.One;
+ var curMdlOffset = System.Numerics.Vector3.Zero;
+
+ foreach (GeolayoutCommand fec in Geolayoutscript)
+ {
+ var c = fec;
+ var switchExpr = c.CommandType;
+
+ switch (switchExpr)
+ {
+ case GeolayoutCommandTypes.CameraPreset:
+ CameraPreset = (CameraPresets)cgCameraPreset.GetCameraPreset(ref c);
+ break;
+ case GeolayoutCommandTypes.x18:
+ var switchExpr1 = (uint)cgx18.GetAsmPointer(ref c);
+ switch (switchExpr1)
+ {
+ case 0x802761D0:
+ EnvironmentEffect = (EnvironmentEffects)cgx18.GetParam1(ref c);
+ break;
+ }
+ break;
+ case GeolayoutCommandTypes.LoadDisplaylist:
+ if (Geopointers.Count == 0)
+ IndexForGeopointers = cIndex;
+ Geopointers.Add(new Geopointer(cgLoadDisplayList.GetDrawingLayer(ref c), cgLoadDisplayList.GetSegGeopointer(ref c), curMdlScale, curMdlOffset, cIndex));
+ break;
+ case GeolayoutCommandTypes.ObjectShadown:
+ cgObjectShadow.GetShadow(c, ObjectShadow);
+ ObjectShadow.Enabled = true;
+ break;
+ case GeolayoutCommandTypes.DrawingDistance:
+ ObjectShadow.Enabled = false;
+ break;
+ case GeolayoutCommandTypes.CameraFrustrum:
+ cgCameraFrustrum.GetCameraFrustrum(c, CameraFrustrum);
+ break;
+ }
+
+ cIndex += 1;
+ }
+
+ // Remove Geopointercommands
+ RemoveOldGeopointerCommands();
+ }
+
+ public void Write(Stream s, int StartOffset)
+ {
+ NewGeoOffset = StartOffset;
+ var commandsToRemove = new List();
+ GeolayoutCommand cmdObjectShadow = null;
+ GeolayoutCommand cmdDrawingDistance = null;
+ int tIndexForGeoPointer = IndexForGeopointers;
+ var isForLevel = false;
+
+ // Einstellungen übernehmen
+ int currentPosition = 0;
+ foreach (GeolayoutCommand fec in Geolayoutscript)
+ {
+ var c = fec;
+ var switchExpr = c.CommandType;
+
+ switch (switchExpr)
+ {
+ case GeolayoutCommandTypes.CameraPreset:
+ cgCameraPreset.SetCameraPreset(ref c, Convert.ToByte(CameraPreset));
+ break;
+ case GeolayoutCommandTypes.x18:
+ var switchExpr1 = cgx18.GetAsmPointer(ref c);
+ switch ((GeoAsmPointer)switchExpr1)
+ {
+ case GeoAsmPointer.EnvironmentEffect:
+ cgx18.SetParam1(ref c, (ushort)EnvironmentEffect);
+ break;
+ }
+ break;
+ case GeolayoutCommandTypes.ObjectShadown:
+ if (ObjectShadow.Enabled)
+ cmdObjectShadow = c;
+ else
+ commandsToRemove.Add(c);
+ break;
+ case GeolayoutCommandTypes.DrawingDistance:
+ if (ObjectShadow.Enabled)
+ commandsToRemove.Add(c);
+ else
+ cmdDrawingDistance = c;
+ break;
+ case GeolayoutCommandTypes.CameraFrustrum:
+ cgCameraFrustrum.SetCameraFrustrum(c, CameraFrustrum);
+ isForLevel = true;
+ break;
+ case GeolayoutCommandTypes.SetScreenRenderArea:
+ isForLevel = true;
+ break;
+ }
+
+ currentPosition += (int)c.Length;
+ }
+
+ // Insert Geopointers
+ foreach (Geopointer g in Geopointers)
+ {
+ var tcommand = new GeolayoutCommand(new byte[] { 0x15, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 });
+ cgLoadDisplayList.SetDrawingLayer(ref tcommand, g.Layer);
+ cgLoadDisplayList.SetSegGeopointer(ref tcommand, g.SegPointer);
+ if (g.Index == -1)
+ {
+ Geolayoutscript.Insert(tIndexForGeoPointer, tcommand);
+ }
+ else
+ {
+ Geolayoutscript.Insert(g.Index, tcommand);
+ }
+
+ tIndexForGeoPointer += 1;
+ }
+
+ // Remove all other commands to remove
+ foreach (var cmd in commandsToRemove)
+ {
+ Geolayoutscript.Remove(cmd);
+ cmd.Close();
+ }
+
+ // Add Geolayout Start command for non-level geolayouts
+ if (!isForLevel)
+ {
+ if (ObjectShadow.Enabled)
+ {
+ // Add Object Shadow
+ if (cmdObjectShadow is null)
+ {
+ cmdObjectShadow = new GeolayoutCommand("16 00 00 00 00 00 00 00");
+ Geolayoutscript.Insert(0, cmdObjectShadow);
+ }
+ cgObjectShadow.SetShadow(cmdObjectShadow, ObjectShadow);
+ }
+ else
+ {
+ if (cmdDrawingDistance == null)
+ Geolayoutscript.Insert(0, new GeolayoutCommand("20 00 0F A0"));
+ }
+ }
+
+ // Update Geopointer indexes after adding and removing other commands
+ Geopointers = GetGeopointersFromGeolayoutScript(Geolayoutscript);
+
+ // Write Geolayout to ROM
+ Geolayoutscript.Write(s, StartOffset);
+
+ // Remove Geopointercommands again
+ RemoveOldGeopointerCommands();
+ }
+
+ public void Close()
+ {
+ foreach (var c in Geolayoutscript)
+ c.Close();
+ }
+
+ public override string ToString()
+ {
+ string output = "";
+ foreach (var cmd in Geolayoutscript)
+ {
+ string tbytelist = "";
+ foreach (byte b in cmd.ToArray())
+ {
+ if (tbytelist != string.Empty)
+ tbytelist += " ";
+ tbytelist += Conversion.Hex(b);
+ }
+
+ if (output != string.Empty)
+ output += Environment.NewLine;
+ output += tbytelist;
+ }
+
+ return output;
+ }
+
+ public string ToString(GeolayoutCommandCollection Geolayoutscript)
+ {
+ string output = "";
+ foreach (var cmd in Geolayoutscript)
+ {
+ string tbytelist = "";
+ foreach (byte b in cmd.ToArray())
+ {
+ if (tbytelist != string.Empty)
+ tbytelist += " ";
+ tbytelist += Conversion.Hex(b);
+ }
+
+ if (output != string.Empty)
+ output += Environment.NewLine;
+ output += tbytelist;
+ }
+
+ return output;
+ }
+
+ public enum NewScriptCreationMode
+ {
+ None,
+ Level,
+ Object
+ }
+ }
+}
\ No newline at end of file
diff --git a/SM64Lib/Geolayout/Geopointer.cs b/SM64Lib/Geolayout/Geopointer.cs
new file mode 100644
index 0000000..22e2a7e
--- /dev/null
+++ b/SM64Lib/Geolayout/Geopointer.cs
@@ -0,0 +1,42 @@
+
+using Newtonsoft.Json;
+
+namespace SM64Lib.Geolayout
+{
+ public class Geopointer
+ {
+ public byte Layer { get; set; } = default;
+ public int SegPointer { get; set; } = 0;
+ public int Index { get; set; } = -1;
+ public System.Numerics.Vector3 ModelScale { get; set; } = System.Numerics.Vector3.One;
+ public System.Numerics.Vector3 ModelOffset { get; set; } = System.Numerics.Vector3.Zero;
+
+ [JsonConstructor]
+ private Geopointer()
+ {
+ }
+
+ public Geopointer(byte Layer) : this()
+ {
+ this.Layer = Layer;
+ }
+
+ public Geopointer(byte Layer, int SegPointer) : this(Layer)
+ {
+ this.SegPointer = SegPointer;
+ }
+
+ public Geopointer(byte Layer, int SegPointer, System.Numerics.Vector3 mdlscale, System.Numerics.Vector3 mdloffset) : this(Layer, SegPointer)
+ {
+ ModelScale = mdlscale;
+ ModelOffset = mdloffset;
+ }
+
+ public Geopointer(byte Layer, int SegPointer, System.Numerics.Vector3 mdlscale, System.Numerics.Vector3 mdloffset, int index) : this(Layer, SegPointer)
+ {
+ ModelScale = mdlscale;
+ ModelOffset = mdloffset;
+ Index = index;
+ }
+ }
+}
\ No newline at end of file
diff --git a/SM64Lib/Geolayout/ObjectShadow.cs b/SM64Lib/Geolayout/ObjectShadow.cs
new file mode 100644
index 0000000..1ac5dce
--- /dev/null
+++ b/SM64Lib/Geolayout/ObjectShadow.cs
@@ -0,0 +1,16 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SM64Lib.Geolayout
+{
+ public class ObjectShadow
+ {
+ public bool Enabled { get; set; } = false;
+ public ObjectShadowType Type { get; set; } = ObjectShadowType.Circle_9Verts;
+ public short Scale { get; set; }
+ public byte Solidity { get; set; } = 0xFF;
+ }
+}
diff --git a/SM64Lib/Geolayout/ObjectShadowType.cs b/SM64Lib/Geolayout/ObjectShadowType.cs
new file mode 100644
index 0000000..000e4c6
--- /dev/null
+++ b/SM64Lib/Geolayout/ObjectShadowType.cs
@@ -0,0 +1,24 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SM64Lib.Geolayout
+{
+ public enum ObjectShadowType
+ {
+ Circle_9Verts = 0,
+ Circle_4Verts = 1,
+ Circle_4Verts_Flat_Unused = 2,
+ SquarePermanent = 10,
+ SquareScaleable = 11,
+ SquareTooglable = 12,
+ /**
+ * This defines an offset after which rectangular shadows with custom
+ * widths and heights can be defined.
+ */
+ RectangleHardcodedOffset = 50,
+ CirclePlayer = 99
+ }
+}
diff --git a/SM64Lib/Geolayout/Script/GeolayoutCommand.cs b/SM64Lib/Geolayout/Script/GeolayoutCommand.cs
new file mode 100644
index 0000000..038b8b9
--- /dev/null
+++ b/SM64Lib/Geolayout/Script/GeolayoutCommand.cs
@@ -0,0 +1,40 @@
+using global::SM64Lib.Script;
+using Newtonsoft.Json;
+using SM64Lib.Json;
+
+namespace SM64Lib.Geolayout.Script
+{
+ [JsonConverter(typeof(GeolayoutscriptCommandJsonConverter))]
+ public class GeolayoutCommand : BaseCommand
+ {
+ public GeolayoutCommand(byte[] bytes) : base(bytes)
+ {
+ }
+
+ public GeolayoutCommand() : base()
+ {
+ }
+
+ public GeolayoutCommand(string bytes, bool enabledHex = true) : base(bytes, enabledHex)
+ {
+ }
+
+ public override GeolayoutCommandTypes CommandType
+ {
+ get
+ {
+ Position = 0;
+ GeolayoutCommandTypes t = (GeolayoutCommandTypes)ReadByte();
+ Position = 0;
+ return t;
+ }
+
+ set
+ {
+ Position = 0;
+ WriteByte((byte)value);
+ Position = 0;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/SM64Lib/Geolayout/Script/GeolayoutCommandCollection.cs b/SM64Lib/Geolayout/Script/GeolayoutCommandCollection.cs
new file mode 100644
index 0000000..962f77f
--- /dev/null
+++ b/SM64Lib/Geolayout/Script/GeolayoutCommandCollection.cs
@@ -0,0 +1,18 @@
+using global::SM64Lib.Script;
+
+namespace SM64Lib.Geolayout.Script
+{
+ public class GeolayoutCommandCollection : BaseCommandCollection
+ {
+ public int IndexOfFirst(GeolayoutCommandTypes cmdType)
+ {
+ for (int index = 0, loopTo = Count - 1; index <= loopTo; index++)
+ {
+ if (this[index].CommandType == cmdType)
+ return index;
+ }
+
+ return -1;
+ }
+ }
+}
\ No newline at end of file
diff --git a/SM64Lib/Geolayout/Script/GeolayoutCommandFunctions.cs b/SM64Lib/Geolayout/Script/GeolayoutCommandFunctions.cs
new file mode 100644
index 0000000..08bb254
--- /dev/null
+++ b/SM64Lib/Geolayout/Script/GeolayoutCommandFunctions.cs
@@ -0,0 +1,277 @@
+using global::System.Numerics;
+using Microsoft.VisualBasic.CompilerServices;
+using System;
+
+namespace SM64Lib.Geolayout.Script
+{
+ namespace Commands
+ {
+ public class cgBackground
+ {
+ public static short GetBackgroundID(GeolayoutCommand command)
+ {
+ command.Position = 0x2;
+ short value = command.ReadInt16();
+ command.Position = 0;
+ return value;
+ }
+
+ public static void SetBackgroundID(GeolayoutCommand command, short ID)
+ {
+ command.Position = 0x2;
+ command.Write(ID);
+ command.Position = 0;
+ }
+
+ public static int GetBackgroundPointer(GeolayoutCommand command)
+ {
+ command.Position = 0x4;
+ int value = command.ReadInt32();
+ command.Position = 0;
+ return value;
+ }
+
+ public static void SetBackgroundPointer(GeolayoutCommand command, int Pointer)
+ {
+ command.Position = 0x4;
+ command.Write(Pointer);
+ command.Position = 0;
+ }
+
+ public static System.Drawing.Color GetRrgbaColor(GeolayoutCommand command)
+ {
+ command.Position = 0x2;
+ byte b1 = command.ReadByte();
+ byte b2 = command.ReadByte();
+ command.Position = 0;
+ return N64Graphics.N64Graphics.RGBA16Color(b1, b2);
+ }
+
+ public static void SetRgbaColor(GeolayoutCommand command, System.Drawing.Color color)
+ {
+ byte b1, b2;
+ N64Graphics.N64Graphics.ColorRGBA16(color, out b1, out b2);
+ command.Position = 0x2;
+ command.WriteByte(b1);
+ command.WriteByte(b2);
+ command.Position = 0;
+ }
+ }
+
+ public class cgCameraPreset
+ {
+ public static byte GetCameraPreset(ref GeolayoutCommand command)
+ {
+ command.Position = 0x3;
+ byte value = command.ReadByte();
+ command.Position = 0;
+ return value;
+ }
+
+ public static void SetCameraPreset(ref GeolayoutCommand command, byte Preset)
+ {
+ command.Position = 0x3;
+ command.Write(Preset);
+ command.Position = 0;
+ }
+ }
+
+ public class cgx18
+ {
+ public static ushort GetParam1(ref GeolayoutCommand command)
+ {
+ command.Position = 0x2;
+ ushort value = command.ReadUInt16();
+ command.Position = 0;
+ return value;
+ }
+
+ public static void SetParam1(ref GeolayoutCommand command, ushort ID)
+ {
+ command.Position = 0x2;
+ command.Write(ID);
+ command.Position = 0;
+ }
+
+ public static int GetAsmPointer(ref GeolayoutCommand command)
+ {
+ command.Position = 0x4;
+ int value = command.ReadInt32();
+ command.Position = 0;
+ return value;
+ }
+
+ public static void SetAsmPointer(ref GeolayoutCommand command, int AsmPointer)
+ {
+ command.Position = 0x4;
+ command.Write(AsmPointer);
+ command.Position = 0;
+ }
+ }
+
+ public class cgLoadDisplayList
+ {
+ public static byte GetDrawingLayer(ref GeolayoutCommand command)
+ {
+ command.Position = 0x1;
+ byte value = command.ReadByte();
+ command.Position = 0;
+ return value;
+ }
+
+ public static void SetDrawingLayer(ref GeolayoutCommand command, byte Layer)
+ {
+ command.Position = 0x1;
+ command.Write(Layer);
+ command.Position = 0;
+ }
+
+ public static int GetSegGeopointer(ref GeolayoutCommand command)
+ {
+ command.Position = 0x4;
+ int value = command.ReadInt32();
+ command.Position = 0;
+ return value;
+ }
+
+ public static void SetSegGeopointer(ref GeolayoutCommand command, int SegPointer)
+ {
+ command.Position = 0x4;
+ command.Write(SegPointer);
+ command.Position = 0;
+ }
+ }
+
+ public class cgLoadDisplayListWithOffset
+ {
+ public static byte GetDrawingLayer(ref GeolayoutCommand command)
+ {
+ command.Position = 0x1;
+ byte value = command.ReadByte();
+ command.Position = 0;
+ return value;
+ }
+
+ public static void SetDrawingLayer(ref GeolayoutCommand command, byte Layer)
+ {
+ command.Position = 0x1;
+ command.Write(Layer);
+ command.Position = 0;
+ }
+
+ public static Vector3 GetOffset(ref GeolayoutCommand command)
+ {
+ command.Position = 2;
+ var value = new Vector3();
+ value.X = command.ReadInt16();
+ value.Y = command.ReadInt16();
+ value.Z = command.ReadInt16();
+ command.Position = 0;
+ return value;
+ }
+
+ public static void SetOffset(ref GeolayoutCommand Command, Vector3 value)
+ {
+ Command.Position = 2;
+ Command.Write(Convert.ToInt16(value.X));
+ Command.Write(Convert.ToInt16(value.Y));
+ Command.Write(Convert.ToInt16(value.Z));
+ Command.Position = 0;
+ }
+
+ public static void SetOffset(ref GeolayoutCommand Command, int X, int Y, int Z)
+ {
+ SetOffset(ref Command, new Vector3(X, Y, Z));
+ }
+
+ public static uint GetSegGeopointer(ref GeolayoutCommand command)
+ {
+ command.Position = 0x8;
+ uint value = command.ReadUInt32();
+ command.Position = 0;
+ return value;
+ }
+
+ public static void SetSegGeopointer(ref GeolayoutCommand command, uint SegPointer)
+ {
+ command.Position = 0x8;
+ command.Write(SegPointer);
+ command.Position = 0;
+ }
+ }
+
+ public class cgx10
+ {
+ public static Vector3 GetOffset(ref GeolayoutCommand command)
+ {
+ command.Position = 2;
+ var value = new Vector3();
+ value.X = command.ReadInt16();
+ value.Y = command.ReadInt16();
+ command.Position += 2;
+ value.Z = command.ReadInt16();
+ command.Position = 0;
+ return value;
+ }
+ }
+
+ public class cgObjectShadow
+ {
+ /*
+ 16 00 00 [AA] 00 [BB] [CC CC]
+ A Shadow type
+ B Shadow solidity (00=invisible, FF=black)
+ C Shadow scale
+ */
+
+ public static void GetShadow(GeolayoutCommand command, ObjectShadow shadow)
+ {
+ command.Position = 3;
+ shadow.Type = (ObjectShadowType)command.ReadByte();
+
+ command.Position = 5;
+ shadow.Solidity = command.ReadByte();
+
+ shadow.Scale = command.ReadInt16();
+
+ command.Position = 0;
+ }
+
+ public static void SetShadow(GeolayoutCommand command, ObjectShadow shadow)
+ {
+ command.Position = 3;
+ command.Write((byte)shadow.Type);
+
+ command.Position = 5;
+ command.Write(shadow.Solidity);
+
+ command.Write(shadow.Scale);
+
+ command.Position = 0;
+ }
+ }
+
+ public class cgCameraFrustrum
+ {
+ public static void GetCameraFrustrum(GeolayoutCommand command, CameraFrustrum frustrum)
+ {
+ command.Position = 4;
+
+ frustrum.CameraNear = command.ReadInt16();
+ frustrum.CameraFar = command.ReadInt16();
+
+ command.Position = 0;
+ }
+
+ public static void SetCameraFrustrum(GeolayoutCommand command, CameraFrustrum frustrum)
+ {
+ command.Position = 4;
+
+ command.Write(frustrum.CameraNear);
+ command.Write(frustrum.CameraFar);
+
+ command.Position = 0;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/SM64Lib/Geolayout/Script/GeolayoutCommandTypes.cs b/SM64Lib/Geolayout/Script/GeolayoutCommandTypes.cs
new file mode 100644
index 0000000..f05aa9d
--- /dev/null
+++ b/SM64Lib/Geolayout/Script/GeolayoutCommandTypes.cs
@@ -0,0 +1,37 @@
+
+namespace SM64Lib.Geolayout.Script
+{
+ public enum GeolayoutCommandTypes
+ {
+ BranchAndStore = 0x0,
+ EndOfGeolayout = 0x1,
+ JumpToSegAddr = 0x2,
+ JumpBack = 0x3,
+ StartOfNode = 0x4,
+ EndOfNode = 0x5,
+ SetScreenRenderArea = 0x8,
+ Scale1 = 0x9,
+ CameraFrustrum = 0xA,
+ x0B = 0xB,
+ x0C = 0xC,
+ x0D = 0xD,
+ x0E = 0xE,
+ CameraPreset = 0xF,
+ x10 = 0x10,
+ x11 = 0x11,
+ x12 = 0x12,
+ LoadDisplaylistWithOffset = 0x13,
+ BillboardModel = 0x14,
+ LoadDisplaylist = 0x15,
+ ObjectShadown = 0x16,
+ x17 = 0x17,
+ x18 = 0x18,
+ Background = 0x19,
+ x1A = 0x1A,
+ x1C = 0x1C,
+ Scale2 = 0x1D,
+ x1E = 0xE,
+ x1f = 0xF,
+ DrawingDistance = 0x20
+ }
+}
\ No newline at end of file
diff --git a/SM64Lib/Geolayout/Script/Geolayoutscript.cs b/SM64Lib/Geolayout/Script/Geolayoutscript.cs
new file mode 100644
index 0000000..2ca3969
--- /dev/null
+++ b/SM64Lib/Geolayout/Script/Geolayoutscript.cs
@@ -0,0 +1,239 @@
+using System.Collections.Generic;
+using global::System.IO;
+using System.Threading.Tasks;
+using Microsoft.VisualBasic.CompilerServices;
+using global::SM64Lib.Data;
+using SM64Lib.SegmentedBanking;
+using System;
+
+namespace SM64Lib.Geolayout.Script
+{
+ public class Geolayoutscript : GeolayoutCommandCollection
+ {
+ public List GeopointerOffsets = new List();
+
+ public Geolayoutscript()
+ {
+ }
+
+ public Task ReadAsync(RomManager rommgr, int segAddress)
+ {
+ var t = new Task(() => Read(rommgr, segAddress));
+ t.Start();
+ return t;
+ }
+
+ public void Read(byte[] data, byte bankID)
+ {
+ var ms = new MemoryStream(data);
+ var segBank = new SegmentedBank(bankID, ms);
+ Read(segBank, 0);
+ }
+
+ public void Read(RomManager rommgr, int segAddress)
+ {
+ var segBank = rommgr.GetSegBank(Convert.ToByte(segAddress >> 24));
+ segBank.ReadDataIfNull(rommgr.RomFile);
+ Read(segBank, segAddress & 0x00ffffff);
+ }
+
+ public void Read(SegmentedBank segBank, int offset)
+ {
+ Close();
+ Clear();
+ GeopointerOffsets.Clear();
+ if (segBank is null)
+ return;
+ var data = new BinaryStreamData(segBank.Data);
+ data.Position = offset;
+ var tb = new List();
+ GeolayoutCommandTypes cb = default;
+ int subNodeIndex = 0;
+ bool ende = false;
+ while (!ende)
+ {
+ if (data.Position >= data.Length)
+ break;
+ cb = (GeolayoutCommandTypes)data.ReadByte();
+ byte lenth = 0;
+ switch (cb)
+ {
+ case GeolayoutCommandTypes.Background:
+ lenth = 0x8;
+ break;
+ case GeolayoutCommandTypes.CameraPreset:
+ lenth = 0x14;
+ break;
+ case GeolayoutCommandTypes.DrawingDistance:
+ lenth = 0x4;
+ break;
+ case GeolayoutCommandTypes.EndOfGeolayout:
+ lenth = 0x4;
+ break;
+ case GeolayoutCommandTypes.EndOfNode:
+ lenth = 0x4;
+ break;
+ case GeolayoutCommandTypes.JumpBack:
+ lenth = 0x4;
+ break;
+ case GeolayoutCommandTypes.JumpToSegAddr:
+ lenth = 0x8;
+ break;
+ case GeolayoutCommandTypes.LoadDisplaylist:
+ lenth = 0x8;
+ break;
+ case GeolayoutCommandTypes.LoadDisplaylistWithOffset:
+ lenth = 0xC;
+ break;
+ case GeolayoutCommandTypes.ObjectShadown:
+ lenth = 0x8;
+ break;
+ case GeolayoutCommandTypes.Scale1:
+ lenth = 0x4;
+ break;
+ case GeolayoutCommandTypes.Scale2:
+ lenth = 0x8;
+ break;
+ case GeolayoutCommandTypes.StartOfNode:
+ lenth = 0x4;
+ break;
+ case GeolayoutCommandTypes.SetScreenRenderArea:
+ lenth = 0xC;
+ break;
+ case GeolayoutCommandTypes.BillboardModel:
+ lenth = 0x8;
+ break;
+ case GeolayoutCommandTypes.BranchAndStore:
+ lenth = 0x8;
+ break;
+ case GeolayoutCommandTypes.CameraFrustrum:
+ {
+ var switchExpr = data.ReadByte();
+ switch (switchExpr)
+ {
+ case 0x1:
+ lenth = 0xC;
+ break;
+ default:
+ lenth = 0x8;
+ break;
+ }
+
+ segBank.Data.Position -= 1;
+ break;
+ }
+ case GeolayoutCommandTypes.x0B:
+ lenth = 0x4;
+ break;
+ case GeolayoutCommandTypes.x0C:
+ lenth = 0x4;
+ break;
+ case GeolayoutCommandTypes.x0D:
+ lenth = 0x8;
+ break;
+ case GeolayoutCommandTypes.x0E:
+ lenth = 0x8;
+ break;
+ case GeolayoutCommandTypes.x10:
+ lenth = 0x10;
+ break;
+ case GeolayoutCommandTypes.x11:
+ lenth = 0x8;
+ break;
+ case GeolayoutCommandTypes.x12:
+ {
+ var switchExpr = data.ReadByte() >> 4;
+ switch (switchExpr)
+ {
+ case 0x8:
+ lenth = 0xC;
+ break;
+ default:
+ lenth = 0x8;
+ break;
+ }
+
+ segBank.Data.Position -= 1;
+ break;
+ }
+ case GeolayoutCommandTypes.x17:
+ lenth = 0x4;
+ break;
+ case GeolayoutCommandTypes.x18:
+ lenth = 0x8;
+ break;
+ case GeolayoutCommandTypes.x1A:
+ lenth = 0x8;
+ break;
+ case GeolayoutCommandTypes.x1C:
+ lenth = 0xC;
+ break;
+ case var @case when @case == GeolayoutCommandTypes.x1E:
+ lenth = 0x8;
+ break;
+ case var case1 when case1 == GeolayoutCommandTypes.x1f:
+ lenth = 0x10;
+ break;
+ default:
+ break;
+ }
+
+ segBank.Data.Position -= 1;
+ if (lenth == 0 || segBank.Data.Position + lenth > segBank.Data.Length)
+ break;
+
+ for (int i = 1, loopTo = lenth; i <= loopTo; i++)
+ tb.Add(data.ReadByte());
+
+ var tCommand = new GeolayoutCommand(tb.ToArray());
+ int bankOffset = (int)(segBank.Data.Position - lenth);
+ tCommand.RomAddress = segBank.RomStart + bankOffset;
+ tCommand.BankAddress = segBank.BankAddress + bankOffset;
+ Add(tCommand);
+ tb.Clear();
+
+ var switchExpr1 = tCommand.CommandType;
+ switch (switchExpr1)
+ {
+ case GeolayoutCommandTypes.EndOfGeolayout:
+ ende = true;
+ break;
+ case GeolayoutCommandTypes.EndOfNode:
+ subNodeIndex -= 1;
+ break;
+ case GeolayoutCommandTypes.StartOfNode:
+ subNodeIndex += 1;
+ break;
+ }
+ }
+ }
+
+ public void Write(Stream s, int GeolayoutStart)
+ {
+ var bw = new BinaryWriter(s);
+
+ // Write new Levelscript
+ s.Position = GeolayoutStart;
+ foreach (GeolayoutCommand c in this)
+ {
+ if (c.CommandType == GeolayoutCommandTypes.LoadDisplaylist)
+ GeopointerOffsets.Add((int)(s.Position + 0x4));
+ foreach (byte b in c.ToArray())
+ bw.Write(b);
+ }
+ }
+
+ public GeolayoutCommand GetFirst(GeolayoutCommandTypes cmdType)
+ {
+ foreach (GeolayoutCommand cmd in this)
+ {
+ if (cmd.CommandType == cmdType)
+ {
+ return cmd;
+ }
+ }
+
+ return null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/SM64Lib/Geolayout/TerrainType.cs b/SM64Lib/Geolayout/TerrainType.cs
new file mode 100644
index 0000000..47c57a0
--- /dev/null
+++ b/SM64Lib/Geolayout/TerrainType.cs
@@ -0,0 +1,14 @@
+
+namespace SM64Lib.Geolayout
+{
+ public enum TerrainTypes
+ {
+ NoramlA = 0x0,
+ NoramlB,
+ SnowTerrain,
+ SandTerrain,
+ BigBoosHount,
+ WaterLevels,
+ SlipperySlide
+ }
+}
\ No newline at end of file
diff --git a/SM64Lib/Json/ArrayReferencePreservngConverter.cs b/SM64Lib/Json/ArrayReferencePreservngConverter.cs
new file mode 100644
index 0000000..258f5a9
--- /dev/null
+++ b/SM64Lib/Json/ArrayReferencePreservngConverter.cs
@@ -0,0 +1,87 @@
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.Collections;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Newtonsoft.Json.Linq;
+
+namespace SM64Lib.Json
+{
+ public class ArrayReferencePreservngConverter : JsonConverter
+ {
+ const string refProperty = "$ref";
+ const string idProperty = "$id";
+ const string valuesProperty = "$values";
+
+ public override bool CanConvert(Type objectType)
+ {
+ return objectType.IsArray;
+ }
+
+ public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
+ {
+ if (reader.TokenType == JsonToken.Null)
+ return null;
+ else if (reader.TokenType == JsonToken.StartArray)
+ {
+ // No $ref. Deserialize as a List to avoid infinite recursion and return as an array.
+ var elementType = objectType.GetElementType();
+ var listType = typeof(List<>).MakeGenericType(elementType);
+ var list = (IList)serializer.Deserialize(reader, listType);
+ if (list == null)
+ return null;
+ var array = Array.CreateInstance(elementType, list.Count);
+ list.CopyTo(array, 0);
+ return array;
+ }
+ else
+ {
+ var obj = JObject.Load(reader);
+ var refId = (string)obj[refProperty];
+ if (refId != null)
+ {
+ var reference = serializer.ReferenceResolver.ResolveReference(serializer, refId);
+ if (reference != null)
+ return reference;
+ }
+ var values = obj[valuesProperty];
+ if (values == null || values.Type == JTokenType.Null)
+ return null;
+ if (!(values is JArray))
+ {
+ throw new JsonSerializationException(string.Format("{0} was not an array", values));
+ }
+ var count = ((JArray)values).Count;
+
+ var elementType = objectType.GetElementType();
+ var array = Array.CreateInstance(elementType, count);
+
+ var objId = (string)obj[idProperty];
+ if (objId != null)
+ {
+ // Add the empty array into the reference table BEFORE poppulating it,
+ // to handle recursive references.
+ serializer.ReferenceResolver.AddReference(serializer, objId, array);
+ }
+
+ var listType = typeof(List<>).MakeGenericType(elementType);
+ using (var subReader = values.CreateReader())
+ {
+ var list = (IList)serializer.Deserialize(subReader, listType);
+ list.CopyTo(array, 0);
+ }
+
+ return array;
+ }
+ }
+
+ public override bool CanWrite { get { return false; } }
+
+ public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/SM64Lib/Json/BaseCommandJsonConverter.cs b/SM64Lib/Json/BaseCommandJsonConverter.cs
new file mode 100644
index 0000000..9970213
--- /dev/null
+++ b/SM64Lib/Json/BaseCommandJsonConverter.cs
@@ -0,0 +1,61 @@
+using Newtonsoft.Json;
+using SM64Lib.Behaviors.Script;
+using SM64Lib.Geolayout.Script;
+using SM64Lib.Model.Fast3D;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SM64Lib.Json
+{
+ internal class BaseCommandJsonConverter : JsonConverter
+ {
+ public override bool CanConvert(Type objectType)
+ {
+ return typeof(GeolayoutscriptCommandJsonConverter).IsAssignableFrom(objectType);
+ }
+
+ public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
+ {
+ var export = serializer.Deserialize(reader);
+ GeolayoutCommand c;
+
+ if (existingValue is not null)
+ c = (GeolayoutCommand)existingValue;
+ else
+ c = new GeolayoutCommand();
+
+ if (export?.Buffer is not null)
+ {
+ c.Position = 0;
+ c.Write(export.Buffer, 0, export.Buffer.Length);
+ c.Position = 0;
+ }
+
+ c.BankAddress = export.BankAddress;
+
+ return c;
+ }
+
+ public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
+ {
+ var cmd = (GeolayoutCommand)value;
+
+ var export = new CmdExport
+ {
+ Buffer = cmd.ToArray(),
+ BankAddress = cmd.BankAddress
+ };
+
+ serializer.Serialize(writer, export);
+ }
+
+ private class CmdExport
+ {
+ public byte[] Buffer { get; set; }
+ public int BankAddress { get; set; }
+ }
+ }
+}
diff --git a/SM64Lib/Json/BehaviorscriptCommandJsonConverter.cs b/SM64Lib/Json/BehaviorscriptCommandJsonConverter.cs
new file mode 100644
index 0000000..914a3d1
--- /dev/null
+++ b/SM64Lib/Json/BehaviorscriptCommandJsonConverter.cs
@@ -0,0 +1,60 @@
+using Newtonsoft.Json;
+using SM64Lib.Behaviors.Script;
+using SM64Lib.Model.Fast3D;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SM64Lib.Json
+{
+ internal class BehaviorscriptCommandJsonConverter : JsonConverter
+ {
+ public override bool CanConvert(Type objectType)
+ {
+ return typeof(BehaviorscriptCommand).IsAssignableFrom(objectType);
+ }
+
+ public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
+ {
+ var export = serializer.Deserialize(reader);
+ BehaviorscriptCommand c;
+
+ if (existingValue is not null)
+ c = (BehaviorscriptCommand)existingValue;
+ else
+ c = new BehaviorscriptCommand();
+
+ if (export?.Buffer is not null)
+ {
+ c.Position = 0;
+ c.Write(export.Buffer, 0, export.Buffer.Length);
+ c.Position = 0;
+ }
+
+ c.BankAddress = export.BankAddress;
+
+ return c;
+ }
+
+ public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
+ {
+ var cmd = (BehaviorscriptCommand)value;
+
+ var export = new CmdExport
+ {
+ Buffer = cmd.ToArray(),
+ BankAddress = cmd.BankAddress
+ };
+
+ serializer.Serialize(writer, export);
+ }
+
+ private class CmdExport
+ {
+ public byte[] Buffer { get; set; }
+ public int BankAddress { get; set; }
+ }
+ }
+}
diff --git a/SM64Lib/Json/ComplexDictionarJsonConverter.cs b/SM64Lib/Json/ComplexDictionarJsonConverter.cs
new file mode 100644
index 0000000..f8f4189
--- /dev/null
+++ b/SM64Lib/Json/ComplexDictionarJsonConverter.cs
@@ -0,0 +1,35 @@
+using Newtonsoft.Json;
+using SM64Lib.Model.Fast3D;
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Z.Collections.Extensions;
+
+namespace SM64Lib.Json
+{
+ internal class ComplexDictionarJsonConverter : JsonConverter
+ {
+ public override bool CanConvert(Type objectType)
+ {
+ return typeof(List>).IsAssignableFrom(objectType);
+ }
+
+ public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
+ {
+ var list = serializer.Deserialize>>(reader);
+ var dic = new Dictionary();
+ dic.AddRange(list.ToArray());
+
+ return dic;
+ }
+
+ public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
+ {
+ if (value is Dictionary)
+ serializer.Serialize(writer, ((Dictionary)value).ToArray());
+ }
+ }
+}
diff --git a/SM64Lib/Json/Fast3DBufferJsonConverter.cs b/SM64Lib/Json/Fast3DBufferJsonConverter.cs
new file mode 100644
index 0000000..5fe3117
--- /dev/null
+++ b/SM64Lib/Json/Fast3DBufferJsonConverter.cs
@@ -0,0 +1,71 @@
+using Newtonsoft.Json;
+using SM64Lib.Model.Fast3D;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.IO.Compression;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SM64Lib.Json
+{
+ internal class Fast3DBufferJsonConverter : JsonConverter
+ {
+ public override bool CanConvert(Type objectType)
+ {
+ return typeof(Fast3DBuffer).IsAssignableFrom(objectType);
+ }
+
+ public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
+ {
+ var export = serializer.Deserialize(reader);
+ Fast3DBuffer c;
+
+ if (existingValue is not null)
+ c = (Fast3DBuffer)existingValue;
+ else
+ c = new Fast3DBuffer();
+
+ if (export?.Buffer is not null)
+ {
+ c.Position = 0;
+
+ if (export.IsDeflateStream)
+ General.DecompressData(export.Buffer, c);
+ else
+ c.Write(export.Buffer, 0, export.Buffer.Length);
+
+ c.Position = 0;
+ }
+
+ c.Fast3DBankStart = export.Fast3DBankStart;
+ c.DLPointers = export.DLPointers.ToArray();
+
+ return c;
+ }
+
+ public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
+ {
+ var buffer = (Fast3DBuffer)value;
+ var export = new Fast3DBufferExport
+ {
+ Buffer = General.CompressData(buffer, CompressionLevel.Fastest),
+ Fast3DBankStart = buffer.Fast3DBankStart,
+ DLPointers = buffer.DLPointers.ToList(),
+ IsDeflateStream = true
+ };
+ buffer.Position = 0;
+
+ serializer.Serialize(writer, export);
+ }
+
+ private class Fast3DBufferExport
+ {
+ public byte[] Buffer { get; set; }
+ public int Fast3DBankStart { get; set; }
+ public List DLPointers { get; set; }
+ public bool IsDeflateStream { get; set; } = false;
+ }
+ }
+}
diff --git a/SM64Lib/Json/GeolayoutscriptCommandJsonConverter.cs b/SM64Lib/Json/GeolayoutscriptCommandJsonConverter.cs
new file mode 100644
index 0000000..1349575
--- /dev/null
+++ b/SM64Lib/Json/GeolayoutscriptCommandJsonConverter.cs
@@ -0,0 +1,61 @@
+using Newtonsoft.Json;
+using SM64Lib.Behaviors.Script;
+using SM64Lib.Geolayout.Script;
+using SM64Lib.Model.Fast3D;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SM64Lib.Json
+{
+ internal class GeolayoutscriptCommandJsonConverter : JsonConverter
+ {
+ public override bool CanConvert(Type objectType)
+ {
+ return typeof(GeolayoutCommand).IsAssignableFrom(objectType);
+ }
+
+ public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
+ {
+ var export = serializer.Deserialize(reader);
+ GeolayoutCommand c;
+
+ if (existingValue is not null)
+ c = (GeolayoutCommand)existingValue;
+ else
+ c = new GeolayoutCommand();
+
+ if (export?.Buffer is not null)
+ {
+ c.Position = 0;
+ c.Write(export.Buffer, 0, export.Buffer.Length);
+ c.Position = 0;
+ }
+
+ c.BankAddress = export.BankAddress;
+
+ return c;
+ }
+
+ public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
+ {
+ var cmd = (GeolayoutCommand)value;
+
+ var export = new CmdExport
+ {
+ Buffer = cmd.ToArray(),
+ BankAddress = cmd.BankAddress
+ };
+
+ serializer.Serialize(writer, export);
+ }
+
+ private class CmdExport
+ {
+ public byte[] Buffer { get; set; }
+ public int BankAddress { get; set; }
+ }
+ }
+}
diff --git a/SM64Lib/Json/JsonHelper.cs b/SM64Lib/Json/JsonHelper.cs
new file mode 100644
index 0000000..73439ec
--- /dev/null
+++ b/SM64Lib/Json/JsonHelper.cs
@@ -0,0 +1,36 @@
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SM64Lib.Json
+{
+ internal class JsonHelper
+ {
+ public static JsonSerializer CreateJsonSerializer(
+ bool allowReferences = false,
+ bool rememberTypeNames = false,
+ bool enableIndentedFormatting = true)
+ {
+ var serializer = JsonSerializer.CreateDefault();
+
+ if (enableIndentedFormatting)
+ serializer.Formatting = Formatting.Indented;
+
+ if (rememberTypeNames)
+ serializer.TypeNameHandling = TypeNameHandling.Auto;
+
+ if (allowReferences)
+ {
+ serializer.PreserveReferencesHandling = PreserveReferencesHandling.All;
+ serializer.ReferenceLoopHandling = ReferenceLoopHandling.Serialize;
+ }
+
+ serializer.Converters.Add(new MemoryStreamJsonConverter());
+
+ return serializer;
+ }
+ }
+}
diff --git a/SM64Lib/Json/LevelscriptCommandJsonConverter.cs b/SM64Lib/Json/LevelscriptCommandJsonConverter.cs
new file mode 100644
index 0000000..fcc71cb
--- /dev/null
+++ b/SM64Lib/Json/LevelscriptCommandJsonConverter.cs
@@ -0,0 +1,62 @@
+using Newtonsoft.Json;
+using SM64Lib.Behaviors.Script;
+using SM64Lib.Geolayout.Script;
+using SM64Lib.Levels.Script;
+using SM64Lib.Model.Fast3D;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SM64Lib.Json
+{
+ internal class LevelscriptCommandJsonConverter : JsonConverter
+ {
+ public override bool CanConvert(Type objectType)
+ {
+ return typeof(LevelscriptCommand).IsAssignableFrom(objectType);
+ }
+
+ public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
+ {
+ var export = serializer.Deserialize(reader);
+ LevelscriptCommand c;
+
+ if (existingValue is not null)
+ c = (LevelscriptCommand)existingValue;
+ else
+ c = new LevelscriptCommand();
+
+ if (export?.Buffer is not null)
+ {
+ c.Position = 0;
+ c.Write(export.Buffer, 0, export.Buffer.Length);
+ c.Position = 0;
+ }
+
+ c.BankAddress = export.BankAddress;
+
+ return c;
+ }
+
+ public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
+ {
+ var cmd = (LevelscriptCommand)value;
+
+ var export = new CmdExport
+ {
+ Buffer = cmd.ToArray(),
+ BankAddress = cmd.BankAddress
+ };
+
+ serializer.Serialize(writer, export);
+ }
+
+ private class CmdExport
+ {
+ public byte[] Buffer { get; set; }
+ public int BankAddress { get; set; }
+ }
+ }
+}
diff --git a/SM64Lib/Json/MemoryStreamJsonConverter.cs b/SM64Lib/Json/MemoryStreamJsonConverter.cs
new file mode 100644
index 0000000..d473ae0
--- /dev/null
+++ b/SM64Lib/Json/MemoryStreamJsonConverter.cs
@@ -0,0 +1,37 @@
+using Newtonsoft.Json;
+using SM64Lib.Behaviors.Script;
+using SM64Lib.Geolayout.Script;
+using SM64Lib.Levels.Script;
+using SM64Lib.Model.Fast3D;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SM64Lib.Json
+{
+ internal class MemoryStreamJsonConverter : JsonConverter
+ {
+ public override bool CanConvert(Type objectType)
+ {
+ return typeof(MemoryStream).IsAssignableFrom(objectType);
+ }
+
+ public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
+ {
+ var msData = serializer.Deserialize(reader);
+ var ms = new MemoryStream();
+ ms.Write(msData, 0, msData.Length);
+ ms.Position = 0;
+ return ms;
+ }
+
+ public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
+ {
+ var ms = (MemoryStream)value;
+ serializer.Serialize(writer, ms.ToArray());
+ }
+ }
+}
diff --git a/SM64Lib/Level/Area Table.txt b/SM64Lib/Level/Area Table.txt
new file mode 100644
index 0000000..8c0917a
--- /dev/null
+++ b/SM64Lib/Level/Area Table.txt
@@ -0,0 +1,12 @@
+Located Bank 0x19.
+Start: 0x19005F00
+End: 0x19006000
+
+One line = one Area
+xx xx xx xx yy yy yy yy 00 00 00 00 00 00 00 ff
+xx = Fast3D Start (ROM Pos)
+zz = Fast3D Length (in Bytes)
+ff = 2D-Camera (0xff = on | 0x0 = off)
+
+How to get to the table entry for the current area:
+Table-Start + AreaID * 0x10
\ No newline at end of file
diff --git a/SM64Lib/Level/AreaBG.cs b/SM64Lib/Level/AreaBG.cs
new file mode 100644
index 0000000..548e406
--- /dev/null
+++ b/SM64Lib/Level/AreaBG.cs
@@ -0,0 +1,16 @@
+using global::System.Drawing;
+
+namespace SM64Lib.Levels
+{
+ public class AreaBG
+ {
+ public AreaBGs Type { get; set; } = AreaBGs.Levelbackground;
+ public Color Color { get; set; } = Color.Black;
+ }
+
+ public enum AreaBGs
+ {
+ Levelbackground,
+ Color
+ }
+}
\ No newline at end of file
diff --git a/SM64Lib/Level/AreaReverbLevel.cs b/SM64Lib/Level/AreaReverbLevel.cs
new file mode 100644
index 0000000..3669380
--- /dev/null
+++ b/SM64Lib/Level/AreaReverbLevel.cs
@@ -0,0 +1,23 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SM64Lib.Levels
+{
+ public enum AreaReverbLevel : byte
+ {
+ None,
+ Level01 = 0x8,
+ Level02 = 0xC,
+ Level03 = 0x10,
+ Level04 = 0x18,
+ Level05 = 0x20,
+ Level06 = 0x28,
+ Level07 = 0x30,
+ Level08 = 0x38,
+ Level09 = 0x40,
+ Level10 = 0x70
+ }
+}
diff --git a/SM64Lib/Level/Enums.cs b/SM64Lib/Level/Enums.cs
new file mode 100644
index 0000000..c4a809b
--- /dev/null
+++ b/SM64Lib/Level/Enums.cs
@@ -0,0 +1,123 @@
+
+namespace SM64Lib.Levels
+{
+ public enum ObjectBank0x0C
+ {
+ Disabled,
+ HauntedObjects,
+ SnowObjects,
+ AssortedEnemies1,
+ DesertObjects,
+ BigBobOmbBoss,
+ AssortedEnemies2,
+ WaterObjects,
+ AssortedEnemies3,
+ PeachYoshi,
+ Switches,
+ LavaObjects
+ }
+
+ public enum ObjectBank0x0D
+ {
+ Disabled,
+ AssortedEnemies4,
+ Moneybags,
+ CastleObjects,
+ GroundEnemies,
+ WaterObjects2,
+ Bowser
+ }
+
+ public enum ObjectBank0x0E
+ {
+ Disabled,
+ HaundetHouse,
+ CoolCoolMountain,
+ InsideCastle,
+ HazyMazeCave,
+ ShiftingSandLand,
+ BobOmbBattlefield,
+ SnowandsLand,
+ WetDryWorld,
+ JollyRogerBay,
+ TinyHugeIsland,
+ TickTockClock,
+ RainbowRide,
+ CastleGrounds,
+ Bowser1Course,
+ VanishCap,
+ Bowser2Course,
+ Bowser3Course,
+ LethalLavaLand,
+ DireDireDocks,
+ WhompsFortress,
+ CastleCourtyard,
+ WingCap,
+ Bowser2Battle,
+ Bowser3Battle,
+ TallTallMountain
+ }
+
+ public enum ObjectBanks
+ {
+ Bank0x0C,
+ Bank0x0D,
+ Bank0x0E
+ }
+
+ public enum Levels : byte
+ {
+ HaundetHouse = 0x4,
+ CoolCoolMountain,
+ InsideCastle,
+ HazyMazeCave,
+ ShiftingSandLand,
+ BobOmbsBattlefield,
+ SnowManLand,
+ WetDryWorld,
+ JollyRogerBay,
+ TinyHugeIsland,
+ TickTockClock,
+ RainbowRide,
+ CastleGrounds,
+ Bowser1Course,
+ VanishCap,
+ Bowser2Course,
+ SecretAquarium,
+ Bowser3Course,
+ LethalLavaLand,
+ DireDireDocks,
+ WhompsFortress,
+ EndCakePicture,
+ CastleCourtyard,
+ PeachsSecretSlide,
+ MetalCap,
+ WingCap,
+ Bowser1Battle,
+ RainbowClouds,
+ Bowser2Battle = 0x21,
+ Bowser3Battle,
+ TallTallMountain = 0x24
+ }
+
+ public enum ScrollingTextureAxis
+ {
+ X,
+ Y
+ }
+
+ public enum SpecialBoxType
+ {
+ Water,
+ ToxicHaze,
+ Mist
+ }
+
+ public enum WaterType
+ {
+ Default = 0x0,
+ JRBWater = 0x20000,
+ GreenWater = 0x30000,
+ LavaWater = 0x40000
+ }
+}
\ No newline at end of file
diff --git a/SM64Lib/Level/ILevelManager.cs b/SM64Lib/Level/ILevelManager.cs
new file mode 100644
index 0000000..05421ba
--- /dev/null
+++ b/SM64Lib/Level/ILevelManager.cs
@@ -0,0 +1,10 @@
+using global::SM64Lib.Data;
+
+namespace SM64Lib.Levels
+{
+ public interface ILevelManager
+ {
+ void LoadLevel(Level lvl, RomManager rommgr, ushort LevelID, uint segAddress);
+ LevelSaveResult SaveLevel(Level lvl, RomManager rommgr, BinaryData output, ref uint curOff);
+ }
+}
\ No newline at end of file
diff --git a/SM64Lib/Level/Level.cs b/SM64Lib/Level/Level.cs
new file mode 100644
index 0000000..fa8474b
--- /dev/null
+++ b/SM64Lib/Level/Level.cs
@@ -0,0 +1,329 @@
+using System.Collections.Generic;
+using System.Data;
+using global::System.IO;
+using System.Linq;
+using Microsoft.VisualBasic;
+using Microsoft.VisualBasic.CompilerServices;
+using global::SM64Lib.Levels.Script;
+using global::SM64Lib.Levels.Script.Commands;
+using global::SM64Lib.Objects.ObjectBanks.Data;
+using global::SM64Lib.SegmentedBanking;
+using SM64Lib.Objects.ModelBanks;
+using Newtonsoft.Json;
+using System;
+
+namespace SM64Lib.Levels
+{
+ public abstract class Level
+ {
+
+ // S h a r e d M e m b e r s
+
+ internal static readonly byte[] LevelscriptStart = new byte[] { 0x80, 0x8, 0x0, 0x0, 0x19, 0x0, 0x0, 0x1C, 0x8, 0x0, 0x0, 0xA, 0x0, 0xA0, 0x0, 0x78, 0x0, 0xA0, 0x0, 0x78, 0x4, 0x0, 0x0, 0x0, 0xC, 0x0, 0x0, 0x0 };
+
+ // F i e l d s
+
+ protected SegmentedBank _Bank0x19 = null;
+
+ // A u t o P r o p e r t i e s
+
+ internal LevelscriptCommand LastGobCmdSegLoad { get; set; } = null;
+ internal LevelscriptCommand LastLobCmdSegLoad { get; set; } = null;
+ [JsonIgnore]
+ internal Dictionary MyObjectBanks { get; private set; } = new Dictionary();
+ public Levelscript Levelscript { get; set; } = new Levelscript();
+ public List Areas { get; private set; } = new List();
+ public ushort LevelID { get; set; } = 0;
+ public LevelBG Background { get; private set; } = new LevelBG();
+ public bool ActSelector { get; set; } = false;
+ public bool HardcodedCameraSettings { get; set; } = false;
+ public bool Closed { get; set; } = false;
+ public int LastRomOffset { get; set; } = -1;
+ public bool NeedToSaveLevelscript { get; set; } = false;
+ public bool NeedToSaveBanks0E { get; set; } = false;
+ public bool OneBank0xESystemEnabled { get; set; } = true;
+ public bool EnableGlobalObjectBank { get; set; } = false;
+ public bool EnableLocalObjectBank { get; set; } = false;
+ public CustomModelBank LocalObjectBank { get; private set; } = new CustomModelBank();
+ [JsonIgnore]
+ public RomManager RomManager { get; set; }
+
+ // O t h e r P r o p e r t i e s
+
+ [JsonIgnore]
+ public IReadOnlyDictionary LoadedObjectBanks
+ {
+ get
+ {
+ return MyObjectBanks;
+ }
+ }
+
+ [JsonIgnore]
+ public SegmentedBank Bank0x19
+ {
+ get
+ {
+ return _Bank0x19;
+ }
+
+ internal set
+ {
+ _Bank0x19 = value;
+ }
+ }
+
+ [JsonIgnore]
+ public int ObjectsCount
+ {
+ get
+ {
+ int tcount = 0;
+ foreach (var a in Areas)
+ tcount += a.Objects.Count;
+ return tcount;
+ }
+ }
+
+ [JsonIgnore]
+ public int WarpsCount
+ {
+ get
+ {
+ int tcount = 0;
+ foreach (var a in Areas)
+ tcount += a.Warps.Count;
+ return tcount;
+ }
+ }
+
+ [JsonIgnore]
+ public long Length
+ {
+ get
+ {
+ long length = 0;
+
+ if (Bank0x19 is not null)
+ length += Bank0x19.Length;
+ else
+ length += RomManagerSettings.DefaultLevelscriptSize;
+
+ if (LocalObjectBank is not null)
+ length += LocalObjectBank.Length;
+
+ foreach (var area in Areas)
+ {
+ length += area.AreaModel.Length;
+ if (OneBank0xESystemEnabled)
+ length += area.Levelscript.Length;
+ }
+
+ return length;
+ }
+ }
+
+ // C o n s t r u c t o r s
+
+ [JsonConstructor]
+ protected Level(JsonConstructorAttribute attr)
+ {
+ }
+
+ protected Level(ushort LevelID, int LevelIndex, RomManager rommgr) : this(rommgr)
+ {
+ this.LevelID = LevelID;
+ CreateNewLevelscript();
+ HardcodedCameraSettings = false;
+ ActSelector = General.ActSelectorDefaultValues[LevelIndex];
+ }
+
+ protected Level(RomManager rommgr)
+ {
+ RomManager = rommgr;
+ }
+
+ // M e t h o d s
+
+ public void CreateNewLevelscript()
+ {
+ {
+ var withBlock = Levelscript;
+ withBlock.Close();
+ withBlock.Clear();
+
+ // Start Loading Commands
+ withBlock.Add(new LevelscriptCommand(new byte[] { 0x1B, 0x4, 0x0, 0x0 }));
+
+ // Loading Commands
+ withBlock.Add(new LevelscriptCommand(new byte[] { 0x17, 0xC, 0x1, 0xE, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 }));
+
+ // Start Model Commands
+ withBlock.Add(new LevelscriptCommand(new byte[] { 0x1D, 0x4, 0x0, 0x0 }));
+
+ // Load Marios Model
+ withBlock.Add(new LevelscriptCommand(new byte[] { 0x25, 0xC, 0x0, 0x1, 0x0, 0x0, 0x0, 0x1, 0x13, 0x0, 0x2E, 0xC0 }));
+
+ // Start End-Of-Level Commands
+ withBlock.Add(new LevelscriptCommand(new byte[] { 0x1E, 0x4, 0x0, 0x0 }));
+
+ // End-Of-Level Commands
+ withBlock.Add(new LevelscriptCommand(new byte[] { 0x2B, 0xC, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 }));
+ withBlock.Add(new LevelscriptCommand(new byte[] { 0x11, 0x8, 0x0, 0x0, 0x80, 0x24, 0xBC, 0xD8 }));
+ withBlock.Add(new LevelscriptCommand(new byte[] { 0x12, 0x8, 0x0, 0x1, 0x80, 0x24, 0xBC, 0xD8 }));
+ withBlock.Add(new LevelscriptCommand(new byte[] { 0x1C, 0x4, 0x0, 0x0 }));
+ withBlock.Add(new LevelscriptCommand(new byte[] { 0x4, 0x4, 0x0, 0x1 }));
+ withBlock.Add(new LevelscriptCommand(new byte[] { 0x2, 0x4, 0x0, 0x0 }));
+
+ // Add the general object bank
+ ChangeObjectBank(null, RomManager.RomConfig.ObjectBankInfoData[Convert.ToByte(0xB)]?.FirstOrDefault());
+ }
+
+ foreach (LevelscriptCommand c in Levelscript)
+ {
+ if (c.CommandType != LevelscriptCommandTypes.LoadRomToRam)
+ continue;
+ if (clLoadRomToRam.GetSegmentedID(c) != 0xE)
+ continue;
+ }
+ }
+
+ public void SetSegmentedBanks(RomManager rommgr)
+ {
+ foreach (LevelscriptCommand cmd in Levelscript)
+ {
+ var switchExpr = cmd.CommandType;
+ switch (switchExpr)
+ {
+ case LevelscriptCommandTypes.LoadRomToRam:
+ case LevelscriptCommandTypes.x1A:
+ case LevelscriptCommandTypes.x18:
+ {
+ cmd.Position = 0;
+ byte bankID = clLoadRomToRam.GetSegmentedID(cmd);
+ int startAddr = clLoadRomToRam.GetRomStart(cmd);
+ int endAddr = clLoadRomToRam.GetRomEnd(cmd);
+ var seg = rommgr.SetSegBank(bankID, startAddr, endAddr);
+ if (cmd.CommandType == LevelscriptCommandTypes.x1A)
+ seg.MakeAsMIO0();
+ break;
+ }
+ }
+ }
+
+ if (Bank0x19 is not null)
+ {
+ rommgr.SetSegBank(Bank0x19);
+ }
+ }
+
+ public LevelscriptCommand GetDefaultPositionCmd()
+ {
+ return (LevelscriptCommand)Levelscript.FirstOrDefault(n => n.CommandType == LevelscriptCommandTypes.DefaultPosition);
+ }
+
+ public void ChangeObjectBankData(byte bankID, ObjectBankData newObd)
+ {
+ ChangeObjectBank(GetObjectBankData(bankID), newObd);
+ MyObjectBanks[bankID] = newObd;
+ }
+
+ public ObjectBankData GetObjectBankData(byte bankID)
+ {
+ if (MyObjectBanks.ContainsKey(bankID))
+ {
+ return MyObjectBanks[bankID];
+ }
+ else
+ {
+ var obd = FindObjectBankData(bankID);
+ MyObjectBanks.Add(bankID, obd);
+ return obd;
+ }
+ }
+
+ protected void ChangeObjectBank(ObjectBankData oldObd, ObjectBankData newObd)
+ {
+ // Remove old commands
+ if (oldObd is not null)
+ {
+ foreach (ObjectBankDataCommand obdCmd in oldObd.Commands)
+ {
+ foreach (var cmd in Levelscript.Where(n => General.CompareTwoByteArrays(n.ToArray(), obdCmd.Command)).ToArray())
+ {
+ cmd.Close();
+ Levelscript.Remove(cmd);
+ }
+ }
+ }
+
+ // Add new commands
+ if (newObd is not null)
+ {
+ foreach (ObjectBankDataCommand obdCmd in newObd.Commands)
+ {
+ int startIndex = Levelscript.IndexOfFirst(LevelscriptCommandTypes.x1D);
+ if (!(obdCmd.CommandType == 0x1A || obdCmd.CommandType == 0x17))
+ {
+ startIndex += 1;
+ }
+
+ var cmd = new LevelscriptCommand(obdCmd.Command);
+ Levelscript.Insert(startIndex, cmd);
+ startIndex += 1;
+ }
+ }
+ }
+
+ protected ObjectBankData FindObjectBankData(byte bankID)
+ {
+ var list = RomManager.RomConfig.ObjectBankInfoData[bankID];
+ var Found = new List();
+ foreach (ObjectBankData obd in list)
+ {
+ foreach (ObjectBankDataCommand obdCmd in obd.Commands)
+ Found.Add(Levelscript.Where(n => General.CompareTwoByteArrays(n.ToArray(), obdCmd.Command)).Any());
+ if (!Found.Contains(false))
+ {
+ return obd;
+ }
+
+ Found.Clear();
+ }
+
+ return null;
+ }
+
+ public void Close()
+ {
+ foreach (var c in Levelscript)
+ c.Close();
+ Levelscript.Clear();
+ foreach (var a in Areas)
+ a.Close();
+ Areas.Clear();
+ Closed = true;
+ }
+
+ public override string ToString()
+ {
+ string output = "";
+ foreach (var cmd in Levelscript)
+ {
+ string tbytelist = "";
+ foreach (byte b in cmd.ToArray())
+ {
+ if (tbytelist != string.Empty)
+ tbytelist += " ";
+ tbytelist += Conversion.Hex(b);
+ }
+
+ if (output != string.Empty)
+ output += Environment.NewLine;
+ output += tbytelist;
+ }
+
+ return output;
+ }
+ }
+}
\ No newline at end of file
diff --git a/SM64Lib/Level/LevelArea.cs b/SM64Lib/Level/LevelArea.cs
new file mode 100644
index 0000000..8a29a2b
--- /dev/null
+++ b/SM64Lib/Level/LevelArea.cs
@@ -0,0 +1,178 @@
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.VisualBasic.CompilerServices;
+using global::SM64Lib.Levels.Script;
+using global::SM64Lib.Levels.Script.Commands;
+using global::SM64Lib.Levels.ScrolTex;
+using global::SM64Lib.Model;
+using Newtonsoft.Json;
+using System;
+
+namespace SM64Lib.Levels
+{
+ public abstract class LevelArea
+ {
+
+ // S h a r e d M e m b e r s
+
+ public static readonly byte[] DefaultNormal3DObject = new byte[] { 0x24, 0x18, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x13, 0x0, 0x0, 0x0 };
+
+ // F i e l d s
+
+ protected bool _GettingAreaCollision = false;
+
+ // A u t o P r o p e r t i e s
+
+ public SpecialBoxList SpecialBoxes { get; private set; } = new SpecialBoxList();
+ public List ScrollingTextures { get; private set; } = new List();
+ public List Objects { get; private set; } = new List();
+ public List MacroObjects { get; private set; } = new List();
+ public List Warps { get; private set; } = new List();
+ public List WarpsForGame { get; private set; } = new List();
+ public ShowMessage ShowMessage { get; private set; } = new ShowMessage();
+ public AreaBG Background { get; private set; } = new AreaBG();
+ public LevelscriptCommandCollection Levelscript { get; set; } = new LevelscriptCommandCollection();
+ public Geolayout.Geolayout Geolayout { get; set; } = new Geolayout.Geolayout(SM64Lib.Geolayout.Geolayout.NewScriptCreationMode.None);
+ public Geolayout.TerrainTypes TerrainType { get; set; } = SM64Lib.Geolayout.TerrainTypes.NoramlA;
+ public byte BGMusic { get; set; } = 0;
+ public byte AreaID { get; set; } = 0;
+ public uint GeolayoutOffset { get; set; } = 0;
+ public ObjectModel AreaModel { get; set; } = new ObjectModel();
+ public bool Enable2DCamera { get; set; } = false;
+ public uint Bank0x0EOffset { get; set; } = 0;
+ public int Bank0xELength { get; set; }
+
+ // O t h e r P r o p e r t i e s
+
+ [JsonIgnore]
+ public int Fast3DBankRomStart
+ {
+ get
+ {
+ return Convert.ToInt32(Bank0x0EOffset);
+ }
+ }
+
+ [JsonIgnore]
+ public int Fast3DLength
+ {
+ get
+ {
+ return CollisionPointer - 0xE000000;
+ }
+ }
+
+ [JsonIgnore]
+ public bool IsCamera2D
+ {
+ get
+ {
+ return Enable2DCamera && Geolayout.CameraPreset == SM64Lib.Geolayout.CameraPresets.PlattfromLevels;
+ }
+ }
+
+ [JsonIgnore]
+ public int CollisionPointer
+ {
+ get
+ {
+ int CollisionPointerRet = default;
+ _GettingAreaCollision = true;
+ foreach (var cmd in Levelscript)
+ {
+ if (cmd.CommandType == LevelscriptCommandTypes.AreaCollision)
+ {
+ CollisionPointerRet = Convert.ToInt32(clAreaCollision.GetAreaCollision((LevelscriptCommand)cmd));
+ }
+ }
+
+ _GettingAreaCollision = false;
+ return CollisionPointerRet;
+ }
+
+ set
+ {
+ if (_GettingAreaCollision)
+ return;
+ foreach (var cmd in Levelscript)
+ {
+ if (cmd.CommandType == LevelscriptCommandTypes.AreaCollision)
+ {
+ clAreaCollision.SetAreaCollision((LevelscriptCommand)cmd, Convert.ToUInt32(value));
+ }
+ }
+ }
+ }
+
+ // M e t h o d s
+
+ public void SetSegmentedBanks(RomManager rommgr)
+ {
+ var bank0xE = rommgr.SetSegBank(0xE, Convert.ToInt32(Bank0x0EOffset), (int)(Bank0x0EOffset + Bank0xELength), AreaID);
+ bank0xE.Data = AreaModel.Fast3DBuffer;
+ }
+
+ internal void UpdateScrollingTextureVertexPointer(int offset)
+ {
+ foreach (ManagedScrollingTexture scrollText in ScrollingTextures)
+ scrollText.VertexPointer += offset;
+ }
+
+ public void Close()
+ {
+ Levelscript.Close();
+ Geolayout.Close();
+ }
+
+ // C o n s t r u c t o r s
+
+ protected LevelArea(byte AreaID) : this(AreaID, 9)
+ {
+ }
+
+ protected LevelArea(byte AreaID, byte LevelID) : this(AreaID, LevelID, true, true)
+ {
+ }
+
+ protected LevelArea(byte AreaID, byte LevelID, bool AddWarps, bool AddObjects)
+ {
+ Geolayout = new Geolayout.Geolayout(SM64Lib.Geolayout.Geolayout.NewScriptCreationMode.Level);
+ this.AreaID = AreaID;
+ {
+ var withBlock = Levelscript;
+ withBlock.Add(new LevelscriptCommand(new byte[] { 0x1F, 0x8, AreaID, 0x0, 0x0, 0x0, 0x0, 0x0 }));
+ withBlock.Add(new LevelscriptCommand(new byte[] { 0x2E, 0x8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 }));
+ withBlock.Add(new LevelscriptCommand(new byte[] { 0x36, 0x8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 }));
+ withBlock.Add(new LevelscriptCommand(new byte[] { 0x31, 0x4, 0x0, 0x2 }));
+ withBlock.Add(new LevelscriptCommand(new byte[] { 0x20, 0x4, 0x0, 0x0 }));
+ }
+
+ if (AddWarps)
+ {
+ Objects.Add(new LevelscriptCommand(new byte[] { 0x24, 0x18, 0x1F, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xA, 0x0, 0x0, 0x13, 0x0, 0x2F, 0x74 }));
+ Warps.Add(new LevelscriptCommand(new byte[] { 0x26, 0x8, 0xA, LevelID, AreaID, 0x0, 0x0, 0x0 }));
+ WarpsForGame.Add(new LevelscriptCommand(new byte[] { 0x26, 0x8, 0xF0, 0x6, 0x1, 0x32, 0x0, 0x0 }));
+ WarpsForGame.Add(new LevelscriptCommand(new byte[] { 0x26, 0x8, 0xF1, 0x6, 0x1, 0x64, 0x0, 0x0 }));
+ }
+
+ if (AddObjects)
+ {
+ //for (int i = 1; i <= 15; i++)
+ //{
+ // var newObj = new LevelscriptCommand(DefaultNormal3DObject);
+ // Objects.Add(newObj);
+ //}
+ }
+ }
+
+ protected LevelArea()
+ {
+ Geolayout = new Geolayout.Geolayout(SM64Lib.Geolayout.Geolayout.NewScriptCreationMode.Level);
+ }
+
+ [JsonConstructor]
+ protected LevelArea(JsonConstructorAttribute attr)
+ {
+ }
+ }
+}
\ No newline at end of file
diff --git a/SM64Lib/Level/LevelBG.cs b/SM64Lib/Level/LevelBG.cs
new file mode 100644
index 0000000..80b501f
--- /dev/null
+++ b/SM64Lib/Level/LevelBG.cs
@@ -0,0 +1,236 @@
+using global::System.Drawing;
+using global::System.IO;
+using Microsoft.VisualBasic.CompilerServices;
+using SM64Lib.Data.System;
+using global::SM64Lib.Model.Fast3D;
+using Newtonsoft.Json;
+using System;
+
+namespace SM64Lib.Levels
+{
+ public class LevelBG
+ {
+ private Bitmap _Image = null;
+
+ // A u t o P r o p e r t i e s
+
+ public byte[] ImageData { get; set; } = null;
+ public bool Enabled { get; set; } = true;
+ public bool IsCustom { get; set; } = false;
+ public Geolayout.BackgroundIDs ID { get; set; } = Geolayout.BackgroundIDs.Ocean;
+
+ // A u t o P r o p e r t i e s
+
+ [JsonIgnore]
+ public Bitmap Image
+ {
+ get
+ {
+ if (_Image is not null)
+ {
+ return _Image;
+ }
+ else
+ {
+ var img = GetImage();
+ _Image = (Bitmap)img;
+ return (Bitmap)img;
+ }
+ }
+ }
+
+ [JsonIgnore]
+ public int ImageLength
+ {
+ get
+ {
+ return ImageData?.Length ?? 0;
+ }
+ }
+
+ [JsonIgnore]
+ public bool HasImage
+ {
+ get
+ {
+ return ImageData is object;
+ }
+ }
+
+ // C o n s t r c u t o r s
+
+ public LevelBG()
+ {
+ }
+
+ public LevelBG(Geolayout.BackgroundIDs ID)
+ {
+ this.ID = ID;
+ }
+
+ public LevelBG(Image Image)
+ {
+ ID = Geolayout.BackgroundIDs.Custom;
+ SetImage(Image);
+ }
+
+ // M e t h o d s
+
+ public void WriteImage(Stream s, int offset)
+ {
+ // Write Image Data
+ s.Position = offset;
+ if (ImageData is not null)
+ {
+ s.Write(ImageData, 0, ImageData.Length);
+ }
+ }
+
+ public void ReadImage(Stream s, int offset)
+ {
+ // Read Image Data
+ ImageData = new byte[131072];
+ s.Position = offset;
+ s.Read(ImageData, 0, ImageData.Length);
+ _Image = null;
+ }
+
+ public void SetImage(Image image)
+ {
+ SetImage(new Bitmap(image));
+ }
+
+ public void SetImage(Bitmap bmp)
+ {
+ var s = new Size(248, 248); // ((&H20140 - &H140) / 256 / 2 / 32) * 31)
+ if (bmp.Size != s)
+ {
+ bmp = (Bitmap)TextureManager.ResizeImage(bmp, s, false);
+ }
+
+ ImageData = BackgroundImageConverter.GetBytes(bmp);
+ _Image = null;
+ }
+
+ public Image GetImage()
+ {
+ if (ImageData is not null)
+ {
+ var s = new Size(248, 248); // ((_ImageByts.Length - &H140) / 256 / 2 / 32) * 31)
+ var img = BackgroundImageConverter.GetImage(ImageData, s);
+ _Image = img;
+ return img;
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ public static byte[] GetBackgroundPointerTable()
+ {
+ return new byte[] { 0xA, 0x0, 0x0, 0x0, 0xA, 0x0, 0x8, 0x0, 0xA, 0x0, 0x10, 0x0, 0xA, 0x0, 0x18, 0x0, 0xA, 0x0, 0x20, 0x0, 0xA, 0x0, 0x28, 0x0, 0xA, 0x0, 0x30, 0x0, 0xA, 0x0, 0x38, 0x0, 0xA, 0x0, 0x0, 0x0, 0xA, 0x0, 0x8, 0x0, 0xA, 0x0, 0x40, 0x0, 0xA, 0x0, 0x48, 0x0, 0xA, 0x0, 0x50, 0x0, 0xA, 0x0, 0x58, 0x0, 0xA, 0x0, 0x60, 0x0, 0xA, 0x0, 0x68, 0x0, 0xA, 0x0, 0x70, 0x0, 0xA, 0x0, 0x78, 0x0, 0xA, 0x0, 0x40, 0x0, 0xA, 0x0, 0x48, 0x0, 0xA, 0x0, 0x80, 0x0, 0xA, 0x0, 0x88, 0x0, 0xA, 0x0, 0x90, 0x0, 0xA, 0x0, 0x98, 0x0, 0xA, 0x0, 0xA0, 0x0, 0xA, 0x0, 0xA8, 0x0, 0xA, 0x0, 0xB0, 0x0, 0xA, 0x0, 0xB8, 0x0, 0xA, 0x0, 0x80, 0x0, 0xA, 0x0, 0x88, 0x0, 0xA, 0x0, 0xC0, 0x0, 0xA, 0x0, 0xC8, 0x0, 0xA, 0x0, 0xD0, 0x0, 0xA, 0x0, 0xD8, 0x0, 0xA, 0x0, 0xE0, 0x0, 0xA, 0x0, 0xE8, 0x0, 0xA, 0x0, 0xF0, 0x0, 0xA, 0x0, 0xF8, 0x0, 0xA, 0x0, 0xC0, 0x0, 0xA, 0x0, 0xC8, 0x0, 0xA, 0x1, 0x0, 0x0, 0xA, 0x1, 0x8, 0x0, 0xA, 0x1, 0x10, 0x0, 0xA, 0x1, 0x18, 0x0, 0xA, 0x1, 0x20, 0x0, 0xA, 0x1, 0x28, 0x0, 0xA, 0x1, 0x30, 0x0, 0xA, 0x1, 0x38, 0x0, 0xA, 0x1, 0x0, 0x0, 0xA, 0x1, 0x8, 0x0, 0xA, 0x1, 0x40, 0x0, 0xA, 0x1, 0x48, 0x0, 0xA, 0x1, 0x50, 0x0, 0xA, 0x1, 0x58, 0x0, 0xA, 0x1, 0x60, 0x0, 0xA, 0x1, 0x68, 0x0, 0xA, 0x1, 0x70, 0x0, 0xA, 0x1, 0x78, 0x0, 0xA, 0x1, 0x40, 0x0, 0xA, 0x1, 0x48, 0x0, 0xA, 0x1, 0x80, 0x0, 0xA, 0x1, 0x88, 0x0, 0xA, 0x1, 0x90, 0x0, 0xA, 0x1, 0x98, 0x0, 0xA, 0x1, 0xA0, 0x0, 0xA, 0x1, 0xA8, 0x0, 0xA, 0x1, 0xB0, 0x0, 0xA, 0x1, 0xB8, 0x0, 0xA, 0x1, 0x80, 0x0, 0xA, 0x1, 0x88, 0x0, 0xA, 0x1, 0xC0, 0x0, 0xA, 0x1, 0xC8, 0x0, 0xA, 0x1, 0xD0, 0x0, 0xA, 0x1, 0xD8, 0x0, 0xA, 0x1, 0xE0, 0x0, 0xA, 0x1, 0xE8, 0x0, 0xA, 0x1, 0xF0, 0x0, 0xA, 0x1, 0xF8, 0x0, 0xA, 0x1, 0xC0, 0x0, 0xA, 0x1, 0xC8, 0x0 };
+ }
+
+ // C l a s s e s
+
+ private class BackgroundImageConverter
+ {
+ public static Bitmap GetImage(byte[] data, Size size)
+ {
+ var ms = new MemoryStream(data);
+ var br = new BinaryReader(ms);
+ var img = new Bitmap(size.Width, size.Height);
+ uint current_address = 0;
+ for (int y = 0, loopTo = (int)(size.Height / (double)31 - 1); y <= loopTo; y++)
+ {
+ for (int x = 0, loopTo1 = (int)(size.Width / (double)31 - 1); x <= loopTo1; x++)
+ {
+ ParseBlock(br, img, current_address, new Rectangle(x * 31, y * 31, 31, 31));
+ current_address += 2048;
+ }
+ }
+
+ ms.Close();
+ return img;
+ }
+
+ private static void ParseBlock(BinaryReader br, Bitmap map, uint address, Rectangle rect)
+ {
+ for (int yy = 0, loopTo = rect.Height - 1; yy <= loopTo; yy++)
+ {
+ for (int xx = 0, loopTo1 = rect.Width - 1; xx <= loopTo1; xx++)
+ {
+ int offset = (int)(address + (yy * (rect.Width + 1) + xx) * 2);
+ br.BaseStream.Position = offset;
+ ushort pixel = SwapInts.SwapUInt16(br.ReadUInt16());
+ byte red = Convert.ToByte((pixel >> 11 & 0x1F) * 8);
+ byte green = Convert.ToByte((pixel >> 6 & 0x1F) * 8);
+ byte blue = Convert.ToByte((pixel >> 1 & 0x1F) * 8);
+ byte alpha = Convert.ToByte((pixel & 1) > 0 ? 0xFF : 0);
+ var pixcol = Color.FromArgb(alpha, red, green, blue);
+ map.SetPixel(rect.X + xx, rect.Y + yy, pixcol);
+ }
+ }
+ }
+
+ public static byte[] GetBytes(Bitmap img)
+ {
+ var ms = new MemoryStream();
+ var bw = new BinaryWriter(ms);
+ int wTiles = (int)(img.Width / (double)31);
+ int hTiles = (int)(img.Height / (double)31);
+ int current_address = 0;
+ for (int y = 0, loopTo = hTiles - 1; y <= loopTo; y++)
+ {
+ for (int x = 0, loopTo1 = wTiles - 1; x <= loopTo1; x++)
+ {
+ ConvertBlock(bw, img, Convert.ToUInt32(current_address), x * 31, y * 31, 0, 0, 0, 0, 31, 31, 32);
+ if (x < wTiles - 1)
+ {
+ ConvertBlock(bw, img, Convert.ToUInt32(current_address), (x + 1) * 31 - 30, y * 31, 30, 0, 1, 0, 31, 31, 32);
+ ConvertBlock(bw, img, Convert.ToUInt32(current_address), (x + 1) * 31 - 30, y * 31, 30, 30, 1, 1, 31, 31, 32);
+ }
+ else
+ {
+ ConvertBlock(bw, img, Convert.ToUInt32(current_address), -30, y * 31, 30, 0, 1, 0, 31, 31, 32);
+ ConvertBlock(bw, img, Convert.ToUInt32(current_address), -30, y * 31, 30, 30, 1, 1, 31, 31, 32);
+ }
+
+ if (y < hTiles - 1)
+ {
+ ConvertBlock(bw, img, Convert.ToUInt32(current_address), x * 31, (y + 1) * 31 - 30, 0, 30, 0, 1, 31, 31, 32);
+ }
+ else
+ {
+ ConvertBlock(bw, img, Convert.ToUInt32(current_address), x * 31, -30, 0, 30, 0, 1, 31, 31, 32);
+ }
+
+ current_address += 2048;
+ }
+ }
+
+ var temp = ms.ToArray();
+ ms.Close();
+ return temp;
+ }
+
+ private static void ConvertBlock(BinaryWriter bw, Bitmap map, uint address, int src_x, int src_y, int start_x, int start_y, int offset_x, int offset_y, int w, int h, int lineWidth)
+ {
+ for (int yy = start_y, loopTo = h - 1; yy <= loopTo; yy++)
+ {
+ for (int xx = start_x, loopTo1 = w - 1; xx <= loopTo1; xx++)
+ {
+ var pixel = map.GetPixel(src_x + xx, src_y + yy);
+ int r = pixel.R / 8;
+ int g = pixel.G / 8;
+ int b = pixel.B / 8;
+ int a = pixel.A == 0xFF ? 1 : 0;
+ bw.BaseStream.Position = address + ((yy + offset_y) * lineWidth + xx + offset_x) * 2;
+ bw.Write(SwapInts.SwapUInt16(Convert.ToUInt16(r << 11 | g << 6 | b << 1 | a)));
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/SM64Lib/Level/LevelExport.cs b/SM64Lib/Level/LevelExport.cs
new file mode 100644
index 0000000..0497a39
--- /dev/null
+++ b/SM64Lib/Level/LevelExport.cs
@@ -0,0 +1,79 @@
+using Newtonsoft.Json;
+using SM64Lib.Levels.Script;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.IO.Compression;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SM64Lib.Levels
+{
+ public class LevelExport
+ {
+ // P r o p e r t i e s
+
+ [JsonProperty]
+ public object Content { get; protected set; }
+ [JsonProperty]
+ public LevelExportContentType ContentType { get; set; }
+
+ // C o n s t r u c t o r s
+
+ private LevelExport(object content, LevelExportContentType type) : this()
+ {
+ Content = content;
+ ContentType = type;
+ }
+
+ public LevelExport(Level level) : this(new List { level }) { }
+ public LevelExport(List levels) : this(levels, LevelExportContentType.Level) { }
+ public LevelExport(LevelArea area) : this(new List { area }) { }
+ public LevelExport(List areas) : this(areas, LevelExportContentType.Area) { }
+ public LevelExport(List cmds, LevelExportContentType contentType) : this((object)cmds, contentType) { }
+
+ [JsonConstructor]
+ private LevelExport() { }
+
+ // F e a t u r e s
+
+ public void WriteToFile(string filePath, CompressionLevel compressionLevel)
+ {
+ var plainText = compressionLevel == CompressionLevel.NoCompression;
+
+ // Open streams
+ var fs = new FileStream(filePath, FileMode.Create, FileAccess.ReadWrite);
+ var output = new DeflateStream(fs, compressionLevel, true);
+ var sw = new StreamWriter(output);
+
+ // Create serializer
+ var serializer = Json.JsonHelper.CreateJsonSerializer(true, true, plainText);
+
+ // Serialize
+ serializer.Serialize(sw, this);
+
+ sw.Flush();
+ output.Close();
+ fs.Close();
+ }
+
+ public static LevelExport ReadFromFile(string filePath)
+ {
+ // Open streams
+ var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read);
+ var input = new DeflateStream(fs, CompressionMode.Decompress);
+ var sr = new StreamReader(input);
+
+ // Create serializer
+ var serializer = Json.JsonHelper.CreateJsonSerializer(true, true);
+
+ // Deserialize
+ var export = (LevelExport)serializer.Deserialize(sr, typeof(LevelExport));
+
+ fs.Close();
+
+ return export;
+ }
+ }
+}
diff --git a/SM64Lib/Level/LevelExportContentType.cs b/SM64Lib/Level/LevelExportContentType.cs
new file mode 100644
index 0000000..95c2c17
--- /dev/null
+++ b/SM64Lib/Level/LevelExportContentType.cs
@@ -0,0 +1,17 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SM64Lib.Levels
+{
+ public enum LevelExportContentType
+ {
+ Unknown,
+ Level,
+ Area,
+ Objects,
+ Warps
+ }
+}
diff --git a/SM64Lib/Level/LevelExportFileFormat.cs b/SM64Lib/Level/LevelExportFileFormat.cs
new file mode 100644
index 0000000..90452d0
--- /dev/null
+++ b/SM64Lib/Level/LevelExportFileFormat.cs
@@ -0,0 +1,14 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SM64Lib.Levels
+{
+ public enum LevelExportFileFormat
+ {
+ JSON,
+ PlainText
+ }
+}
diff --git a/SM64Lib/Level/LevelExportImporter.cs b/SM64Lib/Level/LevelExportImporter.cs
new file mode 100644
index 0000000..a269533
--- /dev/null
+++ b/SM64Lib/Level/LevelExportImporter.cs
@@ -0,0 +1,75 @@
+using SM64Lib.Levels.Script;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SM64Lib.Levels
+{
+ public static class LevelExportImporter
+ {
+ public static void ImportLevel(LevelExport export, RomManager destRomManager, KeyValuePair[] levelIDs)
+ {
+ var levels = export.Content as Level[];
+ if (levels != null)
+ {
+ foreach (var level in levels)
+ {
+ var id = (ushort?)levelIDs.FirstOrDefault(n => n.Key == level).Value;
+ if (id is not null)
+ {
+ level.LevelID = (ushort)id;
+ destRomManager.Levels.Add(level);
+ }
+ }
+ }
+ else
+ throw new NotSupportedException();
+ }
+
+ public static void ImportArea(LevelExport export, Level destLevel, KeyValuePair[] areaIDs)
+ {
+ var areas = export.Content as LevelArea[];
+ if (areas != null)
+ {
+ foreach (var area in areas)
+ {
+ var id = (byte?)areaIDs.FirstOrDefault(n => n.Key == area).Value;
+ if (id is not null)
+ {
+ area.AreaID = (byte)id;
+ destLevel.Areas.Add(area);
+ }
+ }
+ }
+ else
+ throw new NotSupportedException();
+ }
+
+ public static void ImportScript(LevelExport export, LevelArea destArea, List allowedCommands)
+ {
+ var cmds = export.Content as List;
+ if (cmds is not null)
+ {
+ foreach (var cmd in cmds)
+ {
+ if (allowedCommands.Contains(cmd))
+ {
+ switch (export.ContentType)
+ {
+ case LevelExportContentType.Objects:
+ destArea.Objects.Add(cmd);
+ break;
+ case LevelExportContentType.Warps:
+ destArea.Warps.Add(cmd);
+ break;
+ }
+ }
+ }
+ }
+ else
+ throw new NotSupportedException();
+ }
+ }
+}
diff --git a/SM64Lib/Level/LevelInfoDataTabel.cs b/SM64Lib/Level/LevelInfoDataTabel.cs
new file mode 100644
index 0000000..074f749
--- /dev/null
+++ b/SM64Lib/Level/LevelInfoDataTabel.cs
@@ -0,0 +1,143 @@
+using System.Collections.Generic;
+using global::System.IO;
+using System.Linq;
+using global::Newtonsoft.Json.Linq;
+using static SM64Lib.TextValueConverter.TextValueConverter;
+
+namespace SM64Lib.Levels
+{
+ public class LevelInfoDataTabelList : List
+ {
+ public void ReadFromFile(string FileName)
+ {
+ var jobj = JObject.Parse(File.ReadAllText(FileName));
+ if (jobj["Levels"] is not null)
+ {
+ foreach (JObject entry in (JArray)jobj["Levels"])
+ {
+ Add(new Level
+ {
+ Name = entry?["Name"].ToString(),
+ Number = entry?["Number"].ToString(),
+ TypeString = entry?["Type"].ToString(),
+ ID = (ushort)ValueFromText(entry?["ID"].ToString()),
+ Pointer = (uint)ValueFromText(entry?["Pointer"].ToString()),
+ Index = entry["Index"].Value()
+ });
+ }
+ }
+ }
+
+ public Level GetByLevelID(ushort ID)
+ {
+ return this.FirstOrDefault(n => n.ID == ID);
+ }
+
+ public class Level
+ {
+ public ushort ID { get; set; } = 0;
+ public uint Pointer { get; set; } = 0;
+ public string Name { get; set; } = "";
+ public string Number { get; set; } = "";
+ public LevelTypes Type { get; set; } = LevelTypes.Level;
+ public int Index { get; set; } = -1;
+
+ public string TypeString
+ {
+ get
+ {
+ var switchExpr = Type;
+ switch (switchExpr)
+ {
+ case LevelTypes.Level:
+ {
+ return "Level";
+ }
+
+ case LevelTypes.Sidelevel:
+ {
+ return "Side";
+ }
+
+ case LevelTypes.Overworld:
+ {
+ return "OW";
+ }
+
+ case LevelTypes.Bowsercourse:
+ {
+ return "BC";
+ }
+
+ case LevelTypes.Bowserbattle:
+ {
+ return "BB";
+ }
+
+ case LevelTypes.Switchpalace:
+ {
+ return "Switch";
+ }
+
+ default:
+ {
+ return "";
+ }
+ }
+ }
+
+ set
+ {
+ switch (value)
+ {
+ case "Level":
+ {
+ Type = LevelTypes.Level;
+ break;
+ }
+
+ case "Side":
+ {
+ Type = LevelTypes.Sidelevel;
+ break;
+ }
+
+ case "OW":
+ {
+ Type = LevelTypes.Overworld;
+ break;
+ }
+
+ case "BC":
+ {
+ Type = LevelTypes.Bowsercourse;
+ break;
+ }
+
+ case "BB":
+ {
+ Type = LevelTypes.Bowserbattle;
+ break;
+ }
+
+ case "Switch":
+ {
+ Type = LevelTypes.Switchpalace;
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ public enum LevelTypes
+ {
+ Level,
+ Sidelevel,
+ Overworld,
+ Switchpalace,
+ Bowsercourse,
+ Bowserbattle
+ }
+ }
+}
\ No newline at end of file
diff --git a/SM64Lib/Level/LevelList.cs b/SM64Lib/Level/LevelList.cs
new file mode 100644
index 0000000..778b681
--- /dev/null
+++ b/SM64Lib/Level/LevelList.cs
@@ -0,0 +1,22 @@
+using System.Collections.Generic;
+using System.Data;
+using System.Linq;
+
+namespace SM64Lib.Levels
+{
+ public class LevelList : List
+ {
+ public bool NeedToSave
+ {
+ get
+ {
+ return this.Where(n => n.NeedToSaveLevelscript || n.NeedToSaveBanks0E).Count() > 0;
+ }
+ }
+
+ public long Length
+ {
+ get => this.Sum(n => n.Length);
+ }
+ }
+}
\ No newline at end of file
diff --git a/SM64Lib/Level/LevelManager.cs b/SM64Lib/Level/LevelManager.cs
new file mode 100644
index 0000000..a51b088
--- /dev/null
+++ b/SM64Lib/Level/LevelManager.cs
@@ -0,0 +1,894 @@
+using System;
+using System.Collections.Generic;
+using System.Data;
+using global::System.IO;
+using System.Linq;
+using Microsoft.VisualBasic.CompilerServices;
+using global::SM64Lib.Data;
+using global::SM64Lib.Data.System;
+using global::SM64Lib.Geolayout.Script;
+using global::SM64Lib.Geolayout.Script.Commands;
+using global::SM64Lib.Levels.Script;
+using global::SM64Lib.Levels.Script.Commands;
+using global::SM64Lib.Levels.ScrolTex;
+using global::SM64Lib.Model;
+using global::SM64Lib.SegmentedBanking;
+
+namespace SM64Lib.Levels
+{
+ public class LevelManager : ILevelManager
+ {
+ public bool EnableLoadingAreaReverb { get; set; } = true;
+ public bool SortSpecialBoxesByHeight { get; set; } = true;
+
+ ///
+ /// Loads a ROM Manager Level from ROM.
+ ///
+ ///
+ ///
+ ///
+ ///
+ public virtual void LoadLevel(Level lvl, RomManager rommgr, ushort LevelID, uint segAddress)
+ {
+ int customBGStart = 0;
+ int customBGEnd = 0;
+ lvl.LevelID = LevelID;
+
+ // Load Bank 0x19
+ lvl.Bank0x19 = rommgr.GetSegBank(0x19);
+ lvl.Bank0x19.ReadDataIfNull(rommgr.RomFile);
+
+ // Close if not closed & re-open
+ if (!lvl.Closed) lvl.Close();
+ lvl.Closed = false;
+
+ // Lade Levelscript
+ lvl.Levelscript = new Levelscript();
+ lvl.Levelscript.Read(rommgr, Convert.ToInt32(segAddress));
+
+ // Erstelle Areas / Lade Einstellungen
+ bool AreaOnFly = false;
+ LevelArea tArea = null;
+ var CurrentLevelScriptCommands = lvl.Levelscript.ToArray();
+ var cmdsToRemove = new List();
+ foreach (LevelscriptCommand c in CurrentLevelScriptCommands)
+ {
+ var switchExpr = c.CommandType;
+ switch (switchExpr)
+ {
+ case LevelscriptCommandTypes.StartArea:
+ AreaOnFly = true;
+ tArea = new RMLevelArea();
+ tArea.AreaID = clStartArea.GetAreaID(c);
+ tArea.GeolayoutOffset = clStartArea.GetSegGeolayoutAddr(c); // - bank0x19.BankAddress + bank0x19.RomStart
+ tArea.Geolayout.Read(rommgr, Convert.ToInt32(tArea.GeolayoutOffset));
+ break;
+ case LevelscriptCommandTypes.EndOfArea:
+ tArea.Levelscript.Add(c);
+ lvl.Levelscript.Remove(c);
+ lvl.Areas.Add(tArea);
+ AreaOnFly = false;
+ break;
+ case LevelscriptCommandTypes.AreaMusic:
+ tArea.BGMusic = clAreaMusic.GetMusicID(c);
+ break;
+ case LevelscriptCommandTypes.AreaMusicSimple:
+ tArea.BGMusic = clAreaMusicSimple.GetMusicID(c);
+ break;
+ case LevelscriptCommandTypes.Tarrain:
+ tArea.TerrainType = (Geolayout.TerrainTypes)clTerrian.GetTerrainType(c);
+ break;
+ case LevelscriptCommandTypes.Normal3DObject:
+ var scrollTexAddrs = new List(new[] { 0x400000, 0x401700 });
+ if (rommgr.RomConfig.ScrollTexConfig.UseCustomBehavior)
+ scrollTexAddrs.Add(rommgr.RomConfig.ScrollTexConfig.CustomBehaviorAddress);
+ if (scrollTexAddrs.Contains((int)clNormal3DObject.GetSegBehaviorAddr(c)))
+ tArea.ScrollingTextures.Add(new ManagedScrollingTexture(c));
+ else
+ tArea.Objects.Add(c);
+ break;
+ case LevelscriptCommandTypes.ConnectedWarp:
+ if ((new[] { 0xF0, 0xF1 }).Contains(clWarp.GetWarpID(c)))
+ tArea.WarpsForGame.Add(c);
+ else
+ tArea.Warps.Add(c);
+ break;
+ case LevelscriptCommandTypes.PaintingWarp:
+ case LevelscriptCommandTypes.InstantWarp:
+ tArea.Warps.Add(c);
+ break;
+ case LevelscriptCommandTypes.LoadRomToRam:
+ byte bankID = clLoadRomToRam.GetSegmentedID(c);
+ int startAddr = clLoadRomToRam.GetRomStart(c);
+ int endAddr = clLoadRomToRam.GetRomEnd(c);
+ switch (bankID)
+ {
+ case 0xA: // Background-Image
+ customBGStart = startAddr;
+ customBGEnd = endAddr - 0x140;
+ break;
+ case 0x7: // Global Object Bank
+ if (rommgr.GlobalModelBank?.CurSeg is not null && startAddr == rommgr.GlobalModelBank.CurSeg.RomStart && endAddr == rommgr.GlobalModelBank.CurSeg.RomEnd)
+ {
+ lvl.EnableGlobalObjectBank = true;
+ lvl.LastGobCmdSegLoad = c;
+ }
+ break;
+ case 0x9:
+ if (((RMLevel)lvl).Config.EnableLocalObjectBank)
+ {
+ lvl.EnableLocalObjectBank = true;
+ }
+ lvl.LastLobCmdSegLoad = c;
+ break;
+ }
+ break;
+ case LevelscriptCommandTypes.ShowDialog:
+ if (AreaOnFly)
+ {
+ tArea.ShowMessage.Enabled = true;
+ tArea.ShowMessage.DialogID = clShowDialog.GetDialogID(c);
+ }
+ break;
+ case LevelscriptCommandTypes.JumpBack:
+ case LevelscriptCommandTypes.JumpToSegAddr:
+ if (tArea is not null)
+ cmdsToRemove.Add(c);
+ break;
+ }
+
+ if (AreaOnFly && !cmdsToRemove.Contains(c))
+ {
+ lvl.Levelscript.Remove(c);
+ tArea.Levelscript.Add(c);
+ }
+ }
+
+ // Lösche alle Jump-Commands aus dem Levelscript
+ foreach (LevelscriptCommand cmd in cmdsToRemove)
+ {
+ lvl.Levelscript.Remove(cmd);
+ cmd.Close();
+ }
+
+ // Lösche alle Objekte und Warps aus dem Levelscript
+ var lvlscrptidstoremove = new[] { LevelscriptCommandTypes.Normal3DObject, LevelscriptCommandTypes.ConnectedWarp, LevelscriptCommandTypes.PaintingWarp, LevelscriptCommandTypes.InstantWarp };
+ foreach (var a in lvl.Areas)
+ {
+ foreach (var c in a.Levelscript.Where(n => lvlscrptidstoremove.Contains(n.CommandType)).ToArray())
+ a.Levelscript.Remove(c);
+ }
+
+ // Load Local Object Bank
+ if (lvl.LastLobCmdSegLoad is not null)
+ {
+ var seg = new SegmentedBank()
+ {
+ BankID = clLoadRomToRam.GetSegmentedID(lvl.LastLobCmdSegLoad),
+ RomStart = clLoadRomToRam.GetRomStart(lvl.LastLobCmdSegLoad),
+ RomEnd = clLoadRomToRam.GetRomEnd(lvl.LastLobCmdSegLoad)
+ };
+ lvl.LocalObjectBank.ReadFromSeg(rommgr, seg, ((RMLevel)lvl).Config.LocalObjectBank);
+ }
+
+ // Lese Custom Background Image
+ var fs = new FileStream(rommgr.RomFile, FileMode.Open, FileAccess.Read);
+ var br2 = new BinaryReader(fs);
+ lvl.Background.Enabled = false;
+ foreach (LevelArea a in lvl.Areas)
+ {
+ var bgglcmd = a.Geolayout.Geolayoutscript.GetFirst(GeolayoutCommandTypes.Background);
+ if (cgBackground.GetBackgroundPointer(bgglcmd) == 0)
+ {
+ a.Background.Type = AreaBGs.Color;
+ a.Background.Color = cgBackground.GetRrgbaColor(bgglcmd);
+ }
+ else
+ {
+ a.Background.Type = AreaBGs.Levelbackground;
+ lvl.Background.ID = (Geolayout.BackgroundIDs)cgBackground.GetBackgroundID(bgglcmd);
+ lvl.Background.Enabled = true;
+ }
+ }
+
+ if (customBGStart != 0)
+ {
+ lvl.Background.IsCustom = true;
+ }
+
+ foreach (int val in Enum.GetValues(typeof(Geolayout.BackgroundPointers)))
+ {
+ if (val != 0 && customBGStart == val)
+ {
+ lvl.Background.IsCustom = false;
+ }
+ }
+
+ if (lvl.Background.Enabled && lvl.Background.IsCustom) // .ID = Geolayout.BackgroundIDs.Custom Then
+ {
+ fs.Position = customBGStart;
+ lvl.Background.ReadImage(fs, customBGStart);
+ }
+
+ int bank0x19RomStart;
+ int bank0x19RomEnd;
+ BinaryReader brToUse;
+ bank0x19RomStart = 0;
+ bank0x19RomEnd = lvl.Bank0x19.Length;
+ brToUse = new BinaryReader(lvl.Bank0x19.Data);
+
+ // Lese Area-Table
+ foreach (LevelArea a in lvl.Areas)
+ {
+ // Fast3D-Daten
+ brToUse.BaseStream.Position = bank0x19RomStart + 0x5F00 + a.AreaID * 0x10;
+ a.Bank0x0EOffset = Convert.ToUInt32(SwapInts.SwapInt32(brToUse.ReadInt32()));
+ int romEnd0xE = SwapInts.SwapInt32(brToUse.ReadInt32());
+ rommgr.SetSegBank(0xE, Convert.ToInt32(a.Bank0x0EOffset), romEnd0xE, a.AreaID);
+
+ // 2D-Kamera
+ brToUse.BaseStream.Position = bank0x19RomStart + 0x5F0F + a.AreaID * 0x10;
+ a.Enable2DCamera = Bits.GetBoolOfByte(brToUse.ReadByte(), 7);
+ }
+
+ // Lese Area-Modelle
+ foreach (LevelArea a in lvl.Areas)
+ {
+ a.AreaModel.FromStream(fs, Convert.ToInt32(a.Bank0x0EOffset), 0xE000000, a.Fast3DBankRomStart, a.Fast3DLength, a.Geolayout.Geopointers.ToArray(), a.CollisionPointer, rommgr.RomConfig.CollisionBaseConfig);
+ }
+
+ // Lese alle Box-Daten
+ int CurrentBoxOffset = bank0x19RomStart + 0x6A00;
+ foreach (LevelArea a in lvl.Areas.Where(n => n.AreaID < 8))
+ {
+ // Clear special boxes
+ a.SpecialBoxes.Clear();
+
+ // Load special boxes
+ a.SpecialBoxes.AddRange(SpecialBoxList.ReadTable(brToUse.BaseStream, SpecialBoxType.Water, bank0x19RomStart, bank0x19RomStart + 0x6000 + 0x50 * a.AreaID));
+ a.SpecialBoxes.AddRange(SpecialBoxList.ReadTable(brToUse.BaseStream, SpecialBoxType.ToxicHaze, bank0x19RomStart, bank0x19RomStart + 0x6280 + 0x50 * a.AreaID));
+ a.SpecialBoxes.AddRange(SpecialBoxList.ReadTable(brToUse.BaseStream, SpecialBoxType.Mist, bank0x19RomStart, bank0x19RomStart + 0x6500 + 0x50 * a.AreaID));
+
+ for (int i = 0; i < a.SpecialBoxes.Count; i++)
+ {
+ var boxdata = a.AreaModel.Collision.SpecialBoxes.ElementAtOrDefault(i);
+ if (boxdata is not null)
+ {
+ a.SpecialBoxes[i].Y = boxdata.Y;
+ }
+ }
+ }
+
+ // One-Bank-0xE-System
+ lvl.OneBank0xESystemEnabled = true;
+
+ // Act Selector
+ General.PatchClass.Open(fs);
+ lvl.ActSelector = General.PatchClass.get_ActSelector_Enabled(LevelID);
+
+ // Hardcoded Camera
+ lvl.HardcodedCameraSettings = General.PatchClass.get_HardcodedCamera_Enabled(LevelID);
+
+ // Area Reverb
+ if (EnableLoadingAreaReverb)
+ {
+ foreach (var area in lvl.Areas)
+ {
+ if (area is RMLevelArea && area.AreaID >= 1 && area.AreaID <= 3)
+ {
+ fs.Position = 0xEE0C0 + lvl.LevelID * 3 + area.AreaID - 1;
+ ((RMLevelArea)area).ReverbLevel = (AreaReverbLevel)fs.ReadByte();
+ }
+ }
+ }
+
+ fs.Close();
+ }
+
+ ///
+ /// Saves a ROM Manager Level to ROM.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public virtual LevelSaveResult SaveLevel(Level lvl, RomManager rommgr, BinaryData output, ref uint curOff)
+ {
+ var saveres = new LevelSaveResult();
+ BinaryData data0x19;
+ var lid = rommgr.LevelInfoData.GetByLevelID(lvl.LevelID);
+
+ // Write Area Model & Update Scrolling Texture Vertex Pointers & Write Custom Object Bank
+ foreach (LevelArea a in lvl.Areas)
+ {
+ var CollisionBoxTableIndex = new[] { 0, 0x32, 0x33 };
+ a.Bank0x0EOffset = curOff;
+
+ if (SortSpecialBoxesByHeight)
+ a.SpecialBoxes.SortByHeight();
+
+ int oldModelStart = a.AreaModel.Fast3DBuffer.Fast3DBankStart;
+ int newModelStart;
+ int modelOffset;
+
+ // Add the new water boxes
+ a.AreaModel.Collision.SpecialBoxes.Clear();
+ foreach (SpecialBox sp in a.SpecialBoxes)
+ {
+ var boxdata = new Model.Collision.BoxData
+ {
+ X1 = sp.X1,
+ X2 = sp.X2,
+ Z1 = sp.Z1,
+ Z2 = sp.Z2,
+ Y = sp.Y,
+ Index = (short)CollisionBoxTableIndex[(int)sp.Type]
+ };
+
+ switch (sp.Type)
+ {
+ case SpecialBoxType.Water:
+ boxdata.Type = Model.Collision.BoxDataType.Water;
+ CollisionBoxTableIndex[(int)sp.Type] += 1;
+ break;
+ case SpecialBoxType.Mist:
+ boxdata.Type = Model.Collision.BoxDataType.Mist;
+ CollisionBoxTableIndex[(int)sp.Type] += 1;
+ break;
+ case SpecialBoxType.ToxicHaze:
+ boxdata.Type = Model.Collision.BoxDataType.ToxicHaze;
+ CollisionBoxTableIndex[(int)sp.Type] += 10;
+ break;
+ }
+
+ a.AreaModel.Collision.SpecialBoxes.Add(boxdata);
+ }
+
+ // Write Area Model
+ ObjectModel.SaveResult res;
+ res = a.AreaModel.ToBinaryData(output, (int)curOff, (int)curOff, 0xE000000, rommgr.RomConfig.CollisionBaseConfig);
+
+ // Calculate Model Offset & Update Scrolling Texture Vertex Pointers
+ newModelStart = a.AreaModel.Fast3DBuffer.Fast3DBankStart;
+ modelOffset = newModelStart - oldModelStart;
+ if (modelOffset != 0)
+ {
+ a.UpdateScrollingTextureVertexPointer(modelOffset);
+ }
+
+ a.CollisionPointer = res.CollisionPointer;
+ a.Geolayout.Geopointers.Clear();
+ a.Geolayout.Geopointers.AddRange(res.GeoPointers.ToArray());
+ curOff += (uint)(res.Length + 0x20);
+ General.HexRoundUp2(ref curOff);
+
+ a.Bank0xELength = (int)(curOff - a.Bank0x0EOffset);
+ }
+
+ // Write Background Image
+ output.RoundUpPosition();
+ int customBGStart = Convert.ToInt32(curOff);
+ int customBGEnd = 0;
+ if (lvl.Background.IsCustom) // .ID = Geolayout.BackgroundIDs.Custom Then
+ {
+ // Write Custom Background
+ lvl.Background.WriteImage(output.BaseStream, customBGStart);
+
+ // Write Pointer Table
+ var bgPtrTable = LevelBG.GetBackgroundPointerTable();
+ output.Write(bgPtrTable, 0, bgPtrTable.Length);
+ customBGEnd = customBGStart + lvl.Background.ImageLength + bgPtrTable.Length;
+ curOff += (uint)lvl.Background.ImageLength + (uint)bgPtrTable.Length;
+ General.HexRoundUp2(ref curOff);
+ }
+
+ // Generate & Write Local Object Bank
+ uint localObjectBankRomStart = 0;
+ uint localObjectBankRomEnd = 0;
+ bool writeLocalObjectBank = lvl.LocalObjectBank.Models.Count > 0 && lvl.EnableLocalObjectBank;
+ if (writeLocalObjectBank)
+ {
+ localObjectBankRomStart = curOff;
+ curOff += (uint)lvl.LocalObjectBank.WriteToSeg(output, (int)curOff, 0x9, rommgr.RomConfig.CollisionBaseConfig);
+ localObjectBankRomEnd = curOff;
+ ((RMLevel)lvl).Config.LocalObjectBank = lvl.LocalObjectBank.Config;
+ lvl.LocalObjectBank.WriteCollisionPointers(output);
+ }
+ ((RMLevel)lvl).Config.EnableLocalObjectBank = writeLocalObjectBank;
+
+ // Get Bank 0x19
+ if (lvl.Bank0x19 is null)
+ {
+ lvl.Bank0x19 = rommgr.SetSegBank(0x19, Convert.ToInt32(curOff), (int)RomManagerSettings.DefaultLevelscriptSize);
+ lvl.Bank0x19.Data = new MemoryStream();
+ lvl.Bank0x19.Length = (int)RomManagerSettings.DefaultLevelscriptSize;
+ }
+ else
+ {
+ var oldData = lvl.Bank0x19.Data;
+ lvl.Bank0x19 = rommgr.SetSegBank(0x19, Convert.ToInt32(curOff), (int)(curOff + lvl.Bank0x19.Length));
+ lvl.Bank0x19.Data = oldData;
+ }
+
+ data0x19 = new BinaryStreamData(lvl.Bank0x19.Data);
+ saveres.Bank0x19 = lvl.Bank0x19;
+ curOff += (uint)lvl.Bank0x19.Data.Length;
+ General.HexRoundUp2(ref curOff);
+
+ // Update Geolayouts
+ foreach (LevelArea a in lvl.Areas)
+ {
+ // Update Backcolor Command
+ var cmd = a.Geolayout.Geolayoutscript.GetFirst(GeolayoutCommandTypes.Background);
+ if (a.Background.Type == AreaBGs.Levelbackground && lvl.Background.Enabled)
+ {
+ cgBackground.SetBackgroundPointer(cmd, unchecked((int)0x802763D4));
+ cgBackground.SetBackgroundID(cmd, (short)lvl.Background.ID);
+ }
+ else
+ {
+ cgBackground.SetBackgroundPointer(cmd, 0);
+ cgBackground.SetRgbaColor(cmd, a.Background.Color);
+ }
+ }
+
+ // Write Geolayouts
+ int geoOffset = 0x5F00;
+ foreach (LevelArea a in lvl.Areas)
+ {
+ geoOffset -= General.HexRoundUp1(a.Geolayout.Length) + 0x50;
+ a.GeolayoutOffset = (uint)(lvl.Bank0x19.BankAddress + geoOffset);
+ a.Geolayout.Write(lvl.Bank0x19.Data, geoOffset);
+ a.Geolayout.NewGeoOffset = lvl.Bank0x19.RomStart + geoOffset;
+ }
+
+ // Füge Show-Dialog-Command & 2D-Camera-Object ein
+ foreach (LevelArea a in lvl.Areas)
+ {
+ // Show-Dialog-Command
+ if (a.ShowMessage.Enabled)
+ {
+ var cmdShowMsg = new LevelscriptCommand($"30 04 00 {a.ShowMessage.DialogID.ToString("X2")}");
+ int indexOf1E = a.Levelscript.IndexOfFirst(LevelscriptCommandTypes.EndOfArea);
+ a.Levelscript.Insert(indexOf1E, cmdShowMsg);
+ }
+
+ // 2D-Camera-Object
+ if (rommgr.RomConfig.PatchingConfig.Patched2DCamera)
+ {
+ var cmds2d = new List();
+ foreach (LevelscriptCommand obj in a.Objects)
+ {
+ if (obj.CommandType == LevelscriptCommandTypes.Normal3DObject)
+ {
+ if (clNormal3DObject.GetSegBehaviorAddr(obj) == (long)0x130053C4) // Behav-ID: '0x130053C4
+ {
+ cmds2d.Add(obj);
+ }
+ }
+ }
+
+ if (a.Enable2DCamera)
+ {
+ if (cmds2d.Count == 0)
+ {
+ var cmd = new LevelscriptCommand("24 18 1F 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 13 00 53 C4");
+ a.Objects.Add(cmd);
+ }
+ }
+ else if (cmds2d.Count > 0)
+ {
+ foreach (LevelscriptCommand cmd in cmds2d)
+ a.Objects.Remove(cmd);
+ }
+ }
+ }
+
+ Levelscript lvlScript0E = null;
+ Dictionary areaobjwarpoffsetdic = null;
+ SegmentedBank firstBank0xE = null;
+ uint curFirstBank0xEOffset = 0;
+
+ // Add Objects and Warps to new Levelscript
+ lvlScript0E = new Levelscript();
+ firstBank0xE = rommgr.SetSegBank(0xE, Convert.ToInt32(curOff), 0);
+ areaobjwarpoffsetdic = new Dictionary();
+ foreach (LevelArea a in lvl.Areas)
+ {
+ areaobjwarpoffsetdic.Add(a.AreaID, (uint)(firstBank0xE.BankAddress + curFirstBank0xEOffset));
+ foreach (LevelscriptCommand c in a.Objects)
+ {
+ lvlScript0E.Add(c);
+ curFirstBank0xEOffset += (uint)c.Length;
+ }
+
+ foreach (ManagedScrollingTexture c in a.ScrollingTextures)
+ {
+ c.SaveProperties(rommgr.RomConfig.ScrollTexConfig);
+ lvlScript0E.Add(c.Command);
+ curFirstBank0xEOffset += (uint)c.Command.Length;
+ }
+
+ foreach (LevelscriptCommand c in a.Warps)
+ {
+ lvlScript0E.Add(c);
+ curFirstBank0xEOffset += (uint)c.Length;
+ }
+
+ foreach (LevelscriptCommand c in a.WarpsForGame)
+ {
+ lvlScript0E.Add(c);
+ curFirstBank0xEOffset += (uint)c.Length;
+ }
+
+ lvlScript0E.Add(new LevelscriptCommand("07 04 00 00"));
+ curFirstBank0xEOffset += 4;
+ }
+
+ firstBank0xE.Length = (int)General.HexRoundUp1(curFirstBank0xEOffset);
+ lvlScript0E.Write(output, firstBank0xE.RomStart);
+ curOff += (uint)firstBank0xE.Length;
+
+ // Füge Area dem Levelscript hinzu
+ int cIndex2 = lvl.Levelscript.IndexOfFirst(LevelscriptCommandTypes.x1E);
+ foreach (var a in lvl.Areas)
+ {
+ foreach (var c in a.Levelscript)
+ {
+ lvl.Levelscript.Insert(cIndex2, c);
+ cIndex2 += 1;
+ }
+ }
+
+ // Übernehme Level- und Areaeinstellungen
+ int CurrentAreaIndex = 0;
+ var areaobjwarpindextoinsertdic = new Dictionary();
+ var areaidindex = new Dictionary();
+ LevelArea tArea = null;
+ bool foundCmdShowMsg = false;
+ LevelscriptCommand cmdBgSegLoad = null;
+ LevelscriptCommand cmdGobSegLoad = null;
+ LevelscriptCommand cmdGobJump = null;
+ LevelscriptCommand cmdLobSegLoad = null;
+ LevelscriptCommand cmdLobJump = null;
+ var cmdsToInsertAt = new Dictionary();
+ var cmdsToRemove = new List();
+ foreach (var c in lvl.Levelscript)
+ {
+ var switchExpr1 = c.CommandType;
+ switch (switchExpr1)
+ {
+ case LevelscriptCommandTypes.StartArea:
+ {
+ tArea = lvl.Areas[CurrentAreaIndex];
+ byte areaid = tArea.AreaID;
+ areaidindex.Add(areaid, Convert.ToByte(areaidindex.Count));
+ clStartArea.SetSegGeolayoutAddr(c, (uint)(lvl.Areas[CurrentAreaIndex].Geolayout.NewGeoOffset - lvl.Bank0x19.RomStart + lvl.Bank0x19.BankAddress));
+ clStartArea.SetAreaID(c, areaid);
+ areaobjwarpindextoinsertdic.Add(areaid, lvl.Levelscript.IndexOf(c) + 1);
+ break;
+ }
+
+ case LevelscriptCommandTypes.EndOfArea:
+ {
+ if (!foundCmdShowMsg && tArea.ShowMessage.Enabled)
+ {
+ var cmdShowMsg = new LevelscriptCommand($"30 04 00 {tArea.ShowMessage.DialogID.ToString("X2")}");
+ cmdsToInsertAt.Add((LevelscriptCommand)c, cmdShowMsg);
+ }
+
+ foundCmdShowMsg = false;
+ CurrentAreaIndex += 1;
+ tArea = null;
+ break;
+ }
+
+ case LevelscriptCommandTypes.AreaMusic:
+ {
+ clAreaMusic.SetMusicID((LevelscriptCommand)c, lvl.Areas[CurrentAreaIndex].BGMusic);
+ break;
+ }
+
+ case LevelscriptCommandTypes.AreaMusicSimple:
+ {
+ clAreaMusicSimple.SetMusicID((LevelscriptCommand)c, lvl.Areas[CurrentAreaIndex].BGMusic);
+ break;
+ }
+
+ case LevelscriptCommandTypes.Tarrain:
+ {
+ clTerrian.SetTerrainType((LevelscriptCommand)c, (byte)lvl.Areas[CurrentAreaIndex].TerrainType);
+ break;
+ }
+
+ case LevelscriptCommandTypes.LoadRomToRam:
+ {
+ var switchExpr2 = clLoadRomToRam.GetSegmentedID((LevelscriptCommand)c);
+ switch (switchExpr2)
+ {
+ case 0xE: // Bank 0xE
+ clLoadRomToRam.SetRomStart((LevelscriptCommand)c, firstBank0xE.RomStart);
+ clLoadRomToRam.SetRomEnd((LevelscriptCommand)c, firstBank0xE.RomEnd);
+ break;
+ case 0xA:
+ cmdBgSegLoad = (LevelscriptCommand)c;
+ break;
+ case 0x7:
+ if (lvl.LastGobCmdSegLoad == c)
+ {
+ cmdGobSegLoad = (LevelscriptCommand)c;
+ }
+ break;
+ case 0x9:
+ if (lvl.LastLobCmdSegLoad == c)
+ {
+ cmdLobSegLoad = (LevelscriptCommand)c;
+ }
+ break;
+ }
+
+ break;
+ }
+
+ case LevelscriptCommandTypes.ShowDialog:
+ {
+ if ((bool)tArea?.ShowMessage.Enabled && !foundCmdShowMsg)
+ {
+ clShowDialog.SetDialogID((LevelscriptCommand)c, tArea.ShowMessage.DialogID);
+ foundCmdShowMsg = true;
+ }
+ else
+ {
+ cmdsToRemove.Add((LevelscriptCommand)c);
+ }
+
+ break;
+ }
+
+ case LevelscriptCommandTypes.JumpToSegAddr:
+ {
+ int bankID = clJumpToSegAddr.GetSegJumpAddr((LevelscriptCommand)c) >> 24;
+ switch(bankID)
+ {
+ case 0x7:
+ cmdGobJump = (LevelscriptCommand)c;
+ break;
+ case 0x9:
+ cmdLobJump = (LevelscriptCommand)c;
+ break;
+ }
+ break;
+ }
+ }
+ }
+
+ // Füge Jump Commands zur ersten 0xE-Bank hinzu
+ foreach (var e in areaobjwarpindextoinsertdic.OrderByDescending(n => n.Value))
+ {
+ uint segStartAddr = areaobjwarpoffsetdic[e.Key];
+ lvl.Levelscript.Insert(e.Value, new LevelscriptCommand(new byte[] { 0x6, 8, 0, 0, Convert.ToByte((long)(segStartAddr >> 24) & (long)0xFF), Convert.ToByte((long)(segStartAddr >> 16) & (long)0xFF), Convert.ToByte((long)(segStartAddr >> 8) & (long)0xFF), Convert.ToByte((long)segStartAddr & (long)0xFF) }));
+ }
+
+ // Lösche Commands
+ foreach (LevelscriptCommand cmd in cmdsToRemove)
+ lvl.Levelscript.Remove(cmd);
+
+ // Füge neue Commands ein
+ foreach (KeyValuePair kvp in cmdsToInsertAt)
+ {
+ int index = lvl.Levelscript.IndexOf(kvp.Key);
+ lvl.Levelscript.Insert(index, kvp.Value);
+ }
+
+ // Füge Background-Command ein
+ if (lvl.Background.Enabled)
+ {
+ var newbgcmd = cmdBgSegLoad ?? new LevelscriptCommand(new byte[] { 0x17, 0xC, 0, 0xA, 0, 0, 0, 0, 0, 0, 0, 0 });
+ if (lvl.Background.IsCustom && lvl.Background.HasImage) // .ID = Geolayout.BackgroundIDs.Custom Then
+ {
+ clLoadRomToRam.SetRomStart(newbgcmd, customBGStart);
+ clLoadRomToRam.SetRomEnd(newbgcmd, customBGEnd);
+ }
+ else
+ {
+ clLoadRomToRam.SetRomStart(newbgcmd, (int)General.GetBackgroundAddressOfID(lvl.Background.ID, false));
+ clLoadRomToRam.SetRomEnd(newbgcmd, (int)General.GetBackgroundAddressOfID(lvl.Background.ID, true));
+ }
+
+ if (!lvl.Levelscript.Contains(newbgcmd))
+ {
+ int indexoffirstx1e = lvl.Levelscript.IndexOfFirst(LevelscriptCommandTypes.x1D);
+ lvl.Levelscript.Insert(indexoffirstx1e, newbgcmd);
+ }
+ }
+ else if (cmdBgSegLoad is not null)
+ {
+ lvl.Levelscript.Remove(cmdBgSegLoad);
+ }
+
+ // Füge Global Model Bank Command ein
+ if (lvl.EnableGlobalObjectBank)
+ {
+ var newgobjumpcmd = cmdGobJump ?? new LevelscriptCommand("06 08 00 00 07 00 00 00");
+ var newgobcmd = cmdGobSegLoad ?? new LevelscriptCommand("17 0C 00 07 00 00 00 00 00 00 00 00");
+ clLoadRomToRam.SetRomStart(newgobcmd, rommgr.GlobalModelBank.CurSeg.RomStart);
+ clLoadRomToRam.SetRomEnd(newgobcmd, rommgr.GlobalModelBank.CurSeg.RomEnd);
+
+ if (!lvl.Levelscript.Contains(newgobcmd))
+ {
+ int indexoffirstx1d = lvl.Levelscript.IndexOfFirst(LevelscriptCommandTypes.x1D);
+ lvl.Levelscript.Insert(indexoffirstx1d, newgobcmd);
+ lvl.LastGobCmdSegLoad = newgobcmd;
+ }
+
+ if (!lvl.Levelscript.Contains(newgobjumpcmd))
+ {
+ int indexoffirstx1e = lvl.Levelscript.IndexOfFirst(LevelscriptCommandTypes.x25);
+ if (writeLocalObjectBank && cmdLobJump is not null && lvl.Levelscript.IndexOf(cmdLobJump) == indexoffirstx1e - 1)
+ indexoffirstx1e -= 1;
+ lvl.Levelscript.Insert(indexoffirstx1e, newgobjumpcmd);
+ }
+ }
+ else
+ {
+ if (cmdGobJump is not null)
+ lvl.Levelscript.Remove(cmdGobJump);
+
+ if (cmdGobSegLoad is not null)
+ lvl.Levelscript.Remove(cmdGobSegLoad);
+ }
+
+ // Füge Local Model Bank Command ein
+ if (writeLocalObjectBank)
+ {
+ var newlobjumpcmd = cmdLobJump ?? new LevelscriptCommand("06 08 00 00 09 00 00 00");
+ var newlobcmd = cmdLobSegLoad ?? new LevelscriptCommand("17 0C 00 09 00 00 00 00 00 00 00 00");
+ clLoadRomToRam.SetRomStart(newlobcmd, (int)localObjectBankRomStart);
+ clLoadRomToRam.SetRomEnd(newlobcmd, (int)localObjectBankRomEnd);
+
+ if (!lvl.Levelscript.Contains(newlobcmd))
+ {
+ int indexoffirstx1d = lvl.Levelscript.IndexOfFirst(LevelscriptCommandTypes.x1D);
+ lvl.Levelscript.Insert(indexoffirstx1d, newlobcmd);
+ lvl.LastLobCmdSegLoad = newlobcmd;
+ }
+
+ if (!lvl.Levelscript.Contains(newlobjumpcmd))
+ {
+ int indexoffirstx1e = lvl.Levelscript.IndexOfFirst(LevelscriptCommandTypes.x25);
+ lvl.Levelscript.Insert(indexoffirstx1e, newlobjumpcmd);
+ }
+ }
+ else
+ {
+ if (cmdLobJump is not null)
+ lvl.Levelscript.Remove(cmdLobJump);
+
+ if (cmdLobSegLoad is not null)
+ lvl.Levelscript.Remove(cmdLobSegLoad);
+ }
+
+ // Write Level Start (Start of Bank 0x19)
+ lvl.Bank0x19.Data.Position = 0;
+ foreach (byte b in Level.LevelscriptStart)
+ data0x19.Write(b);
+
+ // Write Levelscript
+ lvl.Levelscript.Write(lvl.Bank0x19.Data, Convert.ToInt32(data0x19.Position));
+
+ // Parse Levelscript again!
+ bool AreaOnFly = false;
+ foreach (var c in lvl.Levelscript.ToArray())
+ {
+ var switchExpr3 = c.CommandType;
+ switch (switchExpr3)
+ {
+ case LevelscriptCommandTypes.StartArea:
+ {
+ AreaOnFly = true;
+ break;
+ }
+
+ case LevelscriptCommandTypes.EndOfArea:
+ {
+ AreaOnFly = false;
+ lvl.Levelscript.Remove(c);
+ break;
+ }
+ }
+
+ if (AreaOnFly)
+ lvl.Levelscript.Remove(c);
+ }
+
+ var bwToUse = data0x19 ?? output;
+
+ // Write 4 checkbytes for the One-0xE-Bank-Per-Area-Code
+ lvl.Bank0x19.Data.Position = 0x5FFC;
+ bwToUse.Write(Convert.ToInt32(0x4BC9189A));
+
+ // Write Area Table
+ foreach (LevelArea a in lvl.Areas)
+ {
+ uint off = (uint)(0x5F00 + a.AreaID * 0x10);
+ lvl.Bank0x19.Data.Position = off;
+ bwToUse.Write(Convert.ToUInt32(a.Bank0x0EOffset));
+ bwToUse.Write(Convert.ToUInt32(a.Bank0x0EOffset + a.Bank0xELength));
+ bwToUse.Write(Convert.ToUInt32(0));
+ bwToUse.Write(Convert.ToByte(0x0));
+ bwToUse.Write(Convert.ToByte(0x0));
+ bwToUse.Write(Convert.ToByte(0x0));
+ bwToUse.Write(Bits.ArrayToByte(new[] { false, false, false, false, false, false, false, a.Enable2DCamera }));
+ }
+
+ // Write SpecialBoxes
+ int CurrentBoxOffset = 0x6A00;
+ foreach (LevelArea a in lvl.Areas.Where(n => n.AreaID < 8))
+ {
+ var TableIndex = new[] { 0, 0x32, 0x33 };
+ var TableOffset = new[] { 0x6000 + 0x50 * a.AreaID, 0x6280 + 0x50 * a.AreaID, 0x6500 + 0x50 * a.AreaID };
+
+ foreach (SpecialBoxType t in Enum.GetValues(typeof(SpecialBoxType)))
+ {
+ foreach (SpecialBox w in a.SpecialBoxes.Where(n => n.Type == t))
+ {
+ // Write Table Entry
+ bwToUse.Position = TableOffset[(int)w.Type];
+ bwToUse.Write(Convert.ToInt16(TableIndex[(int)w.Type]));
+ bwToUse.Write(Convert.ToInt16(0x0));
+ bwToUse.Write(Convert.ToInt32(CurrentBoxOffset + lvl.Bank0x19.BankAddress));
+ TableOffset[(int)w.Type] = Convert.ToInt32(bwToUse.Position);
+
+ // Write Box Data
+ bwToUse.Position = CurrentBoxOffset;
+ w.ToBoxData(bwToUse);
+ CurrentBoxOffset = (int)bwToUse.Position;
+
+ if (w.Type == SpecialBoxType.ToxicHaze)
+ TableIndex[(int)w.Type] += 10;
+ else
+ TableIndex[(int)w.Type] += 1;
+ }
+ }
+
+ foreach (int i in TableOffset)
+ {
+ bwToUse.Position = i;
+ bwToUse.Write((ushort)0xFFFF);
+ }
+ }
+
+ // Write Bank0x19
+ lvl.Bank0x19.WriteData(output);
+
+ // Hardcoded Camera Settings & Act Selector
+ General.PatchClass.Open(output);
+ General.PatchClass.set_HardcodedCamera_Enabled(lvl.LevelID, lvl.HardcodedCameraSettings);
+ General.PatchClass.set_ActSelector_Enabled(lvl.LevelID, lvl.ActSelector);
+
+ // Write Pointer to Levelscript
+ output.Position = lid.Pointer;
+ output.Write(Convert.ToInt32(0x100019));
+ output.Write(Convert.ToUInt32(lvl.Bank0x19.RomStart));
+ output.Write(Convert.ToUInt32(lvl.Bank0x19.RomEnd));
+ output.Write(Convert.ToUInt32(0x1900001C));
+ output.Write(Convert.ToUInt32(0x7040000));
+
+ // Write Area Reverb
+ if (EnableLoadingAreaReverb)
+ {
+ foreach (var area in lvl.Areas)
+ {
+ if (area is RMLevelArea && area.AreaID >= 1 && area.AreaID <= 3)
+ {
+ output.Position = 0xEE0C0 + lvl.LevelID * 3 + area.AreaID - 1;
+ output.Write((byte)((RMLevelArea)area).ReverbLevel);
+ }
+ }
+ }
+
+ return saveres;
+ }
+ }
+}
\ No newline at end of file
diff --git a/SM64Lib/Level/LevelNumberTable.cs b/SM64Lib/Level/LevelNumberTable.cs
new file mode 100644
index 0000000..0860bba
--- /dev/null
+++ b/SM64Lib/Level/LevelNumberTable.cs
@@ -0,0 +1,27 @@
+using System;
+using System.Collections.Generic;
+using global::System.IO;
+using Microsoft.VisualBasic.CompilerServices;
+
+namespace SM64Lib.Levels
+{
+ public class LevelNumberTable : List
+ {
+ public void ReadFromROM(string Romfile, int Address = 0xE8D98)
+ {
+ var fs = new FileStream(Romfile, FileMode.Open, FileAccess.Read);
+ ReadFromROM(ref fs);
+ fs.Close();
+ }
+
+ public void ReadFromROM(ref FileStream fs, int Address = 0xE8D98)
+ {
+ Clear();
+ for (int i = 0; i <= 30; i++)
+ {
+ fs.Position = Address + General.GetLevelIDFromIndex(Convert.ToByte(i));
+ Add(Convert.ToByte(fs.ReadByte()));
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/SM64Lib/Level/LevelSaveResult.cs b/SM64Lib/Level/LevelSaveResult.cs
new file mode 100644
index 0000000..d4a2839
--- /dev/null
+++ b/SM64Lib/Level/LevelSaveResult.cs
@@ -0,0 +1,9 @@
+using global::SM64Lib.SegmentedBanking;
+
+namespace SM64Lib.Levels
+{
+ public class LevelSaveResult
+ {
+ public SegmentedBank Bank0x19 { get; set; } = null;
+ }
+}
\ No newline at end of file
diff --git a/SM64Lib/Level/LevelType.cs b/SM64Lib/Level/LevelType.cs
new file mode 100644
index 0000000..f401fa1
--- /dev/null
+++ b/SM64Lib/Level/LevelType.cs
@@ -0,0 +1,10 @@
+
+namespace SM64Lib.Levels
+{
+ public enum LevelType
+ {
+ Original,
+ SM64RomManager,
+ SM64Editor
+ }
+}
\ No newline at end of file
diff --git a/SM64Lib/Level/LevelscriptMAP.txt b/SM64Lib/Level/LevelscriptMAP.txt
new file mode 100644
index 0000000..e47a866
--- /dev/null
+++ b/SM64Lib/Level/LevelscriptMAP.txt
@@ -0,0 +1,7 @@
+0x19000000 - 0x19005F00: Levelscript, Geolayouts (aligned to end)
+0x19005F00 - 0x19006000: Area Table (Contains inportant infos)
+0x19006000 - 0x19006280: Water Table
+0x19006280 - 0x19006500: Toxic Haze Table
+0x19006500 - 0x19006780: Mist Table
+0x19006780 - 0x19007000: Waterbox data
+0x19007000 - 0x1900a000: Reserved
\ No newline at end of file
diff --git a/SM64Lib/Level/OriginalLevel.cs b/SM64Lib/Level/OriginalLevel.cs
new file mode 100644
index 0000000..7dcd7cb
--- /dev/null
+++ b/SM64Lib/Level/OriginalLevel.cs
@@ -0,0 +1,14 @@
+
+namespace SM64Lib.Levels
+{
+ public class OriginalLevel : Level
+ {
+ public OriginalLevel(ushort LevelID, int LevelIndex, RomManager rommgr) : base(LevelID, LevelIndex, rommgr)
+ {
+ }
+
+ public OriginalLevel(RomManager rommgr) : base(rommgr)
+ {
+ }
+ }
+}
\ No newline at end of file
diff --git a/SM64Lib/Level/OriginalLevelArea.cs b/SM64Lib/Level/OriginalLevelArea.cs
new file mode 100644
index 0000000..b87296c
--- /dev/null
+++ b/SM64Lib/Level/OriginalLevelArea.cs
@@ -0,0 +1,22 @@
+
+namespace SM64Lib.Levels
+{
+ public class OriginalLevelArea : LevelArea
+ {
+ public OriginalLevelArea(byte AreaID) : base(AreaID)
+ {
+ }
+
+ public OriginalLevelArea(byte AreaID, byte LevelID) : base(AreaID, LevelID)
+ {
+ }
+
+ public OriginalLevelArea(byte AreaID, byte LevelID, bool AddWarps, bool AddObjects) : base(AreaID, LevelID, AddWarps, AddObjects)
+ {
+ }
+
+ public OriginalLevelArea() : base()
+ {
+ }
+ }
+}
\ No newline at end of file
diff --git a/SM64Lib/Level/OriginalLevelManager.cs b/SM64Lib/Level/OriginalLevelManager.cs
new file mode 100644
index 0000000..082921e
--- /dev/null
+++ b/SM64Lib/Level/OriginalLevelManager.cs
@@ -0,0 +1,18 @@
+using System;
+using global::SM64Lib.Data;
+
+namespace SM64Lib.Levels
+{
+ public class OriginalLevelManager : ILevelManager
+ {
+ public void LoadLevel(Level lvl, RomManager rommgr, ushort LevelID, uint segAddress)
+ {
+ throw new NotImplementedException();
+ }
+
+ public LevelSaveResult SaveLevel(Level lvl, RomManager rommgr, BinaryData output, ref uint curOff)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
\ No newline at end of file
diff --git a/SM64Lib/Level/RMLevel.cs b/SM64Lib/Level/RMLevel.cs
new file mode 100644
index 0000000..8db75d6
--- /dev/null
+++ b/SM64Lib/Level/RMLevel.cs
@@ -0,0 +1,34 @@
+
+using Newtonsoft.Json;
+using SM64Lib.Configuration;
+
+namespace SM64Lib.Levels
+{
+ public class RMLevel : Level
+ {
+ [JsonProperty]
+ public LevelConfig Config { get; private set; }
+
+ [JsonIgnore]
+ public string Name
+ {
+ get => Config.LevelName;
+ set => Config.LevelName = value;
+ }
+
+ public RMLevel(ushort LevelID, int LevelIndex, RomManager rommgr) : base(LevelID, LevelIndex, rommgr)
+ {
+ Config = new LevelConfig();
+ }
+
+ public RMLevel(LevelConfig config, RomManager rommgr) : base(rommgr)
+ {
+ Config = config;
+ }
+
+ [JsonConstructor]
+ private RMLevel(JsonConstructorAttribute attr) : base(attr)
+ {
+ }
+ }
+}
\ No newline at end of file
diff --git a/SM64Lib/Level/RMLevelArea.cs b/SM64Lib/Level/RMLevelArea.cs
new file mode 100644
index 0000000..66541f2
--- /dev/null
+++ b/SM64Lib/Level/RMLevelArea.cs
@@ -0,0 +1,35 @@
+
+using Newtonsoft.Json;
+
+namespace SM64Lib.Levels
+{
+ public class RMLevelArea : LevelArea
+ {
+ // P r o p e r t i e s
+
+ public AreaReverbLevel ReverbLevel { get; set; } = AreaReverbLevel.None;
+
+ // C o n s t r u c t o r s
+
+ public RMLevelArea(byte AreaID) : base(AreaID)
+ {
+ }
+
+ public RMLevelArea(byte AreaID, byte LevelID) : base(AreaID, LevelID)
+ {
+ }
+
+ public RMLevelArea(byte AreaID, byte LevelID, bool AddWarps, bool AddObjects) : base(AreaID, LevelID, AddWarps, AddObjects)
+ {
+ }
+
+ public RMLevelArea() : base()
+ {
+ }
+
+ [JsonConstructor]
+ private RMLevelArea(JsonConstructorAttribute attr) : base(attr)
+ {
+ }
+ }
+}
\ No newline at end of file
diff --git a/SM64Lib/Level/SM64ELevel.cs b/SM64Lib/Level/SM64ELevel.cs
new file mode 100644
index 0000000..e7e437d
--- /dev/null
+++ b/SM64Lib/Level/SM64ELevel.cs
@@ -0,0 +1,14 @@
+
+namespace SM64Lib.Levels
+{
+ public class SM64ELevel : Level
+ {
+ public SM64ELevel(ushort LevelID, int LevelIndex, RomManager rommgr) : base(LevelID, LevelIndex, rommgr)
+ {
+ }
+
+ public SM64ELevel(RomManager rommgr) : base(rommgr)
+ {
+ }
+ }
+}
\ No newline at end of file
diff --git a/SM64Lib/Level/SM64ELevelArea.cs b/SM64Lib/Level/SM64ELevelArea.cs
new file mode 100644
index 0000000..9ff50ce
--- /dev/null
+++ b/SM64Lib/Level/SM64ELevelArea.cs
@@ -0,0 +1,22 @@
+
+namespace SM64Lib.Levels
+{
+ public class SM64ELevelArea : LevelArea
+ {
+ public SM64ELevelArea(byte AreaID) : base(AreaID)
+ {
+ }
+
+ public SM64ELevelArea(byte AreaID, byte LevelID) : base(AreaID, LevelID)
+ {
+ }
+
+ public SM64ELevelArea(byte AreaID, byte LevelID, bool AddWarps, bool AddObjects) : base(AreaID, LevelID, AddWarps, AddObjects)
+ {
+ }
+
+ public SM64ELevelArea() : base()
+ {
+ }
+ }
+}
\ No newline at end of file
diff --git a/SM64Lib/Level/SM64EditorLevelManager.cs b/SM64Lib/Level/SM64EditorLevelManager.cs
new file mode 100644
index 0000000..70c2b2a
--- /dev/null
+++ b/SM64Lib/Level/SM64EditorLevelManager.cs
@@ -0,0 +1,330 @@
+using System;
+using System.Collections.Generic;
+using System.Data;
+using global::System.IO;
+using System.Linq;
+using Microsoft.VisualBasic.CompilerServices;
+using global::SM64Lib.Data;
+using global::SM64Lib.Geolayout.Script;
+using global::SM64Lib.Geolayout.Script.Commands;
+using global::SM64Lib.Levels.Script;
+using global::SM64Lib.Levels.Script.Commands;
+using SM64Lib.Configuration;
+
+namespace SM64Lib.Levels
+{
+ public class SM64EditorLevelManager : ILevelManager
+ {
+
+ ///
+ /// Loads a SM64 Editor Level from ROM.
+ ///
+ ///
+ ///
+ ///
+ ///
+ public void LoadLevel(Level lvl, RomManager rommgr, ushort LevelID, uint segAddress)
+ {
+ int customBGStart = 0;
+ int customBGEnd = 0;
+ lvl.LevelID = LevelID;
+
+ // Load Bank 0x19
+ lvl.Bank0x19 = rommgr.GetSegBank(0x19);
+ lvl.Bank0x19.ReadDataIfNull(rommgr.RomFile);
+ if (lvl.Bank0x19.Length < 0x10000)
+ {
+ lvl.Bank0x19.Length = 0x10000;
+ }
+
+ if (!lvl.Closed)
+ lvl.Close();
+ lvl.Closed = false;
+
+ // Lade Levelscript
+ lvl.Levelscript = new Levelscript();
+ lvl.Levelscript.Read(rommgr, Convert.ToInt32(segAddress));
+
+ // Erstelle Areas / Lade Einstellungen
+ bool AreaOnFly = false;
+ LevelArea tArea = null;
+ var CurrentLevelScriptCommands = lvl.Levelscript.ToArray();
+ var cmdsToRemove = new List();
+ LevelArea firstArea = null;
+ foreach (LevelscriptCommand c in CurrentLevelScriptCommands)
+ {
+ var switchExpr = c.CommandType;
+ switch (switchExpr)
+ {
+ case LevelscriptCommandTypes.StartArea:
+ {
+ AreaOnFly = true;
+ tArea = new SM64ELevelArea();
+ if (firstArea is null)
+ firstArea = tArea;
+ tArea.AreaID = clStartArea.GetAreaID(c);
+ tArea.GeolayoutOffset = clStartArea.GetSegGeolayoutAddr(c);
+ tArea.Geolayout.Read(rommgr, Convert.ToInt32(tArea.GeolayoutOffset));
+ break;
+ }
+
+ case LevelscriptCommandTypes.EndOfArea:
+ {
+ tArea.Levelscript.Add(c);
+ lvl.Levelscript.Remove(c);
+ lvl.Areas.Add(tArea);
+ AreaOnFly = false;
+ break;
+ }
+
+ case LevelscriptCommandTypes.AreaMusic:
+ {
+ tArea.BGMusic = clAreaMusic.GetMusicID(c);
+ break;
+ }
+
+ case LevelscriptCommandTypes.AreaMusicSimple:
+ {
+ tArea.BGMusic = clAreaMusicSimple.GetMusicID(c);
+ break;
+ }
+
+ case LevelscriptCommandTypes.Tarrain:
+ {
+ tArea.TerrainType = (Geolayout.TerrainTypes)clTerrian.GetTerrainType(c);
+ break;
+ }
+
+ case LevelscriptCommandTypes.Normal3DObject:
+ {
+ if (clNormal3DObject.GetSegBehaviorAddr(c) == (long)0x13003420)
+ {
+ tArea.ScrollingTextures.Add(new ScrolTex.ManagedScrollingTexture(c));
+ }
+ else
+ {
+ tArea.Objects.Add(c);
+ }
+
+ break;
+ }
+
+ case LevelscriptCommandTypes.ConnectedWarp:
+ {
+ if ((new[] { 0xF0, 0xF1 }).Contains(clWarp.GetWarpID(c)))
+ {
+ tArea.WarpsForGame.Add(c);
+ }
+ else
+ {
+ tArea.Warps.Add(c);
+ }
+
+ break;
+ }
+
+ case LevelscriptCommandTypes.PaintingWarp:
+ case LevelscriptCommandTypes.InstantWarp:
+ {
+ tArea.Warps.Add(c);
+ break;
+ }
+
+ case LevelscriptCommandTypes.LoadRomToRam:
+ {
+ byte bankID = clLoadRomToRam.GetSegmentedID(c);
+ int startAddr = clLoadRomToRam.GetRomStart(c);
+ int endAddr = clLoadRomToRam.GetRomEnd(c);
+ switch (bankID)
+ {
+ case 0xA: // Background-Image
+ {
+ customBGStart = startAddr;
+ customBGEnd = endAddr - 0x140;
+ break;
+ }
+
+ case 0xE:
+ {
+ rommgr.SetSegBank(bankID, startAddr, endAddr);
+ break;
+ }
+ }
+
+ break;
+ }
+
+ case LevelscriptCommandTypes.ShowDialog:
+ {
+ if (AreaOnFly)
+ {
+ tArea.ShowMessage.Enabled = true;
+ tArea.ShowMessage.DialogID = clShowDialog.GetDialogID(c);
+ }
+
+ break;
+ }
+
+ case LevelscriptCommandTypes.JumpBack:
+ case LevelscriptCommandTypes.JumpToSegAddr:
+ {
+ if (tArea is not null)
+ cmdsToRemove.Add(c);
+ break;
+ }
+ }
+
+ if (AreaOnFly && !cmdsToRemove.Contains(c))
+ {
+ lvl.Levelscript.Remove(c);
+ tArea.Levelscript.Add(c);
+ }
+ }
+
+ // Lösche alle Jump-Commands aus dem Levelscript
+ foreach (LevelscriptCommand cmd in cmdsToRemove)
+ {
+ lvl.Levelscript.Remove(cmd);
+ cmd.Close();
+ }
+
+ // Stelle frisches Levelscript wieder her
+ lvl.CreateNewLevelscript();
+
+ // Lösche alle Objekte und Warps aus dem Levelscript
+ var lvlscrptidstoremove = new[] { LevelscriptCommandTypes.Normal3DObject, LevelscriptCommandTypes.ConnectedWarp, LevelscriptCommandTypes.PaintingWarp, LevelscriptCommandTypes.InstantWarp };
+ foreach (var a in lvl.Areas)
+ {
+ foreach (var c in a.Levelscript.Where(n => lvlscrptidstoremove.Contains(n.CommandType)).ToArray())
+ a.Levelscript.Remove(c);
+ }
+
+ // Lese Custom Background Image
+ var fs = new FileStream(rommgr.RomFile, FileMode.Open, FileAccess.Read);
+ var br2 = new BinaryReader(fs);
+ lvl.Background.Enabled = false;
+ foreach (LevelArea a in lvl.Areas)
+ {
+ var bgglcmd = a.Geolayout.Geolayoutscript.GetFirst(GeolayoutCommandTypes.Background);
+ if (cgBackground.GetBackgroundPointer(bgglcmd) == 0)
+ {
+ a.Background.Type = AreaBGs.Color;
+ a.Background.Color = cgBackground.GetRrgbaColor(bgglcmd);
+ }
+ else
+ {
+ a.Background.Type = AreaBGs.Levelbackground;
+ lvl.Background.ID = (Geolayout.BackgroundIDs)cgBackground.GetBackgroundID(bgglcmd);
+ lvl.Background.Enabled = true;
+ }
+ }
+
+ if (lvl.Background.Enabled && lvl.Background.ID == Geolayout.BackgroundIDs.Custom)
+ {
+ fs.Position = customBGStart;
+ lvl.Background.ReadImage(fs, customBGStart);
+ }
+
+ int bank0x19RomStart;
+ int bank0x19RomEnd;
+ BinaryReader brToUse;
+ bank0x19RomStart = 0;
+ bank0x19RomEnd = lvl.Bank0x19.Length;
+ brToUse = new BinaryReader(lvl.Bank0x19.Data);
+
+ // Lese Area-Modelle
+ var modelBank = rommgr.GetSegBank(0xE);
+ int curMdlStartOffset = modelBank.RomStart;
+ for (int i = 0, loopTo = lvl.Areas.Count - 1; i <= loopTo; i++)
+ {
+ var a = lvl.Areas[i];
+ int newEndOffset = GetModelEnd(fs, modelBank.RomStart, Convert.ToByte(i));
+ a.Bank0x0EOffset = Convert.ToUInt32(curMdlStartOffset);
+ rommgr.SetSegBank(0xE, Convert.ToInt32(a.Bank0x0EOffset), newEndOffset, a.AreaID);
+ a.AreaModel.Collision = new Model.Collision.CollisionMap();
+ a.AreaModel.Collision.FromStream(fs, modelBank.SegToRomAddr(a.CollisionPointer), new CollisionBasicConfig());
+ a.AreaModel.Fast3DBuffer = new Model.Fast3D.Fast3DBuffer();
+ a.AreaModel.FromStream(fs, modelBank.RomStart, 0xE000000, curMdlStartOffset, newEndOffset - curMdlStartOffset, a.Geolayout.Geopointers.ToArray(), a.CollisionPointer, rommgr.RomConfig.CollisionBaseConfig);
+ a.AreaModel.Collision.SpecialBoxes.Clear();
+ curMdlStartOffset = newEndOffset;
+ }
+
+ // Lese alle Box-Daten
+ firstArea.SpecialBoxes.Clear();
+ firstArea.SpecialBoxes.AddRange(SpecialBoxList.ReadTable(brToUse.BaseStream, SpecialBoxType.Water, bank0x19RomStart, bank0x19RomStart + 0x1810));
+ firstArea.SpecialBoxes.AddRange(SpecialBoxList.ReadTable(brToUse.BaseStream, SpecialBoxType.ToxicHaze, bank0x19RomStart, bank0x19RomStart + 0x1850));
+ firstArea.SpecialBoxes.AddRange(SpecialBoxList.ReadTable(brToUse.BaseStream, SpecialBoxType.Mist, bank0x19RomStart, bank0x19RomStart + 0x18A0));
+ var areaWithBoxData = lvl.Areas.FirstOrDefault(n => n.AreaModel.Collision.SpecialBoxes.Any());
+ if (areaWithBoxData is not null)
+ {
+ for (int i = 0, loopTo1 = firstArea.SpecialBoxes.Count - 1; i <= loopTo1; i++)
+ {
+ var boxdata = firstArea.AreaModel.Collision.SpecialBoxes.ElementAtOrDefault(i);
+ if (boxdata is not null)
+ {
+ firstArea.SpecialBoxes[i].Y = boxdata.Y;
+ }
+ }
+ }
+
+ // One-Bank-0xE-System
+ lvl.OneBank0xESystemEnabled = true;
+
+ // Act Selector
+ General.PatchClass.Open(fs);
+ lvl.ActSelector = General.PatchClass.get_ActSelector_Enabled(LevelID);
+
+ // Hardcoded Camera
+ lvl.HardcodedCameraSettings = General.PatchClass.get_HardcodedCamera_Enabled(LevelID);
+ fs.Close();
+
+ // Object-Banks
+ lvl.MyObjectBanks.Clear();
+ lvl.MyObjectBanks.Add(0xC, null);
+ lvl.MyObjectBanks.Add(0xD, null);
+ lvl.MyObjectBanks.Add(0x9, null);
+ }
+
+ public LevelSaveResult SaveLevel(Level lvl, RomManager rommgr, BinaryData output, ref uint curOff)
+ {
+ throw new NotImplementedException();
+ }
+
+ private static int GetModelEnd(Stream s, int startPos, byte areaIndex)
+ {
+ byte cb = 0;
+ int Ausnahmen = 0;
+ s.Position = startPos;
+ bool ende = false;
+
+ do
+ {
+ cb = Convert.ToByte(s.ReadByte());
+ if (s.Position >= s.Length - 1)
+ return Convert.ToInt32(s.Length);
+ else if (cb == 0x1)
+ {
+ var haveFound = true;
+ s.Position -= 1;
+
+ for (int i = 1; i <= 0x100; i++)
+ {
+ if (haveFound && s.ReadByte() != 0x1)
+ haveFound = false;
+ }
+
+ if (haveFound)
+ {
+ if (Ausnahmen == areaIndex)
+ ende = true;
+ else
+ Ausnahmen += 1;
+ }
+ }
+ }
+ while (!ende);
+
+ return Convert.ToInt32(s.Position);
+ }
+ }
+}
\ No newline at end of file
diff --git a/SM64Lib/Level/Script/Levelscript.cs b/SM64Lib/Level/Script/Levelscript.cs
new file mode 100644
index 0000000..f4cf148
--- /dev/null
+++ b/SM64Lib/Level/Script/Levelscript.cs
@@ -0,0 +1,203 @@
+using System;
+using System.Collections.Generic;
+using global::System.IO;
+using System.Linq;
+using Microsoft.VisualBasic.CompilerServices;
+using global::SM64Lib.Data;
+using global::SM64Lib.Levels.Script.Commands;
+using global::SM64Lib.SegmentedBanking;
+
+namespace SM64Lib.Levels.Script
+{
+ public class Levelscript : LevelscriptCommandCollection
+ {
+ public new void Close()
+ {
+ for (int i = 0, loopTo = Count - 1; i <= loopTo; i++)
+ this[i].Close();
+ Clear();
+ }
+
+ public Levelscript()
+ {
+ }
+
+ public void Read(RomManager rommgr, int scriptStartInBank, LevelscriptCommandTypes EndAtCommands = LevelscriptCommandTypes.EndOfLevel, Dictionary segDic = null, bool storeToRommgr = true)
+ {
+ Stream s = null;
+ BinaryReader br = null;
+ FileStream fs = null;
+ BinaryReader brfs = null;
+ var tb = new List();
+ LevelscriptCommandTypes cb = default;
+ bool enableDo = true;
+ SegmentedBank curSegBank = null;
+ var dicBankBinaryReaders = new Dictionary();
+ var brStack = new Stack();
+ var sStack = new Stack();
+ var jumpStack = new Stack();
+ var segStack = new Stack();
+ Close();
+ Read_GetStream(ref curSegBank, ref s, ref br, ref fs, ref brfs, rommgr, scriptStartInBank, dicBankBinaryReaders, segDic);
+ while (enableDo)
+ {
+ try
+ {
+ cb = (LevelscriptCommandTypes)br.ReadByte();
+ byte lenth = 0;
+ lenth = br.ReadByte();
+ if (lenth == 0)
+ lenth = 4;
+ tb.Add((byte)cb);
+ tb.Add(lenth);
+ for (int i = 3, loopTo = lenth; i <= loopTo; i++)
+ tb.Add(br.ReadByte());
+ var curLvlCmd = new LevelscriptCommand(tb.ToArray());
+ int bankOffset = (int)(br.BaseStream.Position - lenth);
+ curLvlCmd.RomAddress = (int)(curSegBank?.RomStart + bankOffset);
+ curLvlCmd.BankAddress = (int)(curSegBank?.BankAddress + bankOffset);
+ Add(curLvlCmd);
+ tb.Clear();
+ var switchExpr = curLvlCmd.CommandType;
+ switch (switchExpr)
+ {
+ case LevelscriptCommandTypes.LoadRomToRam:
+ case LevelscriptCommandTypes.x1A:
+ case LevelscriptCommandTypes.x18:
+ case LevelscriptCommandTypes.x00:
+ case LevelscriptCommandTypes.x01:
+ {
+ var bank = new SegmentedBank()
+ {
+ BankID = clLoadRomToRam.GetSegmentedID(curLvlCmd),
+ RomStart = clLoadRomToRam.GetRomStart(curLvlCmd),
+ RomEnd = clLoadRomToRam.GetRomEnd(curLvlCmd)
+ };
+ if (curLvlCmd.CommandType == LevelscriptCommandTypes.x1A)
+ bank.MakeAsMIO0();
+ if (storeToRommgr)
+ rommgr?.SetSegBank(bank);
+ if (segDic is not null)
+ {
+ if (segDic.ContainsKey(bank.BankID))
+ {
+ segDic[bank.BankID] = bank;
+ }
+ else
+ {
+ segDic.Add(bank.BankID, bank);
+ }
+ }
+
+ if (new[] { LevelscriptCommandTypes.x00, LevelscriptCommandTypes.x01 }.Contains(curLvlCmd.CommandType))
+ {
+ int SegAddr = clLoadRomToRam.GetSegmentedAddressToJump(curLvlCmd);
+ JumpTo(jumpStack, sStack, brStack, segStack, ref curSegBank, ref s, ref br, ref fs, ref brfs, rommgr, SegAddr, dicBankBinaryReaders, segDic);
+ }
+
+ break;
+ }
+
+ case LevelscriptCommandTypes.JumpToSegAddr:
+ {
+ int SegAddr = clJumpToSegAddr.GetSegJumpAddr(curLvlCmd);
+ if (new[] { 0x19, 0xE }.Contains(SegAddr >> 24))
+ {
+ JumpTo(jumpStack, sStack, brStack, segStack, ref curSegBank, ref s, ref br, ref fs, ref brfs, rommgr, SegAddr, dicBankBinaryReaders, segDic);
+ }
+
+ break;
+ }
+
+ case LevelscriptCommandTypes.JumpBack:
+ case (LevelscriptCommandTypes)0xA: // Jump back
+ {
+ curSegBank = segStack.Pop();
+ s = sStack.Pop();
+ br = brStack.Pop();
+ s.Position = jumpStack.Pop();
+ break;
+ }
+ }
+
+ if (curLvlCmd.CommandType == LevelscriptCommandTypes.EndOfLevel || curLvlCmd.CommandType == EndAtCommands)
+ {
+ enableDo = false;
+ }
+ }
+ catch (Exception)
+ {
+ enableDo = false;
+ }
+ }
+
+ // If s Is fs Then s?.Close()
+ fs?.Close();
+ }
+
+ private void JumpTo(Stack jumpStack, Stack sStack, Stack brStack, Stack segStack, ref SegmentedBank curSegBank, ref Stream s, ref BinaryReader br, ref FileStream fs, ref BinaryReader brfs, RomManager rommgr, int scriptStartInBank, Dictionary dicBankBinaryReaders, Dictionary segDic)
+ {
+ jumpStack.Push(Convert.ToInt32(s.Position));
+ sStack.Push(s);
+ brStack.Push(br);
+ segStack.Push(curSegBank);
+ Read_GetStream(ref curSegBank, ref s, ref br, ref fs, ref brfs, rommgr, scriptStartInBank, dicBankBinaryReaders, segDic);
+ }
+
+ private void Read_GetStream(ref SegmentedBank curSegBank, ref Stream s, ref BinaryReader br, ref FileStream fs, ref BinaryReader brfs, RomManager rommgr, int scriptStartInBank, Dictionary dicBankBinaryReaders, Dictionary segDic)
+ {
+ byte bankID = Convert.ToByte(scriptStartInBank >> 24);
+ curSegBank = rommgr.GetSegBank(bankID);
+ if (curSegBank is null && segDic?.ContainsKey(bankID) == true)
+ {
+ curSegBank = segDic[bankID];
+ }
+
+ if (curSegBank?.Data is not null)
+ {
+ s = curSegBank.Data;
+ if (dicBankBinaryReaders.ContainsKey(curSegBank.BankID))
+ {
+ br = dicBankBinaryReaders[curSegBank.BankID];
+ }
+ else
+ {
+ br = new BinaryReader(curSegBank.Data);
+ dicBankBinaryReaders.Add(curSegBank.BankID, br);
+ }
+ }
+ else
+ {
+ if (fs is null)
+ fs = new FileStream(rommgr.RomFile, FileMode.Open, FileAccess.Read);
+ if (brfs is null)
+ brfs = new BinaryReader(fs);
+ s = fs;
+ br = brfs;
+ }
+
+ if (curSegBank is not null)
+ {
+ s.Position = s == fs ? curSegBank.SegToRomAddr(scriptStartInBank) : curSegBank.BankOffsetFromSegAddr(scriptStartInBank);
+ }
+ }
+
+ public void Write(Stream s, int LevelscriptStart)
+ {
+ Write(new BinaryStreamData(s), LevelscriptStart);
+ }
+
+ public void Write(BinaryData data, int LevelscriptStart)
+ {
+ var JumpList = new List();
+
+ // Write new Levelscript
+ data.Position = LevelscriptStart;
+ foreach (LevelscriptCommand c in this)
+ {
+ foreach (byte b in c.ToArray())
+ data.Write(b);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/SM64Lib/Level/Script/LevelscriptCommand.cs b/SM64Lib/Level/Script/LevelscriptCommand.cs
new file mode 100644
index 0000000..b6b138a
--- /dev/null
+++ b/SM64Lib/Level/Script/LevelscriptCommand.cs
@@ -0,0 +1,40 @@
+using System;
+using global::SM64Lib.Script;
+using Newtonsoft.Json;
+
+namespace SM64Lib.Levels.Script
+{
+ [JsonConverter(typeof(Json.LevelscriptCommandJsonConverter))]
+ public class LevelscriptCommand : BaseCommand
+ {
+ public LevelscriptCommand(byte[] bytes) : base(bytes)
+ {
+ }
+
+ public LevelscriptCommand() : base()
+ {
+ }
+
+ public LevelscriptCommand(string bytes, bool enabledHex = true) : base(bytes, enabledHex)
+ {
+ }
+
+ public override LevelscriptCommandTypes CommandType
+ {
+ get
+ {
+ Position = 0;
+ LevelscriptCommandTypes t = (LevelscriptCommandTypes)ReadByte();
+ Position = 0;
+ return t;
+ }
+
+ set
+ {
+ Position = 0;
+ WriteByte((byte)value);
+ Position = 0;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/SM64Lib/Level/Script/LevelscriptCommandCollection.cs b/SM64Lib/Level/Script/LevelscriptCommandCollection.cs
new file mode 100644
index 0000000..ba107b9
--- /dev/null
+++ b/SM64Lib/Level/Script/LevelscriptCommandCollection.cs
@@ -0,0 +1,18 @@
+using global::SM64Lib.Script;
+
+namespace SM64Lib.Levels.Script
+{
+ public class LevelscriptCommandCollection : BaseCommandCollection
+ {
+ public int IndexOfFirst(LevelscriptCommandTypes cmdType)
+ {
+ for (int index = 0, loopTo = Count - 1; index <= loopTo; index++)
+ {
+ if (this[index].CommandType == cmdType)
+ return index;
+ }
+
+ return -1;
+ }
+ }
+}
\ No newline at end of file
diff --git a/SM64Lib/Level/Script/LevelscriptCommandFunctions.cs b/SM64Lib/Level/Script/LevelscriptCommandFunctions.cs
new file mode 100644
index 0000000..24e7663
--- /dev/null
+++ b/SM64Lib/Level/Script/LevelscriptCommandFunctions.cs
@@ -0,0 +1,726 @@
+using System;
+using global::System.Numerics;
+using Microsoft.VisualBasic;
+using Microsoft.VisualBasic.CompilerServices;
+using SM64Lib.Data.System;
+
+namespace SM64Lib.Levels.Script
+{
+ public class ObjBParams
+ {
+ public byte BParam1 { get; set; } = 0;
+ public byte BParam2 { get; set; } = 0;
+ public byte BParam3 { get; set; } = 0;
+ public byte BParam4 { get; set; } = 0;
+
+ public ObjBParams()
+ {
+ }
+
+ public ObjBParams(byte BParam1, byte BParam2, byte BParam3, byte BParam4)
+ {
+ this.BParam1 = BParam1;
+ this.BParam2 = BParam2;
+ this.BParam3 = BParam3;
+ this.BParam4 = BParam4;
+ }
+ }
+
+ namespace Commands
+ {
+ public class clJumpToSegAddr // Jump to
+ {
+ public static int GetSegJumpAddr(LevelscriptCommand Command)
+ {
+ Command.Position = 4;
+ int jumpaddr = Command.ReadInt32();
+ Command.Position = 0;
+ return jumpaddr;
+ }
+
+ public static void SetSegJumpAddr(LevelscriptCommand Command, int SegJumpAddr)
+ {
+ Command.Position = 4;
+ Command.Write(Convert.ToUInt32(SegJumpAddr));
+ Command.Position = 0;
+ }
+ }
+
+ public class clStartArea // Start Area
+ {
+ public static byte GetAreaID(LevelscriptCommand Command)
+ {
+ Command.Position = 2;
+ int areaid = Command.ReadByte();
+ Command.Position = 0;
+ return Convert.ToByte(areaid);
+ }
+
+ public static void SetAreaID(LevelscriptCommand Command, byte AreaID)
+ {
+ Command.Position = 2;
+ Command.Write(AreaID);
+ Command.Position = 0;
+ }
+
+ public static uint GetSegGeolayoutAddr(LevelscriptCommand Command)
+ {
+ Command.Position = 4;
+ uint SegGeolayoutAddr = Command.ReadUInt32();
+ Command.Position = 0;
+ return SegGeolayoutAddr;
+ }
+
+ public static void SetSegGeolayoutAddr(LevelscriptCommand Command, uint SegGeolayoutAddr)
+ {
+ Command.Position = 4;
+ Command.Write(SegGeolayoutAddr);
+ Command.Position = 0;
+ }
+ }
+
+ public class clNormal3DObject // Object
+ {
+ public static byte GetActs(LevelscriptCommand Command)
+ {
+ Command.Position = 2;
+ int Acts = Command.ReadByte();
+ Command.Position = 0;
+ return Convert.ToByte(Acts);
+ }
+
+ public static void SetActs(LevelscriptCommand Command, byte Acts)
+ {
+ Command.Position = 2;
+ Command.Write(Acts);
+ Command.Position = 0;
+ }
+
+ public static byte GetModelID(LevelscriptCommand Command)
+ {
+ Command.Position = 3;
+ int ModelID = Command.ReadByte();
+ Command.Position = 0;
+ return Convert.ToByte(ModelID);
+ }
+
+ public static void SetModelID(LevelscriptCommand Command, byte ModelID)
+ {
+ Command.Position = 3;
+ Command.Write(ModelID);
+ Command.Position = 0;
+ }
+
+ public static Vector3 GetPosition(LevelscriptCommand Command)
+ {
+ Command.Position = 4;
+ var Pos = new Vector3();
+ Pos.X = Command.ReadInt16();
+ Pos.Y = Command.ReadInt16();
+ Pos.Z = Command.ReadInt16();
+ Command.Position = 0;
+ return Pos;
+ }
+
+ public static void SetPosition(LevelscriptCommand Command, Vector3 Pos)
+ {
+ Command.Position = 4;
+ Command.Write(Convert.ToInt16(Pos.X));
+ Command.Write(Convert.ToInt16(Pos.Y));
+ Command.Write(Convert.ToInt16(Pos.Z));
+ Command.Position = 0;
+ }
+
+ public static Vector3 GetRotation(LevelscriptCommand Command)
+ {
+ Command.Position = 10;
+ var Rot = new Vector3();
+ Rot.X = Command.ReadInt16();
+ Rot.Y = Command.ReadInt16();
+ Rot.Z = Command.ReadInt16();
+ Command.Position = 0;
+ return Rot;
+ }
+
+ public static void SetRotation(LevelscriptCommand Command, Vector3 Rot)
+ {
+ Command.Position = 10;
+ Command.Write(Convert.ToInt16(Rot.X));
+ Command.Write(Convert.ToInt16(Rot.Y));
+ Command.Write(Convert.ToInt16(Rot.Z));
+ Command.Position = 0;
+ }
+
+ public static ObjBParams GetParams(LevelscriptCommand Command)
+ {
+ Command.Position = 16;
+ var Params = new ObjBParams();
+ Params.BParam1 = Command.ReadByte();
+ Params.BParam2 = Command.ReadByte();
+ Params.BParam3 = Command.ReadByte();
+ Params.BParam4 = Command.ReadByte();
+ Command.Position = 0;
+ return Params;
+ }
+
+ public static void SetParams(LevelscriptCommand Command, ObjBParams Params)
+ {
+ Command.Position = 16;
+ Command.Write(Params.BParam1);
+ Command.Write(Params.BParam2);
+ Command.Write(Params.BParam3);
+ Command.Write(Params.BParam4);
+ Command.Position = 0;
+ }
+
+ public static uint GetSegBehaviorAddr(LevelscriptCommand Command)
+ {
+ Command.Position = 20;
+ uint SegBehaviorAddr = Command.ReadUInt32();
+ Command.Position = 0;
+ return SegBehaviorAddr;
+ }
+
+ public static void SetSegBehaviorAddr(LevelscriptCommand Command, uint SegBehaviorAddr)
+ {
+ Command.Position = 20;
+ Command.Write(SegBehaviorAddr);
+ Command.Position = 0;
+ }
+
+ public static void UpdateScrollingTexturePointer(LevelscriptCommand Command, uint Difference)
+ {
+ Interaction.MsgBox("UpdateScrollingTexturePointer() is not done!");
+ }
+
+ public static string GetListBoxText(LevelscriptCommand Command)
+ {
+ return "GetListBoxText() is not done!";
+ }
+ }
+
+ public class clWarp // Warp
+ {
+ public static byte GetWarpID(LevelscriptCommand Command)
+ {
+ Command.Position = 2;
+ byte ID = Command.ReadByte();
+ Command.Position = 0;
+ return ID;
+ }
+
+ public static void SetWarpID(LevelscriptCommand Command, byte ID)
+ {
+ Command.Position = 2;
+ Command.Write(ID);
+ Command.Position = 0;
+ }
+
+ public static Levels GetDestinationLevelID(LevelscriptCommand Command)
+ {
+ Command.Position = 3;
+ Levels LevelID = (Levels)Command.ReadByte();
+ Command.Position = 0;
+ return LevelID;
+ }
+
+ public static void SetDestinationLevelID(LevelscriptCommand Command, Levels LevelID)
+ {
+ Command.Position = 3;
+ Command.WriteByte((byte)LevelID);
+ Command.Position = 0;
+ }
+
+ public static byte GetDestinationAreaID(LevelscriptCommand Command)
+ {
+ Command.Position = 4;
+ byte ID = Command.ReadByte();
+ Command.Position = 0;
+ return ID;
+ }
+
+ public static void SetDestinationAreaID(LevelscriptCommand Command, byte ID)
+ {
+ Command.Position = 4;
+ Command.Write(ID);
+ Command.Position = 0;
+ }
+
+ public static byte GetDestinationWarpID(LevelscriptCommand Command)
+ {
+ Command.Position = 5;
+ byte ID = Command.ReadByte();
+ Command.Position = 0;
+ return ID;
+ }
+
+ public static void SetDestinationWarpID(LevelscriptCommand Command, byte ID)
+ {
+ Command.Position = 5;
+ Command.Write(ID);
+ Command.Position = 0;
+ }
+
+ public static bool GetCreateCheckpoint(LevelscriptCommand cmd)
+ {
+ cmd.Position = 6;
+ return Bits.GetBoolOfByte(cmd.ReadByte(), 0);
+ }
+
+ public static void SetCreateCheckpoint(LevelscriptCommand cmd, bool value)
+ {
+ cmd.Position = 6;
+ byte b = cmd.ReadByte();
+ b = Bits.SetInByte(b, 0, value);
+ cmd.Position -= 1;
+ cmd.Write(b);
+ }
+ }
+
+ public class clInstantWarp
+ {
+ public static byte GetCollisionType(LevelscriptCommand cmd)
+ {
+ cmd.Position = 2;
+ byte value = cmd.ReadByte();
+ cmd.Position = 0;
+ return value;
+ }
+
+ public static void SetCollisionType(LevelscriptCommand cmd, byte collisionType)
+ {
+ cmd.Position = 2;
+ cmd.WriteByte(collisionType);
+ cmd.Position = 0;
+ }
+
+ public static byte GetAreaID(LevelscriptCommand cmd)
+ {
+ cmd.Position = 3;
+ byte value = cmd.ReadByte();
+ cmd.Position = 0;
+ return value;
+ }
+
+ public static void SetAreaID(LevelscriptCommand cmd, byte areaID)
+ {
+ cmd.Position = 3;
+ cmd.WriteByte(areaID);
+ cmd.Position = 0;
+ }
+
+ public static Vector3 GetLocation(LevelscriptCommand cmd)
+ {
+ cmd.Position = 4;
+ short x = cmd.ReadInt16();
+ short y = cmd.ReadInt16();
+ short z = cmd.ReadInt16();
+ cmd.Position = 0;
+ return new Vector3(x, y, z);
+ }
+
+ public static void SetLocation(LevelscriptCommand cmd, Vector3 loc)
+ {
+ cmd.Position = 4;
+ cmd.Write(Convert.ToInt16(loc.X));
+ cmd.Write(Convert.ToInt16(loc.Y));
+ cmd.Write(Convert.ToInt16(loc.Z));
+ cmd.Position = 0;
+ }
+ }
+
+ public class clAreaCollision // Area Collision
+ {
+ public static uint GetAreaCollision(LevelscriptCommand Command)
+ {
+ Command.Position = 4;
+ uint AreaCollision = Command.ReadUInt32();
+ Command.Position = 0;
+ return AreaCollision;
+ }
+
+ public static void SetAreaCollision(LevelscriptCommand Command, uint AreaCollision)
+ {
+ Command.Position = 4;
+ Command.Write(AreaCollision);
+ Command.Position = 0;
+ }
+ }
+
+ public class clTerrian // Terrian-Type
+ {
+ public static byte GetTerrainType(LevelscriptCommand Command)
+ {
+ Command.Position = 3;
+ int Type = Command.ReadByte();
+ Command.Position = 0;
+ return Convert.ToByte(Type);
+ }
+
+ public static void SetTerrainType(LevelscriptCommand Command, byte Type)
+ {
+ Command.Position = 3;
+ Command.Write(Type);
+ Command.Position = 0;
+ }
+ }
+
+ public class clAreaMusic // Area Music
+ {
+ public static byte GetMusicID(LevelscriptCommand Command)
+ {
+ Command.Position = 5;
+ int MusicID = Command.ReadByte();
+ Command.Position = 0;
+ return Convert.ToByte(MusicID);
+ }
+
+ public static void SetMusicID(LevelscriptCommand Command, byte MusicID)
+ {
+ Command.Position = 5;
+ Command.Write(MusicID);
+ Command.Position = 0;
+ }
+ }
+
+ public class clAreaMusicSimple // Area Music
+ {
+ public static byte GetMusicID(LevelscriptCommand Command)
+ {
+ Command.Position = 3;
+ int MusicID = Command.ReadByte();
+ Command.Position = 0;
+ return Convert.ToByte(MusicID);
+ }
+
+ public static void SetMusicID(LevelscriptCommand Command, byte MusicID)
+ {
+ Command.Position = 3;
+ Command.Write(MusicID);
+ Command.Position = 0;
+ }
+ }
+
+ public class clLoadRomToRam // 0x17 Load ROM to RAM
+ {
+ public static byte GetParam1(LevelscriptCommand Command)
+ {
+ Command.Position = 2;
+ int value = Command.ReadByte();
+ Command.Position = 0;
+ return Convert.ToByte(value);
+ }
+
+ public static void SetParam1(LevelscriptCommand Command, byte value)
+ {
+ Command.Position = 2;
+ Command.Write(value);
+ Command.Position = 0;
+ }
+
+ public static byte GetSegmentedID(LevelscriptCommand Command)
+ {
+ Command.Position = 3;
+ int value = Command.ReadByte();
+ Command.Position = 0;
+ return Convert.ToByte(value);
+ }
+
+ public static void SetSegmentedID(LevelscriptCommand Command, byte ID)
+ {
+ Command.Position = 3;
+ Command.Write(ID);
+ Command.Position = 0;
+ }
+
+ public static int GetRomStart(LevelscriptCommand Command)
+ {
+ Command.Position = 4;
+ int value = Command.ReadInt32();
+ Command.Position = 0;
+ return value;
+ }
+
+ public static void SetRomStart(LevelscriptCommand Command, int Address)
+ {
+ Command.Position = 4;
+ Command.Write(Address);
+ Command.Position = 0;
+ }
+
+ public static int GetRomEnd(LevelscriptCommand Command)
+ {
+ Command.Position = 8;
+ uint value = Command.ReadUInt32();
+ Command.Position = 0;
+ return Convert.ToInt32(value);
+ }
+
+ public static void SetRomEnd(LevelscriptCommand Command, int Address)
+ {
+ Command.Position = 8;
+ Command.Write(Convert.ToUInt32(Address));
+ Command.Position = 0;
+ }
+
+ public static int GetSegmentedAddressToJump(LevelscriptCommand Command)
+ {
+ Command.Position = 12;
+ uint value = Command.ReadUInt32();
+ Command.Position = 0;
+ return Convert.ToInt32(value);
+ }
+
+ public static void SetSegmentedAddressToJump(LevelscriptCommand Command, int Address)
+ {
+ Command.Position = 12;
+ Command.Write(Convert.ToUInt32(Address));
+ Command.Position = 0;
+ }
+ }
+
+ public class clDefaultPosition
+ {
+ public static byte GetAreaID(LevelscriptCommand Command)
+ {
+ Command.Position = 2;
+ byte value = Command.ReadByte();
+ Command.Position = 0;
+ return value;
+ }
+
+ public static void SetAreaID(LevelscriptCommand Command, byte value)
+ {
+ Command.Position = 2;
+ Command.Write(value);
+ Command.Position = 0;
+ }
+
+ public static Vector3 GetPosition(LevelscriptCommand Command)
+ {
+ Command.Position = 6;
+ var value = new Vector3();
+ value.X = Command.ReadInt16();
+ value.Y = Command.ReadInt16();
+ value.Z = Command.ReadInt16();
+ Command.Position = 0;
+ return value;
+ }
+
+ public static void SetPosition(LevelscriptCommand Command, Vector3 value)
+ {
+ Command.Position = 6;
+ Command.Write(Convert.ToInt16(value.X));
+ Command.Write(Convert.ToInt16(value.Y));
+ Command.Write(Convert.ToInt16(value.Z));
+ Command.Position = 0;
+ }
+
+ public static void SetPosition(LevelscriptCommand Command, int X, int Y, int Z)
+ {
+ SetPosition(Command, new Vector3(X, Y, Z));
+ }
+
+ public static short GetRotation(LevelscriptCommand Command)
+ {
+ Command.Position = 4;
+ short value = Command.ReadInt16();
+ Command.Position = 0;
+ value = Convert.ToInt16(value % 360);
+ if (value < 0)
+ value *= -1;
+ return value;
+ }
+
+ public static void SetRotation(LevelscriptCommand Command, short value)
+ {
+ Command.Position = 4;
+ if (value < 0)
+ value *= -1;
+ value = Convert.ToInt16(value % 360);
+ Command.Write(value);
+ Command.Position = 0;
+ }
+ }
+
+ public class clLoadPolygonWithoutGeo : clLoadPolygonWithGeo
+ {
+ public static byte GetDrawingLayer(LevelscriptCommand Command)
+ {
+ Command.Position = 3;
+ int areaid = Command.ReadByte();
+ Command.Position = 0;
+ return Convert.ToByte(areaid >> 4);
+ }
+
+ public static void SetDrawingLayer(LevelscriptCommand Command, byte layer)
+ {
+ Command.Position = 3;
+ Command.Write(layer << 4);
+ Command.Position = 0;
+ }
+ }
+
+ public class clLoadPolygonWithGeo
+ {
+ public static byte GetModelID(LevelscriptCommand Command)
+ {
+ Command.Position = 3;
+ int areaid = Command.ReadByte();
+ Command.Position = 0;
+ return Convert.ToByte(areaid);
+ }
+
+ public static void SetModelID(LevelscriptCommand Command, byte ModelID)
+ {
+ Command.Position = 3;
+ Command.Write(ModelID);
+ Command.Position = 0;
+ }
+
+ public static int GetSegAddress(LevelscriptCommand Command)
+ {
+ Command.Position = 4;
+ int SegGeolayoutAddr = Command.ReadInt32();
+ Command.Position = 0;
+ return SegGeolayoutAddr;
+ }
+
+ public static void SetSegAddress(LevelscriptCommand Command, int SegAddress)
+ {
+ Command.Position = 4;
+ Command.Write(SegAddress);
+ Command.Position = 0;
+ }
+ }
+
+ public class clShowDialog
+ {
+ public static byte GetDialogID(LevelscriptCommand cmd)
+ {
+ cmd.Position = 3;
+ byte dialogID = cmd.ReadByte();
+ cmd.Position = 0;
+ return dialogID;
+ }
+
+ public static void SetDialogID(LevelscriptCommand cmd, byte dialogID)
+ {
+ cmd.Position = 3;
+ cmd.WriteByte(dialogID);
+ cmd.Position = 0;
+ }
+ }
+
+ public class clScrollingTexture
+ {
+ public static ushort GetCountOfFaces(LevelscriptCommand cmd)
+ {
+ cmd.Position = 4;
+ ushort val = cmd.ReadUInt16();
+ cmd.Position = 0;
+ return val;
+ }
+
+ public static void SetCountOfFaces(LevelscriptCommand cmd, ushort count)
+ {
+ cmd.Position = 4;
+ cmd.Write(count);
+ cmd.Position = 0;
+ }
+
+ public static uint GetVertexPointer(LevelscriptCommand cmd)
+ {
+ cmd.Position = 0x10;
+ int value = cmd.ReadInt32();
+ return Convert.ToUInt32(value);
+ }
+
+ public static void SetVertexPointer(LevelscriptCommand cmd, uint ptr)
+ {
+ cmd.Position = 0x10;
+ cmd.Write(Convert.ToInt32(ptr));
+ cmd.Position = 0;
+ }
+
+ public static byte GetCycleDuration(LevelscriptCommand cmd)
+ {
+ cmd.Position = 7;
+ byte val = cmd.ReadByte();
+ cmd.Position = 0;
+ return val;
+ }
+
+ public static void SetCycleDuration(LevelscriptCommand cmd, byte ptr)
+ {
+ cmd.Position = 7;
+ cmd.WriteByte(ptr);
+ cmd.Position = 0;
+ }
+
+ public static byte GetScrollBehavior(LevelscriptCommand cmd)
+ {
+ cmd.Position = 6;
+ byte val = Convert.ToByte(cmd.ReadByte() & 0xE0);
+ cmd.Position = 0;
+ return val;
+ }
+
+ public static void SetScrollBehavior(LevelscriptCommand cmd, byte behav)
+ {
+ byte val = GetScrollType(cmd);
+ val = (byte)(val | behav);
+ cmd.Position = 6;
+ cmd.WriteByte(val);
+ cmd.Position = 0;
+ }
+
+ public static byte GetScrollType(LevelscriptCommand cmd)
+ {
+ cmd.Position = 6;
+ byte val = Convert.ToByte(cmd.ReadByte() & 0x1F);
+ cmd.Position = 0;
+ return val;
+ }
+
+ public static void SetScrollType(LevelscriptCommand cmd, byte behav)
+ {
+ byte val = GetScrollBehavior(cmd);
+ val = (byte)(val | behav);
+ cmd.Position = 6;
+ cmd.WriteByte(val);
+ cmd.Position = 0;
+ }
+
+ public static short GetScrollSpeed(LevelscriptCommand cmd)
+ {
+ cmd.Position = 8;
+ short val = cmd.ReadInt16();
+ cmd.Position = 0;
+ return val;
+ }
+
+ public static void SetScrollSpeed(LevelscriptCommand cmd, short count)
+ {
+ cmd.Position = 8;
+ cmd.Write(Convert.ToInt16(Math.Min((int)count, 0xFFF)));
+ cmd.Position = 0;
+ }
+
+ public static short GetGroupID(LevelscriptCommand Command)
+ {
+ Command.Position = 14;
+ short id = Command.ReadInt16();
+ Command.Position = 0;
+ return id;
+ }
+
+ public static void SetGroupID(LevelscriptCommand Command, short id)
+ {
+ Command.Position = 14;
+ Command.Write(id);
+ Command.Position = 0;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/SM64Lib/Level/Script/LevelscriptCommandTypes.cs b/SM64Lib/Level/Script/LevelscriptCommandTypes.cs
new file mode 100644
index 0000000..071feba
--- /dev/null
+++ b/SM64Lib/Level/Script/LevelscriptCommandTypes.cs
@@ -0,0 +1,49 @@
+using System;
+
+namespace SM64Lib.Levels.Script
+{
+ [Serializable]
+ public enum LevelscriptCommandTypes
+ {
+ x00 = 0x0,
+ x01 = 0x1,
+ EndOfLevel = 0x2,
+ x03 = 0x3,
+ x04 = 0x4,
+ x05 = 0x5,
+ JumpToSegAddr = 0x6,
+ JumpBack = 0x7,
+ x0A = 0xA,
+ x0C = 0xC,
+ x10 = 0x10,
+ x11 = 0x11,
+ x12 = 0x12,
+ x16 = 0x16,
+ LoadRomToRam = 0x17,
+ x18 = 0x18,
+ x19 = 0x19,
+ x1A = 0x1A,
+ x1B = 0x1B,
+ x1C = 0x1C,
+ x1D = 0x1D,
+ x1E = 0x1E,
+ StartArea = 0x1F,
+ EndOfArea = 0x20,
+ LoadPolygonWithGeo = 0x22,
+ LoadPolygonWithoutGeo = 0x21,
+ Normal3DObject = 0x24,
+ x25 = 0x25,
+ ConnectedWarp = 0x26,
+ PaintingWarp = 0x27,
+ InstantWarp = 0x28,
+ DefaultPosition = 0x2B,
+ AreaCollision = 0x2E,
+ ShowDialog = 0x30,
+ Tarrain = 0x31,
+ x34 = 0x34,
+ AreaMusic = 0x36,
+ AreaMusicSimple = 0x37,
+ Macro3DObject = 0x39,
+ x3B = 0x3B
+ }
+}
\ No newline at end of file
diff --git a/SM64Lib/Level/Script/Managed Commands/IManagedLevelscriptCommand.cs b/SM64Lib/Level/Script/Managed Commands/IManagedLevelscriptCommand.cs
new file mode 100644
index 0000000..f9465a2
--- /dev/null
+++ b/SM64Lib/Level/Script/Managed Commands/IManagedLevelscriptCommand.cs
@@ -0,0 +1,12 @@
+
+namespace SM64Lib.Levels.Script
+{
+ public interface IManagedLevelscriptCommand
+ {
+ LevelscriptCommand Command { get; }
+
+ void LoadProperties();
+ void SaveProperties();
+ void SaveProperties(LevelscriptCommand Command);
+ }
+}
\ No newline at end of file
diff --git a/SM64Lib/Level/Script/Managed Commands/ManagedScrollingTextures.cs b/SM64Lib/Level/Script/Managed Commands/ManagedScrollingTextures.cs
new file mode 100644
index 0000000..6409b37
--- /dev/null
+++ b/SM64Lib/Level/Script/Managed Commands/ManagedScrollingTextures.cs
@@ -0,0 +1,252 @@
+using global::System.ComponentModel;
+using Microsoft.VisualBasic.CompilerServices;
+using SM64Lib.Data.System;
+using global::SM64Lib.Levels.Script;
+using global::SM64Lib.Levels.Script.Commands;
+using SM64Lib.Configuration;
+using Newtonsoft.Json;
+using System;
+
+namespace SM64Lib.Levels.ScrolTex
+{
+ public class ManagedScrollingTexture : IManagedLevelscriptCommand
+ {
+
+ ///
+ /// The underlying levelscript command which provide all the properties.
+ ///
+ ///
+ [JsonProperty]
+ public LevelscriptCommand Command { get; private set; } = null;
+
+ [JsonConstructor]
+ private ManagedScrollingTexture(JsonConstructorAttribute attr)
+ {
+ }
+
+ ///
+ /// Creates a new managed scrolling texture instance with an new levelscript command.
+ ///
+ public ManagedScrollingTexture()
+ {
+ Command = new LevelscriptCommand("24 18 1F 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 40 17 00");
+ SaveProperties();
+ }
+
+ ///
+ /// Creates a new managed scrolling texture instance using some data with an new levelscript command.
+ ///
+ /// Amount of faces to count.
+ /// Pointer to the Vertices.
+ ///
+ public ManagedScrollingTexture(ushort facesCount, int vertexPtr, short groupID) : this()
+ {
+ FacesCount = facesCount;
+ VertexPointer = vertexPtr;
+ GroupID = groupID;
+ }
+
+ ///
+ /// Creates a new managed scrolling texture instance using an existing levelscript command.
+ ///
+ /// The levelscript command to use.
+ public ManagedScrollingTexture(LevelscriptCommand cmd)
+ {
+ Command = cmd;
+ LoadProperties();
+ }
+
+ ///
+ /// Indicates the group (by default deticted to the used texture).
+ ///
+ ///
+ [DisplayName("Group ID")]
+ [Description("Indicates the group ID (by default deticted to the used texture).")]
+ [Category("Grouping")]
+ public short GroupID { get; set; } = 0;
+
+ ///
+ /// Indicates the cycle duration of sine wave or jumping scrolling.
+ ///
+ ///
+ [DisplayName("Behavior")]
+ [Description("Indicates the axis and behavior.")]
+ [Category("Behavior")]
+ public ScrollBehavior Behavior { get; set; } = ScrollBehavior.ScrollUVBackAndForth;
+
+ ///
+ /// Indicates the scroll behavior.
+ ///
+ ///
+ [DisplayName("Type")]
+ [Description("Indicates the scroll type.")]
+ [Category("Behavior")]
+ public ScrollType Type { get; set; } = ScrollType.NormalScrolling;
+
+ ///
+ /// The Scrolling Speed per Frame (less then 0x1000).
+ ///
+ ///
+ [DisplayName("Scrolling Speed")]
+ [Description("The Scrolling Speed per Frame (less then 0x1000).")]
+ [Category("Params")]
+ public short ScrollingSpeed { get; set; } = 60;
+
+ ///
+ /// Indicates the duration of a cycle in frames.
+ ///
+ ///
+ [DisplayName("Cycle Duration")]
+ [Description("Cycle duration of the sine wave or jumpy scrolling in frames.")]
+ [Category("Params")]
+ public byte CycleDuration { get; set; } = 20;
+
+ ///
+ /// Amount of faces to count.
+ ///
+ ///
+ [DisplayName("Vertex Data")]
+ [Description("Amount of vertices to count.")]
+ [Category("Data")]
+ public ushort FacesCount { get; set; } = 0;
+
+ ///
+ /// Pointer to the vertices.
+ ///
+ ///
+ [DisplayName("Vertex Data")]
+ [Description("Pointer to the vertices.")]
+ [Category("Data")]
+ public int VertexPointer { get; set; } = 0;
+ [DisplayName("Act 1")]
+ [Category("Acts")]
+ [Description("If Yes, the Object will be visible if you select Star 1.")]
+ public bool Act1 { get; set; } = true;
+ [DisplayName("Act 2")]
+ [Category("Acts")]
+ [Description("If Yes, the Object will be visible if you select Star 2.")]
+ public bool Act2 { get; set; } = true;
+ [DisplayName("Act 3")]
+ [Category("Acts")]
+ [Description("If Yes, the Object will be visible if you select Star 3.")]
+ public bool Act3 { get; set; } = true;
+ [DisplayName("Act 4")]
+ [Category("Acts")]
+ [Description("If Yes, the Object will be visible if you select Star 4.")]
+ public bool Act4 { get; set; } = true;
+ [DisplayName("Act 5")]
+ [Category("Acts")]
+ [Description("If Yes, the Object will be visible if you select Star 5.")]
+ public bool Act5 { get; set; } = true;
+ [DisplayName("Act 6")]
+ [Category("Acts")]
+ [Description("If Yes, the Object will be visible if you select Star 6.")]
+ public bool Act6 { get; set; } = false;
+
+ [DisplayName("All Acts")]
+ [Category("Acts")]
+ [Description("If Yes, the Object will be visible always.")]
+ public bool AllActs
+ {
+ get
+ {
+ return Act1 && Act2 && Act3 && Act4 && Act5 && !Act6;
+ }
+
+ set
+ {
+ Act1 = value;
+ Act2 = value;
+ Act3 = value;
+ Act4 = value;
+ Act5 = value;
+ Act6 = false;
+ }
+ }
+
+ public void LoadProperties()
+ {
+ CycleDuration = clScrollingTexture.GetCycleDuration(Command);
+ VertexPointer = Convert.ToInt32(clScrollingTexture.GetVertexPointer(Command));
+ FacesCount = clScrollingTexture.GetCountOfFaces(Command);
+ ScrollingSpeed = clScrollingTexture.GetScrollSpeed(Command);
+ Type = (ScrollType)clScrollingTexture.GetScrollType(Command);
+ Behavior = (ScrollBehavior)clScrollingTexture.GetScrollBehavior(Command);
+ GroupID = clScrollingTexture.GetGroupID(Command);
+ var acts = Bits.ByteToBoolArray(clNormal3DObject.GetActs(Command));
+ Act1 = acts[7];
+ Act2 = acts[6];
+ Act3 = acts[5];
+ Act4 = acts[4];
+ Act5 = acts[3];
+ Act6 = acts[2];
+ }
+
+ public void SaveProperties()
+ {
+ SaveProperties((ScrollTexConfig)null);
+ }
+
+ public void SaveProperties(ScrollTexConfig config)
+ {
+ SaveProperties(Command, config);
+ }
+
+ public void SaveProperties(LevelscriptCommand Command)
+ {
+ SaveProperties(Command, null);
+ }
+
+ public void SaveProperties(LevelscriptCommand Command, ScrollTexConfig config)
+ {
+ clScrollingTexture.SetCycleDuration(Command, CycleDuration);
+ clScrollingTexture.SetVertexPointer(Command, Convert.ToUInt32(VertexPointer));
+ clScrollingTexture.SetCountOfFaces(Command, FacesCount);
+ clScrollingTexture.SetScrollSpeed(Command, ScrollingSpeed);
+ clScrollingTexture.SetScrollType(Command, (byte)Type);
+ clScrollingTexture.SetScrollBehavior(Command, (byte)Behavior);
+ clScrollingTexture.SetGroupID(Command, GroupID);
+ clNormal3DObject.SetSegBehaviorAddr(Command,
+ config is not null && config.UseCustomBehavior && config.CustomBehaviorAddress > -1 ? (uint)config.CustomBehaviorAddress : 0x401700);
+ var acts = new[] { false, false, false, false, false, false, false, false };
+ acts[7] = Act1;
+ acts[6] = Act2;
+ acts[5] = Act3;
+ acts[4] = Act4;
+ acts[3] = Act5;
+ acts[2] = Act6;
+ clNormal3DObject.SetActs(Command, Bits.ArrayToByte(acts));
+ }
+
+ public void CopyPropsTo(ManagedScrollingTexture mst)
+ {
+ mst.CycleDuration = CycleDuration;
+ mst.ScrollingSpeed = ScrollingSpeed;
+ mst.Type = Type;
+ mst.Behavior = Behavior;
+
+ mst.Act1 = Act1;
+ mst.Act2 = Act2;
+ mst.Act3 = Act3;
+ mst.Act4 = Act4;
+ mst.Act5 = Act5;
+ mst.Act6 = Act6;
+ }
+ }
+
+ public enum ScrollBehavior
+ {
+ ScrollingX = 0x0,
+ ScrollingY = 0x20,
+ ScrollingZ = 0x40,
+ ScrollUVBackAndForth = 0x80,
+ ScrollUVLeftAndRight = 0xA0
+ }
+
+ public enum ScrollType
+ {
+ NormalScrolling = 0x0,
+ SineWave = 0x1,
+ JumpingScroll = 0x2
+ }
+}
\ No newline at end of file
diff --git a/SM64Lib/Level/ShowMessage.cs b/SM64Lib/Level/ShowMessage.cs
new file mode 100644
index 0000000..31fa78f
--- /dev/null
+++ b/SM64Lib/Level/ShowMessage.cs
@@ -0,0 +1,9 @@
+
+namespace SM64Lib.Levels
+{
+ public class ShowMessage
+ {
+ public bool Enabled { get; set; } = false;
+ public byte DialogID { get; set; } = 0;
+ }
+}
\ No newline at end of file
diff --git a/SM64Lib/Level/SpecialBox.cs b/SM64Lib/Level/SpecialBox.cs
new file mode 100644
index 0000000..f21db16
--- /dev/null
+++ b/SM64Lib/Level/SpecialBox.cs
@@ -0,0 +1,48 @@
+using global::System.IO;
+using SM64Lib.Data;
+using SM64Lib.Data.System;
+
+namespace SM64Lib.Levels
+{
+ public class SpecialBox
+ {
+ public SpecialBoxType Type { get; set; } = SpecialBoxType.Water;
+ public short X1 { get; set; } = 8192;
+ public short Z1 { get; set; } = 8192;
+ public short X2 { get; set; } = -8192;
+ public short Z2 { get; set; } = -8192;
+ public short Y { get; set; } = 0;
+ public short Scale { get; set; } = 16;
+ public bool InvisibleWater { get; set; } = false;
+ public WaterType WaterType { get; set; } = WaterType.Default;
+ public byte Alpha { get; set; } = 78;
+
+ public void ToBoxData(BinaryData data)
+ {
+ // Stand: SM64 Editor v2.0.7
+
+ data.Write(InvisibleWater ? 0x0 : 0x10000); // Type = SpecialBoxType.ToxicHaze OrElse
+ data.Write((short)0xF);
+ data.Write(Scale);
+ data.Write(X1);
+ data.Write(Z1);
+ data.Write(X2);
+ data.Write(Z1);
+ data.Write(X2);
+ data.Write(Z2);
+ data.Write(X1);
+ data.Write(Z2);
+
+ if (Type == SpecialBoxType.ToxicHaze)
+ {
+ data.Write(Alpha); // &HB4
+ data.Write(0x10000);
+ }
+ else
+ {
+ data.Write(0x10000 | Alpha);
+ data.Write((int)WaterType);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/SM64Lib/Level/SpecialBoxList.cs b/SM64Lib/Level/SpecialBoxList.cs
new file mode 100644
index 0000000..ad2dd9b
--- /dev/null
+++ b/SM64Lib/Level/SpecialBoxList.cs
@@ -0,0 +1,67 @@
+using System;
+using System.Collections.Generic;
+using global::System.IO;
+using System.Linq;
+using SM64Lib.Data.System;
+
+namespace SM64Lib.Levels
+{
+ public class SpecialBoxList : List
+ {
+ public void SortByHeight()
+ {
+ var boxes = this.OrderByDescending(n => n.Y).ToArray();
+ Clear();
+ base.AddRange(boxes);
+ }
+
+ public static SpecialBox[] ReadTable(string Romfile, SpecialBoxType Type, int Levelscriptstart, int TabelStart)
+ {
+ var fs = new FileStream(Romfile, FileMode.Open, FileAccess.Read);
+ var temp = ReadTable(fs, Type, Levelscriptstart, TabelStart);
+ fs.Close();
+ return temp;
+ }
+
+ public static SpecialBox[] ReadTable(Stream s, SpecialBoxType Type, int Levelscriptstart, int TabelStart)
+ {
+ var br = new BinaryReader(s);
+ var boxlist = new List();
+ s.Position = TabelStart;
+
+ if (SwapInts.SwapInt32(br.ReadInt32()) == 0x1010101)
+ return Array.Empty();
+ else
+ s.Position -= 0x4;
+
+ while (SwapInts.SwapUInt16(br.ReadUInt16()) != 0xFFFF)
+ {
+ s.Position += 0x2;
+ var tbox = new SpecialBox();
+ tbox.Type = Type;
+ int lastpos = (int)(s.Position + 0x4);
+ s.Position = SwapInts.SwapInt32(br.ReadInt32()) - 0x19000000 + Levelscriptstart;
+
+ if (Type == SpecialBoxType.Water)
+ tbox.InvisibleWater = SwapInts.SwapInt32(br.ReadInt32()) == 0x0;
+ else
+ s.Position += 0x4;
+
+ s.Position += 0x2;
+ tbox.Scale = SwapInts.SwapInt16(br.ReadInt16());
+ tbox.X1 = SwapInts.SwapInt16(br.ReadInt16());
+ tbox.Z1 = SwapInts.SwapInt16(br.ReadInt16());
+ tbox.X2 = SwapInts.SwapInt16(br.ReadInt16());
+ s.Position += 0x4;
+ tbox.Z2 = SwapInts.SwapInt16(br.ReadInt16());
+ s.Position += 0x7;
+ tbox.Alpha = br.ReadByte();
+ tbox.WaterType = (WaterType)SwapInts.SwapInt32(br.ReadInt32());
+ s.Position = lastpos;
+ boxlist.Add(tbox);
+ }
+
+ return boxlist.ToArray();
+ }
+ }
+}
\ No newline at end of file
diff --git a/SM64Lib/Level/WarpTools.cs b/SM64Lib/Level/WarpTools.cs
new file mode 100644
index 0000000..7a70b66
--- /dev/null
+++ b/SM64Lib/Level/WarpTools.cs
@@ -0,0 +1,43 @@
+using System.Collections.Generic;
+using System.Data;
+using System.Linq;
+using Microsoft.VisualBasic.CompilerServices;
+using global::SM64Lib.Levels.Script;
+using global::SM64Lib.Levels.Script.Commands;
+using System;
+
+namespace SM64Lib.Levels
+{
+ public class WarpTools
+ {
+ public static int GetWarpsCountInArea(LevelArea cArea)
+ {
+ int count = 0;
+ count += cArea.Warps.Where(n => new[] { LevelscriptCommandTypes.PaintingWarp, LevelscriptCommandTypes.ConnectedWarp }.Contains(n.CommandType)).Count();
+ count += cArea.WarpsForGame.Concat(cArea.Warps).Count();
+ return count;
+ }
+
+ public static int GetWarpsCountInLevel(Level cLevel)
+ {
+ int count = 0;
+ foreach (LevelArea a in cLevel.Areas)
+ count += GetWarpsCountInArea(a);
+ return count;
+ }
+
+ public static byte GetNextUnusedWarpID(LevelArea cArea)
+ {
+ var forbitten = new List();
+ foreach (LevelscriptCommand cmd in cArea.WarpsForGame.Concat(cArea.Warps))
+ forbitten.Add(clWarp.GetWarpID(cmd));
+ for (int i = byte.MinValue; i <= byte.MaxValue; i++)
+ {
+ if (!forbitten.Contains(Convert.ToByte(i)))
+ return Convert.ToByte(i);
+ }
+
+ return byte.MaxValue;
+ }
+ }
+}
\ No newline at end of file
diff --git a/SM64Lib/Model/Collision/BoxData.cs b/SM64Lib/Model/Collision/BoxData.cs
new file mode 100644
index 0000000..04b33fd
--- /dev/null
+++ b/SM64Lib/Model/Collision/BoxData.cs
@@ -0,0 +1,42 @@
+
+namespace SM64Lib.Model.Collision
+{
+ public class BoxData
+ {
+ public BoxDataType Type { get; set; } = BoxDataType.Water;
+ public short X1 { get; set; }
+ public short X2 { get; set; }
+ public short Z1 { get; set; }
+ public short Z2 { get; set; }
+ public short Y { get; set; }
+ public short Index { get; set; }
+
+ public BoxData()
+ {
+ X1 = 8192;
+ X2 = -8192;
+ Z1 = 8192;
+ Z2 = -8192;
+ Y = 0;
+ Index = 0;
+ }
+
+ public BoxData(Levels.SpecialBox SpecialBox, short Y)
+ {
+ X1 = SpecialBox.X1;
+ X2 = SpecialBox.X2;
+ Z1 = SpecialBox.Z1;
+ Z2 = SpecialBox.Z2;
+ this.Y = Y;
+ }
+
+ public BoxData(BoxData WaterBox)
+ {
+ X1 = WaterBox.X1;
+ X2 = WaterBox.X2;
+ Z1 = WaterBox.Z1;
+ Z2 = WaterBox.Z2;
+ Y = WaterBox.Y;
+ }
+ }
+}
\ No newline at end of file
diff --git a/SM64Lib/Model/Collision/BoxDataType.cs b/SM64Lib/Model/Collision/BoxDataType.cs
new file mode 100644
index 0000000..dc90567
--- /dev/null
+++ b/SM64Lib/Model/Collision/BoxDataType.cs
@@ -0,0 +1,10 @@
+
+namespace SM64Lib.Model.Collision
+{
+ public enum BoxDataType : short
+ {
+ Water = 0,
+ ToxicHaze = 0x32,
+ Mist = 0x33
+ }
+}
\ No newline at end of file
diff --git a/SM64Lib/Model/Collision/ColMesh.cs b/SM64Lib/Model/Collision/ColMesh.cs
new file mode 100644
index 0000000..540e4e4
--- /dev/null
+++ b/SM64Lib/Model/Collision/ColMesh.cs
@@ -0,0 +1,68 @@
+using System.Collections.Generic;
+
+namespace SM64Lib.Model.Collision
+{
+ public class ColMesh
+ {
+ public VertexList Vertices { get; set; } = new VertexList();
+ public TriangleList Triangles { get; set; } = new TriangleList();
+
+ public ColMesh[] SplitMesh()
+ {
+ return SplitMesh(this);
+ }
+
+ public static ColMesh[] SplitMesh(ColMesh mesh)
+ {
+ var meshes = new List();
+ if (mesh.Vertices.Count > short.MaxValue || mesh.Triangles.Count > short.MaxValue)
+ {
+ var curMesh = new ColMesh();
+ var curVertCopies = new Dictionary();
+ foreach (Triangle t in mesh.Triangles)
+ {
+ var newTri = new Triangle();
+ newTri.CollisionType = t.CollisionType;
+ for (int i = 0, loopTo = t.ColParams.Length - 1; i <= loopTo; i++)
+ newTri.ColParams[i] = t.ColParams[i];
+ for (int i = 0, loopTo1 = t.Vertices.Length - 1; i <= loopTo1; i++)
+ {
+ var v = t.Vertices[i];
+ if (curVertCopies.ContainsKey(v))
+ {
+ newTri.Vertices[i] = curVertCopies[v];
+ }
+ else
+ {
+ var newVert = new Vertex();
+ newVert.X = v.X;
+ newVert.Y = v.Y;
+ newVert.Z = v.Z;
+ curMesh.Vertices.Add(newVert);
+ curVertCopies.Add(v, newVert);
+ newTri.Vertices[i] = newVert;
+ }
+ }
+
+ if (curMesh.Vertices.Count > short.MaxValue - 3 || curMesh.Triangles.Count >= short.MaxValue)
+ {
+ meshes.Add(curMesh);
+ curMesh = new ColMesh();
+ curVertCopies.Clear();
+ }
+ }
+
+ if (!meshes.Contains(curMesh) && curMesh.Triangles.Count > 0 && curMesh.Vertices.Count > 0)
+ {
+ meshes.Add(curMesh);
+ }
+ }
+ else
+ {
+ meshes.Add(mesh);
+ }
+
+ return meshes.ToArray();
+ }
+ }
+}
\ No newline at end of file
diff --git a/SM64Lib/Model/Collision/CollisionMap.cs b/SM64Lib/Model/Collision/CollisionMap.cs
new file mode 100644
index 0000000..37632fa
--- /dev/null
+++ b/SM64Lib/Model/Collision/CollisionMap.cs
@@ -0,0 +1,469 @@
+using System;
+using System.Collections.Generic;
+using System.Data;
+using global::System.IO;
+using System.Linq;
+using global::System.Numerics;
+using System.Threading.Tasks;
+using Microsoft.VisualBasic.CompilerServices;
+using global::Pilz.S3DFileParser;
+using global::SM64Lib.Extensions;
+using global::SM64Lib.Data;
+using SM64Lib.Configuration;
+
+namespace SM64Lib.Model.Collision
+{
+ public class CollisionMap : IToObject3D
+ {
+ private int _Length = 0;
+
+ public ColMesh Mesh { get; set; } = new ColMesh();
+ public List SpecialBoxes { get; set; } = new List();
+
+ public void FromRom(string FileName, int RomOffset, CollisionBasicConfig config)
+ {
+ var fs = new FileStream(FileName, FileMode.Open, FileAccess.Read);
+ FromStream(fs, RomOffset, config);
+ fs.Close();
+ }
+
+ public Task FromRomAsync(string FileName, int RomOffset, CollisionBasicConfig config)
+ {
+ var t = new Task(() => FromRom(FileName, RomOffset, config));
+ t.Start();
+ return t;
+ }
+
+ public void FromStream(Stream s, int RomOffset, CollisionBasicConfig config)
+ {
+ FromBinaryData(new BinaryStreamData(s), RomOffset, config);
+ }
+
+ public void FromBinaryData(BinaryData s, int dataOffset, CollisionBasicConfig config)
+ {
+ bool endlessOn = true;
+ Mesh = new ColMesh();
+ SpecialBoxes.Clear();
+ s.Position = dataOffset;
+
+ do
+ {
+ short curVal = s.ReadInt16();
+ switch (curVal)
+ {
+ case 0x40: // S T A R T O F V E R T I C E S
+ // Lese Eckpunkte / Read Vertices
+ for (int i = 1, loopTo = s.ReadUInt16(); i <= loopTo; i++)
+ {
+ var nVert = new Vertex()
+ {
+ X = s.ReadInt16(),
+ Y = s.ReadInt16(),
+ Z = s.ReadInt16()
+ };
+ Mesh.Vertices.Add(nVert);
+ }
+
+ // Lese und erstelle Polygone / Read and build polygones
+ if (Mesh.Vertices.Count > 0)
+ {
+ bool ende = false;
+ while (!ende)
+ {
+ short Coltype = s.ReadInt16();
+ if (!Coltype.IsInRange(0x40, 0x44))
+ {
+ for (int i = 1, loopTo1 = s.ReadUInt16(); i <= loopTo1; i++)
+ {
+ var pol = new Triangle() { CollisionType = Convert.ToByte(Coltype) };
+ for (int iv = 0, loopTo2 = pol.Vertices.Count() - 1; iv <= loopTo2; iv++)
+ pol.Vertices[iv] = Mesh.Vertices[s.ReadInt16()];
+ if (config.CollisionTypesWithParams.Contains(Convert.ToByte(Coltype)))
+ {
+ for (int ip = 0, loopTo3 = pol.ColParams.Count() - 1; ip <= loopTo3; ip++)
+ pol.ColParams[ip] = s.ReadByte();
+ }
+
+ Mesh.Triangles.Add(pol);
+ }
+ }
+ else
+ {
+ ende = true;
+ }
+ }
+ }
+
+ break;
+ case 0x41: // E N D O F 0 x 4 0 C O M M A N D
+ break;
+ case 0x42: // E N D O F C O L L I S I O N D A T A
+ endlessOn = false;
+ break;
+ case 0x43: // S P E C I A L O B J E C T
+ break;
+ case 0x44: // W A T E R B O X E S
+ SpecialBoxes.AddRange(ReadBoxData(s));
+ break;
+ case 0x33: // M I S T - Compatibility with old buggy RM versions
+ SpecialBoxes.AddRange(ReadBoxData(s));
+ break;
+ case 0x32: // T O X I C H A Z E - Compatibility with old buggy RM versions
+ SpecialBoxes.AddRange(ReadBoxData(s));
+ break;
+ }
+ }
+ while (endlessOn);
+ _Length = (int)(s.Position - dataOffset);
+ }
+
+ public Task FromStreamAsync(Stream s, int RomOffset, CollisionBasicConfig config)
+ {
+ var t = new Task(() => FromStream(s, RomOffset, config));
+ t.Start();
+ return t;
+ }
+
+ private static BoxData[] ReadBoxData(BinaryData s)
+ {
+ var spBoxes = new List();
+
+ for (int i = 1, loopTo = s.ReadInt16(); i <= loopTo; i++)
+ {
+ var wb = new BoxData();
+ short type = s.ReadInt16();
+ if (type < 0) // Compatibility with old buggy RM versions
+ {
+ if (type < 0x32)
+ type = 0;
+ else if (type > 0x33)
+ type = 0x33;
+ }
+ wb.Type = (BoxDataType)type;
+ wb.X1 = s.ReadInt16();
+ wb.Z1 = s.ReadInt16();
+ wb.X2 = s.ReadInt16();
+ wb.Z2 = s.ReadInt16();
+ wb.Y = s.ReadInt16();
+ spBoxes.Add(wb);
+ }
+
+ return spBoxes.ToArray();
+ }
+
+ public void FromObject3D(ObjectInputSettings ObjSettings, Object3D model, CollisionSettings colSettings = null)
+ {
+ var dicMatNames = new Dictionary();
+
+ // Clear Lists
+ Mesh.Vertices.Clear();
+ Mesh.Triangles.Clear();
+
+ // Create MatNames
+ foreach (KeyValuePair kvp in model.Materials)
+ dicMatNames.Add(kvp.Value, kvp.Key);
+
+ // Add Faces
+ var dicVertices = new Dictionary();
+ foreach (Mesh m in model.Meshes)
+ {
+ foreach (Face f in m.Faces.OrderBy(n => n.Material))
+ {
+ var cs = colSettings.GetEntry(dicMatNames[f.Material]);
+ if (!cs.IsNonSolid)
+ {
+ var t = new Triangle();
+ t.CollisionType = cs.CollisionType;
+ t.ColParams[0] = cs.CollisionParam1;
+ t.ColParams[1] = cs.CollisionParam2;
+ for (int i = 0, loopTo = Math.Min(f.Points.Count - 1, 2); i <= loopTo; i++)
+ {
+ Vertex v;
+ var curVert = f.Points[i].Vertex;
+ if (dicVertices.ContainsKey(curVert))
+ {
+ v = dicVertices[curVert];
+ }
+ else
+ {
+ v = new Vertex();
+ v.X = General.KeepInInt16Range(General.Round(curVert.X * ObjSettings.Scaling));
+ v.Y = General.KeepInInt16Range(General.Round(curVert.Y * ObjSettings.Scaling));
+ v.Z = General.KeepInInt16Range(General.Round(curVert.Z * ObjSettings.Scaling));
+ Mesh.Vertices.Add(v);
+ dicVertices.Add(curVert, v);
+ }
+
+ t.Vertices[i] = v;
+ }
+
+ Mesh.Triangles.Add(t);
+ }
+ }
+ }
+ }
+
+ public Task FromObject3DAsync(ObjectInputSettings ObjSettings, Object3D model, CollisionSettings colSettings = null)
+ {
+ var t = new Task(() => FromObject3D(ObjSettings, model, colSettings));
+ t.Start();
+ return t;
+ }
+
+ public void ToStream(Stream s, int RomOffset, CollisionBasicConfig config)
+ {
+ ToBinaryData(new BinaryStreamData(s), RomOffset, config);
+ }
+
+ public void ToBinaryData(BinaryData data, int dataOffset, CollisionBasicConfig config)
+ {
+ data.Position = dataOffset;
+ foreach (ColMesh mesh in Mesh.SplitMesh())
+ {
+
+ // V E R T I C E S
+
+ if (mesh.Vertices.Count > 0)
+ {
+ // Start vertices
+ data.Write(Convert.ToInt16(0x40));
+ data.Write(Convert.ToInt16(mesh.Vertices.Count));
+
+ // Write vertices data
+ foreach (var vert in mesh.Vertices)
+ {
+ data.Write(vert.X);
+ data.Write(vert.Y);
+ data.Write(vert.Z);
+ }
+ }
+
+ // P O L Y G O N E S
+
+ foreach (byte curType in UsedPolytypes(mesh))
+ {
+ // Search for all triangles with current collision type
+ var tries = mesh.Triangles.Where(n => n.CollisionType == curType).ToArray();
+ if (tries.Length > 0)
+ {
+ // Write new collision type
+ data.Write(Convert.ToInt16(curType));
+
+ // Write count of triangles
+ data.Write(Convert.ToInt16(tries.Length));
+
+ // Check if collisiontype has params
+ bool hasParams = config.CollisionTypesWithParams.Contains(curType);
+ foreach (Triangle tri in tries)
+ {
+ // Write Vertex Indicies
+ foreach (Vertex vert in tri.Vertices)
+ data.Write(Convert.ToInt16(vert.Index));
+
+ // Write Collision Params, if avaiable
+ if (hasParams)
+ {
+ for (int ip = 0, loopTo = tri.ColParams.Count() - 1; ip <= loopTo; ip++)
+ data.Write(tri.ColParams[ip]);
+ }
+ }
+ }
+ }
+
+ // E N D 0 x 4 0 C O M M A N D
+
+ data.Write(Convert.ToInt16(0x41));
+ }
+
+ // S P E C I A L O B J E C T S
+
+ // Dont know what this is.
+
+ // S P E C I A L B O X E S
+
+ if (SpecialBoxes.Any())
+ WriteBoxData(data, SpecialBoxes);
+
+ // E N D C O L L I S I O N D A T A
+
+ data.Write(Convert.ToInt16(0x42));
+ }
+
+ private static void WriteBoxData(BinaryData data, IEnumerable bodex)
+ {
+ data.Write((short)0x44);
+ data.Write(Convert.ToInt16(bodex.Count()));
+
+ foreach (BoxDataType t in Enum.GetValues(typeof(BoxDataType)))
+ {
+ foreach (var wb in bodex.Where(n => n.Type == t))
+ {
+ data.Write(wb.Index);
+ data.Write(wb.X1);
+ data.Write(wb.Z1);
+ data.Write(wb.X2);
+ data.Write(wb.Z2);
+ data.Write(wb.Y);
+ }
+ }
+ }
+
+ public long Length
+ {
+ get
+ {
+ long LengthRet = default;
+ LengthRet = _Length;
+ General.HexRoundUp2(ref LengthRet);
+ return LengthRet;
+ }
+
+ set
+ {
+ _Length = Convert.ToInt32(value);
+ }
+ }
+
+ public static byte[] UsedPolytypes(ColMesh mesh)
+ {
+ var types = new List();
+ foreach (Triangle tri in mesh.Triangles)
+ {
+ if (!types.Contains(tri.CollisionType))
+ {
+ types.Add(tri.CollisionType);
+ }
+ }
+
+ return types.ToArray();
+ }
+
+ private float[] DropToGroud_GetFoundList(Vector3 pos)
+ {
+ var found = new List();
+ foreach (Triangle tri in Mesh.Triangles)
+ {
+ var a = new Vector3(tri.Vertices[0].X, tri.Vertices[0].Y, tri.Vertices[0].Z);
+ var b = new Vector3(tri.Vertices[1].X, tri.Vertices[1].Y, tri.Vertices[1].Z);
+ var c = new Vector3(tri.Vertices[2].X, tri.Vertices[2].Y, tri.Vertices[2].Z);
+ if (PointInTriangle(new Vector2(pos.X, pos.Z), a, b, c))
+ {
+ found.Add(barryCentric(a, b, c, pos));
+ }
+ }
+
+ return found.ToArray();
+ }
+
+ public float DropToButtom(Vector3 pos)
+ {
+ var found = DropToGroud_GetFoundList(pos);
+ if (found.Any())
+ {
+ return found.Min();
+ }
+ else
+ {
+ return pos.Y;
+ }
+ }
+
+ public float DropToTop(Vector3 pos)
+ {
+ var found = DropToGroud_GetFoundList(pos);
+ if (found.Any())
+ {
+ return found.Max();
+ }
+ else
+ {
+ return pos.Y;
+ }
+ }
+
+ public float DropToNearesGround(Vector3 pos)
+ {
+ var found = DropToGroud_GetFoundList(pos);
+ if (found.Count() == 0)
+ return pos.Y;
+ int closest_index = 0;
+ float closest_abs = 9999999.0F;
+ for (int i = 0, loopTo = found.Count() - 1; i <= loopTo; i++)
+ {
+ float abs = Math.Abs(pos.Y - found[i]);
+ if (abs < closest_abs)
+ {
+ closest_abs = abs;
+ closest_index = i;
+ }
+ }
+
+ return found[closest_index];
+ }
+
+ private static bool PointInTriangle(Vector2 p, Vector3 p0, Vector3 p1, Vector3 p2)
+ {
+ float s = p0.Z * p2.X - p0.X * p2.Z + (p2.Z - p0.Z) * p.X + (p0.X - p2.X) * p.Y;
+ float t = p0.X * p1.Z - p0.Z * p1.X + (p0.Z - p1.Z) * p.X + (p1.X - p0.X) * p.Y;
+ if (s < 0 != t < 0)
+ return false;
+ float A = -p1.Z * p2.X + p0.Z * (p2.X - p1.X) + p0.X * (p1.Z - p2.Z) + p1.X * p2.Z;
+ if (A < 0.0)
+ {
+ s = -s;
+ t = -t;
+ A = -A;
+ }
+
+ return s > 0 && t > 0 && s + t <= A;
+ }
+
+ private static float barryCentric(Vector3 p1, Vector3 p2, Vector3 p3, Vector3 pos)
+ {
+ float det = (p2.Z - p3.Z) * (p1.X - p3.X) + (p3.X - p2.X) * (p1.Z - p3.Z);
+ float l1 = ((p2.Z - p3.Z) * (pos.X - p3.X) + (p3.X - p2.X) * (pos.Z - p3.Z)) / det;
+ float l2 = ((p3.Z - p1.Z) * (pos.X - p3.X) + (p1.X - p3.X) * (pos.Z - p3.Z)) / det;
+ float l3 = 1.0F - l1 - l2;
+ return l1 * p1.Y + l2 * p2.Y + l3 * p3.Y;
+ }
+
+ public Object3D ToObject3D()
+ {
+ var obj = new Object3D();
+ var m = new Mesh();
+
+ // Vertices
+ foreach (var vert in Mesh.Vertices)
+ m.Vertices.Add(new Pilz.S3DFileParser.Vertex()
+ {
+ X = vert.X,
+ Y = vert.Y,
+ Z = vert.Z
+ });
+
+ // Triangles
+ foreach (var tri in Mesh.Triangles)
+ {
+ var newTri = new Face();
+ for (int i = 0; i <= 2; i++)
+ {
+ var p = new Point() { Vertex = m.Vertices[tri.Vertices[i].Index] };
+ newTri.Points.Add(p);
+ }
+
+ newTri.Tag = tri.CollisionType;
+ m.Faces.Add(newTri);
+ }
+
+ obj.Meshes.Add(m);
+ return obj;
+ }
+
+ public Task ToObject3DAsync()
+ {
+ var t = new Task(ToObject3D);
+ t.Start();
+ return t;
+ }
+ }
+}
\ No newline at end of file
diff --git a/SM64Lib/Model/Collision/CollisionSettings.cs b/SM64Lib/Model/Collision/CollisionSettings.cs
new file mode 100644
index 0000000..1e9b1bc
--- /dev/null
+++ b/SM64Lib/Model/Collision/CollisionSettings.cs
@@ -0,0 +1,83 @@
+using System;
+using System.Collections.Generic;
+using global::System.IO;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.VisualBasic.CompilerServices;
+
+namespace SM64Lib.Model.Collision
+{
+ public class CollisionSettings
+ {
+ public List Entries { get; private set; } = new List();
+
+ public async Task Load(string fileName)
+ {
+ if (File.Exists(fileName))
+ {
+ var fs = new FileStream(fileName, FileMode.Open, FileAccess.Read);
+ var sr = new StreamReader(fs);
+ Entries.Clear();
+ while (!sr.EndOfStream)
+ {
+ var e = new Entry();
+ e.FromString(await sr.ReadLineAsync());
+ Entries.Add(e);
+ }
+
+ fs.Close();
+ }
+ }
+
+ public async Task Save(string fileName)
+ {
+ var fs = new FileStream(fileName, FileMode.Create, FileAccess.ReadWrite);
+ var sw = new StreamWriter(fs);
+ foreach (Entry e in Entries)
+ await sw.WriteLineAsync(e.ToString());
+ await sw.FlushAsync();
+ fs.Close();
+ }
+
+ public Entry GetEntry(string matName)
+ {
+ foreach (Entry e in Entries)
+ {
+ if ((e.MaterialName ?? "") == (matName ?? ""))
+ {
+ return e;
+ }
+ }
+
+ var ne = new Entry();
+ ne.MaterialName = matName;
+ Entries.Add(ne);
+ return ne;
+ }
+
+ public class Entry
+ {
+ public string MaterialName { get; set; } = "";
+ public byte CollisionType { get; set; } = 0;
+ public byte CollisionParam1 { get; set; } = 0;
+ public byte CollisionParam2 { get; set; } = 0;
+ public bool IsNonSolid { get; set; } = false;
+
+ public new string ToString()
+ {
+ return $"{MaterialName};{CollisionType};{CollisionParam1};{CollisionParam2};{IsNonSolid.ToString()}";
+ }
+
+ public void FromString(string str)
+ {
+ var parts = str.Split(';');
+ MaterialName = parts[0];
+ CollisionType = Convert.ToByte(parts[1]);
+ CollisionParam1 = Convert.ToByte(parts[2]);
+ CollisionParam2 = Convert.ToByte(parts[3]);
+ if (parts.Count() > 4)
+ IsNonSolid = Convert.ToBoolean(parts[4]);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/SM64Lib/Model/Collision/Triangel.cs b/SM64Lib/Model/Collision/Triangel.cs
new file mode 100644
index 0000000..bdb4855
--- /dev/null
+++ b/SM64Lib/Model/Collision/Triangel.cs
@@ -0,0 +1,24 @@
+
+using Newtonsoft.Json;
+
+namespace SM64Lib.Model.Collision
+{
+ public class Triangle
+ {
+ public byte CollisionType { get; set; } = 0;
+
+ public byte[] ColParams { get; set; } = new byte[] { 0, 0 };
+ [JsonConverter(typeof(Json.ArrayReferencePreservngConverter))]
+ public Vertex[] Vertices { get; set; } = new Vertex[3];
+
+ public TriangleList ParentList { get; set; } = null;
+
+ public int Index
+ {
+ get
+ {
+ return ParentList.IndexOf(this);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/SM64Lib/Model/Collision/TriangelList.cs b/SM64Lib/Model/Collision/TriangelList.cs
new file mode 100644
index 0000000..b507255
--- /dev/null
+++ b/SM64Lib/Model/Collision/TriangelList.cs
@@ -0,0 +1,26 @@
+using System.Collections.Generic;
+
+namespace SM64Lib.Model.Collision
+{
+ public class TriangleList : List
+ {
+ public new void Add(Triangle item)
+ {
+ base.Add(item);
+ item.ParentList = this;
+ }
+
+ public new void Insert(int index, Triangle item)
+ {
+ base.Insert(index, item);
+ item.ParentList = this;
+ }
+
+ public new void AddRange(IEnumerable collection)
+ {
+ foreach (Triangle v in collection)
+ v.ParentList = this;
+ base.AddRange(collection);
+ }
+ }
+}
\ No newline at end of file
diff --git a/SM64Lib/Model/Collision/Vertex.cs b/SM64Lib/Model/Collision/Vertex.cs
new file mode 100644
index 0000000..e490dab
--- /dev/null
+++ b/SM64Lib/Model/Collision/Vertex.cs
@@ -0,0 +1,21 @@
+
+using Newtonsoft.Json;
+
+namespace SM64Lib.Model.Collision
+{
+ public class Vertex
+ {
+ public short X { get; set; } = 0;
+ public short Y { get; set; } = 0;
+ public short Z { get; set; } = 0;
+ public VertexList ParentList { get; set; } = null;
+
+ public int Index
+ {
+ get
+ {
+ return ParentList.IndexOf(this);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/SM64Lib/Model/Collision/VertexList.cs b/SM64Lib/Model/Collision/VertexList.cs
new file mode 100644
index 0000000..9d5b2f9
--- /dev/null
+++ b/SM64Lib/Model/Collision/VertexList.cs
@@ -0,0 +1,26 @@
+using System.Collections.Generic;
+
+namespace SM64Lib.Model.Collision
+{
+ public class VertexList : List
+ {
+ public new void Add(Vertex item)
+ {
+ base.Add(item);
+ item.ParentList = this;
+ }
+
+ public new void Insert(int index, Vertex item)
+ {
+ base.Insert(index, item);
+ item.ParentList = this;
+ }
+
+ public new void AddRange(IEnumerable collection)
+ {
+ foreach (Vertex v in collection)
+ v.ParentList = this;
+ base.AddRange(collection);
+ }
+ }
+}
\ No newline at end of file
diff --git a/SM64Lib/Model/Conversion/FaceCullingMode.cs b/SM64Lib/Model/Conversion/FaceCullingMode.cs
new file mode 100644
index 0000000..3e7b46a
--- /dev/null
+++ b/SM64Lib/Model/Conversion/FaceCullingMode.cs
@@ -0,0 +1,11 @@
+
+namespace SM64Lib.Model.Conversion
+{
+ public enum FaceCullingMode
+ {
+ NoCulling,
+ Front,
+ Back,
+ FrontAndBack = Front | Back
+ }
+}
\ No newline at end of file
diff --git a/SM64Lib/Model/Conversion/Fast3DParsing/Fast3DParser.cs b/SM64Lib/Model/Conversion/Fast3DParsing/Fast3DParser.cs
new file mode 100644
index 0000000..ad733e3
--- /dev/null
+++ b/SM64Lib/Model/Conversion/Fast3DParsing/Fast3DParser.cs
@@ -0,0 +1,485 @@
+using System;
+using System.Collections.Generic;
+using global::System.Drawing;
+using global::System.IO;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.VisualBasic;
+using Microsoft.VisualBasic.CompilerServices;
+using global::Pilz.S3DFileParser;
+using global::SM64Lib.Data;
+using global::SM64Lib.Model.Fast3D.DisplayLists;
+using global::SM64Lib.Model.Fast3D.DisplayLists.Script;
+using global::SM64Lib.Model.Fast3D.DisplayLists.Script.Commands;
+using global::SM64Lib.SegmentedBanking;
+using Converts = System.Convert;
+
+namespace SM64Lib.Model.Conversion.Fast3DParsing
+{
+ public class Fast3DParser
+ {
+
+ /// Parse a Displaylist to an Object3D.
+ /// The Object3D where the model should be parsed to.
+ /// The Displaylist which should be parsed.
+ /// The RomManager Instance to use.
+ /// The Area ID if avaiable.
+ public static void Convert(Object3D obj, DisplayList dl, RomManager rommgr, byte? AreaID)
+ {
+ if (dl.Script.Count == 0 || dl.GeoPointer is null)
+ return;
+ int cmdIndex = 0;
+ DisplayListCommand cmd = null;
+ byte[] cmdarr = null;
+ var knownTextures = new Dictionary();
+ var knownColors = new Dictionary();
+ var knownVertices = new Dictionary();
+ var knownNormals = new Dictionary();
+ var knownUVs = new Dictionary();
+ var knownVertexColors = new Dictionary();
+ var knownShading = new Dictionary();
+ var dicVertexColorShading = new Dictionary();
+ var pointbuffer = new Pilz.S3DFileParser.Point[16];
+ var scaledVertices = new List();
+ Material curTexture = null;
+ Size curTexSize = default;
+ int curTexSegAddr = -1;
+ int curTexWrapT = 10497;
+ int curTexWrapS = 10497;
+ var curTexScale = new System.Numerics.Vector2(1.0F, 1.0F);
+ var curTexPalette = Array.Empty();
+ int curTexPaletteSegAddr = -1;
+ N64Graphics.N64Codec? curTexFormat = default;
+ uint curGeometryMode = 0x22205;
+ Mesh curMesh = null;
+ Color? curColor = default;
+ TextureLoadedInfos curTexLoadedInfos = null;
+ bool useUVOffsetFix = true;
+ int dlDepth = 0;
+
+ // Load Main Segmented Bank
+ var curSeg = GetSegBank(rommgr, dl.GeoPointer.SegPointer, AreaID);
+ if (curSeg is null) return;
+
+ curMesh = new Mesh();
+
+ while (cmdIndex < dl.Script.Count /*&& dl.Script[cmdIndex].CommandType != CommandTypes.EndDisplaylist*/)
+ {
+ cmd = dl.Script[cmdIndex];
+ cmdarr = cmd.ToArray();
+ var switchExpr = cmd.CommandType; // &H20000
+ switch (switchExpr)
+ {
+ case CommandTypes.DisplayList:
+ if (cmdarr[1] != 1)
+ dlDepth += 1;
+ break;
+
+ case CommandTypes.EndDisplaylist:
+ if (dlDepth > 0)
+ dlDepth -= 1;
+ break;
+
+ case CommandTypes.ClearGeometryMode:
+ {
+ curGeometryMode = curGeometryMode & ~F3D_CLEARGEOMETRYMODE.GetGeometryMode(cmd);
+ break;
+ }
+
+ case CommandTypes.SetGeometryMode:
+ {
+ curGeometryMode = curGeometryMode | F3D_CLEARGEOMETRYMODE.GetGeometryMode(cmd);
+ break;
+ }
+
+ case CommandTypes.Movemem:
+ {
+ int segAddr = F3D_MOVEMEM.GetSegmentedOffset(cmd);
+ byte smode = F3D_MOVEMEM.GetLightValueMode(cmd);
+ if (smode == 0x86) // Load Shading Light (Diffuse) Color
+ {
+ if (knownShading.ContainsKey(segAddr))
+ {
+ curColor = knownShading[segAddr];
+ }
+ else
+ {
+ var colordata = new byte[4];
+ var seg = rommgr.GetSegBank(Converts.ToByte(segAddr >> 24), AreaID);
+
+ // Read Color Data
+ seg.Data.Position = segAddr & 0xFFFFFF;
+ seg.Data.Read(colordata, 0, colordata.Length);
+ curColor = Color.FromArgb(0xFF, colordata[0], colordata[1], colordata[2]);
+ if (!dicVertexColorShading.ContainsKey((Color)curColor))
+ {
+ // Create new Vertex Color
+ var vc = new VertexColor() { R = colordata[0] / 256.0F, G = colordata[1] / 256.0F, B = colordata[2] / 256.0F, A = 1.0F };
+ dicVertexColorShading.Add((Color)curColor, vc);
+ }
+
+ // Set as Vertex Color
+ knownShading.Add(segAddr, (Color)curColor);
+ }
+ }
+
+ break;
+ }
+
+ case CommandTypes.Loadtlut:
+ {
+ byte paletteTileDescritpr = cmdarr[4];
+ ushort numColorsToLoadInPalette;
+ curTexPaletteSegAddr = curTexSegAddr;
+ cmd.Position = 5;
+ numColorsToLoadInPalette = (ushort)(cmd.ReadUInt16() >> 6);
+ var seg = rommgr.GetSegBank(Converts.ToByte(curTexPaletteSegAddr >> 24), AreaID);
+ curTexPalette = new byte[numColorsToLoadInPalette * 2 + 1 + 1];
+ int offset = curTexPaletteSegAddr & 0xFFFFFF;
+ for (int i = 1, loopTo = numColorsToLoadInPalette + 1; i <= loopTo; i++)
+ {
+ int ii = i * 2 - 2;
+ seg.Data.Position = offset + ii;
+ curTexPalette[ii] = Converts.ToByte(seg.Data.ReadByte());
+ curTexPalette[ii + 1] = Converts.ToByte(seg.Data.ReadByte());
+ }
+
+ break;
+ }
+
+ case CommandTypes.Triangle1:
+ {
+ var f = new Face();
+ if (curTexFormat is not null)
+ {
+ ProcessTexture(obj, rommgr, AreaID, dl, (N64Graphics.N64Codec)curTexFormat, knownTextures, ref curTexture, curTexSegAddr, curTexSize, curTexWrapT, curTexWrapS, curTexScale, curTexPalette, curTexPaletteSegAddr, curColor, ref curTexLoadedInfos);
+ f.Material = curTexture;
+ }
+
+ for (int i = 1; i <= 3; i++)
+ {
+ byte pindex = F3D_TRI1.GetVertice(cmd, Converts.ToByte(i));
+ if (pindex >= pointbuffer.Length)
+ return;
+ var p = pointbuffer[pindex];
+ if (p is not null)
+ f.Points.Add(p);
+ }
+
+ // Shading (as Vertex Color)
+ if (curTexture?.Color is not null && (curGeometryMode & (long)0x20000) != 0)
+ {
+ var vc = dicVertexColorShading[(Color)curTexture.Color];
+ foreach (Pilz.S3DFileParser.Point p in f.Points)
+ {
+ if (p.VertexColor is null)
+ {
+ if (dicVertexColorShading.ContainsKey((Color)curTexture.Color))
+ {
+ p.VertexColor = vc;
+ if (!curMesh.VertexColors.Contains(vc))
+ {
+ curMesh.VertexColors.Add(vc);
+ }
+ }
+ }
+ }
+ }
+
+ curMesh.Faces.Add(f);
+ break;
+ }
+
+ case CommandTypes.Vertex:
+ {
+ byte num = F3D_VTX.GetNumberOfVertices(cmd);
+ byte startindex = F3D_VTX.GetStartIndexInVertexBuffer(cmd);
+ short datalength = F3D_VTX.GetLengthOfVertexData(cmd);
+ int segAddr = F3D_VTX.GetSegmentedAddress(cmd);
+ if (num > 0)
+ {
+ for (int i = 0, loopTo1 = num; i <= loopTo1; i++)
+ {
+ var p = new Pilz.S3DFileParser.Point();
+ int curSegAddr = segAddr + i * 0x10;
+ var cs = GetSegBank(rommgr, curSegAddr, AreaID);
+ if (cs is null)
+ continue;
+
+ // Vertex
+ if (knownVertices.ContainsKey(curSegAddr))
+ {
+ p.Vertex = knownVertices[curSegAddr];
+ }
+ else
+ {
+ var vert = GetVertexFromStream(cs.Data, cs.BankOffsetFromSegAddr(curSegAddr), dl.GeoPointer.ModelOffset, dl.GeoPointer.ModelScale);
+ p.Vertex = vert;
+ curMesh.Vertices.Add(vert);
+ knownVertices.Add(curSegAddr, vert);
+ }
+
+ // UV
+ if (knownUVs.ContainsKey(curSegAddr))
+ {
+ p.UV = knownUVs[curSegAddr];
+ }
+ else
+ {
+ var uv = GetUVFromStream(cs.Data, cs.BankOffsetFromSegAddr(curSegAddr), curTexScale, curTexSize, useUVOffsetFix);
+ p.UV = uv;
+ curMesh.UVs.Add(uv);
+ knownUVs.Add(curSegAddr, uv);
+ }
+
+ if ((curGeometryMode & (long)0x20000) == 0)
+ {
+ // Vertex Color
+ if (knownVertexColors.ContainsKey(curSegAddr))
+ {
+ p.VertexColor = knownVertexColors[curSegAddr];
+ }
+ else
+ {
+ var vc = GetVertexColorFromStream(cs.Data, cs.BankOffsetFromSegAddr(curSegAddr));
+ p.VertexColor = vc;
+ curMesh.VertexColors.Add(vc);
+ knownVertexColors.Add(curSegAddr, vc);
+ }
+ }
+ // Normal
+ else if (knownNormals.ContainsKey(curSegAddr))
+ {
+ p.Normal = knownNormals[curSegAddr];
+ }
+ else
+ {
+ var n = GetNormalFromStream(cs.Data, cs.BankOffsetFromSegAddr(curSegAddr));
+ p.Normal = n;
+ curMesh.Normals.Add(n);
+ knownNormals.Add(curSegAddr, n);
+ }
+
+ pointbuffer[startindex + i] = p;
+ }
+ }
+
+ break;
+ }
+
+ case CommandTypes.SetImage:
+ {
+ int newAddr = F3D_SETIMG.GetSegmentedAddress(cmd);
+ if ((uint)newAddr != 0xFFFFFFFF)
+ {
+ curTexSegAddr = newAddr;
+ }
+
+ break;
+ }
+
+ case CommandTypes.SetTileSize:
+ {
+ curTexSize = F3D_SETTILESIZE.GetSize(cmd);
+ break;
+ }
+
+ case CommandTypes.SetTile:
+ {
+ cmd.Position = 4;
+ int checkVal = cmd.ReadInt32();
+ cmd.Position = 0;
+ if (checkVal != 0x7000000)
+ {
+ if (cmdarr[4] == 0)
+ {
+ curTexFormat = F3D_SETTILE.GetTextureFormat(cmd);
+ }
+
+ curTexWrapT = F3D_SETTILE.GetWrapT(cmd);
+ curTexWrapS = F3D_SETTILE.GetWrapS(cmd);
+ }
+
+ break;
+ }
+
+ case CommandTypes.Texture:
+ {
+ if ((curGeometryMode & (long)0x40000) == 0x40000)
+ {
+ curTexSize = F3D_TEXTURE.GetTextureSize(cmd);
+ }
+ else
+ {
+ curTexScale = F3D_TEXTURE.GetTextureScaling(cmd);
+ }
+
+ break;
+ }
+
+ case CommandTypes.SetOtherMode_H:
+ {
+ uint bits = F3D_SETOTHERMODE_H.GetModeBits(cmd);
+ bool nearestNeighbor = (bits & (long)0x2000) == 0;
+ useUVOffsetFix = !nearestNeighbor;
+ break;
+ }
+ }
+
+ cmdIndex += 1;
+ }
+
+ //if (curMesh.Faces.Any())
+ obj.Meshes.Add(curMesh);
+ }
+
+ /// Parse a Displaylist to an Object3D.
+ /// The Object3D where the model should be parsed to.
+ /// The Displaylist which should be parsed.
+ /// The RomManager Instance to use.
+ /// The Area ID if avaiable.
+ public static Task ConvertAsync(Object3D obj, DisplayList dl, RomManager rommgr, byte? AreaID)
+ {
+ var t = new Task(() => Convert(obj, dl, rommgr, AreaID));
+ t.Start();
+ return t;
+ }
+
+ private static SegmentedBank GetSegBank(RomManager rommgr, int segAddr, byte? AreaID)
+ {
+ var seg = rommgr.GetSegBank(Converts.ToByte(segAddr >> 24), AreaID);
+ seg.ReadDataIfNull(rommgr.RomFile);
+ return seg;
+ }
+
+ private static void ProcessTexture(Object3D obj, RomManager rommgr, byte? AreaID, DisplayList dl, N64Graphics.N64Codec texFormat, Dictionary knownTextures, ref Material curTexture, int curTexSegAddr, Size curTexSize, int curTexWrapT, int curTexWrapS, System.Numerics.Vector2 curTexScale, byte[] curTexPalette, int curTexPaletteSegAddr, Color? curColor, ref TextureLoadedInfos curTexLoadedInfos)
+ {
+ if (curTexSegAddr == 0 && curTexScale.X == 0 && curTexScale.Y == 0)
+ return;
+ if (knownTextures.ContainsKey(curTexSegAddr))
+ {
+ curTexture = knownTextures[curTexSegAddr];
+ }
+ else
+ {
+ try
+ {
+ var mat = new Material();
+ mat.Wrap = new System.Numerics.Vector2(curTexWrapT, curTexWrapS);
+ mat.Scale = curTexScale;
+ mat.Color = curColor;
+ var seg = GetSegBank(rommgr, curTexSegAddr, AreaID);
+ if (seg is null)
+ return;
+ GetTextureImage(seg.Data, seg.BankOffsetFromSegAddr(curTexSegAddr), mat, texFormat, curTexSize, curTexPalette);
+ if (mat.Image is not null)
+ {
+ mat.Tag = new TextureLoadedInfos(Microsoft.VisualBasic.Conversion.Hex(curTexSegAddr), texFormat, curTexSegAddr, curTexPaletteSegAddr, seg.SegToRomAddr(curTexSegAddr), seg.SegToRomAddr(curTexPaletteSegAddr), mat.Image.Size);
+ }
+
+ curTexture = mat;
+ knownTextures.Add(curTexSegAddr, mat);
+ if (!obj.Materials.ContainsKey(Converts.ToString(curTexSegAddr)))
+ {
+ obj.Materials.Add(Converts.ToString(curTexSegAddr), mat);
+ }
+ }
+ catch (Exception /*ex*/)
+ {
+ //Interaction.MsgBox(ex.Message);
+ }
+ }
+ }
+
+ private static void GetTextureImage(Stream s, int pos, Material mat, N64Graphics.N64Codec texFormat, Size curTexSize, byte[] curTexPalette)
+ {
+ //// Create Image & Graphics
+ //mat.Image = new Bitmap(curTexSize.Width, curTexSize.Height);
+ //var g = Graphics.FromImage(mat.Image);
+
+ //// Get Texture Data
+ //var bytes = new byte[(N64Graphics.N64Graphics.PixelsToBytes(texFormat, curTexSize.Width * curTexSize.Height))];
+ //s.Position = pos;
+ //s.Read(bytes, 0, bytes.Length);
+ //try
+ //{
+ // // Decode Texture
+ // N64Graphics.N64Graphics.RenderTexture(g, bytes.ToArray(), curTexPalette, 0, curTexSize.Width, curTexSize.Height, 1, texFormat, N64Graphics.N64IMode.AlphaCopyIntensity);
+ //}
+ //catch (Exception)
+ //{
+ //}
+
+ // Get Texture Data
+ var bytes = new byte[(N64Graphics.N64Graphics.PixelsToBytes(texFormat, curTexSize.Width * curTexSize.Height))];
+ s.Position = pos;
+ s.Read(bytes, 0, bytes.Length);
+
+ // Convert texture palette
+ var palette = new List();
+ if (curTexPalette != null && curTexPalette.Length >= 2)
+ {
+ for (int i = 0; i + 1 < curTexPalette.Length; i+=2)
+ palette.Add((ushort)((curTexPalette[i] << 8) | curTexPalette[i + 1]));
+ }
+
+ // Create Image & Graphics
+ mat.Image = N64Graphics.TextureFormats.decodeTexture(texFormat, bytes.ToArray(), curTexSize.Width, curTexSize.Height, palette.ToArray());
+ }
+
+ private static Vertex GetVertexFromStream(Stream s, int vtStart, System.Numerics.Vector3 modelOffset, System.Numerics.Vector3 modelScale)
+ {
+ var vert = new Vertex();
+ var br = new BinaryStreamData(s);
+ s.Position = vtStart;
+ vert.X = br.ReadInt16() + modelOffset.X;
+ vert.Y = br.ReadInt16() + modelOffset.Y;
+ vert.Z = br.ReadInt16() + modelOffset.Z;
+ vert.X *= modelScale.X;
+ vert.Y *= modelScale.Y;
+ vert.Z *= modelScale.Z;
+ return vert;
+ }
+
+ private static UV GetUVFromStream(Stream s, int vtStart, System.Numerics.Vector2 curTexScale, Size curTexSize, bool useUVOffsetFix)
+ {
+ var uv = new UV();
+ var br = new BinaryStreamData(s);
+ s.Position = vtStart + 8;
+ uv.U = br.ReadInt16() * curTexScale.X;
+ uv.V = br.ReadInt16() * curTexScale.Y;
+ if (useUVOffsetFix) // Fixes UVs offset
+ {
+ uv.U += 16;
+ uv.V += 16;
+ }
+
+ uv.U /= curTexSize.Width * 32.0F;
+ uv.V /= curTexSize.Height * 32.0F;
+ return uv;
+ }
+
+ private static Normal GetNormalFromStream(Stream s, int vtStart)
+ {
+ var normal = new Normal();
+ s.Position = vtStart + 12;
+ normal.X = s.ReadByte() / 255.0F;
+ normal.Y = s.ReadByte() / 255.0F;
+ normal.Z = s.ReadByte() / 255.0F;
+ // normal.A = s.ReadByte ???
+
+ return normal;
+ }
+
+ private static VertexColor GetVertexColorFromStream(Stream s, int vtStart)
+ {
+ var normal = new VertexColor();
+ s.Position = vtStart + 12;
+ normal.R = s.ReadByte() / 255.0F;
+ normal.G = s.ReadByte() / 255.0F;
+ normal.B = s.ReadByte() / 255.0F;
+ normal.A = s.ReadByte() / 255.0F;
+ return normal;
+ }
+ }
+}
\ No newline at end of file
diff --git a/SM64Lib/Model/Conversion/Fast3DParsing/TextureLoadedInfos.cs b/SM64Lib/Model/Conversion/Fast3DParsing/TextureLoadedInfos.cs
new file mode 100644
index 0000000..4de8e54
--- /dev/null
+++ b/SM64Lib/Model/Conversion/Fast3DParsing/TextureLoadedInfos.cs
@@ -0,0 +1,32 @@
+using global::System.Drawing;
+
+namespace SM64Lib.Model.Conversion.Fast3DParsing
+{
+ public class TextureLoadedInfos
+ {
+ public string Name { get; private set; }
+ public N64Graphics.N64Codec TextureFormat { get; private set; }
+ public int TextureSegAddress { get; private set; }
+ public int TexturePaletteSegAddress { get; private set; }
+ public int TextureRomAddress { get; private set; }
+ public int TexturePaletteRomAddress { get; private set; }
+ public Size TextureSize { get; private set; }
+ public bool IsReadOnly { get; private set; }
+
+ public TextureLoadedInfos(string name, N64Graphics.N64Codec textureFormat, int textureSegAddress, int texturePaletteSegAddress, int textureRomAddress, int texturePaletteRomAddress, Size textureSize) : this(name, textureFormat, textureSegAddress, texturePaletteSegAddress, textureRomAddress, texturePaletteRomAddress, textureSize, false)
+ {
+ }
+
+ public TextureLoadedInfos(string name, N64Graphics.N64Codec textureFormat, int textureSegAddress, int texturePaletteSegAddress, int textureRomAddress, int texturePaletteRomAddress, Size textureSize, bool isReadOnly)
+ {
+ Name = name;
+ TextureFormat = textureFormat;
+ TextureSegAddress = textureSegAddress;
+ TexturePaletteSegAddress = texturePaletteSegAddress;
+ TextureRomAddress = textureRomAddress;
+ TexturePaletteRomAddress = texturePaletteRomAddress;
+ TextureSize = textureSize;
+ IsReadOnly = isReadOnly;
+ }
+ }
+}
\ No newline at end of file
diff --git a/SM64Lib/Model/Conversion/Fast3DWriting/DisplayListType.cs b/SM64Lib/Model/Conversion/Fast3DWriting/DisplayListType.cs
new file mode 100644
index 0000000..4ab2894
--- /dev/null
+++ b/SM64Lib/Model/Conversion/Fast3DWriting/DisplayListType.cs
@@ -0,0 +1,20 @@
+// Namespace Model.Conversion.Fast3DWriting
+
+// Public Enum DisplayListType
+// Unknown = 0
+// Solid = 1
+// Alpha = 2
+// Transparent = 4
+// End Enum
+
+// End Namespace
+// Namespace Model.Conversion.Fast3DWriting
+
+// Public Enum DisplayListType
+// Unknown = 0
+// Solid = 1
+// Alpha = 2
+// Transparent = 4
+// End Enum
+
+// End Namespace
diff --git a/SM64Lib/Model/Conversion/Fast3DWriting/DisplaylistProps.cs b/SM64Lib/Model/Conversion/Fast3DWriting/DisplaylistProps.cs
new file mode 100644
index 0000000..f0fd3d6
--- /dev/null
+++ b/SM64Lib/Model/Conversion/Fast3DWriting/DisplaylistProps.cs
@@ -0,0 +1,15 @@
+using global::SM64Lib.Geolayout;
+
+namespace SM64Lib.Model.Conversion.Fast3DWriting
+{
+ public class DisplaylistProps
+ {
+ public int ID { get; set; }
+ public DefaultGeolayers Layer { get; set; } = DefaultGeolayers.Solid;
+
+ public DisplaylistProps(int ID)
+ {
+ this.ID = ID;
+ }
+ }
+}
\ No newline at end of file
diff --git a/SM64Lib/Model/Conversion/Fast3DWriting/DisplaylistSelectionMode.cs b/SM64Lib/Model/Conversion/Fast3DWriting/DisplaylistSelectionMode.cs
new file mode 100644
index 0000000..ef19839
--- /dev/null
+++ b/SM64Lib/Model/Conversion/Fast3DWriting/DisplaylistSelectionMode.cs
@@ -0,0 +1,19 @@
+
+namespace SM64Lib.Model.Conversion.Fast3DWriting
+{
+ public enum DisplaylistSelectionMode
+ {
+ ///
+ /// Automaticly choose a default Displaylist.
+ ///
+ Automatic,
+ ///
+ /// Force Material to be on a selected default Displaylist.
+ ///
+ Default,
+ ///
+ /// Force Material to be on a custom defined Displaylist.
+ ///
+ Custom
+ }
+}
\ No newline at end of file
diff --git a/SM64Lib/Model/Conversion/Fast3DWriting/DisplaylistSelectionSettings.cs b/SM64Lib/Model/Conversion/Fast3DWriting/DisplaylistSelectionSettings.cs
new file mode 100644
index 0000000..4ea006e
--- /dev/null
+++ b/SM64Lib/Model/Conversion/Fast3DWriting/DisplaylistSelectionSettings.cs
@@ -0,0 +1,15 @@
+using global::SM64Lib.Geolayout;
+using Newtonsoft.Json.Converters;
+using Newtonsoft.Json;
+
+namespace SM64Lib.Model.Conversion.Fast3DWriting
+{
+ public class DisplaylistSelectionSettings
+ {
+ [JsonConverter(typeof(StringEnumConverter))]
+ public DisplaylistSelectionMode SelectionMode { get; set; } = DisplaylistSelectionMode.Automatic;
+ [JsonConverter(typeof(StringEnumConverter))]
+ public DefaultGeolayers DefaultGeolayer { get; set; } = DefaultGeolayers.Solid;
+ public int CustomDisplaylistID { get; set; } = 0;
+ }
+}
\ No newline at end of file
diff --git a/SM64Lib/Model/Conversion/Fast3DWriting/Fast3DWriter.cs b/SM64Lib/Model/Conversion/Fast3DWriting/Fast3DWriter.cs
new file mode 100644
index 0000000..8d9e9b0
--- /dev/null
+++ b/SM64Lib/Model/Conversion/Fast3DWriting/Fast3DWriter.cs
@@ -0,0 +1,2343 @@
+using System;
+using System.Collections.Generic;
+using System.Data;
+using global::System.Drawing;
+using global::System.IO;
+using System.Linq;
+using global::System.Numerics;
+using System.Threading.Tasks;
+using Microsoft.VisualBasic.CompilerServices;
+using global::SM64Lib.N64Graphics;
+using Z.Collections.Extensions;
+using System.Runtime.CompilerServices;
+using static Microsoft.VisualBasic.Conversion;
+
+namespace SM64Lib.Model.Conversion.Fast3DWriting
+{
+
+ public enum TextureFilter
+ {
+ Point = 0,
+ Bilerp = 2,
+ Average = 3
+ }
+
+ public enum PaletteColorStorageType
+ {
+ None = 0,
+ RGBA16 = 2,
+ IA16 = 3
+ }
+
+ ///
+ /// Reduces the number of duplicate verticies.
+ ///
+ public enum ReduceVericesLevel : byte
+ {
+ ///
+ /// No reduction.
+ ///
+ Level0 = 0,
+ ///
+ /// Reduce only in the same 0x04 group. (Best choise!)
+ ///
+ Level1 = 1,
+ ///
+ /// Reduce and push up. (A little buggy!)
+ ///
+ Level2 = 2
+ }
+
+ public class ConvertResult
+ {
+ public States State { get; set; } = States.Successfully;
+ public uint PtrStart { get; set; } = 0;
+ public uint PtrVertex { get; set; } = 0;
+ public List PtrGeometry { get; private set; } = new List();
+ public List ScrollingCommands { get; private set; } = new List();
+ public Dictionary ScrollingNames { get; private set; } = new Dictionary();
+ public MemoryStream Data { get; private set; } = new MemoryStream();
+
+ public enum States
+ {
+ Successfully,
+ Error
+ }
+ }
+
+ public class ConvertSettings
+ {
+ public uint? SegmentedAddress { get; set; }
+ public uint SizeLimit { get; set; } = 0x150000;
+ public double Scale { get; set; } = 500;
+ public Vector3 OffsetPosition { get; set; } = Vector3.Zero;
+ public ReduceVericesLevel ReduceVertLevel { get; set; } = ReduceVericesLevel.Level1;
+ public string ColorTexData { get; set; } = "";
+ public string GeoModeData { get; set; } = "";
+ public string TexTypeData { get; set; } = "";
+ public bool ResizeTextures { get; set; } = false;
+ public bool CenterModel { get; set; } = false;
+ public Model.Fog Fog { get; set; } = null;
+ public string CollisionData { get; set; } = "";
+ public sbyte ForceDisplaylist { get; set; } = -1;
+ public float TransparencyLimit { get; set; } = 0.5f;
+ public Model.Fast3D.TextureFormatSettings TextureFormatSettings { get; set; } = null;
+
+ public bool EnableFog
+ {
+ get
+ {
+ return Fog is object;
+ }
+ }
+ }
+
+ public class SettingsException : Exception
+ {
+ public SettingsException() : base("Undefined ConvertSettings error.")
+ {
+ }
+
+ public SettingsException(string message) : base(message)
+ {
+ }
+ }
+
+ public class Fast3DWriter
+ {
+
+ private enum MaterialType
+ {
+ None,
+ TextureSolid,
+ TextureAlpha,
+ TextureTransparent,
+ ColorSolid,
+ ColorTransparent
+ }
+
+ private class Vertex
+ {
+ public short X { get; set; } = 0;
+ public short Y { get; set; } = 0;
+ public short Z { get; set; } = 0;
+ }
+
+ private class Normal
+ {
+ public byte A { get; set; } = 0;
+ public byte B { get; set; } = 0;
+ public byte C { get; set; } = 0;
+ public byte D { get; set; } = 0;
+ }
+
+ private class VertexColor
+ {
+ public byte R { get; set; } = 0;
+ public byte G { get; set; } = 0;
+ public byte B { get; set; } = 0;
+ public byte A { get; set; } = 0;
+ }
+
+ private class TexCord
+ {
+ public float U { get; set; } = 0;
+ public float V { get; set; } = 0;
+ }
+
+ private class Material
+ {
+ public string Name { get; set; } = "";
+ public bool HasTexture { get; set; }
+ public bool HasPalette { get; set; }
+ public bool HasTextureAlpha { get; set; }
+ public bool HasTransparency { get; set; }
+ public bool EnableTextureColor { get; set; }
+ public bool EnableAlphaMask { get; set; } // For I4/I8 textures.
+ public bool CameFromBMP { get; set; } // For I4/I8 textures.
+ public uint Color { get; set; } = 0;
+ public byte Opacity { get; set; } = 0;
+ public byte OpacityOrg { get; set; } = 0;
+ public uint Offset { get; set; } = 0;
+ public uint PaletteOffset { get; set; } = 0;
+ public uint Size { get; set; } = 0;
+ public uint PaletteSize { get; set; } = 0;
+ public uint TexWidth { get; set; } = 0;
+ public uint TexHeight { get; set; } = 0;
+ public MaterialType Type { get; set; } = MaterialType.None;
+ public N64Codec TexType { get; set; } = N64Codec.RGBA16;
+ public ushort Collision { get; set; } = 0;
+ public byte Collisionp1 { get; set; } = 0;
+ public byte Collisionp2 { get; set; } = 0;
+ public bool EnableGeoMode { get; set; }
+ public uint GeoMode { get; set; } = 0;
+ public TextureEntry Texture { get; set; } = null;
+ public bool EnableScrolling { get; set; } = false;
+ public DisplaylistSelectionSettings DisplaylistSelection { get; set; } = new Fast3DWriting.DisplaylistSelectionSettings();
+ public FaceCullingMode FaceCullingMode { get; set; } = Conversion.FaceCullingMode.Back;
+ public bool EnableMirrorS { get; set; } = false;
+ public bool EnableMirrorT { get; set; } = false;
+ public bool EnableClampS { get; set; } = false;
+ public bool EnableClampT { get; set; } = false;
+ public bool EnableCrystalEffect { get; set; } = false;
+ public float? TransparencyLimit { get; set; } = 0;
+ public TextureFilter TexFilter { get; set; } = TextureFilter.Bilerp;
+ }
+
+ private class FinalVertexData
+ {
+ public byte[] Data { get; set; } = new byte[16];
+ public bool EnableVertexColor { get; set; } = false;
+
+ public bool EnableVertexTransparent
+ {
+ get
+ {
+ byte db = Data.LastOrDefault();
+ return db < 0xFF;
+ }
+ }
+ }
+
+ private class FvGroup
+ {
+ public short NumTri { get; set; } = 0;
+
+ public sbyte[] indexList = new sbyte[2049];
+
+ public List FinalVertexData { get; private set; } = new List();
+
+ public short VertexDataCount
+ {
+ get
+ {
+ return Convert.ToInt16(FinalVertexData.Count);
+ }
+ }
+
+ public bool EnableVertexColors
+ {
+ get
+ {
+ foreach (FinalVertexData fvd in FinalVertexData)
+ {
+ if (fvd.EnableVertexColor)
+ return true;
+ }
+
+ return false;
+ }
+ }
+
+ public bool EnableVertexAlpha
+ {
+ get
+ {
+ foreach (FinalVertexData fvd in FinalVertexData)
+ {
+ if (fvd.EnableVertexTransparent)
+ return true;
+ }
+
+ return false;
+ }
+ }
+ }
+
+ private class F3D
+ {
+ public byte[] data = new byte[8];
+ }
+
+ private class VertexGroupList
+ {
+ public int Position { get; set; } = 0;
+ public int Length { get; set; } = 0;
+ public Material Material { get; set; } = null;
+
+ public int GroupsCount
+ {
+ get
+ {
+ return FinalVertexGroups.Count;
+ }
+ }
+
+ public int StartIndex { get; set; } = 0;
+ public List FinalVertexGroups { get; private set; } = new List();
+
+ public bool EnableVertexColors
+ {
+ get
+ {
+ foreach (FvGroup fvg in FinalVertexGroups)
+ {
+ foreach (FinalVertexData fvd in fvg.FinalVertexData)
+ {
+ if (fvd.EnableVertexColor)
+ return true;
+ }
+ }
+
+ return false;
+ }
+ }
+
+ public bool EnableVertexAlpha
+ {
+ get
+ {
+ if (!EnableVertexColors)
+ return false;
+ foreach (FvGroup fvg in FinalVertexGroups)
+ {
+ foreach (FinalVertexData fvd in fvg.FinalVertexData)
+ {
+ if (fvd.EnableVertexColor)
+ {
+ if (fvd.Data.LastOrDefault() < 0xFF)
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+ }
+ }
+
+ private class TextureEntry
+ {
+ public uint Width { get; set; } = 0;
+ public uint Height { get; set; } = 0;
+ public byte[] Data { get; set; } = Array.Empty();
+ public byte[] Palette { get; set; } = Array.Empty();
+ public Image OriginalImage { get; set; } = null;
+ }
+
+ private class ObjPtrs
+ {
+ public string name = Convert.ToString(0);
+ public uint start_ptr = 0;
+ public uint import_lenght = 0;
+ public uint solid_ptr = 0;
+ public uint alpha_ptr = 0;
+ public uint trans_ptr = 0;
+ public uint geo_ptr = 0;
+ public uint col_ptr = 0;
+ public uint vertex_start = 0;
+ }
+
+ public struct ScrollTex
+ {
+ public int Offset { get; set; }
+ public int VertsCount { get; set; }
+ public int MaterialAddress { get; set; }
+ public string MaterialName { get; set; }
+
+ public ScrollTex(int offset, byte faceCount, int matAddr, string matName)
+ {
+ Offset = offset;
+ VertsCount = faceCount;
+ MaterialAddress = matAddr;
+ MaterialName = matName;
+ }
+ }
+
+ private List verts = new List();
+ private List norms = new List();
+ private List vertexColors = new List();
+ private List uvs = new List();
+ private List materials = new List();
+ private List ignoreFacesWithMaterial = new List();
+ private Dictionary materialBindings = new Dictionary();
+ private List vertexGroups = new List();
+ private List finalVertData = new List();
+ private List textureBank = new List();
+ private List scrollTexts = new List();
+ private Material currentMaterial;
+ private int currentFace = 0;
+ private const byte GEOLAYER_SOLID = 1;
+ private const byte GEOLAYER_ALPHA = 4;
+ private const byte GEOLAYER_TRANSPARENT = 5;
+ private byte curSeg = 0;
+ private uint startSegOffset = 0;
+ private byte[] defaultColor = new byte[24];
+ private ConvertSettings settings = null;
+ private SM64Lib.Data.BinaryData impdata = null;
+ private ConvertResult conRes = new ConvertResult();
+ private readonly byte[] ColtypesWithParams = new byte[] { 14, 44, 36, 37, 39, 45 };
+
+ private uint CurSegAddress
+ {
+ get
+ {
+ return Convert.ToUInt32(curSeg) << 24;
+ }
+ }
+
+ private object TXL2WORDS(object txls, object b_txl)
+ {
+ return NewLateBinding.LateGet(null, typeof(Math), "Max", new object[]
+ {
+ 1,
+ Operators.DivideObject(Operators.MultiplyObject(txls, b_txl), 8)
+ }, null, null, null);
+ }
+
+ private object CALC_DXT(object width, object b_txl)
+ {
+ return Operators.DivideObject(2048, this.TXL2WORDS(RuntimeHelpers.GetObjectValue(width), RuntimeHelpers.GetObjectValue(b_txl)));
+ }
+
+ private object TXL2WORDS_4b(object txls)
+ {
+ return NewLateBinding.LateGet(null, typeof(Math), "Max", new object[]
+ {
+ 1,
+ Operators.DivideObject(txls, 16)
+ }, null, null, null);
+ }
+
+ private object CALC_DXT_4b(object width)
+ {
+ return Operators.DivideObject(2048, this.TXL2WORDS_4b(RuntimeHelpers.GetObjectValue(width)));
+ }
+
+ private void SetLightAndDarkValues(Pilz.S3DFileParser.Shading s)
+ {
+ // Ambient light color
+ defaultColor[0] = s.AmbientColor.R;
+ defaultColor[1] = s.AmbientColor.G;
+ defaultColor[2] = s.AmbientColor.B;
+ defaultColor[3] = 0;
+ defaultColor[4] = s.AmbientColor.R;
+ defaultColor[5] = s.AmbientColor.G;
+ defaultColor[6] = s.AmbientColor.B;
+ defaultColor[7] = 0;
+
+ // Diffuse light color
+ defaultColor[8] = s.DiffuseColor.R;
+ defaultColor[9] = s.DiffuseColor.G;
+ defaultColor[10] = s.DiffuseColor.B;
+ defaultColor[11] = 0;
+ defaultColor[12] = s.DiffuseColor.R;
+ defaultColor[13] = s.DiffuseColor.G;
+ defaultColor[14] = s.DiffuseColor.B;
+ defaultColor[15] = 0;
+
+ // Diffuse light direction
+ if (s.DiffusePosition is not null)
+ {
+ float d = (float)(127 / Math.Sqrt(s.DiffusePosition.X * s.DiffusePosition.X + s.DiffusePosition.Y * s.DiffusePosition.Y + s.DiffusePosition.Z * s.DiffusePosition.Z));
+
+ if (float.IsInfinity(d))
+ d = 0;
+ defaultColor[16] = (byte)Convert.ToSByte(Math.Round(s.DiffusePosition.X * d));
+ defaultColor[17] = (byte)Convert.ToSByte(Math.Round(s.DiffusePosition.Y * d));
+ defaultColor[18] = (byte)Convert.ToSByte(Math.Round(s.DiffusePosition.Z * d));
+ }
+ else
+ {
+ defaultColor[16] = 0x49; // = Most SM64E Like ||| Default by Nintendo: &H28
+ defaultColor[17] = 0x49;
+ defaultColor[18] = 0x49;
+ }
+
+ defaultColor[19] = 0;
+ defaultColor[20] = 0;
+ defaultColor[21] = 0;
+ defaultColor[22] = 0;
+ defaultColor[23] = 0;
+ }
+
+ private void ResetVariables()
+ {
+ vertexGroups.Clear();
+ verts.Clear();
+ norms.Clear();
+ vertexColors.Clear();
+ uvs.Clear();
+ materials.Clear();
+ finalVertData.Clear();
+ currentFace = 0;
+ }
+
+ private void CheckGeoModeInfo(Material m)
+ {
+ m.GeoMode = 0;
+ var gma = settings.GeoModeData.Split(',');
+ foreach (string gme in gma)
+ {
+ var gmd = gme.Split(':');
+ if (m.Name.Equals(gmd[0]))
+ {
+ m.GeoMode = Convert.ToUInt32(gmd[1]);
+ m.EnableGeoMode = true;
+ return;
+ }
+ }
+ }
+
+ private void CheckColorTexInfo(Material m)
+ {
+ m.Color = 0;
+ var gma = settings.ColorTexData.Split(',');
+ foreach (string gme in gma)
+ {
+ var gmd = gme.Split(':');
+ if (m.Name.Equals(gmd[0]))
+ {
+ m.Color = Convert.ToUInt32(gmd[1]);
+ m.EnableTextureColor = true;
+ return;
+ }
+ }
+ }
+
+ private void processMaterialColor(string str, Material mat)
+ {
+ var splitColor = str.Replace(".", ",").Split(' ');
+ uint r = (uint)(Convert.ToSingle(splitColor[0]) * 255);
+ uint g = (uint)(Convert.ToSingle(splitColor[1]) * 255);
+ uint b = (uint)(Convert.ToSingle(splitColor[2]) * 255);
+ mat.Color = Convert.ToUInt32(r << 24 | g << 16 | b << 8 | (long)0xFF);
+ }
+
+ private void processMaterialColorAlpha(float alpha, Material mat)
+ {
+ mat.Color = mat.Color & 0xFFFFFF00U;
+ mat.Color = mat.Color | Convert.ToByte(Convert.ToInt64(0xFF * alpha) & 0xFF);
+ if (alpha < 1.0F)
+ {
+ mat.Type = MaterialType.ColorTransparent;
+ mat.HasTransparency = true;
+ }
+ else if (!mat.HasTexture)
+ {
+ mat.Type = MaterialType.ColorSolid;
+ }
+ }
+
+ private void checkN64CodecInfo(Material m)
+ {
+ var gma = settings.TexTypeData.Split(',');
+ foreach (string gme in gma)
+ {
+ var gmd = gme.Split(':');
+ if (m.Name.Equals(gmd[0]))
+ {
+ var switchExpr = gmd[1];
+ switch (switchExpr)
+ {
+ case "rgba16":
+ {
+ m.TexType = N64Codec.RGBA16;
+ break;
+ }
+
+ case "rgba32":
+ {
+ m.TexType = N64Codec.RGBA32;
+ break;
+ }
+
+ case "ia4":
+ {
+ m.TexType = N64Codec.IA4;
+ break;
+ }
+
+ case "ia8":
+ {
+ m.TexType = N64Codec.IA8;
+ break;
+ }
+
+ case "ia16":
+ {
+ m.TexType = N64Codec.IA16;
+ break;
+ }
+
+ case "i4":
+ {
+ m.TexType = N64Codec.I4;
+ if (gmd.Count() > 2 && (gmd[2] ?? "") == "a")
+ m.EnableAlphaMask = true;
+ break;
+ }
+
+ case "i8":
+ {
+ m.TexType = N64Codec.I8;
+ if (gmd.Count() > 2 && (gmd[2] ?? "") == "a")
+ m.EnableAlphaMask = true;
+ break;
+ }
+ }
+
+ return;
+ }
+ }
+
+ m.TexType = N64Codec.RGBA16;
+ }
+
+ private Material[] GetDuplicates(Material mat)
+ {
+ var foundCopies = new List();
+ if (mat.HasTexture)
+ {
+ foreach (Material checkMat in materials)
+ {
+ if (checkMat != mat)
+ {
+ if (checkMat.HasTexture)
+ {
+ if (mat.TexType == checkMat.TexType && SM64Lib.General.CompareTwoByteArrays(mat.Texture.Data, checkMat.Texture.Data))
+ {
+ if (!foundCopies.Contains(checkMat))
+ {
+ foundCopies.Add(checkMat);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // For Each copyMat As Material In foundCopies
+ // copyMat.texture = mat.texture
+ // copyMat.offset = mat.offset
+ // copyMat.texWidth = mat.texWidth
+ // copyMat.texHeight = mat.texHeight
+ // copyMat.size = 0
+ // copyMat.isTextureCopy = True
+ // Next
+
+ return foundCopies.ToArray();
+ }
+
+ private void MergeDuplicatedTextures()
+ {
+ var matsToRemove = new List();
+
+ foreach (Material mat in materials)
+ {
+ if (!matsToRemove.Contains(mat))
+ {
+ foreach (Material dup in GetDuplicates(mat))
+ {
+ if (!matsToRemove.Contains(dup))
+ {
+ // Remove material
+ matsToRemove.Add(dup);
+
+ // Update material references for vertex group
+ foreach (VertexGroupList mp in vertexGroups)
+ {
+ if (mp.Material == dup)
+ mp.Material = mat;
+ }
+
+ // Update material references for material bindings
+ foreach (var kvp in materialBindings)
+ {
+ if (kvp.Value == dup)
+ materialBindings[kvp.Key] = mat;
+ }
+ }
+ }
+ }
+ }
+
+ foreach (Material mat in matsToRemove)
+ {
+ if (mat.HasTexture && textureBank.Contains(mat.Texture))
+ textureBank.Remove(mat.Texture);
+ materials.Remove(mat);
+ }
+ }
+
+ private void ProcessImage(Pilz.S3DFileParser.Object3D obj, Image img, Material mat)
+ {
+ TextureEntry entry = null;
+ foreach (TextureEntry tex in textureBank)
+ {
+ if (tex.OriginalImage == img)
+ {
+ entry = tex;
+ }
+ }
+
+ // Create & Add texture entry
+ if (entry is null)
+ {
+ entry = new TextureEntry()
+ {
+ Width = mat.TexWidth,
+ Height = mat.TexHeight,
+ OriginalImage = img
+ };
+ }
+
+ // Load Texture from File
+ var bmp = new Bitmap(img);
+
+ // Set texture size
+ mat.TexWidth = Convert.ToUInt32(bmp.Width);
+ mat.TexHeight = Convert.ToUInt32(bmp.Height);
+
+ // Convert texture
+ var argimageData = entry.Data;
+ var argpaletteData = entry.Palette;
+ N64Graphics.N64Graphics.Convert(ref argimageData, ref argpaletteData, mat.TexType, (Bitmap)img);
+ entry.Data = argimageData;
+ entry.Palette = argpaletteData;
+
+ mat.Type = MaterialType.TextureSolid;
+
+ // Check for alpha and transparency
+ {
+ var transparentPixels = 0;
+ var setAsAlpha = false;
+ var setAsTransparent = false;
+
+ // Check textures
+ for (int y = 0, loopTo = bmp.Height - 1; y <= loopTo; y++)
+ {
+ for (int x = 0, loopTo1 = bmp.Width - 1; x <= loopTo1; x++)
+ {
+ var pix = bmp.GetPixel(x, y);
+ switch (mat.TexType)
+ {
+ case N64Codec.RGBA16:
+ case N64Codec.RGBA32:
+ case N64Codec.IA4:
+ case N64Codec.IA8:
+ case N64Codec.IA16:
+ case N64Codec.CI4:
+ case N64Codec.CI8:
+ if (pix.A == 0)
+ setAsAlpha = true;
+ else if (pix.A < 0xFF)
+ transparentPixels++;
+ break;
+ case N64Codec.I4:
+ case N64Codec.I8:
+ if (pix.A < 0xFF || mat.EnableAlphaMask)
+ transparentPixels++;
+ break;
+ }
+ }
+ }
+
+ // Set as transparent if reached the limit
+ var transparentPixelsPercent = (float)transparentPixels / (mat.TexWidth * mat.TexHeight);
+ var reachedTransparentLimit = transparentPixelsPercent > (mat.TransparencyLimit ?? settings.TransparencyLimit);
+ switch (mat.TexType)
+ {
+ case N64Codec.RGBA16:
+ case N64Codec.RGBA32:
+ case N64Codec.IA4:
+ case N64Codec.IA8:
+ case N64Codec.IA16:
+ case N64Codec.CI4:
+ case N64Codec.CI8:
+ if (reachedTransparentLimit)
+ setAsTransparent = true;
+ break;
+ case N64Codec.I4:
+ case N64Codec.I8:
+ if (reachedTransparentLimit || mat.EnableAlphaMask)
+ setAsTransparent = true;
+ break;
+ }
+ if (setAsTransparent)
+ {
+ mat.Type = MaterialType.TextureTransparent;
+ mat.HasTransparency = true;
+ }
+ else if (setAsAlpha)
+ {
+ mat.HasTextureAlpha = true;
+ mat.Type = MaterialType.TextureAlpha;
+ }
+ }
+
+ if (!textureBank.Contains(entry))
+ textureBank.Add(entry);
+ mat.Texture = entry;
+ mat.HasTexture = true;
+ mat.HasPalette = entry.Palette.Any();
+ }
+
+ private void ProcessObject3DModel(Pilz.S3DFileParser.Object3D obj)
+ {
+ var texFormatSettings = settings.TextureFormatSettings;
+
+ // Process Materials
+ ProcessObject3DMaterials(obj, texFormatSettings);
+ foreach (Pilz.S3DFileParser.Mesh mesh in obj.Meshes)
+ {
+ int curIndexStart = verts.Count;
+
+ // Process Vertices
+ foreach (var vert in mesh.Vertices)
+ {
+ var v = new Vertex()
+ {
+ X = General.KeepInInt16Range(General.Round(vert.X * settings.Scale + settings.OffsetPosition.X)),
+ Y = General.KeepInInt16Range(General.Round(vert.Y * settings.Scale + settings.OffsetPosition.Y)),
+ Z = General.KeepInInt16Range(General.Round(vert.Z * settings.Scale + settings.OffsetPosition.Z))
+ };
+ verts.Add(v);
+ }
+
+ // Process Normals
+ foreach (var norm in mesh.Normals)
+ {
+ var n = new Normal()
+ {
+ A = Datatypecastes.LongToByte((long)General.Round(norm.X * 0x7F)),
+ B = Datatypecastes.LongToByte((long)General.Round(norm.Y * 0x7F)),
+ C = Datatypecastes.LongToByte((long)General.Round(norm.Z * 0x7F)),
+ D = 0xFF
+ };
+ norms.Add(n);
+ }
+
+ // Process UVs
+ foreach (Pilz.S3DFileParser.UV tuv in mesh.UVs)
+ {
+ var uv = new TexCord()
+ {
+ U = (float)General.Round(tuv.U * 32F * 32F),
+ V = (float)General.Round(-((tuv.V - 1) * 32F * 32F))
+ };
+ uvs.Add(uv);
+ }
+
+ // Process Vertex Colors
+ foreach (var vertcol in mesh.VertexColors)
+ {
+ var vc = new VertexColor()
+ {
+ R = Datatypecastes.LongToByte((long)General.Round(vertcol.R * 0xFF)),
+ G = Datatypecastes.LongToByte((long)General.Round(vertcol.G * 0xFF)),
+ B = Datatypecastes.LongToByte((long)General.Round(vertcol.B * 0xFF)),
+ A = Datatypecastes.LongToByte((long)General.Round(vertcol.A * 0xFF))
+ };
+ vertexColors.Add(vc);
+ }
+
+ // Process Faces
+ object curTexture = null;
+ foreach (var face in mesh.Faces) // .OrderBy(Function(n) n.Texture)
+ {
+ if (!ignoreFacesWithMaterial.Contains(face.Material))
+ {
+ if (curTexture is null || !curTexture.Equals(face.Material))
+ {
+ curTexture = face.Material;
+ string curMatName = obj.Materials.FirstOrDefault(n => n.Value.Equals(face.Material)).Key;
+ var curMat = materials.FirstOrDefault(n => n.Name.Equals(curMatName));
+ var mp = new VertexGroupList()
+ {
+ Position = currentFace,
+ Material = curMat
+ };
+ currentMaterial = curMat;
+ mp.Length = 0;
+ vertexGroups.Add(mp);
+ }
+
+ materialBindings.TryGetValue(face.Material, out Material mat);
+
+ Vertex va = null;
+ TexCord ta = null;
+ var tanew = new TexCord();
+ Normal na = null;
+ VertexColor vca = null;
+ Vertex vb = null;
+ TexCord tb = null;
+ var tbnew = new TexCord();
+ Normal nb = null;
+ VertexColor vcb = null;
+ Vertex vc = null;
+ TexCord tc = null;
+ var tcnew = new TexCord();
+ Normal nc = null;
+ VertexColor vcc = null;
+ void getVals(Pilz.S3DFileParser.Point point, ref Vertex vert, ref TexCord t, ref Normal normal, ref VertexColor vertcol)
+ {
+ if (point.Vertex is not null)
+ vert = verts[curIndexStart + mesh.Vertices.IndexOf(point.Vertex)];
+ if (point.UV is not null)
+ t = uvs[curIndexStart + mesh.UVs.IndexOf(point.UV)];
+ if (point.Normal is not null)
+ normal = norms[curIndexStart + mesh.Normals.IndexOf(point.Normal)];
+ if (point.VertexColor is not null)
+ vertcol = vertexColors[curIndexStart + mesh.VertexColors.IndexOf(point.VertexColor)];
+ };
+ getVals(face.Points[0], ref va, ref ta, ref na, ref vca);
+ getVals(face.Points[1], ref vb, ref tb, ref nb, ref vcb);
+ getVals(face.Points[2], ref vc, ref tc, ref nc, ref vcc);
+ var fa = new FinalVertexData();
+ var fb = new FinalVertexData();
+ var fc = new FinalVertexData();
+
+ // Modify UV cordinates based on material.
+ void modifyUVCordinates(TexCord tnew, TexCord t)
+ {
+ tnew.U = (float)(t.U * (mat.TexWidth / 32.0) - 16); // "-16" fixes the UVs offset
+ tnew.V = (float)(t.V * (mat.TexHeight / 32.0) - 16); // "-16" fixes the UVs offset
+ };
+ modifyUVCordinates(tanew, ta);
+ modifyUVCordinates(tbnew, tb);
+ modifyUVCordinates(tcnew, tc);
+
+ // Fix UVs to reduce number of (large) faces with broken textures
+ FixUVs(tanew, tbnew, tcnew, Convert.ToInt32(mat.TexWidth), Convert.ToInt32(mat.TexHeight));
+
+ // Vertex Structure: xxxxyyyyzzzz0000uuuuvvvvrrggbbaa
+ void buildVertexStructure(FinalVertexData final, Vertex vert, VertexColor vertcol, TexCord tnew, Normal normal)
+ {
+ final.Data[0] = Convert.ToByte(vert.X >> 8 & 0xFF);
+ final.Data[1] = Convert.ToByte(vert.X & 0xFF);
+ final.Data[2] = Convert.ToByte(vert.Y >> 8 & 0xFF);
+ final.Data[3] = Convert.ToByte(vert.Y & 0xFF);
+ final.Data[4] = Convert.ToByte(vert.Z >> 8 & 0xFF);
+ final.Data[5] = Convert.ToByte(vert.Z & 0xFF);
+ final.Data[6] = 0;
+ final.Data[7] = 0;
+ int uInt, vInt;
+ uInt = Convert.ToInt32(Math.Round(tnew.U));
+ vInt = Convert.ToInt32(Math.Round(tnew.V));
+ final.Data[8] = Convert.ToByte(uInt >> 8 & 0xFF);
+ final.Data[9] = Convert.ToByte(uInt & 0xFF);
+ final.Data[10] = Convert.ToByte(vInt >> 8 & 0xFF);
+ final.Data[11] = Convert.ToByte(vInt & 0xFF);
+ if (vertcol is not null && !mat.EnableCrystalEffect)
+ {
+ final.Data[12] = vertcol.R;
+ final.Data[13] = vertcol.G;
+ final.Data[14] = vertcol.B;
+ final.Data[15] = vertcol.A;
+ final.EnableVertexColor = true;
+ // FIXME: Add warning if Type is not TextureSolid
+ if (final.EnableVertexTransparent)
+ {
+ mat.Type = MaterialType.TextureTransparent;
+ mat.HasTransparency = mat.HasTransparency || final.EnableVertexTransparent;
+ }
+ }
+ else
+ {
+ final.Data[12] = normal.A;
+ final.Data[13] = normal.B;
+ final.Data[14] = normal.C;
+ final.Data[15] = normal.D;
+ final.EnableVertexColor = false;
+ }
+ };
+ buildVertexStructure(fa, va, vca, tanew, na);
+ buildVertexStructure(fb, vb, vcb, tbnew, nb);
+ buildVertexStructure(fc, vc, vcc, tcnew, nc);
+ finalVertData.AddRange(new[] { fa, fb, fc });
+ currentFace += 1;
+ }
+ }
+ }
+ }
+
+ private void ProcessObject3DMaterials(Pilz.S3DFileParser.Object3D obj, Model.Fast3D.TextureFormatSettings texFormatSettings)
+ {
+ var tasks = new List();
+
+ // Start converting each image
+ foreach (var kvp in obj.Materials)
+ {
+ ProcessObject3DMaterial(obj, kvp, texFormatSettings);
+ }
+ }
+
+ private void ProcessObject3DMaterial(Pilz.S3DFileParser.Object3D obj, KeyValuePair kvp, Model.Fast3D.TextureFormatSettings texFormatSettings)
+ {
+ int size = 0;
+ var curEntry = texFormatSettings.GetEntry(kvp.Key);
+
+ if (curEntry.Include)
+ {
+ // Create new Material
+ var m = new Material()
+ {
+ Type = MaterialType.ColorSolid,
+ Color = 0,
+ HasTexture = false,
+ HasTextureAlpha = false,
+ HasTransparency = false,
+ Name = kvp.Key,
+ Collision = 0,
+ Opacity = 0xFF,
+ OpacityOrg = 0xFF,
+ EnableGeoMode = false,
+ EnableTextureColor = false,
+ EnableAlphaMask = false,
+ CameFromBMP = false,
+ EnableScrolling = curEntry.IsScrollingTexture,
+ DisplaylistSelection = curEntry.DisplaylistSelection,
+ EnableMirrorS = curEntry.EnableMirrorS,
+ EnableMirrorT = curEntry.EnableMirrorT,
+ EnableClampS = curEntry.EnableClampS,
+ EnableClampT = curEntry.EnableClampT,
+ EnableCrystalEffect = curEntry.EnableCrystalEffect,
+ FaceCullingMode = curEntry.FaceCullingMode,
+ TransparencyLimit = curEntry.TransparencyLimit,
+ TexFilter = curEntry.TextureFilter
+ };
+
+ // Set default size
+ size = 0x10;
+
+ // Check some things
+ CheckGeoModeInfo(m);
+ CheckColorTexInfo(m);
+
+ // Add material
+ materials.Add(m);
+ materialBindings.Add(kvp.Value, m);
+
+ // Process Material Color
+ if (!m.EnableTextureColor)
+ {
+ uint r = kvp.Value.Color.Value.R;
+ uint g = kvp.Value.Color.Value.G;
+ uint b = kvp.Value.Color.Value.B;
+ uint a = kvp.Value.Color.Value.A;
+ m.Color = r << 24 | g << 16 | b << 8 | a;
+ if (a == (long)0xFF)
+ {
+ m.Type = MaterialType.ColorSolid;
+ }
+ else
+ {
+ m.Type = MaterialType.ColorTransparent;
+ }
+ }
+
+ // Check Texture Type
+ if (texFormatSettings is not null)
+ {
+ m.TexType = N64Graphics.N64Graphics.StringCodec(texFormatSettings.GetEntry(kvp.Key).TextureFormat);
+ }
+
+ // Process Material Image
+ if (kvp.Value.Image is not null)
+ {
+ ProcessImage(obj, kvp.Value.Image, m);
+ size = m.Texture.Data.Length;
+ }
+
+ // Process Material Color Alpha
+ if (kvp.Value.Opacity is not null)
+ {
+ float tempopacity = (float)kvp.Value.Opacity;
+ m.Opacity = Convert.ToByte(Convert.ToInt64(tempopacity * 0xFF) & 0xFF);
+ m.OpacityOrg = m.Opacity;
+ processMaterialColorAlpha(tempopacity, m);
+ }
+
+ // Set offset and size
+ m.Size = Convert.ToUInt32(size);
+ if (m.Texture?.Palette is not null)
+ {
+ m.PaletteSize = Convert.ToUInt32(m.Texture.Palette.Length);
+ }
+ }
+ else
+ ignoreFacesWithMaterial.Add(kvp.Value);
+ }
+
+ private void FixUVs(TexCord uv1, TexCord uv2, TexCord uv3, int matWidth, int matHeight)
+ {
+ if (matWidth <= 0 || matHeight <= 0) return;
+
+ TexCord[] uvs = null;
+ int jump = 0;
+
+ float RoundToJump(float val)
+ {
+ var mod = val % jump;
+
+ if (mod >= 0.5 * jump)
+ val = jump * (float)Math.Ceiling(val / jump); // Round up
+ else
+ val = jump * (float)Math.Floor(val / jump); // Round down
+
+ return val;
+ }
+
+ jump = matWidth * 0x40;
+ uvs = new[] { uv1, uv2, uv3 }.OrderBy(n => n.U).ToArray();
+ if (jump != 0)
+ {
+ // Move near 0
+ float varM;
+ varM = RoundToJump((uvs[0].U + uvs[2].U) / 2);
+ uvs[0].U -= varM;
+ uvs[1].U -= varM;
+ uvs[2].U -= varM;
+
+ // Keep in bounds (max bounds)
+ while (uvs.Last().U > short.MaxValue)
+ {
+ uvs[0].U -= jump;
+ uvs[1].U -= jump;
+ uvs[2].U -= jump;
+ }
+
+ // Keep in bounds (min bounds)
+ while (uvs.First().U < short.MinValue)
+ {
+ uvs[0].U += jump;
+ uvs[1].U += jump;
+ uvs[2].U += jump;
+ }
+ }
+
+ jump = matHeight * 0x40;
+ uvs = new[] { uv1, uv2, uv3 }.OrderBy(n => n.V).ToArray();
+ if (jump != 0)
+ {
+ // Move near 0
+ float varM;
+ varM = RoundToJump((uvs[0].V + uvs[2].V) / 2);
+ uvs[0].V -= varM;
+ uvs[1].V -= varM;
+ uvs[2].V -= varM;
+
+ // Keep in bounds (max bounds)
+ while (uvs.Last().V > short.MaxValue)
+ {
+ uvs[0].V -= jump;
+ uvs[1].V -= jump;
+ uvs[2].V -= jump;
+ }
+
+ // Keep in bounds (min bounds)
+ while (uvs.First().V < short.MinValue)
+ {
+ uvs[0].V += jump;
+ uvs[1].V += jump;
+ uvs[2].V += jump;
+ }
+ }
+ }
+
+ private void removeDuplicateVertices(ReduceVericesLevel level)
+ {
+ if ((int)level < 1)
+ return;
+ int dupCnt = 0;
+ foreach (VertexGroupList mp in vertexGroups)
+ {
+ for (int g = 0; g < mp.GroupsCount; g++)
+ {
+ var fvg = mp.FinalVertexGroups[g];
+ if (fvg.VertexDataCount < 1) continue;
+
+ for (int i = 0; i < fvg.VertexDataCount; i++)
+ {
+ int j = i + 1;
+ while (j < fvg.VertexDataCount)
+ {
+ if (SM64Lib.General.CompareTwoByteArrays(fvg.FinalVertexData[i].Data, fvg.FinalVertexData[j].Data, 16))
+ {
+ moveElementsInGroupUpward(fvg, 1, j);
+ UpdateIndexList(fvg, Convert.ToByte(j), Convert.ToByte(i));
+ UpdatePositions(mp.StartIndex);
+ dupCnt += 1;
+ }
+
+ j += 1;
+ }
+ }
+
+ if (g < mp.GroupsCount - 1 && (int)level > 1)
+ {
+ if (MoveVertsBack(mp.FinalVertexGroups[g], mp.FinalVertexGroups[g + 1]))
+ {
+ g -= 1;
+ }
+ }
+ }
+ }
+ }
+
+ private void UpdateIndexList(FvGroup grp, byte removed, byte replaceWith)
+ {
+ for (int i = 0; i < grp.NumTri * 3; i++)
+ {
+ if (grp.indexList[i] < removed) continue;
+
+ if (grp.indexList[i] == removed)
+ {
+ grp.indexList[i] = Convert.ToSByte(replaceWith);
+ }
+ else
+ {
+ grp.indexList[i] -= 1;
+ }
+ }
+ }
+
+ private void UpdatePositions(int vs)
+ {
+ foreach (VertexGroupList mp in vertexGroups)
+ {
+ if (mp.StartIndex <= vs)
+ continue;
+ if (mp.StartIndex < 0x10)
+ continue;
+ mp.StartIndex -= 0x10;
+ }
+ }
+
+ private bool MoveVertsBack(FvGroup to, FvGroup from)
+ {
+ if (from.VertexDataCount < 3)
+ return false;
+ if (to.VertexDataCount < 14)
+ {
+ to.FinalVertexData.Add(from.FinalVertexData[0]);
+ to.FinalVertexData.Add(from.FinalVertexData[1]);
+ to.FinalVertexData.Add(from.FinalVertexData[2]);
+ to.indexList[to.NumTri * 3] = Convert.ToSByte(to.VertexDataCount);
+ to.indexList[to.NumTri * 3 + 1] = Convert.ToSByte(to.VertexDataCount + 1);
+ to.indexList[to.NumTri * 3 + 2] = Convert.ToSByte(to.VertexDataCount + 2);
+ moveElementsInGroupUpward(from, 3, 0);
+ to.NumTri += 1;
+ return true;
+ }
+
+ return false;
+ }
+
+ private void moveElementsInGroupUpward(FvGroup grp, int amount, int start)
+ {
+ for (int i = 0; i < amount; i++)
+ grp.FinalVertexData.RemoveAt(start);
+ }
+
+ private void BuildVertexGroups()
+ {
+ int vs = 0;
+ for (int i = 0; i < vertexGroups.Count; i++)
+ {
+ {
+ var withBlock = vertexGroups[i];
+ withBlock.StartIndex = vs;
+ int length = 0;
+ if (i < vertexGroups.Count - 1)
+ {
+ length = vertexGroups[i + 1].Position - withBlock.Position;
+ }
+ else
+ {
+ length = currentFace - withBlock.Position;
+ }
+
+ int groupsCount = 0;
+ if (length % 5 == 0)
+ {
+ groupsCount = (int)(length / (double)5);
+ }
+ else
+ {
+ groupsCount = (int)(Math.Truncate(length / (double)5) + 1);
+ }
+
+ for (int j = 0; j < groupsCount; j++)
+ withBlock.FinalVertexGroups.Add(new FvGroup() { NumTri = 0 });
+
+ for (int g = 0; g < groupsCount; g++)
+ {
+ int s = 5;
+ if (g == groupsCount - 1)
+ s = length % 5;
+ if (s == 0)
+ s = 5;
+ var curFvg = withBlock.FinalVertexGroups[g];
+ curFvg.NumTri = Convert.ToInt16(s);
+ for (int j = 0; j < s; j++)
+ {
+ int from = (withBlock.Position + j + g * 5) * 3;
+ curFvg.FinalVertexData.Add(finalVertData[from]);
+ curFvg.FinalVertexData.Add(finalVertData[from + 1]);
+ curFvg.FinalVertexData.Add(finalVertData[from + 2]);
+ curFvg.indexList[j * 3] = Convert.ToSByte(j * 3);
+ curFvg.indexList[j * 3 + 1] = Convert.ToSByte(j * 3 + 1);
+ curFvg.indexList[j * 3 + 2] = Convert.ToSByte(j * 3 + 2);
+ }
+
+ vs += curFvg.VertexDataCount * 0x10;
+ }
+ }
+ }
+ }
+
+ private F3D StrToF3D(string str)
+ {
+ var cmd = new F3D();
+ var b = str.Replace(".", ",").Split(' ');
+ for (int i = 0; i < b.Length; i++)
+ cmd.data[i] = Convert.ToByte($"&H{b[i]}");
+ return cmd;
+ }
+
+ private void ImpF3D(string str)
+ {
+ ImpF3D(StrToF3D(str));
+ }
+
+ private void ImpF3D(params byte[] data)
+ {
+ impdata.Write(data);
+ }
+
+ private void ImpF3D(F3D f3d)
+ {
+ impdata.Write(f3d.data);
+ }
+
+ private void ImpFogStart(int layer)
+ {
+ ImpF3D("BA 00 14 02 00 10 00 00");
+ string cmdF8 = "";
+ cmdF8 = $"F8 00 00 00 {Hex(settings.Fog.Color.R)} {Hex(settings.Fog.Color.G)} {Hex(settings.Fog.Color.B)} FF";
+ ImpF3D(cmdF8);
+ switch (layer)
+ {
+ case 0:
+ {
+ ImpF3D("B9 00 03 1D C8 11 22 30");
+ break;
+ }
+
+ case 1:
+ {
+ ImpF3D("B9 00 03 1D C8 11 20 78");
+ break;
+ }
+
+ case 2:
+ {
+ ImpF3D("B9 00 03 1D C8 11 2D 58");
+ break;
+ }
+
+ case 3:
+ {
+ ImpF3D("B9 00 03 1D C8 10 4D D8");
+ break;
+ }
+ // ImpF3D("B9 00 03 1D 00 11 24 78")
+ case 4:
+ {
+ ImpF3D("B9 00 03 1D C8 11 30 78");
+ break;
+ }
+
+ case 5:
+ {
+ ImpF3D("B9 00 03 1D C8 10 49 D8");
+ break;
+ }
+
+ case 6:
+ {
+ ImpF3D("B9 00 03 1D C8 10 4D D8");
+ break;
+ }
+
+ case 7:
+ {
+ ImpF3D("B9 00 03 1D C8 10 45 D8");
+ break;
+ }
+ }
+
+ var switchExpr = settings.Fog.Type;
+ switch (switchExpr)
+ {
+ case global::SM64Lib.Model.FogPreset.SubtleFog1:
+ {
+ ImpF3D("BC 00 00 08 19 00 E8 00");
+ break;
+ }
+
+ case global::SM64Lib.Model.FogPreset.SubtleFog2:
+ {
+ ImpF3D("BC 00 00 08 12 00 F0 00");
+ break;
+ }
+
+ case global::SM64Lib.Model.FogPreset.ModerateFog1:
+ {
+ ImpF3D("BC 00 00 08 0E 49 F2 B7");
+ break;
+ }
+
+ case global::SM64Lib.Model.FogPreset.ModerateFog2:
+ {
+ ImpF3D("BC 00 00 08 0C 80 F4 80");
+ break;
+ }
+
+ case global::SM64Lib.Model.FogPreset.ModerateFog3:
+ {
+ ImpF3D("BC 00 00 08 0A 00 F7 00");
+ break;
+ }
+
+ case global::SM64Lib.Model.FogPreset.ModerateFog4:
+ {
+ ImpF3D("BC 00 00 08 08 55 F8 AB");
+ break;
+ }
+
+ case global::SM64Lib.Model.FogPreset.IntenseFog:
+ {
+ ImpF3D("BC 00 00 08 07 24 F9 DC");
+ break;
+ }
+
+ case global::SM64Lib.Model.FogPreset.VeryIntenseFog:
+ {
+ ImpF3D("BC 00 00 08 05 00 FC 00");
+ break;
+ }
+
+ case global::SM64Lib.Model.FogPreset.HardcoreFog:
+ {
+ ImpF3D("BC 00 00 08 02 50 FF 00");
+ break;
+ }
+ }
+
+ ImpF3D("B7 00 00 00 00 01 00 00");
+ }
+
+ private void ImpFogEnd(int layer)
+ {
+ ImpF3D("BA 00 14 02 00 00 00 00");
+ switch (layer)
+ {
+ case 0:
+ {
+ ImpF3D("B9 00 03 1D 00 44 22 30");
+ break;
+ }
+
+ case 1:
+ {
+ ImpF3D("B9 00 03 1D 00 44 20 78");
+ break;
+ }
+
+ case 2:
+ {
+ ImpF3D("B9 00 03 1D 00 44 2D 58");
+ break;
+ }
+
+ case 3:
+ {
+ ImpF3D("B9 00 03 1D 00 40 4D D8");
+ break;
+ }
+ // ImpF3D("B9 00 03 1D 00 44 24 78")
+ case 4:
+ {
+ ImpF3D("B9 00 03 1D 00 44 30 78");
+ break;
+ }
+
+ case 5:
+ {
+ ImpF3D("B9 00 03 1D 00 40 49 D8");
+ break;
+ }
+
+ case 6:
+ {
+ ImpF3D("B9 00 03 1D 00 40 4D D8");
+ break;
+ }
+
+ case 7:
+ {
+ ImpF3D("B9 00 03 1D 00 40 45 D8");
+ break;
+ }
+ }
+
+ ImpF3D("B6 00 00 00 00 01 00 00"); // B6 00 00 00 00 01 02 00 --> Smoothen Shading?
+ }
+
+ private byte GetTypeFromTexType(N64Codec texType, bool advanced = false)
+ {
+ switch (texType)
+ {
+ case N64Codec.CI4:
+ {
+ return Convert.ToByte(advanced ? 0x50 : 0x40);
+ }
+
+ case N64Codec.CI8:
+ {
+ return Convert.ToByte(advanced ? 0x50 : 0x48);
+ }
+
+ case N64Codec.I4:
+ {
+ return Convert.ToByte(advanced ? 0x90 : 0x80);
+ }
+
+ case N64Codec.I8:
+ {
+ return Convert.ToByte(advanced ? 0x90 : 0x88);
+ }
+
+ case N64Codec.IA4:
+ {
+ return Convert.ToByte(advanced ? 0x70 : 0x60);
+ }
+
+ case N64Codec.IA8:
+ {
+ return Convert.ToByte(advanced ? 0x70 : 0x68);
+ }
+
+ case N64Codec.IA16:
+ {
+ return 0x70;
+ }
+
+ case N64Codec.RGBA16:
+ {
+ return 0x10;
+ }
+
+ case N64Codec.RGBA32:
+ {
+ return 0x18;
+ }
+
+ default:
+ {
+ return default;
+ }
+ }
+ }
+
+ private byte BytesPerType(N64Codec type)
+ {
+ switch (type)
+ {
+ case N64Codec.RGBA16:
+ {
+ return 2;
+ }
+
+ case N64Codec.RGBA32:
+ {
+ return 4;
+ }
+
+ case N64Codec.I4:
+ case N64Codec.IA4:
+ case N64Codec.CI4:
+ {
+ return 0; // Special case
+ }
+
+ case N64Codec.IA8:
+ case N64Codec.I8:
+ case N64Codec.CI8:
+ {
+ return 1;
+ }
+
+ default:
+ {
+ return 2;
+ }
+ }
+ }
+
+ private byte GetTexelIncrement(N64Codec type)
+ {
+ switch (type)
+ {
+ case N64Codec.I4:
+ case N64Codec.IA4:
+ {
+ return 3;
+ }
+
+ case N64Codec.IA8:
+ case N64Codec.I8:
+ {
+ return 1;
+ }
+
+ default:
+ {
+ return 0;
+ }
+ }
+ }
+
+ private byte GetTexelShift(N64Codec type)
+ {
+ switch (type)
+ {
+ case N64Codec.I4:
+ case N64Codec.IA4:
+ case N64Codec.CI4:
+ {
+ return 2;
+ }
+
+ case N64Codec.IA8:
+ case N64Codec.I8:
+ case N64Codec.CI8:
+ {
+ return 1;
+ }
+
+ default:
+ {
+ return 0;
+ }
+ }
+ }
+
+ private void ImpCmdFD(uint offset, N64Codec texType)
+ {
+ uint off = startSegOffset + offset;
+ byte type = GetTypeFromTexType(texType, true);
+ ImpF3D($"FD {Hex(type)} 00 00 {Hex(curSeg & 0xFF)} {Hex(off >> 16 & (long)0xFF)} {Hex(off >> 8 & (long)0xFF)} {Hex(off & (long)0xFF)}");
+ }
+
+ private void ImpCmdF5_First(N64Codec texType)
+ {
+ byte type = GetTypeFromTexType(texType, true);
+ ImpF3D($"F5 {Hex(type)} 00 00 07 00 00 00");
+ }
+
+ private void ImpCmdF5_Second(Material mat, uint texWidth, uint texHeight)
+ {
+ // Create upper
+ byte type = GetTypeFromTexType(mat.TexType);
+ float lineScale = 1.0F;
+ byte bpt = BytesPerType(mat.TexType);
+ if (bpt != 0)
+ {
+ lineScale = (float)(bpt / 4.0);
+ }
+ else
+ {
+ lineScale = 0.125F;
+ }
+
+ if (mat.TexType == N64Codec.RGBA32)
+ {
+ lineScale /= 2;
+ }
+
+ ushort line = Convert.ToUInt16(Convert.ToUInt16(Math.Truncate(texWidth * lineScale)) & 0x1FF);
+ uint upper = Convert.ToUInt32((Convert.ToUInt32(type) << 16 | Convert.ToUInt32(line) << 8) & (long)0xFFFFFF);
+
+ // Create lower (shift)
+ byte maskS = Convert.ToByte(Convert.ToInt64(Math.Ceiling(Math.Log(texWidth, 2))) & 0xF);
+ byte maskT = Convert.ToByte(Convert.ToInt64(Math.Ceiling(Math.Log(texHeight, 2))) & 0xF);
+ uint lower = Convert.ToUInt32((Convert.ToUInt32(maskT) << 14 | Convert.ToUInt32(maskS) << 4) & (long)0xFFFFFF); // &HFFC3F0 for only shift
+ if (mat.EnableMirrorS)
+ {
+ lower = Convert.ToUInt32(lower | (long)0x100); // S axis
+ }
+
+ if (mat.EnableMirrorT)
+ {
+ lower = Convert.ToUInt32(lower | (long)0x40000); // T axis
+ }
+
+ if (mat.EnableClampS)
+ {
+ lower = Convert.ToUInt32(lower | (long)0x80000); // T axis
+ }
+
+ if (mat.EnableClampT)
+ {
+ lower = Convert.ToUInt32(lower | (long)0x200); // S axis
+ }
+
+ // Create Command
+ ImpF3D($"F5 {Hex(upper >> 16 & (long)0xFF)} {Hex(upper >> 8 & (long)0xFF)} {Hex(upper & (long)0xFF)} 00 {Hex(lower >> 16 & (long)0xFF)} {Hex(lower >> 8 & (long)0xFF)} {Hex(lower & (long)0xFF)}");
+ }
+
+ private void AddCmdF3(Material mat)
+ {
+ uint numTexels = (uint)((mat.TexWidth * mat.TexHeight + GetTexelIncrement(mat.TexType) >> GetTexelShift(mat.TexType)) - (long)1);
+ int bpt = BytesPerType(mat.TexType);
+ uint tl;
+ uint lower;
+ string cmd;
+ if (bpt != 0)
+ {
+ tl = Convert.ToUInt32(Operators.AndObject(CALC_DXT(mat.TexWidth, bpt), 4095));
+ }
+ else
+ {
+ tl = Convert.ToUInt32(Operators.AndObject(CALC_DXT_4b(mat.TexWidth), 4095));
+ }
+
+ lower = Convert.ToUInt32((numTexels << 12 | tl) & (long)0xFFFFFF);
+ cmd = $"F3 00 00 00 07 {Hex(lower >> 16 & (long)0xFF)} {Hex(lower >> 8 & (long)0xFF)} {Hex(lower & (long)0xFF)}";
+ ImpF3D(cmd);
+ }
+
+ private void AddCmdF2(Material mat)
+ {
+ ushort width = Convert.ToUInt16(mat.TexWidth - (long)1 << 2 & 0xFFF);
+ ushort height = Convert.ToUInt16(mat.TexHeight - (long)1 << 2 & 0xFFF);
+ uint data = Convert.ToUInt32(Convert.ToInt32(width) << 12 | height);
+ string cmd = $"F2 00 00 00 00 {Hex(data >> 16 & (long)0xFF)} {Hex(data >> 8 & (long)0xFF)} {Hex(data & (long)0xFF)}";
+ ImpF3D(cmd);
+ }
+
+ private void AddCmdFC(Material mat, ref string lastCmd)
+ {
+ string cmd = string.Empty;
+ var colorFormula = Model.Fast3D.DisplayLists.Script.Commands.F3D_SETCOMBINE.Formula.Output(Model.Fast3D.DisplayLists.Script.Commands.F3D_SETCOMBINE.CCMUX.SHADE);
+ bool isColorPresent = Convert.ToDouble(Convert.ToString(mat.Color) + Convert.ToString(0xFFFFFF00U)) != 0xFFFFFF00U;
+ if (mat.HasTexture)
+ {
+ colorFormula = Model.Fast3D.DisplayLists.Script.Commands.F3D_SETCOMBINE.Formula.Multiply(Model.Fast3D.DisplayLists.Script.Commands.F3D_SETCOMBINE.CCMUX.TEXEL0, Model.Fast3D.DisplayLists.Script.Commands.F3D_SETCOMBINE.CCMUX.SHADE);
+ }
+ else
+ {
+ var switchExpr1 = mat.Type;
+ switch (switchExpr1)
+ {
+ case MaterialType.None:
+ case MaterialType.TextureSolid:
+ case MaterialType.TextureAlpha:
+ case MaterialType.TextureTransparent:
+ {
+ colorFormula = Model.Fast3D.DisplayLists.Script.Commands.F3D_SETCOMBINE.Formula.Output(Model.Fast3D.DisplayLists.Script.Commands.F3D_SETCOMBINE.CCMUX.SHADE);
+ break;
+ }
+
+ case MaterialType.ColorSolid:
+ case MaterialType.ColorTransparent:
+ {
+ if (isColorPresent)
+ {
+ colorFormula = Model.Fast3D.DisplayLists.Script.Commands.F3D_SETCOMBINE.Formula.Multiply(Model.Fast3D.DisplayLists.Script.Commands.F3D_SETCOMBINE.CCMUX.SHADE, Model.Fast3D.DisplayLists.Script.Commands.F3D_SETCOMBINE.CCMUX.ENVIRONMENT);
+ }
+ else
+ {
+ colorFormula = Model.Fast3D.DisplayLists.Script.Commands.F3D_SETCOMBINE.Formula.Output(Model.Fast3D.DisplayLists.Script.Commands.F3D_SETCOMBINE.CCMUX.SHADE);
+ }
+
+ break;
+ }
+ }
+ }
+
+ var alphaFormula = Model.Fast3D.DisplayLists.Script.Commands.F3D_SETCOMBINE.Formula.Output(Model.Fast3D.DisplayLists.Script.Commands.F3D_SETCOMBINE.ACMUX.SHADE);
+ bool isTransPresent = Convert.ToDouble(Convert.ToString(mat.Color) + Convert.ToString(0xFFU)) != 0xFFU;
+ if (mat.HasTexture)
+ {
+ var switchExpr2 = mat.Type;
+ switch (switchExpr2)
+ {
+ case MaterialType.None:
+ case MaterialType.TextureSolid:
+ case MaterialType.ColorSolid:
+ {
+ alphaFormula = Model.Fast3D.DisplayLists.Script.Commands.F3D_SETCOMBINE.Formula.Output(Model.Fast3D.DisplayLists.Script.Commands.F3D_SETCOMBINE.ACMUX.ONE);
+ break;
+ }
+
+ case MaterialType.TextureAlpha:
+ case MaterialType.TextureTransparent:
+ {
+ // With Fog multiplying SHADE is not something you want because it will be alpha fog so just output TEXEL0 and hope it is fine
+ if (settings.EnableFog)
+ {
+ alphaFormula = Model.Fast3D.DisplayLists.Script.Commands.F3D_SETCOMBINE.Formula.Output(Model.Fast3D.DisplayLists.Script.Commands.F3D_SETCOMBINE.ACMUX.TEXEL0);
+ }
+ else
+ {
+ alphaFormula = Model.Fast3D.DisplayLists.Script.Commands.F3D_SETCOMBINE.Formula.Multiply((byte)Model.Fast3D.DisplayLists.Script.Commands.F3D_SETCOMBINE.ACMUX.TEXEL0, (byte)Model.Fast3D.DisplayLists.Script.Commands.F3D_SETCOMBINE.ACMUX.SHADE);
+ }
+
+ break;
+ }
+
+ case MaterialType.ColorTransparent:
+ {
+ if (isTransPresent)
+ {
+ alphaFormula = Model.Fast3D.DisplayLists.Script.Commands.F3D_SETCOMBINE.Formula.Multiply((byte)Model.Fast3D.DisplayLists.Script.Commands.F3D_SETCOMBINE.ACMUX.TEXEL0, (byte)Model.Fast3D.DisplayLists.Script.Commands.F3D_SETCOMBINE.ACMUX.ENVIRONMENT);
+ }
+ else if (settings.EnableFog)
+ {
+ alphaFormula = Model.Fast3D.DisplayLists.Script.Commands.F3D_SETCOMBINE.Formula.Output(Model.Fast3D.DisplayLists.Script.Commands.F3D_SETCOMBINE.ACMUX.TEXEL0);
+ }
+ else
+ {
+ alphaFormula = Model.Fast3D.DisplayLists.Script.Commands.F3D_SETCOMBINE.Formula.Multiply((byte)Model.Fast3D.DisplayLists.Script.Commands.F3D_SETCOMBINE.ACMUX.TEXEL0, (byte)Model.Fast3D.DisplayLists.Script.Commands.F3D_SETCOMBINE.ACMUX.SHADE);
+ }
+
+ break;
+ }
+ }
+ }
+ else
+ {
+ var switchExpr3 = mat.Type;
+ switch (switchExpr3)
+ {
+ case MaterialType.None:
+ case MaterialType.TextureSolid:
+ case MaterialType.ColorSolid:
+ {
+ alphaFormula = Model.Fast3D.DisplayLists.Script.Commands.F3D_SETCOMBINE.Formula.Output(Model.Fast3D.DisplayLists.Script.Commands.F3D_SETCOMBINE.ACMUX.ONE);
+ break;
+ }
+
+ case MaterialType.TextureAlpha:
+ case MaterialType.TextureTransparent:
+ {
+ // With Fog multiplying SHADE is not something you want because it will be alpha fog so just output TEXEL0
+ if (settings.EnableFog)
+ {
+ alphaFormula = Model.Fast3D.DisplayLists.Script.Commands.F3D_SETCOMBINE.Formula.Output(Model.Fast3D.DisplayLists.Script.Commands.F3D_SETCOMBINE.ACMUX.ONE);
+ }
+ else
+ {
+ alphaFormula = Model.Fast3D.DisplayLists.Script.Commands.F3D_SETCOMBINE.Formula.Output(Model.Fast3D.DisplayLists.Script.Commands.F3D_SETCOMBINE.ACMUX.SHADE);
+ }
+
+ break;
+ }
+
+ case MaterialType.ColorTransparent:
+ {
+ if (isTransPresent)
+ {
+ // If there is no material, may as well provide more options for alpha modulate
+ if (settings.EnableFog)
+ {
+ alphaFormula = Model.Fast3D.DisplayLists.Script.Commands.F3D_SETCOMBINE.Formula.Output(Model.Fast3D.DisplayLists.Script.Commands.F3D_SETCOMBINE.ACMUX.ENVIRONMENT);
+ }
+ else
+ {
+ alphaFormula = Model.Fast3D.DisplayLists.Script.Commands.F3D_SETCOMBINE.Formula.Multiply((byte)Model.Fast3D.DisplayLists.Script.Commands.F3D_SETCOMBINE.ACMUX.ENVIRONMENT, (byte)Model.Fast3D.DisplayLists.Script.Commands.F3D_SETCOMBINE.ACMUX.SHADE);
+ }
+ }
+ else if (settings.EnableFog)
+ {
+ alphaFormula = Model.Fast3D.DisplayLists.Script.Commands.F3D_SETCOMBINE.Formula.Output(Model.Fast3D.DisplayLists.Script.Commands.F3D_SETCOMBINE.ACMUX.ONE);
+ }
+ else
+ {
+ alphaFormula = Model.Fast3D.DisplayLists.Script.Commands.F3D_SETCOMBINE.Formula.Output(Model.Fast3D.DisplayLists.Script.Commands.F3D_SETCOMBINE.ACMUX.SHADE);
+ }
+
+ break;
+ }
+ }
+ }
+
+ cmd = Model.Fast3D.DisplayLists.Script.Commands.F3D_SETCOMBINE.Make(colorFormula, alphaFormula, settings.EnableFog);
+ if (!string.IsNullOrEmpty(cmd) && (lastCmd ?? "") != (cmd ?? ""))
+ {
+ ImpF3D(cmd);
+ lastCmd = cmd;
+ }
+ }
+
+ private void ImpTriCmds(Material mat, FvGroup grp, int offset, ref bool enabledVertexColors)
+ {
+ if (grp.VertexDataCount < 3)
+ return;
+ uint off = (uint)(startSegOffset + offset);
+ int amount = grp.VertexDataCount * 0x10;
+ if (mat.EnableScrolling)
+ {
+ AddScrollingTexture(grp, Convert.ToInt32(off), Convert.ToInt32(mat.Offset), mat.Name);
+ }
+
+ if (grp.EnableVertexColors)
+ {
+ if (!enabledVertexColors)
+ {
+ ImpF3D("B6 00 00 00 00 02 00 00");
+ enabledVertexColors = true;
+ }
+ }
+ else if (enabledVertexColors)
+ {
+ ImpF3D("B7 00 00 00 00 02 00 00");
+ enabledVertexColors = false;
+ }
+
+ ImpF3D($"04 {Hex(amount - 0x10 & 0xFF)} 00 {Hex(amount & 0xFF)} {Hex(curSeg)} {Hex(off >> 16 & (long)0xFF)} {Hex(off >> 8 & (long)0xFF)} {Hex(off & (long)0xFF)}");
+ for (int i = 0, loopTo = grp.NumTri - 1; i <= loopTo; i++)
+ {
+ byte a = Convert.ToByte(grp.indexList[i * 3] * 0xA);
+ byte b = Convert.ToByte(grp.indexList[i * 3 + 1] * 0xA);
+ byte c = Convert.ToByte(grp.indexList[i * 3 + 2] * 0xA);
+ ImpF3D($"BF 00 00 00 00 {Hex(a)} {Hex(b)} {Hex(c)}");
+ }
+ }
+
+ private void ResetCrystalEffectCommands(ref bool hasCrystalEffectEnabled, ref bool needToResetCrystalEffectCommands)
+ {
+ ImpF3D("B6 00 00 00 00 04 00 00");
+ ImpF3D("BB 00 00 01 FF FF FF FF");
+ hasCrystalEffectEnabled = false;
+ needToResetCrystalEffectCommands = false;
+ }
+
+ private void ImpMaterialCmds(Material mat, ref bool hasCrystalEffectEnabled, ref bool needToResetCrystalEffectCommands, ref bool disabledBackfaceCulling)
+ {
+ if (mat.EnableCrystalEffect)
+ {
+ if (!hasCrystalEffectEnabled)
+ {
+ ImpF3D($"B7 00 00 00 00 04 00 00");
+ ImpF3D("BB 00 00 01 08 00 08 00");
+ hasCrystalEffectEnabled = true;
+ needToResetCrystalEffectCommands = true;
+ }
+ }
+ else if (needToResetCrystalEffectCommands)
+ {
+ ResetCrystalEffectCommands(ref hasCrystalEffectEnabled, ref needToResetCrystalEffectCommands);
+ }
+
+ if (mat.HasPalette)
+ {
+ ImpCmdFD(mat.PaletteOffset, N64Codec.RGBA16);
+ ImpF3D("F5 00 01 00 01 00 00 00");
+ ushort num = Convert.ToUInt16(Convert.ToInt64(mat.PaletteSize / (double)2 - 1) << 6);
+ ImpF3D($"F0 00 00 00 01 {Hex((num >> 8) & 0xFF)} {Hex(num & 0xFF)} 00");
+ }
+
+ if (((mat.FaceCullingMode & FaceCullingMode.Back) != FaceCullingMode.Back))
+ {
+ if (!disabledBackfaceCulling)
+ {
+ ImpF3D($"B6 00 00 00 00 00 20 00"); // Disable back face culling
+ disabledBackfaceCulling = true;
+ }
+ }
+ else if (disabledBackfaceCulling)
+ {
+ ImpF3D($"B7 00 00 00 00 00 20 00"); // Enable back face culling
+ disabledBackfaceCulling = false;
+ }
+
+ if (mat.HasTexture)
+ {
+ ImpCmdFD(mat.Offset, mat.TexType);
+ ImpCmdF5_First(mat.TexType);
+ ImpF3D("E6 00 00 00 00 00 00 00");
+ AddCmdF3(mat);
+ ImpF3D("E7 00 00 00 00 00 00 00");
+ ImpCmdF5_Second(mat, mat.TexWidth, mat.TexHeight);
+ AddCmdF2(mat);
+ }
+ }
+
+ private void AddScrollingTexture(FvGroup grp, int vertPtr, int matAddr, string matName)
+ {
+ var scrollTex = new ScrollTex(Convert.ToInt32(curSeg) << 24 | vertPtr, Convert.ToByte(grp.VertexDataCount), matAddr, matName);
+ scrollTexts.Add(scrollTex);
+ }
+
+ private void MergeScrollingTextures()
+ {
+ short curScrollingGroupID = 0;
+ var scrollingGroups = new Dictionary();
+ scrollTexts = new List(scrollTexts.OrderBy(n => n.Offset));
+
+ short getScrollingGroup(int curMatAddr)
+ {
+ if (scrollingGroups.ContainsKey(curMatAddr))
+ return scrollingGroups[curMatAddr];
+ else
+ {
+ short newID = curScrollingGroupID;
+ scrollingGroups.Add(curMatAddr, newID);
+ curScrollingGroupID += 1;
+ return newID;
+ }
+ };
+
+ while (scrollTexts.Count > 0)
+ {
+ int startOff = scrollTexts[0].Offset;
+ int endOff = startOff + scrollTexts[0].VertsCount * 0x10;
+ int count = 0;
+ int curMatAddr = scrollTexts[0].MaterialAddress;
+ string curMatName = scrollTexts[0].MaterialName;
+
+ foreach (ScrollTex st in scrollTexts)
+ {
+ if (st.MaterialAddress == curMatAddr)
+ {
+ if (st.Offset <= endOff)
+ {
+ if ((endOff - startOff) / (double)0x10 <= ushort.MaxValue)
+ {
+ int newEndOffset = st.Offset + st.VertsCount * 0x10;
+ endOff = newEndOffset;
+ }
+ else
+ break;
+ }
+ else if (st.Offset > endOff)
+ break;
+
+ count += 1;
+ }
+ else
+ break;
+ }
+
+ int vertsCount = (int)((endOff - startOff) / (double)0x10);
+ if (vertsCount > 0)
+ {
+ short groupID = getScrollingGroup(curMatAddr);
+ scrollTexts.RemoveRange(0, count);
+ conRes.ScrollingCommands.Add(new SM64Lib.Levels.ScrolTex.ManagedScrollingTexture(Convert.ToUInt16(vertsCount), startOff, groupID));
+ conRes.ScrollingNames.AddIfNotContainsKey(groupID, curMatName);
+ }
+ }
+ }
+
+ private void ImpColorCmdFB(Material mat)
+ {
+ byte r = Convert.ToByte(mat.Color >> 24 & (long)0xFF);
+ byte g = Convert.ToByte(mat.Color >> 16 & (long)0xFF);
+ byte b = Convert.ToByte(mat.Color >> 8 & (long)0xFF);
+ ImpF3D($"FB 00 00 00 {Hex(r)} {Hex(g)} {Hex(b)} {Hex(mat.Opacity)}");
+ }
+
+ private void AlignPosition()
+ {
+ impdata.RoundUpPosition();
+ }
+
+ ///
+ /// Adds a command that is requied on the end of a display list if CI textures are enabled
+ ///
+ private void SetOtherMode_H_Revert()
+ {
+ // Reset CI texture palette color format
+ ImpF3D("BA 00 0E 02 00 00 00 00");
+
+ // If it ever is required, then use this command instead. It also reverts the texture filter to "Bilerp".
+ //ImpF3D("BA 00 0C 04 00 00 20 00");
+ }
+
+ ///
+ /// Adds a command to enable CI textures
+ ///
+ private void SetOtherMode_H(Material mat, ref bool ciEnabled, ref bool nonDefaultFilter)
+ {
+ var citextypes = new[] { N64Codec.CI4, N64Codec.CI8 };
+ var iscitexture = citextypes.Contains(mat.TexType);
+ var paletteColorStorageType = iscitexture ? PaletteColorStorageType.RGBA16 : PaletteColorStorageType.None;
+
+ byte ss = 0xC;
+ byte nn = 0x4;
+ int options = 0;
+
+ // G_MDSFT_TEXTLUT (Palette color storage type)
+ ciEnabled = iscitexture;
+ options |= ((int)paletteColorStorageType) << 0xE;
+
+ // G_MDSFT_TEXTFILT (Texture Filter)
+ nonDefaultFilter = mat.TexFilter != TextureFilter.Bilerp;
+ options |= ((int)mat.TexFilter) << 0xC;
+
+ // Normally we need to set "ss" to E for G_MDSFT_TEXTLUT and to C for G_MDSFT_TEXTFILT.
+ // Because it they are nearby the other, we can combine them, so set to the lower value C and increase "nn" by the difference (+2 = 4).
+ // This saves one command. Else it would be two commands, for each option one command.
+ impdata.Write(new byte[] { 0xBA, 0, ss, nn });
+ impdata.Write(options);
+ }
+
+ private void ImportObj(Pilz.S3DFileParser.Object3D model)
+ {
+ bool enabledVertexColors;
+ bool enableForcing = settings.ForceDisplaylist != -1;
+ uint importStart = 0;
+ uint startVerts = 0;
+ Material lastMaterial = null;
+ uint lastFBColor = default;
+ bool hasCrystalEffectEnabled, needToResetCrystalEffectCommands, disabledBackfaceCulling;
+ bool ciEnabled;
+ bool nonDefaultFilter;
+ string lastCmdFC;
+ var dlsToCreate = new List();
+ var dicMatDlIDs = new Dictionary();
+ ProcessObject3DModel(model);
+ conRes.PtrStart = Convert.ToUInt32((long)CurSegAddress | impdata.Position);
+ importStart = Convert.ToUInt32(impdata.Position);
+
+ // Write default color
+ impdata.Write(defaultColor);
+
+ // Remove duplicated textures
+ // FIXME: This function does not account for materials properties like Opacity
+ // MergeDuplicatedTextures()
+
+ // Write materials
+ foreach (Material mt in materials)
+ {
+ if (mt.HasTexture)
+ {
+ mt.Offset = Convert.ToUInt32(impdata.Position);
+ impdata.Write(mt.Texture.Data);
+ AlignPosition();
+ if (mt.HasPalette)
+ {
+ mt.PaletteOffset = Convert.ToUInt32(impdata.Position);
+ impdata.Write(mt.Texture.Palette);
+ AlignPosition();
+ }
+ }
+ }
+
+ // Prepaire vertices
+ BuildVertexGroups();
+ removeDuplicateVertices(settings.ReduceVertLevel);
+
+ // Write vertices
+ conRes.PtrVertex = Convert.ToUInt32((long)CurSegAddress | impdata.Position);
+ startVerts = Convert.ToUInt32(impdata.Position);
+ foreach (VertexGroupList mp in vertexGroups)
+ {
+ for (int g = 0; g < mp.GroupsCount; g++)
+ {
+ if (mp.FinalVertexGroups[g].VertexDataCount >= 1)
+ {
+ for (int i = 0; i < mp.FinalVertexGroups[g].VertexDataCount; i++)
+ {
+ var data = mp.FinalVertexGroups[g].FinalVertexData[i].Data;
+ impdata.Write(data);
+ }
+ }
+ }
+ }
+
+ int createDefaultDL(Geolayout.DefaultGeolayers layerID)
+ {
+ DisplaylistProps dlProp = null;
+ int newLayerID = ((int)layerID + 1) * -1;
+ foreach (DisplaylistProps dl in dlsToCreate)
+ {
+ if (dlProp is null && dl.ID == newLayerID)
+ dlProp = dl;
+ }
+
+ if (dlProp is null)
+ dlProp = new DisplaylistProps(newLayerID);
+
+ dlProp.Layer = layerID;
+ if (!dlsToCreate.Contains(dlProp))
+ dlsToCreate.Add(dlProp);
+
+ return dlProp.ID;
+ };
+ int createCustomDL(int dlID)
+ {
+ DisplaylistProps dlProp = null;
+
+ // Search dlProp
+ foreach (var prop in settings.TextureFormatSettings.CustomDisplayLists)
+ {
+ if (dlProp is null && prop.ID == dlID)
+ dlProp = prop;
+ }
+
+ if (!dlsToCreate.Contains(dlProp))
+ dlsToCreate.Add(dlProp);
+
+ return dlProp.ID;
+ };
+
+ // Check which DLs should be created
+ if (enableForcing)
+ {
+ int dlID = createDefaultDL((Geolayout.DefaultGeolayers)settings.ForceDisplaylist);
+ foreach (Material mat in materials)
+ dicMatDlIDs.Add(mat, dlID);
+ }
+ else
+ {
+ foreach (Material mat in materials)
+ {
+ int dlID = -1;
+ var switchExpr = mat.DisplaylistSelection.SelectionMode;
+
+ switch (switchExpr)
+ {
+ case DisplaylistSelectionMode.Automatic:
+ if (mat.HasTransparency)
+ dlID = createDefaultDL(Geolayout.DefaultGeolayers.Translucent);
+ else if (mat.HasTextureAlpha)
+ dlID = createDefaultDL(Geolayout.DefaultGeolayers.Alpha);
+ else
+ dlID = createDefaultDL(Geolayout.DefaultGeolayers.Solid);
+ break;
+ case DisplaylistSelectionMode.Default:
+ dlID = createDefaultDL(mat.DisplaylistSelection.DefaultGeolayer);
+ break;
+ case DisplaylistSelectionMode.Custom:
+ dlID = createCustomDL(mat.DisplaylistSelection.CustomDisplaylistID);
+ break;
+ }
+
+ dicMatDlIDs.Add(mat, dlID);
+ }
+ }
+
+ // Create DLs
+ foreach (DisplaylistProps dlProps in dlsToCreate)
+ {
+ // Add Geopointer
+ conRes.PtrGeometry.Add(new Geolayout.Geopointer((byte)dlProps.Layer, Convert.ToInt32(CurSegAddress | impdata.Position)));
+
+ // Reset some stuff
+ enabledVertexColors = false;
+ hasCrystalEffectEnabled = false;
+ needToResetCrystalEffectCommands = false;
+ disabledBackfaceCulling = false;
+ ciEnabled = false;
+ nonDefaultFilter = false;
+ lastMaterial = null;
+ lastFBColor = default;
+ lastCmdFC = string.Empty;
+
+ // Create DL
+ ImpF3D("E7 00 00 00 00 00 00 00");
+ if (settings.EnableFog)
+ ImpF3D("B9 00 02 01 00 00 00 00");
+ ImpF3D("B7 00 00 00 00 00 00 00");
+ ImpF3D("BB 00 00 01 FF FF FF FF");
+ ImpF3D("E8 00 00 00 00 00 00 00");
+ ImpF3D("E6 00 00 00 00 00 00 00");
+ ImpF3D("03 88 00 10 0E 00 00 00");
+ ImpF3D("03 86 00 10 0E 00 00 08");
+ if (settings.EnableFog)
+ ImpFogStart((int)dlProps.Layer);
+
+ for (int i = 0, loopTo2 = vertexGroups.Count - 1; i <= loopTo2; i++)
+ {
+ var mp = vertexGroups[i];
+ if (dicMatDlIDs[mp.Material] != dlProps.ID)
+ continue;
+
+ // Geomode
+ if (mp.Material.EnableGeoMode)
+ {
+ ImpF3D("B6 00 00 00 FF FF FF FF");
+ ImpF3D($"B7 00 00 00 {Hex(mp.Material.GeoMode >> 24 & (long)0xFF)} {Hex(mp.Material.GeoMode >> 16 & (long)0xFF)} {Hex(mp.Material.GeoMode >> 8 & (long)0xFF)} {Hex(mp.Material.GeoMode & (long)0xFF)}");
+ }
+
+ if (lastMaterial != mp.Material)
+ {
+ lastMaterial = mp.Material;
+
+ AddCmdFC(mp.Material, ref lastCmdFC);
+ if ((mp.Material.Type == MaterialType.ColorSolid || mp.Material.Type == MaterialType.ColorTransparent) && lastFBColor != mp.Material.Color)
+ ImpColorCmdFB(mp.Material);
+ SetOtherMode_H(mp.Material, ref ciEnabled, ref nonDefaultFilter);
+ ImpMaterialCmds(mp.Material, ref hasCrystalEffectEnabled, ref needToResetCrystalEffectCommands, ref disabledBackfaceCulling);
+ }
+
+ int grpOff = 0;
+ for (int ii = 0, loopTo3 = mp.GroupsCount - 1; ii <= loopTo3; ii++)
+ {
+ ImpTriCmds(mp.Material, mp.FinalVertexGroups[ii], (int)(startVerts + (mp.StartIndex + grpOff)), ref enabledVertexColors);
+ grpOff += mp.FinalVertexGroups[ii].VertexDataCount * 0x10;
+ }
+
+ if (mp.Material.EnableGeoMode)
+ {
+ if (i + 1 < vertexGroups.Count && vertexGroups[i + 1].Material.EnableGeoMode)
+ continue;
+ ImpF3D("B6 00 00 00 FF FF FF FF");
+ ImpF3D("B7 00 00 00 00 02 20 05");
+ }
+ }
+
+ if (enabledVertexColors)
+ ImpF3D("B7 00 00 00 00 02 00 00");
+ if (settings.EnableFog)
+ ImpFogEnd((int)dlProps.Layer);
+ ImpF3D("FC FF FF FF FF FE 79 3C");
+ ImpF3D("BB 00 00 00 FF FF FF FF");
+ if (needToResetCrystalEffectCommands)
+ ResetCrystalEffectCommands(ref hasCrystalEffectEnabled, ref needToResetCrystalEffectCommands);
+ if (ciEnabled /*|| nonDefaultFilter*/)
+ SetOtherMode_H_Revert();
+ ImpF3D("B8 00 00 00 00 00 00 00");
+ MergeScrollingTextures();
+ }
+
+ ResetVariables();
+ }
+
+ ///
+ /// Converts a Object3D to an N64 Model and an SM64 Collision.
+ ///
+ /// The stream where to write the Fast3D and Collision data.
+ /// The convert settings.
+ /// The input model.
+ ///
+ public ConvertResult ConvertModel(Stream s, ConvertSettings settings, Pilz.S3DFileParser.Object3D input)
+ {
+ this.settings = settings;
+ impdata = new Data.BinaryStreamData(s);
+
+ // Segmented Address
+ if (settings.SegmentedAddress is not null)
+ {
+ startSegOffset = (uint)(settings.SegmentedAddress & 0xFFFFFF);
+ curSeg = (byte)(settings.SegmentedAddress >> 24 & 0xFF);
+ }
+
+ // Shading
+ SetLightAndDarkValues(input.Shading);
+
+ // Convert
+ ImportObj(input);
+ ResetVariables();
+ return conRes;
+ }
+ }
+}
\ No newline at end of file
diff --git a/SM64Lib/Model/Fast3D/DisplayLists/DisplayList.cs b/SM64Lib/Model/Fast3D/DisplayLists/DisplayList.cs
new file mode 100644
index 0000000..6c77bbd
--- /dev/null
+++ b/SM64Lib/Model/Fast3D/DisplayLists/DisplayList.cs
@@ -0,0 +1,90 @@
+using System;
+using System.Threading.Tasks;
+using global::Pilz.S3DFileParser;
+using global::SM64Lib.Geolayout;
+using global::SM64Lib.Model.Fast3D.DisplayLists.Script;
+
+namespace SM64Lib.Model.Fast3D.DisplayLists
+{
+ public class DisplayList
+ {
+ public DisplayListScript Script { get; private set; } = new DisplayListScript();
+ public Geopointer GeoPointer { get; set; } = null;
+ // Public Property Data As Stream = Nothing
+
+ public DisplayList()
+ {
+ }
+
+ public DisplayList(Geopointer gp)
+ {
+ GeoPointer = gp;
+ }
+
+ public void TryFromStream(Geopointer gp, RomManager rommgr, byte? AreaID)
+ {
+ try
+ {
+ FromStream(gp, rommgr, AreaID);
+ }
+ catch (Exception)
+ {
+ }
+ }
+
+ public Task TryFromStreamAsync(Geopointer gp, RomManager rommgr, byte? AreaID)
+ {
+ var t = new Task(() => TryFromStream(gp, rommgr, AreaID));
+ t.Start();
+ return t;
+ }
+
+ public void FromStream(Geopointer gp, RomManager rommgr, byte? AreaID)
+ {
+ GeoPointer = gp;
+ Script.FromStream(rommgr, gp.SegPointer, AreaID);
+ }
+
+ public Task FromStreamAsync(Geopointer gp, RomManager rommgr, byte? AreaID)
+ {
+ var t = new Task(() => FromStream(gp, rommgr, AreaID));
+ t.Start();
+ return t;
+ }
+
+ public void TryToObject3D(Object3D obj, RomManager rommgr, byte? AreaID)
+ {
+ try
+ {
+ ToObject3D(obj, rommgr, AreaID);
+ }
+ catch (Exception)
+ {
+ }
+ }
+
+ public Task TryToObject3DAsync(Object3D obj, RomManager rommgr, byte? AreaID)
+ {
+ var t = new Task(() => TryToObject3D(obj, rommgr, AreaID));
+ t.Start();
+ return t;
+ }
+
+ public void ToObject3D(Object3D obj, RomManager rommgr, byte? AreaID)
+ {
+ Conversion.Fast3DParsing.Fast3DParser.Convert(obj, this, rommgr, AreaID);
+ }
+
+ public Task ToObject3DAsync(Object3D obj, RomManager rommgr, byte? AreaID)
+ {
+ var t = new Task(() => ToObject3D(obj, rommgr, AreaID));
+ t.Start();
+ return t;
+ }
+
+ public void Close()
+ {
+ Script.Close();
+ }
+ }
+}
\ No newline at end of file
diff --git a/SM64Lib/Model/Fast3D/DisplayLists/DisplayListCollection.cs b/SM64Lib/Model/Fast3D/DisplayLists/DisplayListCollection.cs
new file mode 100644
index 0000000..1a1282f
--- /dev/null
+++ b/SM64Lib/Model/Fast3D/DisplayLists/DisplayListCollection.cs
@@ -0,0 +1,28 @@
+using System.Collections.Generic;
+using global::Pilz.S3DFileParser;
+
+namespace SM64Lib.Model.Fast3D.DisplayLists
+{
+ public class DisplayListCollection : List
+ {
+ public Object3D ToObject3D(RomManager rommgr, byte? AreaID)
+ {
+ var obj = new Object3D();
+ foreach (DisplayList dl in this)
+ dl.ToObject3D(obj, rommgr, AreaID);
+ return obj;
+ }
+
+ public new void Clear()
+ {
+ Close();
+ base.Clear();
+ }
+
+ public void Close()
+ {
+ foreach (var dl in this)
+ dl.Close();
+ }
+ }
+}
\ No newline at end of file
diff --git a/SM64Lib/Model/Fast3D/DisplayLists/Script/CommandTypes.cs b/SM64Lib/Model/Fast3D/DisplayLists/Script/CommandTypes.cs
new file mode 100644
index 0000000..16c36cd
--- /dev/null
+++ b/SM64Lib/Model/Fast3D/DisplayLists/Script/CommandTypes.cs
@@ -0,0 +1,22 @@
+
+namespace SM64Lib.Model.Fast3D.DisplayLists.Script
+{
+ public enum CommandTypes
+ {
+ NOOP = 0x0,
+ Movemem = 0x3,
+ DisplayList = 0x6,
+ EndDisplaylist = 0xB8,
+ Vertex = 0x4,
+ SetOtherMode_H = 0xBA,
+ Triangle1 = 0xBF,
+ ClearGeometryMode = 0xB6,
+ SetGeometryMode = 0xB7,
+ Loadtlut = 0xF0,
+ SetTileSize = 0xF2,
+ SetImage = 0xFD,
+ Loadback = 0xF3,
+ SetTile = 0xF5,
+ Texture = 0xBB
+ }
+}
\ No newline at end of file
diff --git a/SM64Lib/Model/Fast3D/DisplayLists/Script/Commands.cs b/SM64Lib/Model/Fast3D/DisplayLists/Script/Commands.cs
new file mode 100644
index 0000000..8794fe7
--- /dev/null
+++ b/SM64Lib/Model/Fast3D/DisplayLists/Script/Commands.cs
@@ -0,0 +1,486 @@
+using System;
+using global::System.Drawing;
+using global::System.Numerics;
+using Microsoft.VisualBasic.CompilerServices;
+
+namespace SM64Lib.Model.Fast3D.DisplayLists.Script.Commands
+{
+ public class F3D_VTX
+ {
+ public static byte GetNumberOfVertices(DisplayListCommand cmd)
+ {
+ cmd.Position = 1;
+ byte value = cmd.ReadByte();
+ cmd.Position = 0;
+ return (byte)(value >> 4);
+ }
+
+ public static byte GetStartIndexInVertexBuffer(DisplayListCommand cmd)
+ {
+ cmd.Position = 1;
+ byte value = cmd.ReadByte();
+ cmd.Position = 0;
+ return Convert.ToByte(value & 0xF);
+ }
+
+ public static short GetLengthOfVertexData(DisplayListCommand cmd)
+ {
+ cmd.Position = 2;
+ short value = cmd.ReadInt16();
+ cmd.Position = 0;
+ return value;
+ }
+
+ public static int GetSegmentedAddress(DisplayListCommand cmd)
+ {
+ cmd.Position = 4;
+ int value = cmd.ReadInt32();
+ cmd.Position = 0;
+ return value;
+ }
+
+ public static void SetSegmentedAddress(DisplayListCommand cmd, int value)
+ {
+ cmd.Position = 4;
+ cmd.Write(value);
+ cmd.Position = 0;
+ }
+ }
+
+ public class F3D_TRI1
+ {
+ public static byte GetVertice(DisplayListCommand cmd, byte VerticeNumber)
+ {
+ cmd.Position = 5 + VerticeNumber - 1;
+ byte value = cmd.ReadByte();
+ cmd.Position = 0;
+ if (value > 0)
+ value = (byte)(value / (double)0xA);
+ return value;
+ }
+ }
+
+ public class F3D_SETTILE
+ {
+ public static N64Graphics.N64Codec GetTextureFormat(DisplayListCommand cmd)
+ {
+ cmd.Position = 1;
+ byte type = cmd.ReadByte();
+ cmd.Position = 0;
+ switch (type)
+ {
+ case 0x10:
+ {
+ return N64Graphics.N64Codec.RGBA16;
+ }
+
+ case 0x18:
+ {
+ return N64Graphics.N64Codec.RGBA32;
+ }
+
+ case 0x40:
+ {
+ return N64Graphics.N64Codec.CI4;
+ }
+
+ case 0x48:
+ {
+ return N64Graphics.N64Codec.CI8;
+ }
+
+ case 0x60:
+ {
+ return N64Graphics.N64Codec.IA4;
+ }
+
+ case 0x68:
+ {
+ return N64Graphics.N64Codec.IA8;
+ }
+
+ case 0x70:
+ {
+ return N64Graphics.N64Codec.IA16;
+ }
+
+ case 0x80:
+ case 0x90:
+ {
+ return N64Graphics.N64Codec.I4;
+ }
+
+ case 0x88:
+ {
+ return N64Graphics.N64Codec.I8;
+ }
+
+ default:
+ {
+ return default;
+ }
+ }
+ }
+
+ public static byte GetWrapT(DisplayListCommand cmd)
+ {
+ cmd.Position = 5;
+ byte val = cmd.ReadByte();
+ cmd.Position = 0;
+ return Convert.ToByte((val >> 2) & 0x2);
+ }
+
+ public static byte GetWrapS(DisplayListCommand cmd)
+ {
+ cmd.Position = 6;
+ byte val = cmd.ReadByte();
+ cmd.Position = 0;
+ return Convert.ToByte(val & 0x2);
+ }
+ }
+
+ public class F3D_TEXTURE
+ {
+ public static Size GetTextureSize(DisplayListCommand cmd)
+ {
+ cmd.Position = 4;
+ ushort tsX = cmd.ReadUInt16();
+ ushort tsY = cmd.ReadUInt16();
+ cmd.Position = 0;
+ tsX = (ushort)(tsX >> 6);
+ if (tsX == 31)
+ {
+ tsX = 32;
+ }
+ else if (tsX == 62)
+ {
+ tsX = 64;
+ }
+
+ tsY = (ushort)(tsY >> 6);
+ if (tsY == 31)
+ {
+ tsY = 32;
+ }
+ else if (tsY == 62)
+ {
+ tsY = 64;
+ }
+
+ return new Size(tsX, tsY);
+ }
+
+ public static Vector2 GetTextureScaling(DisplayListCommand cmd)
+ {
+ cmd.Position = 4;
+ ushort tsX = cmd.ReadUInt16();
+ ushort tsY = cmd.ReadUInt16();
+ cmd.Position = 0;
+ var vec = new Vector2();
+ if (tsX != 0xFFFF)
+ {
+ vec.X = tsX / 65536.0F;
+ }
+ else
+ {
+ vec.X = 1.0F;
+ }
+
+ if (tsY != 0xFFFF)
+ {
+ vec.Y = tsY / 65536.0F;
+ }
+ else
+ {
+ vec.Y = 1.0F;
+ }
+
+ return vec;
+ }
+ }
+
+ public class F3D_SETIMG
+ {
+ public static int GetSegmentedAddress(DisplayListCommand cmd)
+ {
+ cmd.Position = 4;
+ int value = cmd.ReadInt32();
+ cmd.Position = 0;
+ return value;
+ }
+
+ public static void SetSegmentedAddress(DisplayListCommand cmd, int value)
+ {
+ cmd.Position = 4;
+ cmd.Write(value);
+ cmd.Position = 0;
+ }
+
+ public static ColorFormat GetColorFormat(DisplayListCommand cmd)
+ {
+ cmd.Position = 1;
+ byte val = cmd.ReadByte();
+ cmd.Position = 0;
+ return (ColorFormat)(val >> 5);
+ }
+
+ public static BitSize GetBitSize(DisplayListCommand cmd)
+ {
+ cmd.Position = 1;
+ byte val = cmd.ReadByte();
+ cmd.Position = 0;
+ return (BitSize)(val >> 3 & 0x3);
+ }
+
+ public enum ColorFormat : byte
+ {
+ ///
+ /// Color and alpha
+ ///
+ RGBA = 0,
+ ///
+ /// Luminance and Chrominance
+ ///
+ YUV = 1,
+ ///
+ /// Index and look-up palette
+ ///
+ CI = 2,
+ ///
+ /// Grayscale and alpha
+ ///
+ IA = 3,
+ ///
+ /// Grayscale
+ ///
+ I = 4
+ }
+
+ public enum BitSize : byte
+ {
+ _4 = 0,
+ _8 = 1,
+ _16 = 2,
+ _32 = 3
+ }
+ }
+
+ public class F3D_SETTILESIZE
+ {
+ public static Size GetSize(DisplayListCommand cmd)
+ {
+ cmd.Position = 4;
+ int var = cmd.ReadInt32();
+ cmd.Position = 0;
+ short w = Convert.ToInt16(var >> 12 & 0xFFF);
+ short h = Convert.ToInt16(var & 0xFFF);
+ w = Convert.ToInt16((w >> 2) + 1);
+ h = Convert.ToInt16((h >> 2) + 1);
+ return new Size(w, h);
+ }
+
+ public static void SetSize(DisplayListCommand cmd, Size size)
+ {
+ int w = size.Width;
+ int h = size.Height;
+ w = w - 1 << 2;
+ h = h - 1 << 2;
+ int var = w << 12 | h;
+ cmd.Position = 4;
+ cmd.Write(var);
+ cmd.Position = 0;
+ }
+ }
+
+ public class F3D_MOVEMEM
+ {
+ public static int GetSegmentedOffset(DisplayListCommand cmd)
+ {
+ cmd.Position = 4;
+ int value = cmd.ReadInt32();
+ cmd.Position = 0;
+ return value;
+ }
+
+ public static void SetSegmentedOffset(DisplayListCommand cmd, int value)
+ {
+ cmd.Position = 4;
+ cmd.Write(value);
+ cmd.Position = 0;
+ }
+
+ public static byte GetLightValueMode(DisplayListCommand cmd)
+ {
+ cmd.Position = 1;
+ byte value = cmd.ReadByte();
+ cmd.Position = 0;
+ return value;
+ }
+ }
+
+ public class F3D_CLEARGEOMETRYMODE
+ {
+ public static uint GetGeometryMode(DisplayListCommand cmd)
+ {
+ cmd.Position = 4;
+ uint flag = cmd.ReadUInt32();
+ cmd.Position = 0;
+ return flag;
+ }
+ }
+
+ public class F3D_SETRGEOMETRYMODE : F3D_CLEARGEOMETRYMODE
+ {
+ }
+
+ public class F3D_SETOTHERMODE_H
+ {
+ public static uint GetModeBits(DisplayListCommand cmd)
+ {
+ cmd.Position = 4;
+ uint bits = cmd.ReadUInt32();
+ cmd.Position = 0;
+ return bits;
+ }
+ }
+
+ public class F3D_SETCOMBINE
+ {
+ public enum CCMUX : byte
+ {
+ COMBINED = 0,
+ TEXEL0,
+ TEXEL1,
+ PRIMITIVE,
+ SHADE,
+ ENVIRONMENT,
+ CENTER,
+ COMBINED_ALPHA,
+ TEXEL0_ALPHA,
+ TEXEL1_ALPHA,
+ PRIMITIVE_ALPHA,
+ SHADE_ALPHA,
+ ENV_ALPHA,
+ LOD_FRACTION,
+ PRIM_LOD_FRAC,
+ SCALE = 6,
+ NOISE = 7,
+ K4 = 7,
+ K5 = 15,
+ ONE = 6,
+ ZERO = 31
+ }
+
+ public enum ACMUX : byte
+ {
+ COMBINED = 0,
+ TEXEL0,
+ TEXEL1,
+ PRIMITIVE,
+ SHADE,
+ ENVIRONMENT,
+ PRIM_LOD_FRAC,
+ ONE = 6,
+ ZERO = 7,
+ LOD_FRACTION = 0
+ }
+
+ public class Formula
+ {
+ public readonly byte a;
+ public readonly byte b;
+ public readonly byte c;
+ public readonly byte d;
+
+ public Formula(byte a, byte b, byte c, byte d) // CC formula is (a - b) * c + d
+ {
+ this.a = a;
+ this.b = b;
+ this.c = c;
+ this.d = d;
+ }
+
+ public static Formula Output(CCMUX a)
+ {
+ return new Formula((byte)CCMUX.ZERO, (byte)CCMUX.ZERO, (byte)CCMUX.ZERO, (byte)a); // (0 - 0) * 0 + a = a
+ }
+
+ public static Formula Output(ACMUX a)
+ {
+ return new Formula((byte)ACMUX.ZERO, (byte)ACMUX.ZERO, (byte)ACMUX.ZERO, (byte)a);
+ }
+
+ public static Formula Multiply(CCMUX a, CCMUX b)
+ {
+ return new Formula((byte)a, (byte)CCMUX.ZERO, (byte)b, (byte)CCMUX.ZERO); // (a - 0) * b + 0 = a * b
+ }
+
+ public static Formula Multiply(byte a, byte b)
+ {
+ return new Formula(a, (byte)ACMUX.ZERO, b, (byte)ACMUX.ZERO); // (a - 0) * b + 0 = a * b
+ }
+ }
+
+ private static uint _SHIFTL(uint v, uint s, uint w)
+ {
+ return Convert.ToUInt32((v & (1 << Convert.ToInt32(w)) - 1) << Convert.ToInt32(s));
+ }
+
+ private static uint GCCc0w0(uint saRGB0, uint mRGB0, uint saA0, uint mA0)
+ {
+ return _SHIFTL(saRGB0, 20, 4) | _SHIFTL(mRGB0, 15, 5) | _SHIFTL(saA0, 12, 3) | _SHIFTL(mA0, 9, 3);
+ }
+
+ private static uint GCCc1w0(uint saRGB1, uint mRGB1)
+ {
+ return _SHIFTL(saRGB1, 5, 4) | _SHIFTL(mRGB1, 0, 5);
+ }
+
+ private static uint GCCc0w1(uint sbRGB0, uint aRGB0, uint sbA0, uint aA0)
+ {
+ return _SHIFTL(sbRGB0, 28, 4) | _SHIFTL(aRGB0, 15, 3) | _SHIFTL(sbA0, 12, 3) | _SHIFTL(aA0, 9, 3);
+ }
+
+ private static uint GCCc1w1(uint sbRGB1, uint saA1, uint mA1, uint aRGB1, uint sbA1, uint aA1)
+ {
+ return _SHIFTL(sbRGB1, 24, 4) | _SHIFTL(saA1, 21, 3) | _SHIFTL(mA1, 18, 3) | _SHIFTL(aRGB1, 6, 3) | _SHIFTL(sbA1, 3, 3) | _SHIFTL(aA1, 0, 3);
+ }
+
+ // For Jabo plugin you can't specify anything you want for 2nd cycle, only combined or the same as previous :(
+ public static string Make(Formula color, Formula alpha, bool isFog)
+ {
+ if (!isFog)
+ {
+ return Make(color, alpha, color, alpha);
+ }
+ else
+ {
+ return Make(color, alpha, Formula.Output(CCMUX.COMBINED), Formula.Output(ACMUX.COMBINED));
+ }
+ }
+
+ // TODO: Let user specify custom combiner with this
+ public static string Make(Formula color0, Formula alpha0, Formula color1, Formula alpha1)
+ {
+ return Make((CCMUX)color0.a, (CCMUX)color0.b, (CCMUX)color0.c, (CCMUX)color0.d, (ACMUX)alpha0.a, (ACMUX)alpha0.b, (ACMUX)alpha0.c, (ACMUX)alpha0.d, (CCMUX)color1.a, (CCMUX)color1.b, (CCMUX)color1.c, (CCMUX)color1.d, (ACMUX)alpha1.a, (ACMUX)alpha1.b, (ACMUX)alpha1.c, (ACMUX)alpha1.d);
+ }
+
+ private static string Make(CCMUX a0, CCMUX b0, CCMUX c0, CCMUX d0, ACMUX Aa0, ACMUX Ab0, ACMUX Ac0, ACMUX Ad0, CCMUX a1, CCMUX b1, CCMUX c1, CCMUX d1, ACMUX Aa1, ACMUX Ab1, ACMUX Ac1, ACMUX Ad1)
+ {
+ uint w0 = _SHIFTL(0xFC, 24, 8) | _SHIFTL(GCCc0w0((uint)a0, (uint)c0, (uint)Aa0, (uint)Ac0) | GCCc1w0((uint)a1, (uint)c1), 0, 24);
+ uint w1 = GCCc0w1((uint)b0, (uint)d0, (uint)Ab0, (uint)Ad0) | GCCc1w1((uint)b1, (uint)Aa1, (uint)Ac1, (uint)d1, (uint)Ab1, (uint)Ad1);
+ var w0bytes = BitConverter.GetBytes(w0);
+ var w1bytes = BitConverter.GetBytes(w1);
+ // Little endian assumed
+ string ret = w0bytes[3].ToString("X");
+ ret += " " + w0bytes[2].ToString("X");
+ ret += " " + w0bytes[1].ToString("X");
+ ret += " " + w0bytes[0].ToString("X");
+ ret += " " + w1bytes[3].ToString("X");
+ ret += " " + w1bytes[2].ToString("X");
+ ret += " " + w1bytes[1].ToString("X");
+ ret += " " + w1bytes[0].ToString("X");
+ return ret;
+ }
+ }
+}
\ No newline at end of file
diff --git a/SM64Lib/Model/Fast3D/DisplayLists/Script/DisplayListCommand.cs b/SM64Lib/Model/Fast3D/DisplayLists/Script/DisplayListCommand.cs
new file mode 100644
index 0000000..1f9d0a2
--- /dev/null
+++ b/SM64Lib/Model/Fast3D/DisplayLists/Script/DisplayListCommand.cs
@@ -0,0 +1,59 @@
+using System;
+using global::System.IO;
+using System.Linq;
+using Microsoft.VisualBasic;
+using global::SM64Lib.Script;
+
+namespace SM64Lib.Model.Fast3D.DisplayLists.Script
+{
+ public class DisplayListCommand : Data.BinaryStreamData, ICommand
+ {
+ public CommandTypes CommandType { get; set; } = CommandTypes.EndDisplaylist;
+ public int RomAddress { get; set; } = 0;
+ public int BankAddress { get; set; } = 0;
+
+ public bool IsDirty
+ {
+ get
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ private DisplayListCommand() : base(new MemoryStream())
+ {
+ }
+
+ public DisplayListCommand(byte CommandType) : this()
+ {
+ this.CommandType = (CommandTypes)CommandType;
+ SetLength(0x8);
+ Position = 0;
+ WriteByte(CommandType);
+ Position = 0;
+ }
+
+ public DisplayListCommand(string CommandType) : this(Convert.ToByte(CommandType, 16))
+ {
+ }
+
+ public DisplayListCommand(byte[] bytes) : this()
+ {
+ CommandType = (CommandTypes)bytes[0];
+ SetLength(bytes.Count());
+ foreach (var b in bytes)
+ WriteByte(b);
+ Position = 0;
+ }
+
+ public override string ToString()
+ {
+ return $"{RomAddress.ToString("X")} ({BankAddress.ToString("X")}): {General.CommandByteArrayToString(ToArray())}";
+ }
+
+ public byte[] ToArray()
+ {
+ return ((MemoryStream)BaseStream).ToArray();
+ }
+ }
+}
\ No newline at end of file
diff --git a/SM64Lib/Model/Fast3D/DisplayLists/Script/DisplayListScript.cs b/SM64Lib/Model/Fast3D/DisplayLists/Script/DisplayListScript.cs
new file mode 100644
index 0000000..5cf9485
--- /dev/null
+++ b/SM64Lib/Model/Fast3D/DisplayLists/Script/DisplayListScript.cs
@@ -0,0 +1,119 @@
+using System.Collections.Generic;
+using global::System.IO;
+using Microsoft.VisualBasic.CompilerServices;
+using global::SM64Lib.SegmentedBanking;
+using SM64Lib.Data;
+using System;
+
+namespace SM64Lib.Model.Fast3D.DisplayLists.Script
+{
+ public class DisplayListScript : List
+ {
+ public void FromStream(object input, int segAddress, byte? AreaID)
+ {
+ Close();
+
+ var rommgr = input as RomManager;
+ var data = input as BinaryData;
+ var lastPositions = new Stack();
+ SegmentedBank curSeg = null;
+
+ void getSegBank(int segAddr)
+ {
+ if (rommgr is not null)
+ {
+ curSeg = FromStream_GetSegBank(rommgr, segAddress, AreaID);
+ data = new BinaryStreamData(curSeg.Data);
+ }
+ }
+
+ getSegBank(segAddress);
+ data.Position = curSeg is object ? curSeg.BankOffsetFromSegAddr(segAddress) : segAddress & 0xffffff;
+
+ bool continueDo = true;
+ while (data.Position < curSeg.Length && continueDo)
+ {
+ // Read Command
+ var cmdbytes = new byte[8];
+ data.Read(cmdbytes);
+
+ // Create & Add Command
+ var cmd = new DisplayListCommand(cmdbytes)
+ {
+ RomAddress = (int)(curSeg?.RomStart ?? 0 + data.Position),
+ BankAddress = (int)(curSeg?.BankAddress ?? 0 + data.Position)
+ };
+ Add(cmd);
+
+ switch (cmd.CommandType)
+ {
+ case CommandTypes.NOOP:
+ {
+ cmd.Position = 0;
+ int checkVal = cmd.ReadInt32();
+ cmd.Position = 0;
+ if (checkVal != 0)
+ break;
+ break;
+ }
+
+ case CommandTypes.DisplayList:
+ {
+ cmd.Position = 4;
+ int segAddr = cmd.ReadInt32();
+ cmd.Position = 0;
+ getSegBank(segAddr);
+
+ if (curSeg is not null)
+ {
+ if (cmdbytes[1] != 1)
+ lastPositions.Push(Convert.ToInt32(data.Position));
+ else
+ lastPositions.Clear();
+ data.Position = curSeg.BankOffsetFromSegAddr(segAddr);
+ }
+ else
+ break;
+
+ break;
+ }
+
+ case CommandTypes.EndDisplaylist:
+ {
+ if (lastPositions.Count > 0)
+ curSeg.Data.Position = lastPositions.Pop();
+ else
+ continueDo = false;
+ break;
+ }
+ }
+ }
+ }
+
+ private SegmentedBank FromStream_GetSegBank(RomManager rommgr, int segAddr, byte? areaID)
+ {
+ SegmentedBank seg = null;
+
+ if (rommgr is not null)
+ {
+ seg = rommgr.GetSegBank(Convert.ToByte(segAddr >> 24), areaID);
+ seg?.ReadDataIfNull(rommgr.RomFile);
+ }
+
+ return seg;
+ }
+
+ public void ToStream(Stream s, uint pos)
+ {
+ s.Position = pos;
+ foreach (DisplayListCommand cmd in this)
+ s.Write(cmd.ToArray(), 0, Convert.ToInt32(cmd.Length));
+ }
+
+ public void Close()
+ {
+ foreach (var cmd in this)
+ cmd.Close();
+ }
+ }
+}
\ No newline at end of file
diff --git a/SM64Lib/Model/Fast3D/Fast3DBuffer.cs b/SM64Lib/Model/Fast3D/Fast3DBuffer.cs
new file mode 100644
index 0000000..29382ee
--- /dev/null
+++ b/SM64Lib/Model/Fast3D/Fast3DBuffer.cs
@@ -0,0 +1,143 @@
+using System;
+using global::System.IO;
+using System.Threading.Tasks;
+using Microsoft.VisualBasic.CompilerServices;
+using global::Pilz.S3DFileParser;
+using global::SM64Lib.Data;
+using global::SM64Lib.Geolayout;
+using System.Linq;
+
+namespace SM64Lib.Model.Fast3D
+{
+ [Newtonsoft.Json.JsonConverter(typeof(Json.Fast3DBufferJsonConverter))]
+ public class Fast3DBuffer : MemoryStream
+ {
+ public Conversion.Fast3DWriting.ConvertResult ConvertResult { get; set; } = null;
+ public int Fast3DBankStart { get; set; } = 0xE000000;
+ public Geopointer[] DLPointers { get; set; } = new Geopointer[] { };
+
+ ///
+ /// Creates a Fast3D Model from a Obj File
+ ///
+ public void FromModel(ObjectInputSettings ObjSettings, Object3D model, TextureFormatSettings texFormatSettings = null)
+ {
+ // Setup Settings
+ var conSettings = new Conversion.Fast3DWriting.ConvertSettings()
+ {
+ CenterModel = ObjSettings.CenterModel,
+ Scale = ObjSettings.Scaling,
+ ResizeTextures = ObjSettings.ResizeTextures,
+ ReduceVertLevel = (Conversion.Fast3DWriting.ReduceVericesLevel)ObjSettings.ReduceDupVertLevel,
+ SegmentedAddress = (uint?)0xE000000,
+ ForceDisplaylist = ObjSettings.ForceDisplaylist,
+ Fog = ObjSettings.Fog,
+ TransparencyLimit = ObjSettings.TransparencyLimit,
+ TextureFormatSettings = texFormatSettings
+ };
+ model.Shading = ObjSettings.Shading;
+
+ // Convert Model
+ var con = new Conversion.Fast3DWriting.Fast3DWriter();
+ ConvertResult = con.ConvertModel(this, conSettings, model);
+
+ // Fit to align
+ base.SetLength(General.HexRoundUp1(base.Length));
+
+ // Copy Geopointer etc.
+ DLPointers = ConvertResult.PtrGeometry.ToArray();
+ Fast3DBankStart = 0xE000000;
+ }
+
+ public void FromStream(Stream s, int BankRomStart, int BankRamStart, int Fast3DStart, int Fast3DLength, Geopointer[] DisplayListpointer)
+ {
+ FromBinaryData(new BinaryStreamData(s), BankRomStart, BankRamStart, Fast3DStart, Fast3DLength, DisplayListpointer);
+ }
+
+ public void FromBinaryData(BinaryData data, int BankRomStart, int BankRamStart, int Fast3DStart, int Fast3DLength, Geopointer[] DisplayListpointer)
+ {
+ DLPointers = DisplayListpointer;
+ Fast3DBankStart = Fast3DStart - BankRomStart + BankRamStart;
+ data.Position = Fast3DStart;
+ base.SetLength(Fast3DLength);
+ base.Position = 0;
+ for (int i = 1, loopTo = Fast3DLength; i <= loopTo; i++)
+ base.WriteByte(data.ReadByte());
+ }
+
+ public Task FromModelAsync(ObjectInputSettings ObjSettings, Object3D model, TextureFormatSettings texFormatSettings = null)
+ {
+ var t = new Task(() => FromModel(ObjSettings, model, texFormatSettings));
+ t.Start();
+ return t;
+ }
+
+ public Task FromStreamAsync(Stream s, int BankRomStart, int BankRamStart, int Fast3DStart, int Fast3DLength, Geopointer[] DisplayListpointer)
+ {
+ var t = new Task(() => FromStream(s, BankRomStart, BankRamStart, Fast3DStart, Fast3DLength, DisplayListpointer));
+ t.Start();
+ return t;
+ }
+
+ public void ToStream(Stream s, int RomPos, int BankRomStart, int BankRamStart)
+ {
+ ToBinaryData(new BinaryStreamData(s), RomPos, BankRomStart, BankRamStart);
+ }
+
+ public void ToBinaryData(BinaryData data, int dataPos, int BankRomStart, int BankRamStart)
+ {
+ data.Position = dataPos;
+
+ // Update all Pointers
+ int newBankStart = dataPos - BankRomStart + BankRamStart;
+ int tdif = newBankStart - Fast3DBankStart;
+ foreach (Geopointer geop in DLPointers)
+ {
+ var geopointerSegBank = geop.SegPointer >> 24;
+ if (geopointerSegBank == Fast3DBankStart >> 24)
+ {
+ var ende = false;
+ Position = geop.SegPointer - Fast3DBankStart;
+ do
+ {
+ var switchExpr = ReadByte();
+ switch (switchExpr)
+ {
+ case 0xB8:
+ {
+ ende = true;
+ break;
+ }
+
+ case 0x3:
+ case 0x4:
+ case 0x6:
+ case 0xFD:
+ {
+ base.Position += 3;
+ var sd = new BinaryStreamData(this);
+ int p = sd.ReadInt32();
+ p += tdif;
+ base.Position -= 4;
+ sd.Write(p);
+ break;
+ }
+
+ default:
+ {
+ base.Position += 7;
+ break;
+ }
+ }
+ }
+ while (!ende && base.Position < base.Length);
+ geop.SegPointer += tdif;
+ }
+ }
+
+ // Write Fast3D
+ foreach (byte b in base.ToArray())
+ data.Write(b);
+ Fast3DBankStart = newBankStart;
+ }
+ }
+}
\ No newline at end of file
diff --git a/SM64Lib/Model/Fast3D/TextureConverters.cs b/SM64Lib/Model/Fast3D/TextureConverters.cs
new file mode 100644
index 0000000..7a06978
--- /dev/null
+++ b/SM64Lib/Model/Fast3D/TextureConverters.cs
@@ -0,0 +1,14 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SM64Lib.Model.Fast3D
+{
+ public enum TextureConverters
+ {
+ Internal,
+ NConvert
+ }
+}
diff --git a/SM64Lib/Model/Fast3D/TextureFormatSettings.cs b/SM64Lib/Model/Fast3D/TextureFormatSettings.cs
new file mode 100644
index 0000000..fadb744
--- /dev/null
+++ b/SM64Lib/Model/Fast3D/TextureFormatSettings.cs
@@ -0,0 +1,100 @@
+using System;
+using System.Collections.Generic;
+using global::System.Drawing;
+using global::System.IO;
+using System.Linq;
+using System.Threading.Tasks;
+using global::Newtonsoft.Json.Linq;
+using global::SM64Lib.Model.Conversion.Fast3DWriting;
+using SM64Lib.Model.Conversion;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Converters;
+
+namespace SM64Lib.Model.Fast3D
+{
+ public class TextureFormatSettings
+ {
+ public List Entries { get; private set; } = new List();
+ public List CustomDisplayLists { get; private set; } = new List();
+
+ public async Task Load(string fileName)
+ {
+ if (File.Exists(fileName))
+ {
+ bool success = false;
+ var streamReader = new StreamReader(fileName);
+ string content = await streamReader.ReadToEndAsync();
+ streamReader.Close();
+ Entries.Clear();
+ CustomDisplayLists.Clear();
+ try
+ {
+ var settings = JObject.Parse(content).ToObject();
+ Entries.AddRange(settings.Entries);
+ CustomDisplayLists.AddRange(settings.CustomDisplayLists);
+ success = true;
+ }
+ catch (Exception)
+ {
+ }
+
+ if (!success)
+ {
+ try
+ {
+ Entries.AddRange(JArray.Parse(content).ToObject());
+ success = true;
+ }
+ catch (Exception)
+ {
+ }
+ }
+ }
+ }
+
+ public async Task Save(string fileName)
+ {
+ var sw = new StreamWriter(fileName);
+ await sw.WriteAsync(JObject.FromObject(this).ToString());
+ sw.Flush();
+ sw.Close();
+ }
+
+ public Entry GetEntry(string matName)
+ {
+ foreach (Entry e in Entries)
+ {
+ if ((e.MaterialName ?? "") == (matName ?? ""))
+ {
+ return e;
+ }
+ }
+
+ var ne = new Entry();
+ ne.MaterialName = matName;
+ Entries.Add(ne);
+ return ne;
+ }
+
+ public class Entry
+ {
+ public bool Include { get; set; } = true;
+ public string MaterialName { get; set; } = "";
+ public string TextureFormat { get; set; } = "";
+ public bool IsScrollingTexture { get; set; } = false;
+ public DisplaylistSelectionSettings DisplaylistSelection { get; set; } = new DisplaylistSelectionSettings();
+ [JsonConverter(typeof(StringEnumConverter))]
+ public FaceCullingMode FaceCullingMode { get; set; } = FaceCullingMode.Back;
+ public bool EnableMirrorS { get; set; } = false;
+ public bool EnableMirrorT { get; set; } = false;
+ public bool EnableClampS { get; set; } = false;
+ public bool EnableClampT { get; set; } = false;
+ public bool EnableCrystalEffect { get; set; } = false;
+ public float? TransparencyLimit { get; set; } = null;
+ [JsonConverter(typeof(StringEnumConverter))]
+ public RotateFlipType RotateFlip { get; set; } = RotateFlipType.RotateNoneFlipNone;
+ [JsonConverter(typeof(StringEnumConverter)), JsonProperty("TextureFilterV2")]
+ public TextureFilter TextureFilter { get; set; } = TextureFilter.Bilerp;
+ }
+ }
+}
\ No newline at end of file
diff --git a/SM64Lib/Model/Fast3D/TextureManager.cs b/SM64Lib/Model/Fast3D/TextureManager.cs
new file mode 100644
index 0000000..739e826
--- /dev/null
+++ b/SM64Lib/Model/Fast3D/TextureManager.cs
@@ -0,0 +1,175 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using global::System.Drawing;
+using global::System.Drawing.Drawing2D;
+using Microsoft.VisualBasic.CompilerServices;
+
+namespace SM64Lib.Model.Fast3D
+{
+ public static class TextureManager
+ {
+ public static void PrepaireImage(ref Bitmap bmp, RotateFlipType rotateFlipTexture, N64Graphics.N64Codec texFormat, bool fitImageSize, TextureConverters converterToUse = TextureConverters.Internal)
+ {
+ if (fitImageSize)
+ {
+ int maxPixels = GetMaxPixls(texFormat);
+
+ // Resize Texture
+ if (bmp.Height * bmp.Width > maxPixels)
+ {
+ int curPixels = bmp.Height * bmp.Width;
+ float verhälltnis = Convert.ToSingle(Math.Sqrt(curPixels / (double)maxPixels));
+ float newHeight = bmp.Height / verhälltnis;
+ float newWidth = bmp.Width / verhälltnis;
+ int nhlog = Convert.ToInt32(Math.Truncate(Math.Log(newHeight, 2)));
+ int nwlog = Convert.ToInt32(Math.Truncate(Math.Log(newWidth, 2)));
+ newHeight = Convert.ToSingle(Math.Pow(2, nhlog));
+ newWidth = Convert.ToSingle(Math.Pow(2, nwlog));
+ bmp = (Bitmap)ResizeImage(bmp, new Size(Convert.ToInt32(newWidth), Convert.ToInt32(newHeight)), converterToUse:converterToUse);
+ }
+ }
+
+ RotateFlipImage(bmp, rotateFlipTexture);
+ }
+
+ public static Image ResizeImage(Image image, Size size, bool preserveAspectRatio = false, bool forceSize = false, TextureConverters converterToUse = TextureConverters.Internal)
+ {
+ var result = new Size();
+
+ if (preserveAspectRatio)
+ {
+ float val = (float)(image.Size.Width / (double)size.Width);
+ float num = (float)(image.Size.Height / (double)size.Height);
+
+ if (num > val)
+ {
+ result.Width = Convert.ToInt32(Math.Truncate(size.Width * val));
+ result.Height = size.Height;
+ }
+ else if (num < val)
+ {
+ result.Width = size.Width;
+ result.Height = Convert.ToInt32(Math.Truncate(size.Height * num));
+ }
+ else
+ result = size;
+ }
+ else
+ result = size;
+
+ var finalResult = forceSize ? size : result;
+ Image newImage = new Bitmap(image, finalResult);
+
+ bool needsPointToDraw() =>
+ forceSize && result.Width / (double)result.Height != size.Width / (double)size.Height;
+
+ Point getPointToDraw()
+ {
+ int px, py;
+ px = (int)((size.Width - result.Width) / (double)2);
+ py = (int)((size.Height - result.Height) / (double)2);
+ return new Point(px, py);
+ }
+
+ switch (converterToUse)
+ {
+ case TextureConverters.Internal:
+ {
+ using (var g = Graphics.FromImage(newImage))
+ {
+ g.SmoothingMode = SmoothingMode.HighQuality;
+ g.PixelOffsetMode = PixelOffsetMode.HighQuality;
+ g.PageUnit = GraphicsUnit.Pixel;
+ g.InterpolationMode = InterpolationMode.HighQualityBicubic;
+
+ Point pointToDraw;
+ if (needsPointToDraw())
+ pointToDraw = getPointToDraw();
+ else
+ pointToDraw = Point.Empty;
+
+ g.Clear(Color.Transparent);
+ g.DrawImage(image, new Rectangle(pointToDraw, result));
+ g.Dispose();
+ }
+ }
+ break;
+ case TextureConverters.NConvert:
+ {
+ var arguments = new List();
+ arguments.Add("-out png");
+ arguments.Add($"-resize {result.Width} {result.Height}");
+ arguments.Add("-overwrite");
+
+ var sourceFilePath = Path.GetTempFileName();
+ using (var fs = new FileStream(sourceFilePath, FileMode.Create, FileAccess.ReadWrite))
+ image.Save(fs, System.Drawing.Imaging.ImageFormat.Png);
+ arguments.Add($"-o \"{sourceFilePath}\"");
+ arguments.Add($"\"{sourceFilePath}\"");
+
+ var p = new Process();
+ p.StartInfo.CreateNoWindow = true;
+ p.StartInfo.FileName = FilePathsConfiguration.DefaultConfiguration.Files["nconvert.exe"];
+ p.StartInfo.Arguments = string.Join(" ", arguments.ToArray());
+
+ p.Start();
+ p.WaitForExit();
+
+ using (var fs = new FileStream(sourceFilePath, FileMode.Open, FileAccess.Read))
+ image = Image.FromStream(fs);
+
+ if (needsPointToDraw())
+ {
+ using (var g = Graphics.FromImage(newImage))
+ {
+ Point pointToDraw = getPointToDraw();
+ g.Clear(Color.Transparent);
+ g.DrawImage(image, new Rectangle(pointToDraw, result));
+ g.Dispose();
+ }
+ }
+ }
+ break;
+ }
+
+ return newImage;
+ }
+
+ public static void RotateFlipImage(Bitmap bmp, RotateFlipType rotateFlipTexture)
+ {
+ if (rotateFlipTexture != RotateFlipType.RotateNoneFlipNone)
+ {
+ bmp.RotateFlip(rotateFlipTexture);
+ }
+ }
+
+ public static int GetMaxPixls(N64Graphics.N64Codec texFormat)
+ {
+ switch (texFormat)
+ {
+ case N64Graphics.N64Codec.CI4:
+ return 64 * 64;
+ case N64Graphics.N64Codec.CI8:
+ return 32 * 64;
+ case N64Graphics.N64Codec.I4:
+ return 128 * 64;
+ case N64Graphics.N64Codec.I8:
+ return 64 * 64;
+ case N64Graphics.N64Codec.IA4:
+ return 128 * 64;
+ case N64Graphics.N64Codec.IA8:
+ return 64 * 64;
+ case N64Graphics.N64Codec.IA16:
+ return 32 * 64;
+ case N64Graphics.N64Codec.RGBA16:
+ return 32 * 64;
+ case N64Graphics.N64Codec.RGBA32:
+ return 32 * 32;
+ default:
+ return 32 * 32;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/SM64Lib/Model/Fog.cs b/SM64Lib/Model/Fog.cs
new file mode 100644
index 0000000..5d67a2d
--- /dev/null
+++ b/SM64Lib/Model/Fog.cs
@@ -0,0 +1,9 @@
+
+namespace SM64Lib.Model
+{
+ public class Fog
+ {
+ public System.Drawing.Color Color { get; set; } = System.Drawing.Color.White;
+ public FogPreset Type { get; set; } = new FogPreset();
+ }
+}
\ No newline at end of file
diff --git a/SM64Lib/Model/FogPreset.cs b/SM64Lib/Model/FogPreset.cs
new file mode 100644
index 0000000..14ec5bf
--- /dev/null
+++ b/SM64Lib/Model/FogPreset.cs
@@ -0,0 +1,16 @@
+
+namespace SM64Lib.Model
+{
+ public enum FogPreset
+ {
+ SubtleFog1 = 0,
+ SubtleFog2,
+ ModerateFog1,
+ ModerateFog2,
+ ModerateFog3,
+ ModerateFog4,
+ IntenseFog,
+ VeryIntenseFog,
+ HardcoreFog
+ }
+}
\ No newline at end of file
diff --git a/SM64Lib/Model/ObjectInputSettings.cs b/SM64Lib/Model/ObjectInputSettings.cs
new file mode 100644
index 0000000..edf08a7
--- /dev/null
+++ b/SM64Lib/Model/ObjectInputSettings.cs
@@ -0,0 +1,25 @@
+
+namespace SM64Lib.Model
+{
+ public class ObjectInputSettings
+ {
+ public sbyte ForceDisplaylist { get; set; } = -1;
+ public double Scaling { get; set; } = 1.0F;
+ public ReduceDuplicateVerticesLevel ReduceDupVertLevel { get; set; } = ReduceDuplicateVerticesLevel.Level1;
+ public Fog Fog { get; set; } = null;
+ public bool ResizeTextures { get; set; } = true;
+ public bool CenterModel { get; set; } = false;
+ public Pilz.S3DFileParser.Shading Shading { get; private set; } = new Pilz.S3DFileParser.Shading();
+ public float TransparencyLimit { get; set; } = 0;
+
+ public enum ReduceDuplicateVerticesLevel
+ {
+ /// Don't reduce vertices.
+ Level0,
+ /// Reduce only, if in the same 0x4 group.
+ Level1,
+ /// Reduce and push up. [Buggy]
+ Level2
+ }
+ }
+}
\ No newline at end of file
diff --git a/SM64Lib/Model/ObjectModel.cs b/SM64Lib/Model/ObjectModel.cs
new file mode 100644
index 0000000..2e07c7e
--- /dev/null
+++ b/SM64Lib/Model/ObjectModel.cs
@@ -0,0 +1,135 @@
+using System.Collections.Generic;
+using global::System.IO;
+using System.Threading.Tasks;
+using Microsoft.VisualBasic.CompilerServices;
+using global::SM64Lib.Data;
+using global::SM64Lib.Geolayout;
+using SM64Lib.Configuration;
+using System;
+
+namespace SM64Lib.Model
+{
+ public class ObjectModel
+ {
+
+ public Collision.CollisionMap Collision { get; set; } = null;
+ public Fast3D.Fast3DBuffer Fast3DBuffer { get; set; } = null;
+
+ public void FromROM(string Romfile, int BankRomStart, int BankRamStart, int Fast3DStart, int Fast3DLength, Geopointer[] DisplayListpointer, int Collisionpointer = -1, CollisionBasicConfig collisionConfig = null)
+ {
+ var fs = new FileStream(Romfile, FileMode.Open, FileAccess.Read);
+ FromStream(fs, BankRomStart, BankRamStart, Fast3DStart, Fast3DLength, DisplayListpointer, Collisionpointer, collisionConfig);
+ fs.Close();
+ }
+
+ public void FromBinaryData(BinaryData data, int BankRomStart, int BankRamStart, int Fast3DStart, int Fast3DLength, Geopointer[] DisplayListpointer, int Collisionpointer = -1, CollisionBasicConfig collisionConfig = null)
+ {
+ // Load Collision
+ if (Collisionpointer > -1)
+ {
+ Collision = new Collision.CollisionMap();
+ int cRomStart = Collisionpointer - BankRamStart + BankRomStart;
+ Collision.FromBinaryData(data, cRomStart, collisionConfig);
+ }
+ else
+ {
+ Collision = null;
+ }
+
+ // Read Fast3D Buffer
+ if (Fast3DBuffer is not null)
+ Fast3DBuffer.Close();
+ Fast3DBuffer = new Fast3D.Fast3DBuffer();
+ Fast3DBuffer.FromBinaryData(data, BankRomStart, BankRamStart, Fast3DStart, Fast3DLength, DisplayListpointer);
+ }
+
+ public void FromStream(Stream s, int BankRomStart, int BankRamStart, int Fast3DStart, int Fast3DLength, Geopointer[] DisplayListpointer, int Collisionpointer = -1, CollisionBasicConfig collisionConfig = null)
+ {
+ FromBinaryData(new BinaryStreamData(s), BankRomStart, BankRamStart, Fast3DStart, Fast3DLength, DisplayListpointer, Collisionpointer, collisionConfig);
+ }
+
+ public void FromModel(ObjectInputSettings ObjSettings, Pilz.S3DFileParser.Object3D vmap, Pilz.S3DFileParser.Object3D colmap, Fast3D.TextureFormatSettings texFormatSettings, Collision.CollisionSettings colSettings = null)
+ {
+ // Add Collision
+ Collision = new Collision.CollisionMap();
+ Collision.FromObject3D(ObjSettings, colmap, colSettings);
+
+ // Add Fast3DBuffer (replacement for 'Add Displaylists')
+ if (Fast3DBuffer is not null)
+ Fast3DBuffer.Close();
+ Fast3DBuffer = new Fast3D.Fast3DBuffer();
+ Fast3DBuffer.FromModel(ObjSettings, vmap, texFormatSettings);
+ }
+
+ public Task FromModelAsync(ObjectInputSettings ObjSettings, Pilz.S3DFileParser.Object3D vmap, Pilz.S3DFileParser.Object3D colmap, Fast3D.TextureFormatSettings texFormatSettings = null, Collision.CollisionSettings colSettings = null)
+ {
+ var t = new Task(() => FromModel(ObjSettings, vmap, colmap, texFormatSettings, colSettings));
+ t.Start();
+ return t;
+ }
+
+ public SaveResult ToRom(string Romfile, int RomPos, int BankRomStart, int BankRamStart, CollisionBasicConfig collisionConfig)
+ {
+ var fs = new FileStream(Romfile, FileMode.Open, FileAccess.ReadWrite);
+ var treturn = ToStream(fs, RomPos, BankRomStart, BankRamStart, collisionConfig);
+ fs.Close();
+ return treturn;
+ }
+
+ public SaveResult ToStream(Stream s, int RomPos, int BankRomStart, int BankRamStart, CollisionBasicConfig collisionConfig)
+ {
+ return ToBinaryData(new BinaryStreamData(s), RomPos, BankRomStart, BankRamStart, collisionConfig);
+ }
+
+ public SaveResult ToBinaryData(BinaryData data, int dataPos, int BankRomStart, int BankRamStart, CollisionBasicConfig collisionConfig)
+ {
+ var tresult = new SaveResult();
+ data.Position = dataPos;
+
+ // Write Fast3D
+ if (Fast3DBuffer is not null)
+ {
+ Fast3DBuffer.ToBinaryData(data, Convert.ToInt32(data.Position), BankRomStart, BankRamStart);
+ tresult.GeoPointers.AddRange(Fast3DBuffer.DLPointers);
+ tresult.Length += Fast3DBuffer.Length;
+ }
+ else
+ {
+ tresult.GeoPointers.Clear();
+ }
+
+ // Write Collision
+ if (Collision is not null)
+ {
+ int colStart = (int)General.HexRoundUp1(data.Position);
+ tresult.CollisionPointer = colStart - BankRomStart + BankRamStart;
+ Collision.ToBinaryData(data, colStart, collisionConfig);
+ Collision.Length = data.Position - colStart;
+ tresult.Length += Collision.Length;
+ }
+ else
+ {
+ tresult.CollisionPointer = -1;
+ }
+
+ return tresult;
+ }
+
+ public int Length
+ {
+ get
+ {
+ return (int)(
+ (Fast3DBuffer is object ? General.HexRoundUp1(Fast3DBuffer.Length) : 0) +
+ (Collision is object ? General.HexRoundUp1(Collision.Length) : 0));
+ }
+ }
+
+ public class SaveResult
+ {
+ public int CollisionPointer { get; set; } = -1;
+ public List GeoPointers { get; set; } = new List();
+ public long Length { get; set; } = 0;
+ }
+ }
+}
\ No newline at end of file
diff --git a/SM64Lib/Music/InstrumentSetList.cs b/SM64Lib/Music/InstrumentSetList.cs
new file mode 100644
index 0000000..ae75c93
--- /dev/null
+++ b/SM64Lib/Music/InstrumentSetList.cs
@@ -0,0 +1,124 @@
+using System.Collections.Generic;
+using Microsoft.VisualBasic.CompilerServices;
+using global::SM64Lib.Data;
+using System;
+
+namespace SM64Lib.Music
+{
+ public class InstrumentSetList
+ {
+ public List Sets { get; private set; } = new List();
+
+ public int Count
+ {
+ get
+ {
+ return Sets.Count;
+ }
+ }
+
+ public byte NInstLength
+ {
+ get
+ {
+ return Convert.ToByte(Sets.Count + 1);
+ }
+ }
+
+ public void ReadNInst(BinaryData s, int RomAddress)
+ {
+ s.Position = RomAddress;
+ Sets.Clear();
+ for (int i = 0, loopTo = s.ReadByte() - 1; i <= loopTo; i++)
+ Sets.Add(s.ReadByte());
+ }
+
+ public void WriteNInst(BinaryData s, int RomAddress)
+ {
+ s.Position = RomAddress;
+ s.WriteByte(Convert.ToByte(Sets.Count));
+ foreach (byte b in Sets)
+ s.WriteByte(b);
+ }
+ }
+
+ // Public Enum InstSets As Byte
+ // ''' NInst 00 - SFX
+ // NInst_00_SFX
+ // ''' NInst 01 - SFX
+ // NInst_01_SFX
+ // ''' NInst 02 - SFX
+ // NInst_02_SFX
+ // ''' NInst 03 - SFX
+ // NInst_03_SFX
+ // ''' NInst 04 - SFX
+ // NInst_04_SFX
+ // ''' NInst 05 - SFX
+ // NInst_05_SFX
+ // ''' NInst 06 - SFX
+ // NInst_06_SFX
+ // ''' NInst 07 - SFX
+ // NInst_07_SFX
+ // ''' NInst 08 - SFX
+ // NInst_08_SFX
+ // ''' NInst 09 - SFX
+ // NInst_09_SFX
+ // ''' NInst 10 - SFX
+ // NInst_10_SFX
+ // ''' NInst 11 - Snow
+ // NInst_11_Snow
+ // ''' NInst 12 - Unused
+ // NInst_12_Unused
+ // ''' NInst 13 - Slide
+ // NInst_13_Slide
+ // ''' NInst 14 - Inside Castle
+ // NInst_14_Inside_Castle
+ // ''' NInst 15 - Shifting Sand Land
+ // NInst_15_Shifting_Sand_Land
+ // ''' NInst 16 - Haunted House
+ // NInst_16_Haunted_House
+ // ''' NInst 17 - Title Screen
+ // NInst_17_Title_Screen
+ // ''' NInst 18 - Bowser Battle
+ // NInst_18_Bowser_Battle
+ // ''' NInst 19 - Water
+ // NInst_19_Water
+ // ''' NInst 20 - Piranha Plant
+ // NInst_20_Piranha_Plant
+ // ''' NInst 21 - Hazy Maze
+ // NInst_21_Hazy_Maze
+ // ''' NInst 22 - Star Select
+ // NInst_22_Star_Select
+ // ''' NInst 23 - Wing Cap
+ // NInst_23_Wing_Cap
+ // ''' NInst 24 - Metal Cap
+ // NInst_24_Metal_Cap
+ // ''' NInst 25 - Bowser Course
+ // NInst_25_Bowser_Course
+ // ''' NInst 26 - Fanfare
+ // NInst_26_Fanfare
+ // ''' NInst 27 - Boss Fight
+ // NInst_27_Boss_Fight
+ // ''' NInst 28 - Looping Stairs
+ // NInst_28_Looping_Stairs
+ // ''' NInst 29 - Final Bowser
+ // NInst_29_Final_Bowser
+ // ''' NInst 30 - Peach Message
+ // NInst_30_Peach_Message
+ // ''' NInst 31 - Star Catch
+ // NInst_31_Star_Catch
+ // ''' NInst 32 - Toad
+ // NInst_32_Toad
+ // ''' NInst 33 - Merry-Go-Round
+ // NInst_33_Merry_Go_Round
+ // ''' NInst 34 - Bob-Omb- Battlefield
+ // NInst_34_Bob_Omb_Battlefield
+ // ''' NInst 35 - Ending
+ // NInst_35_Ending
+ // ''' NInst 36 - File Select
+ // NInst_36_File_Select
+ // ''' NInst 37 - Credits
+ // NInst_37_Credits
+ // End Enum
+
+}
\ No newline at end of file
diff --git a/SM64Lib/Music/MusicList.cs b/SM64Lib/Music/MusicList.cs
new file mode 100644
index 0000000..6df384f
--- /dev/null
+++ b/SM64Lib/Music/MusicList.cs
@@ -0,0 +1,351 @@
+using System;
+using System.Collections.Generic;
+using System.Data;
+using global::System.IO;
+using System.Linq;
+using Microsoft.VisualBasic.CompilerServices;
+using global::SM64Lib.Data;
+
+namespace SM64Lib.Music
+{
+ public class MusicList : List
+ {
+
+ // C o n s t s
+
+ public const int addrMusicStart = 0x1210000;
+
+ // A u t o P r o p e r t i e s
+
+ public bool EnableMusicHack { get; set; } = false;
+ public bool NeedToSaveNInsts { get; set; } = false;
+ public bool NeedToSaveSequenceNames { get; set; } = false;
+ public bool NeedToSaveSequences { get; set; } = false;
+ public bool NeedToSaveMusicHackSettings { get; set; } = false;
+
+ // O t h e r P r o p e r t i e s
+
+ public bool NeedToSave
+ {
+ get
+ {
+ return NeedToSaveNInsts || NeedToSaveMusicHackSettings || NeedToSaveSequenceNames || NeedToSaveSequences;
+ }
+ }
+
+ public long Length
+ {
+ get
+ {
+ return this.Sum(n => n.Lenght + 0x8) + 0x4;
+ }
+ }
+
+ // M e t h o d s
+
+ public void Read(RomManager rommgr)
+ {
+ BinaryData s = rommgr.GetBinaryRom(FileAccess.Read);
+ s.Position = 0x1210002;
+ short musicCount = s.ReadInt16();
+
+ // Read NInsts
+ var tNInstList = ReadNInsts(s, 0x7F0000, musicCount);
+
+ // Read Sequence Names
+ var tNames = ReadSequenceNames(rommgr);
+ if (!tNames.Any())
+ {
+ tNames = ReadSequenceNames(s, 0x7F1000, musicCount);
+ }
+
+ // Read Sequences
+ Clear();
+ AddRange(ReadSequences(s, addrMusicStart, musicCount, tNInstList, tNames));
+
+ // Check for Music Hack
+ s.Position = 0xD213A;
+ ushort t001 = s.ReadUInt16();
+ s.Position = 0xD213E;
+ ushort t002 = s.ReadUInt16();
+ EnableMusicHack = t001 == 0x807C & t002 == 0x0;
+ s.Close();
+ }
+
+ public void Write(RomManager rommgr, ref int lastPosition)
+ {
+ BinaryData s = rommgr.GetBinaryRom(FileAccess.ReadWrite);
+
+ // Enable/Disable Music Hack
+ if (NeedToSaveMusicHackSettings)
+ {
+ s.Position = 0xD213A;
+ s.Write(Convert.ToUInt16(EnableMusicHack ? 0x807C : 0x801D));
+ s.Position = 0xD213E;
+ s.Write(Convert.ToUInt16(EnableMusicHack ? 0x0 : 0xE000));
+ s.Position = 0xD215A;
+ s.Write(Convert.ToUInt16(EnableMusicHack ? 0x807C : 0x801D));
+ s.Position = 0xD215E;
+ s.Write(Convert.ToUInt16(EnableMusicHack ? 0x0 : 0xE000));
+ s.Position = 0xD459A;
+ s.Write(Convert.ToUInt16(EnableMusicHack ? 0x807C : 0x801D));
+ s.Position = 0xD459E;
+ s.Write(Convert.ToUInt16(EnableMusicHack ? 0x0 : 0xE000));
+ s.Position = 0xEE2B0;
+ s.Write(Convert.ToUInt32(EnableMusicHack ? 0xBD00 : 0x6D00));
+ s.Position = 0xD48B4;
+ s.Write(Convert.ToUInt32(EnableMusicHack ? 0x3C02803D : 0x3C02807C)); // &H3C02807C
+ s.Position = 0xD48B8;
+ s.Write(Convert.ToUInt32(EnableMusicHack ? 0x34420000 : 0x34420000));
+ }
+
+ var arrMe = ToArray();
+
+ // Write Music Names
+ if (NeedToSaveSequenceNames)
+ {
+ WriteSequenceNames(rommgr, arrMe);
+ }
+
+ // Write NInsts
+ if (NeedToSaveNInsts)
+ {
+ WriteNInst(s, 0x7F0000, arrMe);
+ }
+
+ // Write Music Sequences
+ lastPosition = WriteSequences(s, addrMusicStart, arrMe, NeedToSaveSequences);
+
+ // Reset NeedToSave Properties
+ NeedToSaveSequences = false;
+ NeedToSaveNInsts = false;
+ NeedToSaveSequenceNames = false;
+ NeedToSaveMusicHackSettings = false;
+ s.Close();
+ }
+
+ // S h a r e d M e t h o d s
+
+ private static InstrumentSetList[] ReadNInsts(BinaryData s, int TableStart, short Count)
+ {
+ var tNInstList = new List();
+ s.Position = TableStart;
+ for (int i = 0, loopTo = Count - 1; i <= loopTo; i++)
+ {
+ ushort startoff = s.ReadUInt16();
+ var n = new InstrumentSetList();
+ tNInstList.Add(n);
+ int offBefore = Convert.ToInt32(s.Position);
+ n.ReadNInst(s, TableStart + startoff);
+ s.Position = offBefore;
+ }
+
+ return tNInstList.ToArray();
+ }
+
+ private static string[] ReadSequenceNames(BinaryData s, int RomAddress, int Count)
+ {
+ s.Position = RomAddress;
+ var tNames = new List();
+ for (int i = 0, loopTo = Count - 1; i <= loopTo; i++)
+ {
+ if (s.ReadByte() == 0xFF)
+ {
+ s.Position -= 1;
+ }
+ else
+ {
+ s.Position -= 1;
+ tNames.Add(s.ReadString());
+ }
+ }
+
+ return tNames.ToArray();
+ }
+
+ private static string[] ReadSequenceNames(RomManager rommgr)
+ {
+ return rommgr.RomConfig.MusicConfig.SqeuenceNames.ToArray();
+ }
+
+ private static MusicSequence[] ReadSequences(BinaryData s, int TableStart, int Count, InstrumentSetList[] tNInstList, string[] tNames)
+ {
+ s.Position = TableStart + 4;
+ var tSequences = new List();
+ for (int i = 0, loopTo = Count - 1; i <= loopTo; i++)
+ {
+ uint startoff = s.ReadUInt32();
+ uint len = s.ReadUInt32();
+ var ms = new MusicSequence();
+ tSequences.Add(ms);
+ if (tNames.Length > i)
+ ms.Name = tNames[i];
+ if (tNInstList.Length > i)
+ ms.InstrumentSets = tNInstList[i];
+ int offBefore = Convert.ToInt32(s.Position);
+ ms.ReadData(s, (int)(TableStart + startoff), Convert.ToInt32(len));
+ s.Position = offBefore;
+ }
+
+ return tSequences.ToArray();
+ }
+
+ public static void Prepaire(RomManager rommgr)
+ {
+ BinaryData s = rommgr.GetBinaryRom(FileAccess.ReadWrite);
+ s.Position = 0x7B0863;
+ byte musicCount = s.ReadByte();
+
+ // Set original Names
+ var tNames = new[] {
+ "No Music",
+ "Star Catch",
+ "Title Screen",
+ "Bob-Omb Battlefield",
+ "Inside Castle",
+ "Dire, Dire Docks",
+ "Lethal Lava Land",
+ "Bowser Battle",
+ "Snow",
+ "Slide",
+ "Haunted House",
+ "Piranha Plant Lullaby",
+ "Hazy Maze Cave",
+ "Star Select",
+ "Wing Cap",
+ "Metal Cap",
+ "Bowser Message",
+ "Bowser Course",
+ "High Score",
+ "Merry-Go-Round",
+ "Start and End Race with Koopa the Quick",
+ "Star Appears",
+ "Boss Fight",
+ "Take a Key",
+ "Endless Stairs",
+ "Final Boss",
+ "Staff Credits",
+ "Puzzle Solved",
+ "Toad Message",
+ "Peach Message",
+ "Introduction Scene",
+ "Last Star Fanfare",
+ "Ending Scene",
+ "File Select",
+ "Lakitu Appears"
+ };
+
+ // Read original sequences
+ var tSequences = ReadSequences(s, 0x7B0860, musicCount, Array.Empty(), tNames);
+ s.Position = 0xDC0B8;
+ s.Write(0);
+
+ // Write sequences to the new Position
+ WriteSequences(s, addrMusicStart, tSequences, true);
+
+ // Write new sequence names
+ WriteSequenceNames(rommgr, tSequences);
+
+ // Write NInsts
+ WriteNInst(s, 0x7F0000, tSequences);
+
+ // Edit ASM-Code to load from the new location
+ s.Position = 0x7B085F;
+ s.Write(17);
+ s.Position = 0xD4714;
+ s.Write(0x3C040121);
+ s.Position = 0xD471C;
+ s.Write(0x24840000);
+ s.Position = 0xD4768;
+ s.Write(0x3C040121);
+ s.Position = 0xD4770;
+ s.Write(0x24840000);
+ s.Position = 0xD4784;
+ s.Write(0x3C050121);
+ s.Position = 0xD4788;
+ s.Write(0x24A50000);
+ s.Position = 0xD48B4;
+ s.Write(0x3C02807C);
+ s.Position = 0xD48B8;
+ s.Write(0x34420000);
+
+ // Edit ASM-Code to load from new location
+ s.Position = 0xD48C6;
+ s.Write(Convert.ToInt16(0x7F));
+ s.Position = 0xD48CC;
+ s.Write(0x34840000);
+ s.Position = 0xD48DA;
+ s.Write(Convert.ToInt16(0x200));
+
+ // Check for Music Hack
+ s.Position = 0xD213A;
+ ushort t001 = s.ReadUInt16();
+ s.Position = 0xD213E;
+ ushort t002 = s.ReadUInt16();
+ if (t001 == 0x805D & t002 == 0xC000)
+ {
+ s.Position = 0xD213A;
+ s.Write(Convert.ToUInt16(0x807C));
+ s.Position = 0xD213E;
+ s.Write(Convert.ToUInt16(0x0));
+ s.Position = 0xD215A;
+ s.Write(Convert.ToUInt16(0x807C));
+ s.Position = 0xD215E;
+ s.Write(Convert.ToUInt16(0x0));
+ s.Position = 0xD459A;
+ s.Write(Convert.ToUInt16(0x807C));
+ s.Position = 0xD459E;
+ s.Write(Convert.ToUInt16(0x0));
+ s.Position = 0xEE2B0;
+ s.Write(Convert.ToUInt32(0xBD00));
+ }
+
+ s.Close();
+ }
+
+ private static void WriteSequenceNames(RomManager rommgr, MusicSequence[] sequences)
+ {
+ rommgr.RomConfig.MusicConfig.SqeuenceNames.Clear();
+ rommgr.RomConfig.MusicConfig.SqeuenceNames.AddRange(sequences.Select(n => n.Name));
+ }
+
+ private static void WriteNInst(BinaryData s, int TableStart, MusicSequence[] sequences)
+ {
+ s.Position = TableStart;
+ int lastNInstDataOffset = TableStart + sequences.Length * 2;
+ foreach (MusicSequence ms in sequences)
+ {
+ s.Write(Convert.ToUInt16(lastNInstDataOffset - TableStart));
+ int lastOff = Convert.ToInt32(s.Position);
+ ms.InstrumentSets.WriteNInst(s, lastNInstDataOffset);
+ lastNInstDataOffset += ms.InstrumentSets.NInstLength;
+ s.Position = lastOff;
+ }
+ }
+
+ private static int WriteSequences(BinaryData s, int TableStart, MusicSequence[] sequences, bool WriteData)
+ {
+ s.Position = TableStart;
+ s.Write(Convert.ToInt16(3));
+ s.Write(Convert.ToInt16(sequences.Length));
+
+ // Write sequences to the new Position
+ int curMsDataOff = General.HexRoundUp1(TableStart + 4 + sequences.Length * 0x8);
+ foreach (MusicSequence ms in sequences)
+ {
+ if (WriteData)
+ {
+ s.Write(Convert.ToUInt32(curMsDataOff - TableStart));
+ s.Write(Convert.ToUInt32(ms.Lenght));
+ int lastOff = Convert.ToInt32(s.Position);
+ ms.WriteData(s, curMsDataOff);
+ s.Position = lastOff;
+ }
+
+ curMsDataOff += General.HexRoundUp1(ms.Lenght);
+ }
+
+ return curMsDataOff;
+ }
+ }
+}
\ No newline at end of file
diff --git a/SM64Lib/Music/MusicSequence.cs b/SM64Lib/Music/MusicSequence.cs
new file mode 100644
index 0000000..07d95a1
--- /dev/null
+++ b/SM64Lib/Music/MusicSequence.cs
@@ -0,0 +1,49 @@
+using System;
+using global::SM64Lib.Data;
+
+namespace SM64Lib.Music
+{
+ public class MusicSequence
+ {
+ private InstrumentSetList _InstrumentSets = new InstrumentSetList();
+
+ public byte[] BinaryData { get; set; } = Array.Empty();
+ public string Name { get; set; } = "";
+
+ public InstrumentSetList InstrumentSets
+ {
+ get
+ {
+ return _InstrumentSets;
+ }
+
+ internal set
+ {
+ _InstrumentSets = value;
+ }
+ }
+
+ public int Lenght
+ {
+ get
+ {
+ return (int)BinaryData?.Length;
+ }
+ }
+
+ public void ReadData(BinaryData s, int RomAddress, int Length)
+ {
+ BinaryData = new byte[Length];
+ s.Position = RomAddress;
+ s.Read(BinaryData, 0, Length);
+ }
+
+ public void WriteData(BinaryData s, int RomAddress)
+ {
+ s.Position = RomAddress;
+ s.Write(BinaryData, 0, BinaryData.Length);
+ while (s.Position % 0x10 != 0)
+ s.WriteByte(0xFF);
+ }
+ }
+}
\ No newline at end of file
diff --git a/SM64Lib/My Project/Application.Designer.cs b/SM64Lib/My Project/Application.Designer.cs
new file mode 100644
index 0000000..2be760e
--- /dev/null
+++ b/SM64Lib/My Project/Application.Designer.cs
@@ -0,0 +1,11 @@
+// ------------------------------------------------------------------------------
+//
+// Dieser Code wurde von einem Tool generiert.
+// Laufzeitversion:4.0.30319.42000
+//
+// Änderungen an dieser Datei können falsches Verhalten verursachen und gehen verloren, wenn
+// der Code erneut generiert wird.
+//
+// ------------------------------------------------------------------------------
+
+
diff --git a/SM64Lib/My Project/Application.myapp b/SM64Lib/My Project/Application.myapp
new file mode 100644
index 0000000..758895d
--- /dev/null
+++ b/SM64Lib/My Project/Application.myapp
@@ -0,0 +1,10 @@
+
+
+ false
+ false
+ 0
+ true
+ 0
+ 1
+ true
+
diff --git a/SM64Lib/My Project/MyNamespace.Static.1.Designer.cs b/SM64Lib/My Project/MyNamespace.Static.1.Designer.cs
new file mode 100644
index 0000000..1db54d6
--- /dev/null
+++ b/SM64Lib/My Project/MyNamespace.Static.1.Designer.cs
@@ -0,0 +1,192 @@
+// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Diagnostics;
+using Microsoft.VisualBasic;
+
+/* TODO ERROR: Skipped IfDirectiveTrivia *//* TODO ERROR: Skipped DisabledTextTrivia *//* TODO ERROR: Skipped EndIfDirectiveTrivia */
+ /* TODO ERROR: Skipped IfDirectiveTrivia *//* TODO ERROR: Skipped DisabledTextTrivia *//* TODO ERROR: Skipped ElifDirectiveTrivia *//* TODO ERROR: Skipped DisabledTextTrivia *//* TODO ERROR: Skipped ElifDirectiveTrivia */
+ /* TODO ERROR: Skipped DefineDirectiveTrivia *//* TODO ERROR: Skipped DefineDirectiveTrivia *//* TODO ERROR: Skipped DefineDirectiveTrivia *//* TODO ERROR: Skipped DefineDirectiveTrivia */
+ /* TODO ERROR: Skipped ElifDirectiveTrivia *//* TODO ERROR: Skipped DisabledTextTrivia *//* TODO ERROR: Skipped ElifDirectiveTrivia *//* TODO ERROR: Skipped DisabledTextTrivia *//* TODO ERROR: Skipped ElifDirectiveTrivia *//* TODO ERROR: Skipped DisabledTextTrivia *//* TODO ERROR: Skipped ElifDirectiveTrivia *//* TODO ERROR: Skipped DisabledTextTrivia *//* TODO ERROR: Skipped ElifDirectiveTrivia *//* TODO ERROR: Skipped DisabledTextTrivia *//* TODO ERROR: Skipped EndIfDirectiveTrivia */
+ /* TODO ERROR: Skipped IfDirectiveTrivia */
+namespace SM64Lib.My
+{
+
+ /* TODO ERROR: Skipped IfDirectiveTrivia */
+ [System.CodeDom.Compiler.GeneratedCode("MyTemplate", "11.0.0.0")]
+ [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
+
+ /* TODO ERROR: Skipped IfDirectiveTrivia *//* TODO ERROR: Skipped DisabledTextTrivia *//* TODO ERROR: Skipped ElifDirectiveTrivia */
+ internal partial class MyApplication : Microsoft.VisualBasic.ApplicationServices.ApplicationBase
+ {
+ /* TODO ERROR: Skipped ElifDirectiveTrivia *//* TODO ERROR: Skipped DisabledTextTrivia *//* TODO ERROR: Skipped EndIfDirectiveTrivia */
+ }
+
+ /* TODO ERROR: Skipped EndIfDirectiveTrivia */
+ /* TODO ERROR: Skipped IfDirectiveTrivia */
+ [System.CodeDom.Compiler.GeneratedCode("MyTemplate", "11.0.0.0")]
+ [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
+
+ /* TODO ERROR: Skipped IfDirectiveTrivia */
+ internal partial class MyComputer : Microsoft.VisualBasic.Devices.Computer
+ {
+ /* TODO ERROR: Skipped ElifDirectiveTrivia *//* TODO ERROR: Skipped DisabledTextTrivia *//* TODO ERROR: Skipped EndIfDirectiveTrivia */
+ [DebuggerHidden()]
+ [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
+ public MyComputer() : base()
+ {
+ }
+ }
+ /* TODO ERROR: Skipped EndIfDirectiveTrivia */
+ [HideModuleName()]
+ [System.CodeDom.Compiler.GeneratedCode("MyTemplate", "11.0.0.0")]
+ internal static class MyProject
+ {
+
+ /* TODO ERROR: Skipped IfDirectiveTrivia */
+ [System.ComponentModel.Design.HelpKeyword("My.Computer")]
+ internal static MyComputer Computer
+ {
+ [DebuggerHidden()]
+ get
+ {
+ return m_ComputerObjectProvider.GetInstance;
+ }
+ }
+
+ private readonly static ThreadSafeObjectProvider m_ComputerObjectProvider = new ThreadSafeObjectProvider();
+ /* TODO ERROR: Skipped EndIfDirectiveTrivia */
+ /* TODO ERROR: Skipped IfDirectiveTrivia */
+ [System.ComponentModel.Design.HelpKeyword("My.Application")]
+ internal static MyApplication Application
+ {
+ [DebuggerHidden()]
+ get
+ {
+ return m_AppObjectProvider.GetInstance;
+ }
+ }
+
+ private readonly static ThreadSafeObjectProvider m_AppObjectProvider = new ThreadSafeObjectProvider();
+ /* TODO ERROR: Skipped EndIfDirectiveTrivia */
+ /* TODO ERROR: Skipped IfDirectiveTrivia */
+ [System.ComponentModel.Design.HelpKeyword("My.User")]
+ internal static Microsoft.VisualBasic.ApplicationServices.User User
+ {
+ [DebuggerHidden()]
+ get
+ {
+ return m_UserObjectProvider.GetInstance;
+ }
+ }
+
+ private readonly static ThreadSafeObjectProvider m_UserObjectProvider = new ThreadSafeObjectProvider();
+ /* TODO ERROR: Skipped ElifDirectiveTrivia *//* TODO ERROR: Skipped DisabledTextTrivia *//* TODO ERROR: Skipped EndIfDirectiveTrivia */
+ /* TODO ERROR: Skipped IfDirectiveTrivia *//* TODO ERROR: Skipped DisabledTextTrivia *//* TODO ERROR: Skipped EndIfDirectiveTrivia */
+ /* TODO ERROR: Skipped IfDirectiveTrivia */
+ [System.ComponentModel.Design.HelpKeyword("My.WebServices")]
+ internal static MyWebServices WebServices
+ {
+ [DebuggerHidden()]
+ get
+ {
+ return m_MyWebServicesObjectProvider.GetInstance;
+ }
+ }
+
+ [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
+ [MyGroupCollection("System.Web.Services.Protocols.SoapHttpClientProtocol", "Create__Instance__", "Dispose__Instance__", "")]
+ internal sealed class MyWebServices
+ {
+ [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
+ [DebuggerHidden()]
+ public override bool Equals(object o)
+ {
+ return base.Equals(o);
+ }
+
+ [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
+ [DebuggerHidden()]
+ public override int GetHashCode()
+ {
+ return base.GetHashCode();
+ }
+
+ [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
+ [DebuggerHidden()]
+ internal new Type GetType()
+ {
+ return typeof(MyWebServices);
+ }
+
+ [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
+ [DebuggerHidden()]
+ public override string ToString()
+ {
+ return base.ToString();
+ }
+
+ [DebuggerHidden()]
+ private static T Create__Instance__(T instance) where T : new()
+ {
+ if (instance == null)
+ {
+ return new T();
+ }
+ else
+ {
+ return instance;
+ }
+ }
+
+ [DebuggerHidden()]
+ private void Dispose__Instance__(ref T instance)
+ {
+ instance = default;
+ }
+
+ [DebuggerHidden()]
+ [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
+ public MyWebServices() : base()
+ {
+ }
+ }
+
+ private readonly static ThreadSafeObjectProvider m_MyWebServicesObjectProvider = new ThreadSafeObjectProvider();
+ /* TODO ERROR: Skipped EndIfDirectiveTrivia */
+ /* TODO ERROR: Skipped IfDirectiveTrivia *//* TODO ERROR: Skipped DisabledTextTrivia *//* TODO ERROR: Skipped EndIfDirectiveTrivia */
+ [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
+ [System.Runtime.InteropServices.ComVisible(false)]
+ internal sealed class ThreadSafeObjectProvider where T : new()
+ {
+ internal T GetInstance
+ {
+ /* TODO ERROR: Skipped IfDirectiveTrivia */
+ [DebuggerHidden()]
+ get
+ {
+ var Value = m_Context.Value;
+ if (Value == null)
+ {
+ Value = new T();
+ m_Context.Value = Value;
+ }
+
+ return Value;
+ }
+ /* TODO ERROR: Skipped ElseDirectiveTrivia *//* TODO ERROR: Skipped DisabledTextTrivia *//* TODO ERROR: Skipped EndIfDirectiveTrivia */
+ }
+
+ [DebuggerHidden()]
+ [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
+ public ThreadSafeObjectProvider() : base()
+ {
+ }
+
+ /* TODO ERROR: Skipped IfDirectiveTrivia */
+ private readonly Microsoft.VisualBasic.MyServices.Internal.ContextValue m_Context = new Microsoft.VisualBasic.MyServices.Internal.ContextValue();
+ /* TODO ERROR: Skipped ElseDirectiveTrivia *//* TODO ERROR: Skipped DisabledTextTrivia *//* TODO ERROR: Skipped EndIfDirectiveTrivia */
+ }
+ }
+}
+/* TODO ERROR: Skipped EndIfDirectiveTrivia */
diff --git a/SM64Lib/My Project/MyNamespace.Static.2.Designer.cs b/SM64Lib/My Project/MyNamespace.Static.2.Designer.cs
new file mode 100644
index 0000000..9979605
--- /dev/null
+++ b/SM64Lib/My Project/MyNamespace.Static.2.Designer.cs
@@ -0,0 +1,254 @@
+// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+// See Compiler::LoadXmlSolutionExtension
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Data;
+using System.Diagnostics;
+using System.Linq;
+using System.Xml.Linq;
+using Microsoft.VisualBasic;
+using Microsoft.VisualBasic.CompilerServices;
+
+namespace SM64Lib.My
+{
+ [Embedded()]
+ [DebuggerNonUserCode()]
+ [System.Runtime.CompilerServices.CompilerGenerated()]
+ [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
+ internal sealed class InternalXmlHelper
+ {
+ [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
+ private InternalXmlHelper()
+ {
+ }
+
+ public static string get_Value(IEnumerable source)
+ {
+ foreach (XElement item in source)
+ return item.Value;
+ return null;
+ }
+
+ public static void set_Value(IEnumerable source, string value)
+ {
+ foreach (XElement item in source)
+ {
+ item.Value = value;
+ break;
+ }
+ }
+
+ public static string get_AttributeValue(IEnumerable source, XName name)
+ {
+ foreach (XElement item in source)
+ return Convert.ToString(item.Attribute(name));
+ return null;
+ }
+
+ public static void set_AttributeValue(IEnumerable source, XName name, string value)
+ {
+ foreach (XElement item in source)
+ {
+ item.SetAttributeValue(name, value);
+ break;
+ }
+ }
+
+ public static string get_AttributeValue(XElement source, XName name)
+ {
+ return Convert.ToString(source.Attribute(name));
+ }
+
+ public static void set_AttributeValue(XElement source, XName name, string value)
+ {
+ source.SetAttributeValue(name, value);
+ }
+
+ [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
+ public static XAttribute CreateAttribute(XName name, object value)
+ {
+ if (value is null)
+ {
+ return null;
+ }
+
+ return new XAttribute(name, value);
+ }
+
+ [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
+ public static XAttribute CreateNamespaceAttribute(XName name, XNamespace ns)
+ {
+ var a = new XAttribute(name, ns.NamespaceName);
+ a.AddAnnotation(ns);
+ return a;
+ }
+
+ [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
+ public static object RemoveNamespaceAttributes(string[] inScopePrefixes, XNamespace[] inScopeNs, List attributes, object obj)
+ {
+ if (obj is not null)
+ {
+ XElement elem = obj as XElement;
+ if (elem is not null)
+ {
+ return RemoveNamespaceAttributes(inScopePrefixes, inScopeNs, attributes, elem);
+ }
+ else
+ {
+ IEnumerable elems = obj as IEnumerable;
+ if (elems is not null)
+ {
+ return RemoveNamespaceAttributes(inScopePrefixes, inScopeNs, attributes, elems);
+ }
+ }
+ }
+
+ return obj;
+ }
+
+ [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
+ public static IEnumerable RemoveNamespaceAttributes(string[] inScopePrefixes, XNamespace[] inScopeNs, List attributes, IEnumerable obj)
+ {
+ if (obj is not null)
+ {
+ IEnumerable elems = obj as IEnumerable;
+ if (elems is not null)
+ {
+ return elems.Select(new RemoveNamespaceAttributesClosure(inScopePrefixes, inScopeNs, attributes).ProcessXElement);
+ }
+ else
+ {
+ return obj.Cast