Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds FX3 colorization support #367

Merged
merged 21 commits into from
Apr 21, 2023
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Console/Common/BaseCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ public void Execute(HashSet<string> reportingTags, Action onCompleted, Action<Ex
GetRenderGraphs(reportingTags).Init().StartRendering(onCompleted, onError);
}

public void Dispose()
public virtual void Dispose()
{
if (_config == null || !_config.Global.NoClear) {
_graphs?.ClearDisplay();
Expand Down
5 changes: 4 additions & 1 deletion Console/Common/BaseOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,9 @@ internal abstract class BaseOptions : IConfiguration
[Option("retry-interval", HelpText = "In seconds, interval between Websocket connection retry attempts. Default: 5 seconds")]
public int WebsocketRetryInterval { get; set; } = 5;

[Option("colorize", HelpText = "Enable frame-by-frame colorization")]
public bool Colorize { get; set; } = false;

public IGlobalConfig Global { get; }
public IVirtualDmdConfig VirtualDmd { get; }
public IVirtualAlphaNumericDisplayConfig VirtualAlphaNumericDisplay { get; }
Expand Down Expand Up @@ -201,7 +204,7 @@ public GlobalConfig(BaseOptions options)
public ResizeMode Resize => _options.Resize;
public bool FlipHorizontally => _options.FlipHorizontally;
public bool FlipVertically => _options.FlipVertically;
public bool Colorize => false;
public bool Colorize => _options.Colorize;
public bool QuitWhenDone => _options.QuitWhenDone;
public int QuitAfter => _options.QuitAfter;
public bool NoClear => _options.NoClear;
Expand Down
33 changes: 32 additions & 1 deletion Console/Mirror/MirrorCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
using System.Collections.Generic;
using DmdExt.Common;
using LibDmd;
using LibDmd.Common;
using LibDmd.Converter;
using LibDmd.DmdDevice;
using LibDmd.Input.FutureDmd;
using LibDmd.Input.PinballFX;
Expand All @@ -17,7 +17,9 @@ class MirrorCommand : BaseCommand
{
private readonly MirrorOptions _options;
private readonly IConfiguration _config;
private ColorizationLoader _colorizationLoader;
private RenderGraph _graph;
private IDisposable _nameSubscription;

public MirrorCommand(IConfiguration config, MirrorOptions options)
{
Expand Down Expand Up @@ -53,6 +55,16 @@ protected override void CreateRenderGraphs(RenderGraphCollection graphs, HashSet
reportingTags.Add("In:PinballFX3Legacy");
} else {
_graph.Source = new PinballFX3MemoryGrabber { FramesPerSecond = _options.FramesPerSecond };

var latest = new SwitchingConverter();
_graph.Converter = latest;

if (_config.Global.Colorize) {
_colorizationLoader = new ColorizationLoader();
var nameGrabber = new PinballFX3GameNameMemoryGrabber();
_nameSubscription = nameGrabber.GetFrames().Subscribe(name => { latest.Switch(LoadColorizer(name)); });
}

reportingTags.Add("In:PinballFX3");
}
break;
Expand Down Expand Up @@ -102,7 +114,26 @@ protected override void CreateRenderGraphs(RenderGraphCollection graphs, HashSet
throw new ArgumentOutOfRangeException();
}
graphs.Add(_graph);

if (_colorizationLoader!= null) {
graphs.ClearColor();
}
graphs.SetDimensions(new LibDmd.Input.Dimensions(_options.ResizeTo[0], _options.ResizeTo[1]));
}

public override void Dispose()
{
base.Dispose();
_nameSubscription?.Dispose();
}

private IConverter LoadColorizer(string gameName)
{
var serumColorizer = _colorizationLoader.LoadSerum(gameName, _config.Global.ScalerMode);
if (serumColorizer != null) {
return serumColorizer;
}
return _colorizationLoader.LoadColorizer(gameName, _config.Global.ScalerMode)?.gray2;
}
}
}
204 changes: 204 additions & 0 deletions LibDmd/Converter/ColorizationLoader.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
using System;
using System.Collections.Generic;
using System.Configuration.Assemblies;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using LibDmd.Common;
using LibDmd.Converter.Colorize;
using Microsoft.Win32;
using NLog;

namespace LibDmd.Converter
{
public class ColorizationLoader
{
public struct ColorizerResult
{
public Coloring coloring;
public Gray2Colorizer gray2;
public Gray4Colorizer gray4;
public VniAnimationSet vni;

}

private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
private static readonly string AssemblyPath = Path.GetDirectoryName(new Uri(Assembly.GetExecutingAssembly().CodeBase).LocalPath);
private string _altcolorPath;

public ColorizationLoader()
{
_altcolorPath = GetColorPath();
}

public Serum.Serum LoadSerum(string gameName, ScalerMode scalerMode)
{
if (_altcolorPath == null)
{
return null;
}

var serumPath = Path.Combine(_altcolorPath, gameName, gameName + ".cRZ");
if (File.Exists(serumPath))
{
try
{
var serum = new Serum.Serum(_altcolorPath, gameName);
if (serum.IsLoaded)
{
Logger.Info($"Serum colorizer v{Serum.Serum.GetVersion()} initialized.");
Logger.Info($"Loading colorization at {serumPath}...");
serum.ScalerMode = scalerMode;
return serum;
}

Logger.Warn($"Found Serum coloring file at {serumPath}, but could not load colorizer.");

}
catch (Exception e)
{
Logger.Warn(e, "Error initializing colorizer: {0}", e.Message);
}
}

return null;
}

public ColorizerResult? LoadColorizer(string gameName, ScalerMode scalerMode)
{
if (_altcolorPath == null)
{
return null;
}

var palPath1 = Path.Combine(_altcolorPath, gameName, gameName + ".pal");
var palPath2 = Path.Combine(_altcolorPath, gameName, "pin2dmd.pal");
var vniPath1 = Path.Combine(_altcolorPath, gameName, gameName + ".vni");
var vniPath2 = Path.Combine(_altcolorPath, gameName, "pin2dmd.vni");

var palPath = File.Exists(palPath1) ? palPath1 : palPath2;
var vniPath = File.Exists(vniPath1) ? vniPath1 : vniPath2;
if (File.Exists(palPath))
{
try
{
Logger.Info("Loading palette file at {0}...", palPath);
var coloring = new Coloring(palPath);
VniAnimationSet vni = null;
if (File.Exists(vniPath))
{
Logger.Info("Loading virtual animation file at {0}...", vniPath);
vni = new VniAnimationSet(vniPath);
Logger.Info("Loaded animation set {0}", vni);
Logger.Info("Animation Dimensions: {0}x{1}", vni.MaxWidth, vni.MaxHeight);

}
else
{
Logger.Info("No animation set found");
}

var gray2Colorizer = new Gray2Colorizer(coloring, vni);
var gray4Colorizer = new Gray4Colorizer(coloring, vni);

gray2Colorizer.ScalerMode = scalerMode;
gray4Colorizer.ScalerMode = scalerMode;

return new ColorizerResult
{
coloring = coloring,
gray2 = gray2Colorizer,
gray4 = gray4Colorizer,
vni = vni,
};
}
catch (Exception e)
{
Logger.Warn(e, "Error initializing colorizer: {0}", e.Message);
}

}
else
{
Logger.Info("No palette file found at {0}.", palPath);
}

return null;
}

private static string GetColorPath()
{
// first, try executing assembly.
var altcolor = Path.Combine(AssemblyPath, "altcolor");
if (Directory.Exists(altcolor))
{
Logger.Info("Determined color path from assembly path: {0}", altcolor);
return altcolor;
}

// then, try vpinmame location
var vpmPath = GetDllPath("VPinMAME.dll");
if (vpmPath != null)
{
altcolor = Path.Combine(Path.GetDirectoryName(vpmPath), "altcolor");
if (Directory.Exists(altcolor))
{
Logger.Info("Determined color path from VPinMAME.dll location: {0}", altcolor);
return altcolor;
}
}

// then, try vpinmame from the COM registration
RegistryKey reg = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Classes\VPinMAME.Controller\CLSID");
if (reg != null)
{
var clsid = reg.GetValue(null).ToString();

var x64Suffix = Environment.Is64BitOperatingSystem ? @"WOW6432Node\" : "";
reg = Registry.ClassesRoot.OpenSubKey(x64Suffix + @"CLSID\" + clsid + @"\InprocServer32");
if (reg != null)
{
altcolor = Path.Combine(Path.GetDirectoryName(reg.GetValue(null).ToString()), "altcolor");
if (Directory.Exists(altcolor))
{
Logger.Info("Determined color path from VPinMAME registry: {0}", altcolor);
return altcolor;
}


}
}

Logger.Info("No altcolor folder found, ignoring palettes.");
return null;
}

private static string GetDllPath(string name)
{
const int maxPath = 260;
var builder = new StringBuilder(maxPath);
var hModule = GetModuleHandle(name);
if (hModule == IntPtr.Zero)
{
return null;
}
var size = GetModuleFileName(hModule, builder, builder.Capacity);
return size <= 0 ? null : builder.ToString();
}

[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr GetModuleHandle(string lpModuleName);

[DllImport("kernel32.dll", SetLastError = true)]
[PreserveSig]
public static extern uint GetModuleFileName
(
[In] IntPtr hModule,
[Out] StringBuilder lpFilename,
[In][MarshalAs(UnmanagedType.U4)] int nSize
);
}
}
Loading