mirror of
https://gitea.tendokyu.moe/yellowberry/WACCALauncher.git
synced 2026-09-22 14:54:27 +03:00
Oops, All Rewritten!
This commit is contained in:
+4
-1
@@ -360,4 +360,7 @@ MigrationBackup/
|
||||
.ionide/
|
||||
|
||||
# Fody - auto-generated XML schema
|
||||
FodyWeavers.xsd
|
||||
FodyWeavers.xsd
|
||||
|
||||
# Rider
|
||||
.idea/
|
||||
-13
@@ -1,13 +0,0 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Rider ignored files
|
||||
/modules.xml
|
||||
/contentModel.xml
|
||||
/.idea.WACCALauncher.iml
|
||||
/projectSettingsUpdater.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
@@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="UserContentModel">
|
||||
<attachedFolders />
|
||||
<explicitIncludes />
|
||||
<explicitExcludes />
|
||||
</component>
|
||||
</project>
|
||||
-13
@@ -1,13 +0,0 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Rider ignored files
|
||||
/contentModel.xml
|
||||
/.idea.WACCALauncher.iml
|
||||
/modules.xml
|
||||
/projectSettingsUpdater.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
-8
@@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="UserContentModel">
|
||||
<attachedFolders />
|
||||
<explicitIncludes />
|
||||
<explicitExcludes />
|
||||
</component>
|
||||
</project>
|
||||
@@ -0,0 +1,32 @@
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Text;
|
||||
|
||||
namespace WACCALauncher
|
||||
{
|
||||
internal static class FontLoader
|
||||
{
|
||||
public static Font loadedFont;
|
||||
|
||||
[System.Runtime.InteropServices.DllImport("gdi32.dll")]
|
||||
private static extern IntPtr AddFontMemResourceEx(IntPtr pbFont, uint cbFont,
|
||||
IntPtr pdv, [System.Runtime.InteropServices.In] ref uint pcFonts);
|
||||
|
||||
private static readonly PrivateFontCollection fontCollection = new PrivateFontCollection();
|
||||
|
||||
public static Font LoadFont()
|
||||
{
|
||||
if (loadedFont != null) return loadedFont;
|
||||
var fontData = Properties.Resources.menufont;
|
||||
var fontPtr = System.Runtime.InteropServices.Marshal.AllocCoTaskMem(fontData.Length);
|
||||
System.Runtime.InteropServices.Marshal.Copy(fontData, 0, fontPtr, fontData.Length);
|
||||
uint dummy = 0;
|
||||
fontCollection.AddMemoryFont(fontPtr, Properties.Resources.menufont.Length);
|
||||
AddFontMemResourceEx(fontPtr, (uint)Properties.Resources.menufont.Length, IntPtr.Zero, ref dummy);
|
||||
System.Runtime.InteropServices.Marshal.FreeCoTaskMem(fontPtr);
|
||||
|
||||
loadedFont = new Font(fontCollection.Families[0], 22.5F);
|
||||
return loadedFont;
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 Yellowberry
|
||||
Copyright (c) 2023-2025 Yellowberry
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
using Newtonsoft.Json;
|
||||
using System.IO;
|
||||
|
||||
namespace WACCALauncher
|
||||
{
|
||||
// TODO: implement themes
|
||||
public enum LauncherTheme
|
||||
{
|
||||
Dark
|
||||
}
|
||||
|
||||
[JsonObject]
|
||||
public class LauncherSettings
|
||||
{
|
||||
[JsonProperty]
|
||||
public string DefaultProfile { get; set; } = string.Empty;
|
||||
|
||||
[JsonProperty]
|
||||
public bool UseWatchdog { get; set; } = true;
|
||||
|
||||
[JsonProperty]
|
||||
public LauncherTheme Theme { get; set; } = LauncherTheme.Dark;
|
||||
|
||||
[JsonProperty]
|
||||
public bool StrictMode { get; set; } = true;
|
||||
|
||||
[JsonProperty]
|
||||
public string ProfileDir { get; set; } = "_profiles";
|
||||
|
||||
public static LauncherSettings Load()
|
||||
{
|
||||
return JsonConvert.DeserializeObject<LauncherSettings>(File.ReadAllText("launcher.json"));
|
||||
}
|
||||
|
||||
public static void Save(LauncherSettings settings)
|
||||
{
|
||||
if (settings == null) settings = new LauncherSettings();
|
||||
|
||||
File.WriteAllText("launcher.json", JsonConvert.SerializeObject(settings, Formatting.Indented));
|
||||
}
|
||||
}
|
||||
}
|
||||
Generated
+10
-17
@@ -29,7 +29,7 @@
|
||||
private void InitializeComponent()
|
||||
{
|
||||
this.menuLabel = new System.Windows.Forms.Label();
|
||||
this.waccaListTest = new WACCALauncher.WaccaList();
|
||||
this.menuListBox = new WACCALauncher.WaccaList();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// menuLabel
|
||||
@@ -45,20 +45,13 @@
|
||||
this.menuLabel.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
|
||||
this.menuLabel.Visible = false;
|
||||
//
|
||||
// waccaListTest
|
||||
// menuListBox
|
||||
//
|
||||
this.waccaListTest.BackColor = System.Drawing.Color.Black;
|
||||
this.waccaListTest.BorderStyle = System.Windows.Forms.BorderStyle.None;
|
||||
this.waccaListTest.DrawMode = System.Windows.Forms.DrawMode.OwnerDrawVariable;
|
||||
this.waccaListTest.Enabled = false;
|
||||
this.waccaListTest.ForeColor = System.Drawing.Color.White;
|
||||
this.waccaListTest.FormattingEnabled = true;
|
||||
this.waccaListTest.ItemHeight = 40;
|
||||
this.waccaListTest.Location = new System.Drawing.Point(200, 240);
|
||||
this.waccaListTest.Name = "waccaListTest";
|
||||
this.waccaListTest.Size = new System.Drawing.Size(680, 600);
|
||||
this.waccaListTest.TabIndex = 1;
|
||||
this.waccaListTest.Visible = false;
|
||||
this.menuListBox.Enabled = false;
|
||||
this.menuListBox.FormattingEnabled = true;
|
||||
this.menuListBox.Name = "menuListBox";
|
||||
this.menuListBox.TabIndex = 1;
|
||||
this.menuListBox.Visible = false;
|
||||
//
|
||||
// MainForm
|
||||
//
|
||||
@@ -66,7 +59,7 @@
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.BackColor = System.Drawing.Color.Black;
|
||||
this.ClientSize = new System.Drawing.Size(1080, 1080);
|
||||
this.Controls.Add(this.waccaListTest);
|
||||
this.Controls.Add(this.menuListBox);
|
||||
this.Controls.Add(this.menuLabel);
|
||||
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
|
||||
this.KeyPreview = true;
|
||||
@@ -75,7 +68,7 @@
|
||||
this.StartPosition = System.Windows.Forms.FormStartPosition.Manual;
|
||||
this.Text = "WACCA Launcher";
|
||||
this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.MainForm_FormClosing);
|
||||
this.Load += new System.EventHandler(this.Form1_Load);
|
||||
this.Load += new System.EventHandler(this.MainForm_Load);
|
||||
this.ResumeLayout(false);
|
||||
|
||||
}
|
||||
@@ -83,7 +76,7 @@
|
||||
#endregion
|
||||
|
||||
private System.Windows.Forms.Label menuLabel;
|
||||
private WaccaList waccaListTest;
|
||||
private WaccaList menuListBox;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+521
-512
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,189 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace WACCALauncher
|
||||
{
|
||||
internal interface IMenuItem
|
||||
{
|
||||
string Name { get; }
|
||||
|
||||
void Select(MainForm form);
|
||||
}
|
||||
|
||||
internal class MenuSeparator : IMenuItem
|
||||
{
|
||||
public string Name => "";
|
||||
|
||||
// this should never be called
|
||||
public void Select(MainForm form)
|
||||
{
|
||||
return;
|
||||
}
|
||||
public override string ToString()
|
||||
{
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
internal class MenuAction : IMenuItem
|
||||
{
|
||||
public string Name { get; }
|
||||
private readonly Action _action;
|
||||
|
||||
public MenuAction(string name, Action action = null)
|
||||
{
|
||||
Name = name;
|
||||
_action = action;
|
||||
}
|
||||
|
||||
public void Select(MainForm form)
|
||||
{
|
||||
_action?.Invoke();
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Name;
|
||||
}
|
||||
}
|
||||
|
||||
internal class MenuReturn : IMenuItem
|
||||
{
|
||||
public string Name { get; }
|
||||
|
||||
public MenuReturn(string name = "Return")
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
|
||||
public void Select(MainForm form)
|
||||
{
|
||||
form._menuManager.MenuBack();
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Name;
|
||||
}
|
||||
}
|
||||
|
||||
internal class Menu : IMenuItem
|
||||
{
|
||||
public string Name { get; }
|
||||
public string Heading { get; }
|
||||
public List<IMenuItem> Items { get; } = new List<IMenuItem>();
|
||||
|
||||
public bool MainMenu { get; }
|
||||
|
||||
public Menu(string name, string heading = null, bool main = false)
|
||||
{
|
||||
Name = name;
|
||||
Heading = heading ?? name;
|
||||
MainMenu = main;
|
||||
}
|
||||
|
||||
public Menu(string name, List<IMenuItem> items, bool main = false)
|
||||
{
|
||||
Name = Heading = name;
|
||||
Items = items;
|
||||
MainMenu = main;
|
||||
}
|
||||
|
||||
public List<IMenuItem> GetItems()
|
||||
{
|
||||
if(MainMenu) return Items;
|
||||
|
||||
var list = new List<IMenuItem>();
|
||||
|
||||
list.AddRange(Items);
|
||||
list.Add(new MenuSeparator());
|
||||
list.Add(new MenuReturn());
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
public void Select(MainForm form)
|
||||
{
|
||||
form._menuManager.NavigateToSubmenu(this);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Name;
|
||||
}
|
||||
}
|
||||
|
||||
public enum ProfileMenuType
|
||||
{
|
||||
OneTimeLaunch,
|
||||
SetDefault,
|
||||
Editing
|
||||
}
|
||||
|
||||
internal class ProfileMenu : Menu
|
||||
{
|
||||
public ProfileMenuType MenuType { get; }
|
||||
|
||||
public ProfileMenu(string name, string heading = null, ProfileMenuType type = ProfileMenuType.OneTimeLaunch) : base(name, heading)
|
||||
{
|
||||
heading = heading ?? "Select Profile";
|
||||
MenuType = type;
|
||||
FillList();
|
||||
}
|
||||
|
||||
internal void FillList()
|
||||
{
|
||||
if(Items.Count > 0) Items.Clear();
|
||||
|
||||
foreach (var profile in MainForm.Profiles)
|
||||
{
|
||||
Items.Add(new MenuProfileItem(profile, MenuType));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class MenuProfileItem : IMenuItem
|
||||
{
|
||||
public string Name { get => ProfileData.Name; }
|
||||
|
||||
public ProfileMenuType MenuType { get; }
|
||||
|
||||
public Profile ProfileData { get; }
|
||||
|
||||
public MenuProfileItem(Profile profile, ProfileMenuType type)
|
||||
{
|
||||
ProfileData = profile;
|
||||
MenuType = type;
|
||||
}
|
||||
|
||||
public void Select(MainForm form)
|
||||
{
|
||||
// TODO: implement profile config toggle editing interface
|
||||
switch(MenuType)
|
||||
{
|
||||
case ProfileMenuType.OneTimeLaunch:
|
||||
form.SelectProfile(ProfileData);
|
||||
break;
|
||||
case ProfileMenuType.SetDefault:
|
||||
form.settings.DefaultProfile = ProfileData.Name;
|
||||
LauncherSettings.Save(form.settings);
|
||||
|
||||
MainForm.DefaultProfile = ProfileData;
|
||||
|
||||
form._menuManager.RefreshList();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
switch (MenuType)
|
||||
{
|
||||
case ProfileMenuType.SetDefault:
|
||||
return $"[{(MainForm.DefaultProfile == ProfileData ? 'X' : ' ')}] {Name}";
|
||||
default:
|
||||
return Name;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace WACCALauncher
|
||||
{
|
||||
internal class MenuManager
|
||||
{
|
||||
private Menu _currentMenu;
|
||||
private Stack<Tuple<Menu, int>> _menuLevels = new Stack<Tuple<Menu, int>>();
|
||||
private WaccaList _list;
|
||||
private MainForm _form;
|
||||
|
||||
public MenuManager(Menu root, WaccaList list, MainForm form)
|
||||
{
|
||||
_currentMenu = root;
|
||||
_list = list;
|
||||
_list.AssignMenuManager(this);
|
||||
_form = form;
|
||||
UpdateList();
|
||||
}
|
||||
|
||||
public void CursorUp()
|
||||
{
|
||||
// move cursor up
|
||||
var idx = ((_list.SelectedIndex - 1) + _list.Items.Count) % _list.Items.Count;
|
||||
_list.SelectedIndex = idx;
|
||||
if (_list.Items[idx] is MenuSeparator) CursorUp();
|
||||
_list.Invalidate();
|
||||
}
|
||||
|
||||
public void CursorDown()
|
||||
{
|
||||
// move cursor down
|
||||
var idx = (_list.SelectedIndex + 1) % _list.Items.Count;
|
||||
_list.SelectedIndex = idx;
|
||||
if (_list.Items[idx] is MenuSeparator) CursorDown();
|
||||
_list.Invalidate();
|
||||
}
|
||||
|
||||
public void MenuBack()
|
||||
{
|
||||
// back from current menu item
|
||||
if (_menuLevels.Count < 1)
|
||||
{
|
||||
_form._state = LauncherState.Launching;
|
||||
_form.MenuHide();
|
||||
if (MainForm.DefaultProfile == null) _form.SetDefaultProfile();
|
||||
}
|
||||
else NavigateBack();
|
||||
}
|
||||
|
||||
public void MenuSelect()
|
||||
{
|
||||
// select menu item
|
||||
(_list.SelectedItem as IMenuItem).Select(_form);
|
||||
}
|
||||
|
||||
public Menu GetCurrentMenu()
|
||||
{
|
||||
return _currentMenu;
|
||||
}
|
||||
|
||||
public void NavigateToSubmenu(Menu menu)
|
||||
{
|
||||
_menuLevels.Push(new Tuple<Menu, int>(_currentMenu, _list.SelectedIndex));
|
||||
_currentMenu = menu;
|
||||
if (menu is ProfileMenu profileMenu) profileMenu.FillList();
|
||||
UpdateList();
|
||||
}
|
||||
|
||||
public void NavigateBack()
|
||||
{
|
||||
var back = _menuLevels.Pop();
|
||||
_currentMenu = back.Item1;
|
||||
UpdateList(back.Item2);
|
||||
}
|
||||
|
||||
public void ReturnToRoot()
|
||||
{
|
||||
while(_menuLevels.Count > 0) NavigateBack();
|
||||
}
|
||||
|
||||
private void UpdateList(int idx = 0)
|
||||
{
|
||||
_list.Items.Clear();
|
||||
_list.Items.AddRange(_currentMenu.GetItems().ToArray());
|
||||
_form.MenuUpdateLabel(_currentMenu.Heading);
|
||||
if (_list.Items.Count > 0) _list.SelectedIndex = idx;
|
||||
}
|
||||
|
||||
internal void RefreshList()
|
||||
{
|
||||
_list.Invalidate();
|
||||
}
|
||||
}
|
||||
}
|
||||
+130
@@ -0,0 +1,130 @@
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace WACCALauncher
|
||||
{
|
||||
public enum ProfileType
|
||||
{
|
||||
Generic,
|
||||
WACCA
|
||||
}
|
||||
|
||||
public class ProfileLoadException : Exception
|
||||
{
|
||||
public ProfileLoadException(string message) : base(message) { }
|
||||
}
|
||||
|
||||
[JsonObject]
|
||||
public class Profile
|
||||
{
|
||||
[JsonIgnore]
|
||||
public string ConfigPath;
|
||||
|
||||
[JsonProperty, JsonRequired]
|
||||
public string Name { get; set; }
|
||||
|
||||
[JsonProperty, JsonRequired]
|
||||
public ProfileType Type { get; set; }
|
||||
|
||||
[JsonProperty, JsonRequired]
|
||||
public string BasePath;
|
||||
|
||||
[JsonProperty, JsonRequired]
|
||||
public string GamePath;
|
||||
|
||||
[JsonProperty]
|
||||
public string UpdaterPath;
|
||||
|
||||
[JsonProperty]
|
||||
public string UpdaterArgs;
|
||||
|
||||
public DirectoryInfo GetBaseDir()
|
||||
{
|
||||
return new DirectoryInfo(BasePath);
|
||||
}
|
||||
|
||||
public string GetGamePath()
|
||||
{
|
||||
return Path.Combine(BasePath, GamePath);
|
||||
}
|
||||
|
||||
// for WACCA type
|
||||
|
||||
[JsonProperty]
|
||||
public List<string> Configs { get; set; } = new List<string>();
|
||||
|
||||
[JsonProperty]
|
||||
public bool InjectGame = true;
|
||||
|
||||
[JsonProperty]
|
||||
public string InjectDLL = "mercuryhook.dll";
|
||||
|
||||
public string GetAmdaemonArgs()
|
||||
{
|
||||
return string.Format("-f -c {0}", string.Join(" ", Configs));
|
||||
}
|
||||
|
||||
// static methods
|
||||
|
||||
public static Profile LoadFromJson(string path)
|
||||
{
|
||||
var loaded = JsonConvert.DeserializeObject<Profile>(File.ReadAllText(path));
|
||||
loaded.ConfigPath = path;
|
||||
|
||||
if (!Directory.Exists(loaded.BasePath))
|
||||
{
|
||||
throw new ProfileLoadException("Base dir not found");
|
||||
}
|
||||
else if (!File.Exists(loaded.GetGamePath()))
|
||||
{
|
||||
throw new ProfileLoadException("Game file not found");
|
||||
}
|
||||
|
||||
if (loaded.Type == ProfileType.WACCA)
|
||||
{
|
||||
var binPath = Path.Combine(loaded.BasePath, "bin");
|
||||
if (!CheckAmdaemonConfigs(binPath, loaded.Configs))
|
||||
{
|
||||
throw new ProfileLoadException("configs missing, check config and files");
|
||||
}
|
||||
else if (!CheckForInjector(binPath, loaded.InjectDLL))
|
||||
{
|
||||
throw new ProfileLoadException("Segatools missing, check config and files");
|
||||
}
|
||||
}
|
||||
|
||||
return loaded;
|
||||
}
|
||||
|
||||
public void SaveToJson()
|
||||
{
|
||||
File.WriteAllText(ConfigPath, JsonConvert.SerializeObject(this, Formatting.Indented));
|
||||
}
|
||||
|
||||
private static bool CheckForInjector(string path, string dll)
|
||||
{
|
||||
return File.Exists(Path.Combine(path, dll)) &&
|
||||
File.Exists(Path.Combine(path, "segatools.ini")) &&
|
||||
File.Exists(Path.Combine(path, "inject.exe"));
|
||||
}
|
||||
|
||||
public static bool CheckAmdaemonConfigs(string path, List<string> configs)
|
||||
{
|
||||
int missingFiles = 0;
|
||||
|
||||
foreach (var cfg in configs)
|
||||
{
|
||||
if (!File.Exists(Path.Combine(path, cfg))) missingFiles++;
|
||||
}
|
||||
|
||||
return missingFiles == 0;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Name;
|
||||
}
|
||||
}
|
||||
}
|
||||
+26
-14
@@ -1,10 +1,6 @@
|
||||
using Microsoft.Win32;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Windows.Forms;
|
||||
|
||||
@@ -18,21 +14,29 @@ namespace WACCALauncher
|
||||
|
||||
public static Screen CurrentScreen = Screen.PrimaryScreen;
|
||||
|
||||
public static bool IsCorrectRes()
|
||||
public static bool IsCorrectRes(Screen selected = null)
|
||||
{
|
||||
return CurrentScreen.Bounds.Width == 1080
|
||||
&& CurrentScreen.Bounds.Height == 1920;
|
||||
var screen = selected ?? CurrentScreen;
|
||||
|
||||
// 1080p vertical resolution
|
||||
var correctPixelRes = screen.Bounds.Width == 1080
|
||||
&& screen.Bounds.Height == 1920;
|
||||
|
||||
// 9:16 aspect ratio check
|
||||
var correctAspect = (screen.Bounds.Width / screen.Bounds.Height) == 0.5625;
|
||||
|
||||
return correctPixelRes || correctAspect;
|
||||
}
|
||||
|
||||
public static bool IsCorrectVer()
|
||||
public static bool IsRecommendedWinVer()
|
||||
{
|
||||
// ensures Enterprise 2016 LTSB is used
|
||||
// Enterprise 2016 LTSB is recommended, for various reasons
|
||||
return BuildNumber == 14393 && ReleaseId == 1607;
|
||||
}
|
||||
|
||||
public static bool IsCorrectEnv()
|
||||
public static bool IsRecommendedEnv()
|
||||
{
|
||||
return IsCorrectRes() && IsCorrectVer();
|
||||
return IsCorrectRes() && IsRecommendedWinVer();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -41,9 +45,17 @@ namespace WACCALauncher
|
||||
[STAThread]
|
||||
private static void Main()
|
||||
{
|
||||
// for testing
|
||||
if(Screen.AllScreens.Length > 1)
|
||||
CurrentScreen = Screen.AllScreens[1];
|
||||
// if we have multiple screens, try to start on one with proper resolution, if any
|
||||
if (Screen.AllScreens.Length > 1)
|
||||
{
|
||||
foreach (Screen screen in Screen.AllScreens)
|
||||
{
|
||||
if (IsCorrectRes(screen))
|
||||
{
|
||||
CurrentScreen = screen;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// when running as system shell, we must set our working directory manually
|
||||
var whereAmI = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
@@ -8,9 +7,9 @@ using System.Runtime.InteropServices;
|
||||
[assembly: AssemblyTitle("WACCALauncher")]
|
||||
[assembly: AssemblyDescription("me when i launch my wacca")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("YellowberryHN")]
|
||||
[assembly: AssemblyCompany("Yellowberry")]
|
||||
[assembly: AssemblyProduct("WACCALauncher")]
|
||||
[assembly: AssemblyCopyright("Copyright © YellowberryHN 2024")]
|
||||
[assembly: AssemblyCopyright("Copyright © Yellowberry 2025")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
@@ -32,5 +31,5 @@ using System.Runtime.InteropServices;
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("0.11.0.0")]
|
||||
[assembly: AssemblyFileVersion("0.11.0.0")]
|
||||
[assembly: AssemblyVersion("0.12.0.0")]
|
||||
[assembly: AssemblyFileVersion("0.12.0.0")]
|
||||
|
||||
Generated
+20
@@ -60,6 +60,26 @@ namespace WACCALauncher.Properties {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized resource of type System.Drawing.Icon similar to (Icon).
|
||||
/// </summary>
|
||||
internal static System.Drawing.Icon Dark {
|
||||
get {
|
||||
object obj = ResourceManager.GetObject("Dark", resourceCulture);
|
||||
return ((System.Drawing.Icon)(obj));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized resource of type System.Drawing.Icon similar to (Icon).
|
||||
/// </summary>
|
||||
internal static System.Drawing.Icon Light {
|
||||
get {
|
||||
object obj = ResourceManager.GetObject("Light", resourceCulture);
|
||||
return ((System.Drawing.Icon)(obj));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized resource of type System.Byte[].
|
||||
/// </summary>
|
||||
|
||||
@@ -118,6 +118,12 @@
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
|
||||
<data name="Dark" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
||||
<value>..\Resources\Dark.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
|
||||
</data>
|
||||
<data name="Light" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
||||
<value>..\Resources\Light.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
|
||||
</data>
|
||||
<data name="menufont" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
||||
<value>..\Resources\font.ttf;System.Byte[], mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</data>
|
||||
|
||||
@@ -1,15 +1,22 @@
|
||||
# WACCA Launcher
|
||||
# WACCALauncher
|
||||
|
||||
Allows you to run multiple versions of WACCA from the same drive.
|
||||
Also allows you to run arbitrary programs as well.
|
||||
|
||||
## Configuration
|
||||
## Recommended Folder Structure
|
||||
|
||||
You'll want a directory structure something like this:
|
||||
|
||||
```
|
||||
WACCA/
|
||||
├── _profiles/
|
||||
│ ├── WACCA.json
|
||||
│ ├── Reverse.json
|
||||
│ ├── OSD.json
|
||||
│ └── ...
|
||||
├── WACCALauncher.exe
|
||||
├── wacca.ini
|
||||
├── launcher.json
|
||||
├── ...
|
||||
└── Versions/
|
||||
├── WACCA/
|
||||
│ ├── bin/
|
||||
@@ -22,51 +29,116 @@ WACCA/
|
||||
└── Omega Supermix Deluxe
|
||||
```
|
||||
|
||||
Each version must have segatools already configured, with a `start.bat` file present.
|
||||
## Profiles
|
||||
|
||||
You'll want to create a `wacca.ini` file next to the launcher that looks something like this:
|
||||
A profile is an entry in the list of profiles displayed in the launcher.
|
||||
It lets you define launch options for whatever you're launching.
|
||||
|
||||
```
|
||||
[general]
|
||||
default_ver = reverse
|
||||
Profiles are .json files, you can have as many profiles in your profiles folder as you want.
|
||||
One limitation to be aware of is if you have a profile that shares a name with another one,
|
||||
weird things happen. But I'm not sure why you would want that anyway.
|
||||
|
||||
[versions]
|
||||
wacca = C:\WACCA\Versions\WACCA
|
||||
wacca_s = C:\WACCA\Versions\WACCA S
|
||||
lily = C:\WACCA\Versions\Lily
|
||||
lily_r = C:\WACCA\Versions\Lily R
|
||||
reverse = C:\WACCA\Versions\Reverse
|
||||
offline = C:\WACCA\Versions\Offline
|
||||
Profiles are validated when the launcher starts, and will throw an error if the configuration is invalid,
|
||||
or if the configured files or folders do not exist.
|
||||
|
||||
The filename of a profile is only used for sorting, so you can sort your profiles however you wish.
|
||||
|
||||
There are currently only 2 valid types of profiles:
|
||||
- `WACCA`, meant for launching WACCA versions
|
||||
- `Generic`, meant for launching arbitrary programs
|
||||
|
||||
### WACCA Profiles
|
||||
|
||||
A WACCA profile contains information about where key game files are, and how to launch the game.
|
||||
|
||||
Each WACCA profile must have segatools already configured in the `bin` folder.
|
||||
|
||||
Here's an example of what a WACCA Reverse profile would look like:
|
||||
|
||||
```json
|
||||
{
|
||||
"Name": "Reverse",
|
||||
"Type": "WACCA",
|
||||
"BasePath": "C:\\WACCA\\Versions\\Reverse",
|
||||
"GamePath": "WindowsNoEditor\\Mercury\\Binaries\\Win64\\Mercury-Win64-Shipping.exe",
|
||||
"Configs": [
|
||||
"config.json",
|
||||
"config_lan_install_server.json",
|
||||
"config_region_jpn.json"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Versions can be omitted, but all paths provided must be valid.
|
||||
### Generic Profiles
|
||||
|
||||
If you have a custom version of the game you wish to launch, you may do so by adding
|
||||
`num_customs = 1` under `[general]`, and then adding a block at the end of the file
|
||||
that looks something like this:
|
||||
A generic profile can be used to launch pretty much any program.
|
||||
|
||||
```
|
||||
[custom_1]
|
||||
name = WACCA Omega Supermix Deluxe
|
||||
path = C:\WACCA\Versions\Omega Supermix Deluxe
|
||||
type = reverse
|
||||
Here's an example of a profile you can use to launch Portal 2, for some reason:
|
||||
|
||||
```json
|
||||
{
|
||||
"Name": "Portal 2 (why?)",
|
||||
"Type": "Generic",
|
||||
"BasePath": "C:\\Program Files (x86)\\Steam\\steamapps\\common\\Portal 2",
|
||||
"GamePath": "portal2.exe"
|
||||
}
|
||||
```
|
||||
|
||||
`type` must match one of the versions listed above, it will likely be `reverse` unless specified.
|
||||
### Updaters
|
||||
|
||||
Profiles have optional support for an update executable/script to be run before running the game itself.
|
||||
|
||||
Updaters can be added to any type of profile, and have a few specific behaviors to look out for. Updaters
|
||||
are run from the directory they live in, not from the `BasePath` directory, for consistency reasons.
|
||||
|
||||
To add an updater to a profile, simply provide the full path to the updater in the `UpdaterPath` key.
|
||||
If you need to pass arguments to the updater, you may use the `UpdaterArgs` key, but this may be omitted
|
||||
if not needed.
|
||||
|
||||
```json
|
||||
{
|
||||
"Name": "Omega Supermix Deluxe",
|
||||
"Type": "WACCA",
|
||||
"BasePath": "C:\\WACCA\\Versions\\Omega Supermix Deluxe",
|
||||
"GamePath": "WindowsNoEditor\\Mercury\\Binaries\\Win64\\Mercury-Win64-Shipping.exe",
|
||||
"Configs": [
|
||||
"config.json",
|
||||
"config_lan_install_server.json",
|
||||
"config_region_jpn.json"
|
||||
],
|
||||
"UpdaterPath": "C:\\WACCA\\Versions\\Omega Supermix Deluxe\\bin\\updater\\wupdate.exe",
|
||||
"UpdaterArgs": "-t OSD"
|
||||
}
|
||||
```
|
||||
|
||||
You may skip the updater for the default profile by holding the Volume Up button while launching.
|
||||
The `...` will turn into `!!!` while you are holding it.
|
||||
|
||||
## Usage
|
||||
|
||||
When you start the launcher, you will be presented with a loading prompt.
|
||||
You may press `TEST` or <kbd>Esc</kbd> to open the configuration menu. This menu allows you
|
||||
to change the default startup version, as well as launch a version manually.
|
||||
This menu is controlled in the same way you would navigate the in-game test menu.
|
||||
You may press the `TEST` button or <kbd>Esc</kbd> to open the launcher menu.
|
||||
This menu allows you to change the default startup profile, as well as launch a profile manually.
|
||||
It is controlled in the same way you would navigate the in-game test menu.
|
||||
|
||||
If you do not interfere with the loading prompt, the selected default version
|
||||
You can also perform various tasks helpful for cab management, like open File Explorer, reload profiles,
|
||||
or reboot your cab without a hard power cycle.
|
||||
|
||||
If you do not interfere with the loading prompt, the selected default profile
|
||||
will be launched within 5 seconds.
|
||||
|
||||
## Watchdog
|
||||
|
||||
By default, the process watchdog is enabled. WACCALauncher runs in the background while you are playing,
|
||||
and if it detects the game closed in some way, it will clean up and attempt to open the game again,
|
||||
with the same 5-second grace period as the initial launch. You may interrupt this by going into the
|
||||
launcher menu, as before.
|
||||
|
||||
If this behavior is undesired, it can be disabled by setting `UseWatchdog` to false in `launcher.json`.
|
||||
|
||||
## Auto-launch
|
||||
|
||||
You can configure your cab to start the launcher instead of explorer.exe when you log in.
|
||||
You can configure your cab to start the launcher instead of the Windows Desktop when you log in.
|
||||
**This should only be done on a cab. You have been warned.**
|
||||
|
||||
If you've installed the launcher in the suggested location of `C:\WACCA`,
|
||||
@@ -78,7 +150,9 @@ softlocked on a black screen at next boot!**
|
||||
|
||||
## Notice
|
||||
|
||||
This code was never intended to be public, as such, it is a bit of a dumpster fire.
|
||||
This code was originally never intended to be public, as such, it is a bit of a dumpster fire.
|
||||
|
||||
As of version 0.12, I've cleaned it up a bit, but there's still a lot of work to be done.
|
||||
If you want to help make the code a little less awful, issues and pull requests are welcome.
|
||||
|
||||
## License
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 314 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 314 KiB |
+12
-3
@@ -37,9 +37,12 @@
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<ApplicationIcon>Resources\Dark.ico</ApplicationIcon>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="INIFileParser, Version=2.5.2.0, Culture=neutral, PublicKeyToken=79af7b307b65cf3c, processorArchitecture=MSIL">
|
||||
<HintPath>packages\ini-parser.2.5.2\lib\net20\INIFileParser.dll</HintPath>
|
||||
<Reference Include="Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||
<HintPath>packages\Newtonsoft.Json.13.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="SharpDX, Version=4.2.0.0, Culture=neutral, PublicKeyToken=b4dcf0f35e5521f1, processorArchitecture=MSIL">
|
||||
<HintPath>packages\SharpDX.4.2.0\lib\net45\SharpDX.dll</HintPath>
|
||||
@@ -58,18 +61,22 @@
|
||||
<Reference Include="System.Xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="FontLoader.cs" />
|
||||
<Compile Include="LauncherSettings.cs" />
|
||||
<Compile Include="Menu.cs" />
|
||||
<Compile Include="MainForm.cs">
|
||||
<SubType>Form</SubType>
|
||||
</Compile>
|
||||
<Compile Include="MainForm.Designer.cs">
|
||||
<DependentUpon>MainForm.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="MenuManager.cs" />
|
||||
<Compile Include="Program.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Profile.cs" />
|
||||
<Compile Include="WaccaList.cs">
|
||||
<SubType>Component</SubType>
|
||||
</Compile>
|
||||
<Compile Include="WaccaVFD.cs" />
|
||||
<EmbeddedResource Include="MainForm.resx">
|
||||
<DependentUpon>MainForm.cs</DependentUpon>
|
||||
</EmbeddedResource>
|
||||
@@ -104,6 +111,8 @@
|
||||
<Content Include="LICENSE.txt">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<None Include="Resources\Light.ico" />
|
||||
<None Include="Resources\Dark.ico" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
|
||||
|
||||
Binary file not shown.
+133
-12
@@ -1,11 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Data;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace WACCALauncher
|
||||
@@ -14,31 +8,158 @@ namespace WACCALauncher
|
||||
{
|
||||
private MenuManager menuManager;
|
||||
|
||||
private Brush bgBrush;
|
||||
private Brush textBrush;
|
||||
private Brush highlightBrush;
|
||||
|
||||
private PointF[] upArrow { get; }
|
||||
private PointF[] downArrow { get; }
|
||||
|
||||
public WaccaList()
|
||||
{
|
||||
SetStyle(
|
||||
ControlStyles.OptimizedDoubleBuffer |
|
||||
ControlStyles.ResizeRedraw |
|
||||
ControlStyles.UserPaint,
|
||||
true);
|
||||
DrawMode = DrawMode.OwnerDrawFixed;
|
||||
|
||||
BackColor = Color.Black;
|
||||
ForeColor = Color.White;
|
||||
BorderStyle = BorderStyle.None;
|
||||
DrawMode = DrawMode.OwnerDrawVariable;
|
||||
ItemHeight = 40;
|
||||
Size = new Size(680, 600);
|
||||
Location = new Point(200, 240);
|
||||
|
||||
upArrow = new PointF[] {
|
||||
new PointF(Bounds.Width - 30, 30),
|
||||
new PointF(Bounds.Width - 20, 10),
|
||||
new PointF(Bounds.Width - 10, 30)
|
||||
};
|
||||
|
||||
downArrow = new PointF[] {
|
||||
new PointF(Bounds.Width - 30, Bounds.Height - 30),
|
||||
new PointF(Bounds.Width - 20, Bounds.Height - 10),
|
||||
new PointF(Bounds.Width - 10, Bounds.Height - 30)
|
||||
};
|
||||
|
||||
bgBrush = new SolidBrush(BackColor);
|
||||
textBrush = new SolidBrush(ForeColor);
|
||||
highlightBrush = Brushes.Red;
|
||||
}
|
||||
|
||||
public void AssignMenuManager(MenuManager manager)
|
||||
// hide scrollbar, we draw our own
|
||||
protected override CreateParams CreateParams
|
||||
{
|
||||
get
|
||||
{
|
||||
CreateParams cp = base.CreateParams;
|
||||
cp.Style = cp.Style & ~0x200000;
|
||||
return cp;
|
||||
}
|
||||
}
|
||||
|
||||
[System.Runtime.InteropServices.DllImport("user32.dll")]
|
||||
private static extern int SendMessage(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);
|
||||
|
||||
private const int LB_GETTOPINDEX = 0x018E;
|
||||
|
||||
private int GetTopIndex()
|
||||
{
|
||||
return SendMessage(Handle, LB_GETTOPINDEX, IntPtr.Zero, IntPtr.Zero);
|
||||
}
|
||||
|
||||
private int GetBottomIndex()
|
||||
{
|
||||
int topIdx = GetTopIndex();
|
||||
|
||||
int itemsVisible = ClientSize.Height / 40;
|
||||
int bottomIndex = topIdx + itemsVisible - 1;
|
||||
|
||||
return Math.Min(bottomIndex, Items.Count - 1);
|
||||
}
|
||||
|
||||
private bool AtTop
|
||||
{
|
||||
get => GetTopIndex() == 0;
|
||||
}
|
||||
|
||||
private bool AtBottom
|
||||
{
|
||||
get => GetBottomIndex() == Items.Count - 1;
|
||||
}
|
||||
|
||||
private bool Scrollable
|
||||
{
|
||||
get => !(AtTop && AtBottom);
|
||||
}
|
||||
|
||||
internal void AssignMenuManager(MenuManager manager)
|
||||
{
|
||||
menuManager = manager;
|
||||
}
|
||||
|
||||
protected override void OnDrawItem(DrawItemEventArgs e)
|
||||
{
|
||||
e.Graphics.FillRectangle(new SolidBrush(BackColor), e.Bounds);
|
||||
|
||||
if (Items.Count < 1 || e.Index < 0) return;
|
||||
|
||||
var text = Items[e.Index].ToString().ToUpper();
|
||||
var item = Items[e.Index];
|
||||
var bounds = e.Bounds;
|
||||
|
||||
e.Graphics.FillRectangle(bgBrush, bounds);
|
||||
|
||||
var text = item.ToString().ToUpper();
|
||||
var selected = (e.State & DrawItemState.Selected) == DrawItemState.Selected;
|
||||
|
||||
e.Graphics.DrawString(text, Font, selected ? Brushes.Red : Brushes.White, e.Bounds);
|
||||
if (Scrollable)
|
||||
{
|
||||
bounds = new Rectangle(bounds.X, bounds.Y, bounds.Width - 40, bounds.Height);
|
||||
}
|
||||
|
||||
e.Graphics.DrawString(text, Font, selected ? highlightBrush : textBrush, bounds, new StringFormat() { Trimming = StringTrimming.EllipsisCharacter, FormatFlags = StringFormatFlags.NoWrap });
|
||||
|
||||
if (!AtTop && GetTopIndex() == e.Index)
|
||||
{
|
||||
e.Graphics.FillPolygon(textBrush, upArrow);
|
||||
}
|
||||
if (!AtBottom && GetBottomIndex() == e.Index)
|
||||
{
|
||||
e.Graphics.FillPolygon(textBrush, downArrow);
|
||||
}
|
||||
|
||||
base.OnDrawItem(e);
|
||||
}
|
||||
|
||||
protected override void OnPaint(PaintEventArgs e)
|
||||
{
|
||||
Region iRegion = new Region(e.ClipRectangle);
|
||||
e.Graphics.FillRegion(bgBrush, iRegion);
|
||||
if (Items.Count > 0)
|
||||
{
|
||||
for (int i = 0; i < Items.Count; ++i)
|
||||
{
|
||||
Rectangle irect = GetItemRectangle(i);
|
||||
if (e.ClipRectangle.IntersectsWith(irect))
|
||||
{
|
||||
OnDrawItem(new DrawItemEventArgs(
|
||||
e.Graphics,
|
||||
Font,
|
||||
irect, i,
|
||||
SelectedIndex == i ? DrawItemState.Selected : DrawItemState.Default,
|
||||
ForeColor,
|
||||
BackColor
|
||||
));
|
||||
iRegion.Complement(irect);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
base.OnPaint(e);
|
||||
}
|
||||
|
||||
protected override void OnSelectedValueChanged(EventArgs e)
|
||||
{
|
||||
base.OnSelectedValueChanged(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
-445
@@ -1,445 +0,0 @@
|
||||
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 WACCA
|
||||
{
|
||||
//
|
||||
// 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;
|
||||
|
||||
/// <summary>
|
||||
/// Establish a connection to a VFD, and prepare it for use.
|
||||
/// </summary>
|
||||
/// <param name="portName">The port that the VFD connected to</param>
|
||||
public VFD(string portName = "COM2")
|
||||
{
|
||||
port = new SerialPort(portName, 115200);
|
||||
port.Open();
|
||||
Reset();
|
||||
}
|
||||
|
||||
private void VFD_Write(byte 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)
|
||||
{
|
||||
// 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)
|
||||
{
|
||||
byte hi = (byte)(((x) & 0x100) >> 8);
|
||||
byte lo = (byte)((x) & 0xFF);
|
||||
VFD_Write(new byte[] {hi, lo});
|
||||
}
|
||||
|
||||
public void Write(string text)
|
||||
{
|
||||
VFD_Write(text);
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
VFD_Write(new byte[] { 0x1B, 0x0B });
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
VFD_Write(new byte[] { 0x1B, 0x0C });
|
||||
}
|
||||
|
||||
public void TestPayload() // fucked
|
||||
{
|
||||
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 enum Bright {
|
||||
_0 = 0,
|
||||
_25 = 1,
|
||||
_50 = 2,
|
||||
_75 = 3,
|
||||
_100 = 4
|
||||
}
|
||||
|
||||
public void Brightness(Bright brightness)
|
||||
{
|
||||
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(new byte[] { 0x1B, 0x22 });
|
||||
VFD_WriteShort(left);
|
||||
}
|
||||
|
||||
public void Cursor(short left, byte top)
|
||||
{
|
||||
VFD_Write(new byte[] { 0x1B, 0x30 });
|
||||
VFD_WriteShort(left);
|
||||
VFD_Write(top);
|
||||
}
|
||||
|
||||
public enum Lang {
|
||||
SIMP_CHINESE,
|
||||
TRAD_CHINESE,
|
||||
JAPANESE,
|
||||
KOREAN
|
||||
}
|
||||
|
||||
public void Language(Lang lang)
|
||||
{
|
||||
language = lang;
|
||||
VFD_Write(new byte[] { 0x1B, 0x32, (byte)language });
|
||||
}
|
||||
|
||||
public enum Font
|
||||
{
|
||||
_16_16,
|
||||
_6_8
|
||||
}
|
||||
|
||||
public void FontSize(Font 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(new byte[] { 0x1B, 0x40 });
|
||||
VFD_WriteShort(left);
|
||||
VFD_Write(top);
|
||||
VFD_WriteShort(width);
|
||||
VFD_Write(height);
|
||||
}
|
||||
|
||||
public void ScrollSpeed(byte divisor)
|
||||
{
|
||||
VFD_Write(new byte[] { 0x1B, 0x41, (byte)divisor });
|
||||
}
|
||||
|
||||
public void ScrollText(string text)
|
||||
{
|
||||
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(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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+4
-1
@@ -1,3 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2"/></startup></configuration>
|
||||
<startup>
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2"/>
|
||||
</startup>
|
||||
</configuration>
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="ini-parser" version="2.5.2" targetFramework="net452" />
|
||||
<package id="Newtonsoft.Json" version="13.0.3" targetFramework="net452" />
|
||||
<package id="SharpDX" version="4.2.0" targetFramework="net452" />
|
||||
<package id="SharpDX.DirectInput" version="4.2.0" targetFramework="net452" />
|
||||
</packages>
|
||||
Reference in New Issue
Block a user