Oops, All Rewritten!

This commit is contained in:
Zsolt Zitting
2025-03-29 01:09:26 -07:00
parent 9c91fa3160
commit debd5c06e5
26 changed files with 1336 additions and 1085 deletions
+3
View File
@@ -361,3 +361,6 @@ MigrationBackup/
# Fody - auto-generated XML schema # Fody - auto-generated XML schema
FodyWeavers.xsd FodyWeavers.xsd
# Rider
.idea/
-13
View File
@@ -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
-8
View File
@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="UserContentModel">
<attachedFolders />
<explicitIncludes />
<explicitExcludes />
</component>
</project>
-13
View File
@@ -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
View File
@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="UserContentModel">
<attachedFolders />
<explicitIncludes />
<explicitExcludes />
</component>
</project>
+32
View File
@@ -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
View File
@@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) 2023 Yellowberry Copyright (c) 2023-2025 Yellowberry
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
+42
View File
@@ -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));
}
}
}
+10 -17
View File
@@ -29,7 +29,7 @@
private void InitializeComponent() private void InitializeComponent()
{ {
this.menuLabel = new System.Windows.Forms.Label(); this.menuLabel = new System.Windows.Forms.Label();
this.waccaListTest = new WACCALauncher.WaccaList(); this.menuListBox = new WACCALauncher.WaccaList();
this.SuspendLayout(); this.SuspendLayout();
// //
// menuLabel // menuLabel
@@ -45,20 +45,13 @@
this.menuLabel.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; this.menuLabel.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
this.menuLabel.Visible = false; this.menuLabel.Visible = false;
// //
// waccaListTest // menuListBox
// //
this.waccaListTest.BackColor = System.Drawing.Color.Black; this.menuListBox.Enabled = false;
this.waccaListTest.BorderStyle = System.Windows.Forms.BorderStyle.None; this.menuListBox.FormattingEnabled = true;
this.waccaListTest.DrawMode = System.Windows.Forms.DrawMode.OwnerDrawVariable; this.menuListBox.Name = "menuListBox";
this.waccaListTest.Enabled = false; this.menuListBox.TabIndex = 1;
this.waccaListTest.ForeColor = System.Drawing.Color.White; this.menuListBox.Visible = false;
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;
// //
// MainForm // MainForm
// //
@@ -66,7 +59,7 @@
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.BackColor = System.Drawing.Color.Black; this.BackColor = System.Drawing.Color.Black;
this.ClientSize = new System.Drawing.Size(1080, 1080); this.ClientSize = new System.Drawing.Size(1080, 1080);
this.Controls.Add(this.waccaListTest); this.Controls.Add(this.menuListBox);
this.Controls.Add(this.menuLabel); this.Controls.Add(this.menuLabel);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None; this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
this.KeyPreview = true; this.KeyPreview = true;
@@ -75,7 +68,7 @@
this.StartPosition = System.Windows.Forms.FormStartPosition.Manual; this.StartPosition = System.Windows.Forms.FormStartPosition.Manual;
this.Text = "WACCA Launcher"; this.Text = "WACCA Launcher";
this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.MainForm_FormClosing); 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); this.ResumeLayout(false);
} }
@@ -83,7 +76,7 @@
#endregion #endregion
private System.Windows.Forms.Label menuLabel; private System.Windows.Forms.Label menuLabel;
private WaccaList waccaListTest; private WaccaList menuListBox;
} }
} }
+508 -499
View File
File diff suppressed because it is too large Load Diff
+189
View File
@@ -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;
}
}
}
}
+96
View File
@@ -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
View File
@@ -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
View File
@@ -1,10 +1,6 @@
using Microsoft.Win32; using Microsoft.Win32;
using System; using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.IO; using System.IO;
using System.Linq;
using System.Reflection; using System.Reflection;
using System.Windows.Forms; using System.Windows.Forms;
@@ -18,21 +14,29 @@ namespace WACCALauncher
public static Screen CurrentScreen = Screen.PrimaryScreen; public static Screen CurrentScreen = Screen.PrimaryScreen;
public static bool IsCorrectRes() public static bool IsCorrectRes(Screen selected = null)
{ {
return CurrentScreen.Bounds.Width == 1080 var screen = selected ?? CurrentScreen;
&& CurrentScreen.Bounds.Height == 1920;
// 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; return BuildNumber == 14393 && ReleaseId == 1607;
} }
public static bool IsCorrectEnv() public static bool IsRecommendedEnv()
{ {
return IsCorrectRes() && IsCorrectVer(); return IsCorrectRes() && IsRecommendedWinVer();
} }
/// <summary> /// <summary>
@@ -41,9 +45,17 @@ namespace WACCALauncher
[STAThread] [STAThread]
private static void Main() private static void Main()
{ {
// for testing // if we have multiple screens, try to start on one with proper resolution, if any
if(Screen.AllScreens.Length > 1) if (Screen.AllScreens.Length > 1)
CurrentScreen = Screen.AllScreens[1]; {
foreach (Screen screen in Screen.AllScreens)
{
if (IsCorrectRes(screen))
{
CurrentScreen = screen;
}
}
}
// when running as system shell, we must set our working directory manually // when running as system shell, we must set our working directory manually
var whereAmI = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); var whereAmI = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
+4 -5
View File
@@ -1,5 +1,4 @@
using System.Reflection; using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following // General Information about an assembly is controlled through the following
@@ -8,9 +7,9 @@ using System.Runtime.InteropServices;
[assembly: AssemblyTitle("WACCALauncher")] [assembly: AssemblyTitle("WACCALauncher")]
[assembly: AssemblyDescription("me when i launch my wacca")] [assembly: AssemblyDescription("me when i launch my wacca")]
[assembly: AssemblyConfiguration("")] [assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("YellowberryHN")] [assembly: AssemblyCompany("Yellowberry")]
[assembly: AssemblyProduct("WACCALauncher")] [assembly: AssemblyProduct("WACCALauncher")]
[assembly: AssemblyCopyright("Copyright © YellowberryHN 2024")] [assembly: AssemblyCopyright("Copyright © Yellowberry 2025")]
[assembly: AssemblyTrademark("")] [assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")] [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 // You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below: // by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")] // [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("0.11.0.0")] [assembly: AssemblyVersion("0.12.0.0")]
[assembly: AssemblyFileVersion("0.11.0.0")] [assembly: AssemblyFileVersion("0.12.0.0")]
+20
View File
@@ -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> /// <summary>
/// Looks up a localized resource of type System.Byte[]. /// Looks up a localized resource of type System.Byte[].
/// </summary> /// </summary>
+6
View File
@@ -118,6 +118,12 @@
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader> </resheader>
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" /> <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"> <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> <value>..\Resources\font.ttf;System.Byte[], mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data> </data>
+105 -31
View File
@@ -1,15 +1,22 @@
# WACCA Launcher # WACCALauncher
Allows you to run multiple versions of WACCA from the same drive. 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: You'll want a directory structure something like this:
``` ```
WACCA/ WACCA/
├── _profiles/
│ ├── WACCA.json
│ ├── Reverse.json
│ ├── OSD.json
│ └── ...
├── WACCALauncher.exe ├── WACCALauncher.exe
├── wacca.ini ├── launcher.json
├── ...
└── Versions/ └── Versions/
├── WACCA/ ├── WACCA/
│ ├── bin/ │ ├── bin/
@@ -22,51 +29,116 @@ WACCA/
└── Omega Supermix Deluxe └── 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.
``` Profiles are .json files, you can have as many profiles in your profiles folder as you want.
[general] One limitation to be aware of is if you have a profile that shares a name with another one,
default_ver = reverse weird things happen. But I'm not sure why you would want that anyway.
[versions] Profiles are validated when the launcher starts, and will throw an error if the configuration is invalid,
wacca = C:\WACCA\Versions\WACCA or if the configured files or folders do not exist.
wacca_s = C:\WACCA\Versions\WACCA S
lily = C:\WACCA\Versions\Lily The filename of a profile is only used for sorting, so you can sort your profiles however you wish.
lily_r = C:\WACCA\Versions\Lily R
reverse = C:\WACCA\Versions\Reverse There are currently only 2 valid types of profiles:
offline = C:\WACCA\Versions\Offline - `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 A generic profile can be used to launch pretty much any program.
`num_customs = 1` under `[general]`, and then adding a block at the end of the file
that looks something like this:
``` Here's an example of a profile you can use to launch Portal 2, for some reason:
[custom_1]
name = WACCA Omega Supermix Deluxe ```json
path = C:\WACCA\Versions\Omega Supermix Deluxe {
type = reverse "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 ## Usage
When you start the launcher, you will be presented with a loading prompt. 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 You may press the `TEST` button or <kbd>Esc</kbd> to open the launcher menu.
to change the default startup version, as well as launch a version manually. This menu allows you to change the default startup profile, as well as launch a profile manually.
This menu is controlled in the same way you would navigate the in-game test menu. 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. 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 ## 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.** **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`, 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 ## 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. If you want to help make the code a little less awful, issues and pull requests are welcome.
## License ## License
Binary file not shown.

After

Width:  |  Height:  |  Size: 314 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 314 KiB

+12 -3
View File
@@ -37,9 +37,12 @@
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit> <Prefer32Bit>false</Prefer32Bit>
</PropertyGroup> </PropertyGroup>
<PropertyGroup>
<ApplicationIcon>Resources\Dark.ico</ApplicationIcon>
</PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="INIFileParser, Version=2.5.2.0, Culture=neutral, PublicKeyToken=79af7b307b65cf3c, processorArchitecture=MSIL"> <Reference Include="Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>packages\ini-parser.2.5.2\lib\net20\INIFileParser.dll</HintPath> <HintPath>packages\Newtonsoft.Json.13.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
</Reference> </Reference>
<Reference Include="SharpDX, Version=4.2.0.0, Culture=neutral, PublicKeyToken=b4dcf0f35e5521f1, processorArchitecture=MSIL"> <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> <HintPath>packages\SharpDX.4.2.0\lib\net45\SharpDX.dll</HintPath>
@@ -58,18 +61,22 @@
<Reference Include="System.Xml" /> <Reference Include="System.Xml" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="FontLoader.cs" />
<Compile Include="LauncherSettings.cs" />
<Compile Include="Menu.cs" />
<Compile Include="MainForm.cs"> <Compile Include="MainForm.cs">
<SubType>Form</SubType> <SubType>Form</SubType>
</Compile> </Compile>
<Compile Include="MainForm.Designer.cs"> <Compile Include="MainForm.Designer.cs">
<DependentUpon>MainForm.cs</DependentUpon> <DependentUpon>MainForm.cs</DependentUpon>
</Compile> </Compile>
<Compile Include="MenuManager.cs" />
<Compile Include="Program.cs" /> <Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Profile.cs" />
<Compile Include="WaccaList.cs"> <Compile Include="WaccaList.cs">
<SubType>Component</SubType> <SubType>Component</SubType>
</Compile> </Compile>
<Compile Include="WaccaVFD.cs" />
<EmbeddedResource Include="MainForm.resx"> <EmbeddedResource Include="MainForm.resx">
<DependentUpon>MainForm.cs</DependentUpon> <DependentUpon>MainForm.cs</DependentUpon>
</EmbeddedResource> </EmbeddedResource>
@@ -104,6 +111,8 @@
<Content Include="LICENSE.txt"> <Content Include="LICENSE.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
<None Include="Resources\Light.ico" />
<None Include="Resources\Dark.ico" />
</ItemGroup> </ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild"> <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
Binary file not shown.
+133 -12
View File
@@ -1,11 +1,5 @@
using System; using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing; using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms; using System.Windows.Forms;
namespace WACCALauncher namespace WACCALauncher
@@ -14,31 +8,158 @@ namespace WACCALauncher
{ {
private MenuManager menuManager; private MenuManager menuManager;
private Brush bgBrush;
private Brush textBrush;
private Brush highlightBrush;
private PointF[] upArrow { get; }
private PointF[] downArrow { get; }
public WaccaList() public WaccaList()
{ {
SetStyle(
ControlStyles.OptimizedDoubleBuffer |
ControlStyles.ResizeRedraw |
ControlStyles.UserPaint,
true);
DrawMode = DrawMode.OwnerDrawFixed;
BackColor = Color.Black; BackColor = Color.Black;
ForeColor = Color.White;
BorderStyle = BorderStyle.None; BorderStyle = BorderStyle.None;
DrawMode = DrawMode.OwnerDrawVariable;
ItemHeight = 40; ItemHeight = 40;
Size = new Size(680, 600); Size = new Size(680, 600);
Location = new Point(200, 240); 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; menuManager = manager;
} }
protected override void OnDrawItem(DrawItemEventArgs e) protected override void OnDrawItem(DrawItemEventArgs e)
{ {
e.Graphics.FillRectangle(new SolidBrush(BackColor), e.Bounds);
if (Items.Count < 1 || e.Index < 0) return; 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; 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
View File
@@ -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
View File
@@ -1,3 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<configuration> <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
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<packages> <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" version="4.2.0" targetFramework="net452" />
<package id="SharpDX.DirectInput" version="4.2.0" targetFramework="net452" /> <package id="SharpDX.DirectInput" version="4.2.0" targetFramework="net452" />
</packages> </packages>