From 5da9d62616f1138964ee851947bc09c4d5a25249 Mon Sep 17 00:00:00 2001 From: Zsolt Zitting Date: Mon, 23 Oct 2023 03:35:10 -0700 Subject: [PATCH] Add one-time version selection, VFD util updates, added launch option for offline version. --- MainForm.cs | 67 +++++++-- WaccaVFD.cs | 393 ++++++++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 405 insertions(+), 55 deletions(-) diff --git a/MainForm.cs b/MainForm.cs index 3a7a672..70f5541 100644 --- a/MainForm.cs +++ b/MainForm.cs @@ -12,6 +12,7 @@ using IniParser.Model; using SharpDX.DirectInput; using System.Linq; using System.Reflection; +using WACCA; namespace WACCALauncher { @@ -43,6 +44,7 @@ namespace WACCALauncher private bool _gameRunning = false; public MenuManager _menuManager; + private VFD _vfd; public MainForm() { @@ -218,15 +220,16 @@ namespace WACCALauncher private static void vfd_test() { - var vfd = new WaccaVFD(); - vfd.Power(true); - vfd.Clear(); - vfd.Brightness(WaccaVFD.bright.BRIGHT_50); - vfd.Cursor(0, 0); + var vfd = new VFD(); + vfd.Reset(); + vfd.PowerOn(); + vfd.Brightness(VFD.Bright._50); vfd.CanvasShift(0); + vfd.Cursor(0, 0); + vfd.FontSize(VFD.Font._16_16); vfd.Write("Testing VFD!"); - vfd.Cursor(0, 16); - vfd.ScrollSpeed(2); + vfd.Cursor(0, 2); + vfd.ScrollSpeed(15); vfd.ScrollText(Math.PI.ToString() + " "); vfd.ScrollStart(); } @@ -266,8 +269,8 @@ namespace WACCALauncher LoadVersionsFromConfig(); var mainMenu = new ConfigMenu("Launcher Settings", items: new List() { + new ConfigMenu("one-time launch", ConfigMenuAction.Menu, items: GetOTLMenu()), new ConfigMenu("set default version", ConfigMenuAction.Menu, items: GetDefaultVersionMenu()), - new ConfigMenu("test VFD", ConfigMenuAction.Command, method: vfd_test), new ConfigMenu("exit to windows", ConfigMenuAction.Command, method: Application.Exit), new ConfigMenu("launch game", ConfigMenuAction.Return) }); @@ -285,7 +288,7 @@ namespace WACCALauncher foreach (var ver in Versions) { var name = ver.GameVersion == VersionType.Custom ? ver.CustomName : ver.ToString(); - defVerMenu.Add(new ConfigMenu($"({(ver == DefaultVer ? 'X' : ' ')}) {name}", ConfigMenuAction.VersionSelect, version: ver)); + defVerMenu.Add(new ConfigMenu($"({(ver == DefaultVer ? 'X' : ' ')}) {name}", ConfigMenuAction.VersionSelect, version: ver, defVer: true)); } defVerMenu.Add(new ConfigMenu("Return to settings", ConfigMenuAction.Return)); @@ -293,6 +296,21 @@ namespace WACCALauncher return defVerMenu; } + public List GetOTLMenu() + { + var otlMenu = new List(); + + foreach (var ver in Versions) + { + var name = ver.GameVersion == VersionType.Custom ? ver.CustomName : ver.ToString(); + otlMenu.Add(new ConfigMenu(name, ConfigMenuAction.VersionSelect, version: ver, defVer: false)); + } + + otlMenu.Add(new ConfigMenu("Return to settings", ConfigMenuAction.Return)); + + return otlMenu; + } + private static void KillExplorer() { Process.Start(@"C:\Windows\System32\taskkill.exe", @"/F /IM explorer.exe"); @@ -304,7 +322,7 @@ namespace WACCALauncher if (processes.Length == 0) Process.Start("explorer.exe"); } - private void LaunchGame(Version version) + public void LaunchGame(Version version) { Console.WriteLine("launching game"); _gameProcess.StartInfo.FileName = version.BatchPath; @@ -438,6 +456,11 @@ namespace WACCALauncher Controls.Add(errorLabel); } + public void StopTimer() + { + _delayTimer.Stop(); + } + private void MainForm_FormClosing(object sender, FormClosingEventArgs e) { OpenExplorer(); @@ -452,6 +475,7 @@ namespace WACCALauncher Lily, Lily_R, Reverse, + Offline, Custom = 10 } @@ -516,6 +540,7 @@ namespace WACCALauncher private readonly Action _method; private readonly List _options; private readonly Version _version; + private readonly bool _defVer; public void Select(MainForm form) { @@ -535,15 +560,26 @@ namespace WACCALauncher } else if (_action == ConfigMenuAction.VersionSelect && _version != null) { - Console.WriteLine($"setting default version to {_version}"); - form.SetDefaultVer(_version); - // TODO: this is kinda jank, fix this - form._menuManager.UpdateCurrentMenuItems(form.GetDefaultVersionMenu()); + if(_defVer) + { + Console.WriteLine($"setting default version to {_version}"); + form.SetDefaultVer(_version); + // TODO: this is kinda jank, fix this + form._menuManager.UpdateCurrentMenuItems(form.GetDefaultVersionMenu()); + } + else + { + Console.WriteLine($"one-time launch for {_version}"); + form.MenuHide(); + form.StopTimer(); + form.LaunchGame(_version); + } + } else if (_action == ConfigMenuAction.Return) { form._menuManager.MenuBack(); } } - public ConfigMenu(string name, ConfigMenuAction action = ConfigMenuAction.None, Action method = null, List items = null, List options = null, Version version = null) + public ConfigMenu(string name, ConfigMenuAction action = ConfigMenuAction.None, Action method = null, List items = null, List options = null, Version version = null, bool defVer = false) { this.Name = name; this._action = action; @@ -561,6 +597,7 @@ namespace WACCALauncher this._method = method; this._options = options; this._version = version; + this._defVer = defVer; } public override string ToString() diff --git a/WaccaVFD.cs b/WaccaVFD.cs index e511b68..6a173de 100644 --- a/WaccaVFD.cs +++ b/WaccaVFD.cs @@ -1,36 +1,239 @@ using System; using System.Text; using System.IO.Ports; +using System.Security.Policy; +using System.Drawing; +using System.Drawing.Imaging; +using static System.Net.Mime.MediaTypeNames; +using System.Linq; +using System.Data.Common; +using System.Runtime.InteropServices; +using System.Drawing.Drawing2D; +using System.ComponentModel; +using System.Globalization; -namespace WACCALauncher +namespace WACCA { - class WaccaVFD + // + // Summary: + // Represents an ordered pair of integer x- and y-coordinates that defines a point + // in a two-dimensional plane. + [Serializable] + [TypeConverter(typeof(PointConverter))] + [ComVisible(true)] + public struct VFDPoint + { + public static readonly VFDPoint Empty; + + private short x; + + private byte y; + + + [Browsable(false)] + public bool IsEmpty + { + get + { + if (x == 0) + { + return y == 0; + } + + return false; + } + } + + public short X + { + get + { + return x; + } + set + { + x = value; + } + } + + public byte Y + { + get + { + return y; + } + set + { + y = value; + } + } + + public VFDPoint(short x, byte y) + { + this.x = x; + this.y = y; + } + + public VFDPoint(Size sz) + { + x = (short)sz.Width; + y = (byte)sz.Height; + } + + public static explicit operator Size(VFDPoint p) + { + return new Size(p.X, p.Y); + } + + public static VFDPoint operator +(VFDPoint pt, Size sz) + { + return Add(pt, sz); + } + + public static VFDPoint operator -(VFDPoint pt, Size sz) + { + return Subtract(pt, sz); + } + + public static bool operator ==(VFDPoint left, VFDPoint right) + { + if (left.X == right.X) + { + return left.Y == right.Y; + } + + return false; + } + + public static bool operator !=(VFDPoint left, VFDPoint right) + { + return !(left == right); + } + + public static VFDPoint Add(VFDPoint pt, Size sz) + { + return new VFDPoint((short)(pt.X + sz.Width), (byte)(pt.Y + sz.Height)); + } + + public static VFDPoint Subtract(VFDPoint pt, Size sz) + { + return new VFDPoint((short)(pt.X - sz.Width), (byte)(pt.Y - sz.Height)); + } + + public override bool Equals(object obj) + { + if (!(obj is VFDPoint)) + { + return false; + } + + VFDPoint point = (VFDPoint)obj; + if (point.X == X) + { + return point.Y == Y; + } + + return false; + } + + public override int GetHashCode() + { + return x ^ y; + } + + public void Offset(short dx, byte dy) + { + X += dx; + Y += dy; + } + + public void Offset(VFDPoint p) + { + Offset(p.X, p.Y); + } + + public override string ToString() + { + return "{X=" + X.ToString(CultureInfo.CurrentCulture) + ",Y=" + Y.ToString(CultureInfo.CurrentCulture) + "}"; + } + } + + class VFD { SerialPort port; + public Lang language { get; private set; } = Lang.SIMP_CHINESE; + public Font font { get; private set; } = Font._16_16; + public Bright brightness { get; private set; } = Bright._100; + public bool power { get; private set; } = false; - public WaccaVFD(string portName = "COM2") + /// + /// Establish a connection to a VFD, and prepare it for use. + /// + /// The port that the VFD connected to + public VFD(string portName = "COM2") { - this.port = new SerialPort(portName, 115200); + port = new SerialPort(portName, 115200); port.Open(); Reset(); } private void VFD_Write(byte number) { - VFD_Write($"{(char)number}"); + Console.WriteLine(BitConverter.ToString(new byte[] { number })); + port.Write(new byte[] { number }, 0, 1); + } + + private void VFD_Write(byte[] bytes) + { + Console.WriteLine(BitConverter.ToString(bytes)); + port.Write(bytes, 0, bytes.Length); } private void VFD_Write(string text) { - Console.WriteLine(BitConverter.ToString(Encoding.Default.GetBytes(text))); - port.Write(text); + // Get correct encoding for current language + int codeNumber; + switch(language) + { + case Lang.SIMP_CHINESE: + codeNumber = 936; // GB2312 + break; + case Lang.TRAD_CHINESE: + codeNumber = 950; // Big5 + break; + case Lang.JAPANESE: + codeNumber = 932; // Shift-JIS + break; + case Lang.KOREAN: + codeNumber = 949; // KSC5601 + break; + default: + codeNumber = 932; + break; + } + + // Convert Unicode string to encoded bytes + Encoding unicodeEncoding = Encoding.Unicode; + Encoding correctEncoding = Encoding.GetEncoding(codeNumber); + byte[] unicodeBytes = unicodeEncoding.GetBytes(text); + byte[] encodedBytes = Encoding.Convert(unicodeEncoding, correctEncoding, unicodeBytes); + + Console.WriteLine(BitConverter.ToString(correctEncoding.GetBytes(text))); + port.Write(encodedBytes, 0, encodedBytes.Length); } + /* + #define LEFT_HI(x) (((x) & 0x100) >> 8) + #define LEFT_LO(x) ((x) & 0xFF) + + #define FTB_PORT_WRITE_LEFT(x) {FTB_PORT.write(LEFT_HI(x)); FTB_PORT.write(LEFT_LO(x));} + */ + private void VFD_WriteShort(short x) { - char hi = (char)((x & 0x100) >> 8); - char lo = (char)(x & 0xFF); - VFD_Write($"{hi}{lo}"); + byte hi = (byte)(((x) & 0x100) >> 8); + byte lo = (byte)((x) & 0xFF); + VFD_Write(new byte[] {hi, lo}); } public void Write(string text) @@ -40,71 +243,89 @@ namespace WACCALauncher public void Reset() { - VFD_Write("\x1B\x0B"); + VFD_Write(new byte[] { 0x1B, 0x0B }); } public void Clear() { - VFD_Write("\x1B\x0C"); + VFD_Write(new byte[] { 0x1B, 0x0C }); } - public enum bright { - BRIGHT_0 = 0, - BRIGHT_25 = 1, - BRIGHT_50 = 2, - BRIGHT_75 = 3, - BRIGHT_100 = 4 - } - - public void Brightness(bright brightness) + public void TestPayload() // fucked { - VFD_Write("\x1B\x20" + (char)brightness); + VFD_Write(Encoding.ASCII.GetString(new byte[] { 0x1b, 0x0c, 0x1b, 0x52, 0x1b, 0x40, 0x00, 0x00, 0x00, 0x00, 0x9f, 0x02, 0x1b, 0x41, 0x00, 0x1b, 0x50, 0x50, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x1b, 0x51})); } - public void Power(bool on) + public enum Bright { + _0 = 0, + _25 = 1, + _50 = 2, + _75 = 3, + _100 = 4 + } + + public void Brightness(Bright brightness) { - VFD_Write("\x1B\x21" + (on ? "\x01" : "\x00")); + VFD_Write(new byte[] { 0x1B, 0x20, (byte)brightness }); + } + + public void PowerOn() + { + Power(true); + } + + public void PowerOff() + { + Power(false); + } + + private void Power(bool on) + { + VFD_Write(new byte[] { 0x1B, 0x21, (byte)(on ? 0x01 : 0x00) }); } public void CanvasShift(short left) { - VFD_Write("\x1B\x22"); + VFD_Write(new byte[] { 0x1B, 0x22 }); VFD_WriteShort(left); } public void Cursor(short left, byte top) { - VFD_Write("\x1B\x30"); + VFD_Write(new byte[] { 0x1B, 0x30 }); VFD_WriteShort(left); VFD_Write(top); } - public enum lang { + public enum Lang { SIMP_CHINESE, TRAD_CHINESE, JAPANESE, KOREAN } - public void Language(lang language) + public void Language(Lang lang) { - VFD_Write("\x1B\x32" + (char)language); + language = lang; + VFD_Write(new byte[] { 0x1B, 0x32, (byte)language }); } - public enum font_size + public enum Font { - FONT_16_16, - FONT_6_8 + _16_16, + _6_8 } - public void FontSize(font_size size) + public void FontSize(Font size) { - VFD_Write("\x1B\x33" + (char)size); + // 3 + font = size; + VFD_Write(new byte[] { 0x1B, 0x33, (byte)size }); } public void CreateScrollBox(short left, byte top, short width, byte height) { - VFD_Write("\x1B\x40"); + VFD_Write(new byte[] { 0x1B, 0x40 }); VFD_WriteShort(left); VFD_Write(top); VFD_WriteShort(width); @@ -113,20 +334,112 @@ namespace WACCALauncher public void ScrollSpeed(byte divisor) { - VFD_Write("\x1B\x33" + (char)divisor); + VFD_Write(new byte[] { 0x1B, 0x41, (byte)divisor }); } public void ScrollText(string text) { - if (text.Length > 255) throw new ArgumentOutOfRangeException("Text is too long."); - VFD_Write("\x1B\x50"); - VFD_Write((byte)text.Length); + if (text.Length >= 0x100) throw new ArgumentOutOfRangeException("Text is too long."); + VFD_Write(new byte[] { 0x1B, 0x50, (byte)text.Length }); VFD_Write(text); } public void ScrollStart() { - VFD_Write("\x1B\x51"); + VFD_Write(new byte[] { 0x1B, 0x51 }); + } + + public void ScrollStop() + { + VFD_Write(new byte[] { 0x1B, 0x52 }); + } + + public enum BlinkMode + { + Off = 0, + Invert = 1, + All = 2 + } + + public void BlinkSet(BlinkMode blink, byte interval) + { + VFD_Write(new byte[] { 0x1B, 0x23, (byte)blink, interval }); + } + + public void ClearLine(byte line) + { + Cursor(0, line); + VFD_Write("".PadLeft(20)); + Cursor(0, line); + } + + public void DrawBitmap(Bitmap bmp, Point origin) + { + if (bmp.PixelFormat != PixelFormat.Format1bppIndexed) + throw new ArgumentException("Provided bitmap is not monochrome"); + + // We have to do it this way because of a GDI+ bug + bmp.RotateFlip(RotateFlipType.Rotate270FlipNone); + RotateNoneFlipYMono(bmp); + + Rectangle bounds = new Rectangle(new Point(), bmp.Size); + + var data = bmp.LockBits(bounds, ImageLockMode.ReadOnly, PixelFormat.Format1bppIndexed); + + VFD_Write("\x1B\x2E"); + VFD_WriteShort((short)origin.X); + VFD_Write((byte)origin.Y); + VFD_WriteShort((short)bmp.Height); // Inverted because image was flipped + VFD_Write((byte)((bmp.Width / 8)-1)); + + int bytes = ( bmp.Width * bmp.Height ) / 8; + + // Create a byte array to hold the pixel data + byte[] pixelData = new byte[bytes]; + + // Copy the data from the pointer to the byte array + Marshal.Copy(data.Scan0, pixelData, 0, bytes); + + VFD_Write(pixelData); + + bmp.UnlockBits(data); + } + + private static void RotateNoneFlipYMono(Bitmap bmp) + { + if (bmp == null || bmp.PixelFormat != PixelFormat.Format1bppIndexed) + throw new ArgumentException("Provided bitmap is not monochrome"); + + var height = bmp.Height; + var width = bmp.Width; + // width in dwords + var stride = (width + 31) >> 5; + // total image size + var size = stride * height; + // alloc storage for pixels + var bytes = new int[size]; + + // get image pixels + var rect = new Rectangle(Point.Empty, bmp.Size); + var bd = bmp.LockBits(rect, ImageLockMode.WriteOnly, PixelFormat.Format1bppIndexed); + Marshal.Copy(bd.Scan0, bytes, 0, size); + + // flip by swapping dwords + int halfSize = size >> 1; + for (int y1 = 0, y2 = size - stride; y1 < halfSize; y1 += stride, y2 -= stride) + { + int end = y1 + stride; + for (int x1 = y1, x2 = y2; x1 < end; x1++, x2++) + { + bytes[x1] ^= bytes[x2]; + bytes[x2] ^= bytes[x1]; + bytes[x1] ^= bytes[x2]; + } + } + + // copy pixels back + Marshal.Copy(bytes, 0, bd.Scan0, size); + bmp.UnlockBits(bd); } } }