organize and cleanup a ton of win32 imports, do cleanup elsewhere with that

This commit is contained in:
CasualPokePlayer 2023-10-28 05:04:41 -07:00
parent b4a6b06fba
commit 686119c7dd
22 changed files with 654 additions and 517 deletions

View File

@ -9,7 +9,7 @@ using BizHawk.Common;
using BizHawk.Common.CollectionExtensions; using BizHawk.Common.CollectionExtensions;
using static BizHawk.Common.RawInputImports; using static BizHawk.Common.RawInputImports;
using static BizHawk.Common.Win32Imports; using static BizHawk.Common.WmImports;
using RawKey = Vortice.DirectInput.Key; using RawKey = Vortice.DirectInput.Key;
@ -35,7 +35,7 @@ namespace BizHawk.Bizware.Input
{ {
var wc = default(WNDCLASS); var wc = default(WNDCLASS);
wc.lpfnWndProc = _wndProc; wc.lpfnWndProc = _wndProc;
wc.hInstance = GetModuleHandle(null); wc.hInstance = Win32Imports.GetModuleHandle(null);
wc.lpszClassName = "RawKeyInputClass"; wc.lpszClassName = "RawKeyInputClass";
var atom = RegisterClass(ref wc); var atom = RegisterClass(ref wc);
@ -125,7 +125,7 @@ namespace BizHawk.Bizware.Input
nHeight: 1, nHeight: 1,
hWndParent: HWND_MESSAGE, hWndParent: HWND_MESSAGE,
hMenu: IntPtr.Zero, hMenu: IntPtr.Zero,
hInstance: GetModuleHandle(null), hInstance: Win32Imports.GetModuleHandle(null),
lpParam: IntPtr.Zero); lpParam: IntPtr.Zero);
if (window == IntPtr.Zero) if (window == IntPtr.Zero)

View File

@ -46,10 +46,10 @@ namespace BizHawk.Bizware.Input
// similar code shouldn't be needed on other platforms (which have global message queues and not thread local message queues) // similar code shouldn't be needed on other platforms (which have global message queues and not thread local message queues)
if (!OSTailoredCode.IsUnixHost) if (!OSTailoredCode.IsUnixHost)
{ {
while (Win32Imports.PeekMessage(out var msg, IntPtr.Zero, 0, 0, Win32Imports.PM_REMOVE)) while (WmImports.PeekMessage(out var msg, IntPtr.Zero, 0, 0, WmImports.PM_REMOVE))
{ {
Win32Imports.TranslateMessage(ref msg); WmImports.TranslateMessage(ref msg);
Win32Imports.DispatchMessage(ref msg); WmImports.DispatchMessage(ref msg);
} }
} }

View File

@ -12,6 +12,8 @@ using BizHawk.Common;
using BizHawk.Common.PathExtensions; using BizHawk.Common.PathExtensions;
using BizHawk.Emulation.Common; using BizHawk.Emulation.Common;
using static BizHawk.Common.HeapApiImports;
// some helpful p/invoke from http://www.codeproject.com/KB/audio-video/Motion_Detection.aspx?msg=1142967 // some helpful p/invoke from http://www.codeproject.com/KB/audio-video/Motion_Detection.aspx?msg=1142967
namespace BizHawk.Client.EmuHawk namespace BizHawk.Client.EmuHawk
{ {
@ -19,7 +21,7 @@ namespace BizHawk.Client.EmuHawk
"Uses the Microsoft AVIFIL32 system to write .avi files. Audio is uncompressed; Video can be compressed with any installed VCM codec. Splits on 2G and resolution change.")] "Uses the Microsoft AVIFIL32 system to write .avi files. Audio is uncompressed; Video can be compressed with any installed VCM codec. Splits on 2G and resolution change.")]
internal class AviWriter : IVideoWriter internal class AviWriter : IVideoWriter
{ {
private CodecToken _currVideoCodecToken = null; private CodecToken _currVideoCodecToken;
private AviWriterSegment _currSegment; private AviWriterSegment _currSegment;
private readonly IDialogParent _dialogParent; private readonly IDialogParent _dialogParent;
@ -35,9 +37,7 @@ namespace BizHawk.Client.EmuHawk
private bool IsOpen => _nameProvider != null; private bool IsOpen => _nameProvider != null;
public void Dispose() public void Dispose()
{ => _currSegment?.Dispose();
_currSegment?.Dispose();
}
/// <summary>sets the codec token to be used for video compression</summary> /// <summary>sets the codec token to be used for video compression</summary>
/// <exception cref="ArgumentException"><paramref name="token"/> does not inherit <see cref="CodecToken"/></exception> /// <exception cref="ArgumentException"><paramref name="token"/> does not inherit <see cref="CodecToken"/></exception>
@ -57,12 +57,16 @@ namespace BizHawk.Client.EmuHawk
{ {
var (dir, baseName, ext) = template.SplitPathToDirFileAndExt(); var (dir, baseName, ext) = template.SplitPathToDirFileAndExt();
yield return template; yield return template;
int counter = 1; var counter = 1;
for (;;) while (counter < int.MaxValue)
{ {
yield return Path.Combine(dir, $"{baseName}_{counter}{ext}"); yield return Path.Combine(dir, $"{baseName}_{counter}{ext}");
counter++; counter++;
} }
yield return Path.Combine(dir, $"{baseName}_{counter}{ext}");
throw new InvalidOperationException("Reached maximum names");
} }
/// <summary> /// <summary>
@ -70,9 +74,7 @@ namespace BizHawk.Client.EmuHawk
/// set a video codec token first. /// set a video codec token first.
/// </summary> /// </summary>
public void OpenFile(string baseName) public void OpenFile(string baseName)
{ => OpenFile(CreateBasicNameProvider(baseName));
OpenFile(CreateBasicNameProvider(baseName));
}
// thread communication // thread communication
// synchronized queue with custom messages // synchronized queue with custom messages
@ -86,17 +88,16 @@ namespace BizHawk.Client.EmuHawk
{ {
while (true) while (true)
{ {
object o = _threadQ.Take(); var o = _threadQ.Take();
if (o is IVideoProvider provider) switch (o)
{ {
case IVideoProvider provider:
AddFrameEx(provider); AddFrameEx(provider);
} break;
else if (o is short[] arr) case short[] arr:
{
AddSamplesEx(arr); AddSamplesEx(arr);
} break;
else default:
{
// anything else is assumed to be quit time // anything else is assumed to be quit time
return; return;
} }
@ -121,6 +122,7 @@ namespace BizHawk.Client.EmuHawk
public int BackgroundColor { get; } public int BackgroundColor { get; }
public int VsyncNumerator { get; } public int VsyncNumerator { get; }
public int VsyncDenominator { get; } public int VsyncDenominator { get; }
public VideoCopy(IVideoProvider c) public VideoCopy(IVideoProvider c)
{ {
_vb = (int[])c.GetVideoBuffer().Clone(); _vb = (int[])c.GetVideoBuffer().Clone();
@ -134,9 +136,7 @@ namespace BizHawk.Client.EmuHawk
} }
public int[] GetVideoBuffer() public int[] GetVideoBuffer()
{ => _vb;
return _vb;
}
} }
/// <summary>opens an avi file for recording, with <paramref name="nameProvider"/> being used to name files</summary> /// <summary>opens an avi file for recording, with <paramref name="nameProvider"/> being used to name files</summary>
@ -149,14 +149,14 @@ namespace BizHawk.Client.EmuHawk
throw new InvalidOperationException("Tried to start recording an AVI with no video codec token set"); throw new InvalidOperationException("Tried to start recording an AVI with no video codec token set");
} }
_threadQ = new System.Collections.Concurrent.BlockingCollection<object>(30); _threadQ = new(30);
_workerT = new System.Threading.Thread(new System.Threading.ThreadStart(ThreadProc)); _workerT = new(ThreadProc) { IsBackground = true };
_workerT.Start(); _workerT.Start();
} }
public void CloseFile() public void CloseFile()
{ {
_threadQ.Add(new object()); // acts as stop message _threadQ.Add(new()); // acts as stop message
_workerT.Join(); _workerT.Join();
_currSegment?.Dispose(); _currSegment?.Dispose();
_currSegment = null; _currSegment = null;
@ -169,7 +169,7 @@ namespace BizHawk.Client.EmuHawk
{ {
if (!_workerT.IsAlive) if (!_workerT.IsAlive)
{ {
throw new Exception("AVI Worker thread died!"); throw new("AVI Worker thread died!");
} }
} }
} }
@ -183,7 +183,7 @@ namespace BizHawk.Client.EmuHawk
Segment(); Segment();
} }
_currSegment.AddFrame(source); _currSegment!.AddFrame(source);
} }
/// <exception cref="Exception">worker thrread died</exception> /// <exception cref="Exception">worker thrread died</exception>
@ -195,7 +195,7 @@ namespace BizHawk.Client.EmuHawk
{ {
if (!_workerT.IsAlive) if (!_workerT.IsAlive)
{ {
throw new Exception("AVI Worker thread died!"); throw new("AVI Worker thread died!");
} }
} }
} }
@ -208,7 +208,7 @@ namespace BizHawk.Client.EmuHawk
Segment(); Segment();
} }
_currSegment.AddSamples(samples); _currSegment!.AddSamples(samples);
} }
private void ConsiderLengthSegment() private void ConsiderLengthSegment()
@ -218,7 +218,7 @@ namespace BizHawk.Client.EmuHawk
return; return;
} }
long len = _currSegment.GetLengthApproximation(); var len = _currSegment.GetLengthApproximation();
const long segment_length_limit = 2 * 1000 * 1000 * 1000; // 2GB const long segment_length_limit = 2 * 1000 * 1000 * 1000; // 2GB
// const long segment_length_limit = 10 * 1000 * 1000; //for testing // const long segment_length_limit = 10 * 1000 * 1000; //for testing
@ -228,11 +228,6 @@ namespace BizHawk.Client.EmuHawk
} }
} }
private void StartRecording()
{
// i guess theres nothing to do here
}
private void Segment() private void Segment()
{ {
if (!IsOpen) if (!IsOpen)
@ -240,18 +235,12 @@ namespace BizHawk.Client.EmuHawk
return; return;
} }
if (_currSegment == null) _currSegment?.Dispose();
{ _currSegment = new();
StartRecording();
}
else
{
_currSegment.Dispose();
}
_currSegment = new AviWriterSegment();
_nameProvider.MoveNext(); _nameProvider.MoveNext();
_currSegment.OpenFile(_nameProvider.Current, _parameters, _currVideoCodecToken); _currSegment.OpenFile(_nameProvider.Current, _parameters, _currVideoCodecToken);
try try
{ {
_currSegment.OpenStreams(); _currSegment.OpenStreams();
@ -278,18 +267,26 @@ namespace BizHawk.Client.EmuHawk
a_samplerate = 44100, a_samplerate = 44100,
a_channels = 2 a_channels = 2
}; };
var temp = new AviWriterSegment();
string tempfile = Path.GetTempFileName(); var tempSegment = new AviWriterSegment();
File.Delete(tempfile); var tempFile = Path.GetTempFileName();
tempfile = Path.ChangeExtension(tempfile, "avi"); File.Delete(tempFile);
temp.OpenFile(tempfile, tempParams, null); tempFile = Path.ChangeExtension(tempFile, "avi");
var ret = temp.AcquireVideoCodecToken(_dialogParent.AsWinFormsHandle().Handle, _currVideoCodecToken); tempSegment.OpenFile(tempFile, tempParams, null);
CodecToken token = (CodecToken)ret;
try
{
var ret = tempSegment.AcquireVideoCodecToken(_dialogParent.AsWinFormsHandle().Handle, _currVideoCodecToken);
var token = (CodecToken)ret;
config.AviCodecToken = token?.Serialize(); config.AviCodecToken = token?.Serialize();
temp.CloseFile();
File.Delete(tempfile);
return token; return token;
} }
finally
{
tempSegment.CloseFile();
File.Delete(tempFile);
}
}
private class Parameters private class Parameters
{ {
@ -329,11 +326,18 @@ namespace BizHawk.Client.EmuHawk
public void PopulateWAVEFORMATEX(ref AVIWriterImports.WAVEFORMATEX wfex) public void PopulateWAVEFORMATEX(ref AVIWriterImports.WAVEFORMATEX wfex)
{ {
const int WAVE_FORMAT_PCM = 1; const int WAVE_FORMAT_PCM = 1;
int bytes = 0; var bytes = a_bits switch
if (a_bits == 16) bytes = 2; {
else if (a_bits == 8) bytes = 1; 16 => 2,
else throw new InvalidOperationException($"only 8/16 bits audio are supported by {nameof(AviWriter)} and you chose: {a_bits}"); 8 => 1,
if (a_channels is not (1 or 2)) throw new InvalidOperationException($"only 1/2 channels audio are supported by {nameof(AviWriter)} and you chose: {a_channels}"); _ => throw new InvalidOperationException($"only 8/16 bits audio are supported by {nameof(AviWriter)} and you chose: {a_bits}")
};
if (a_channels is not (1 or 2))
{
throw new InvalidOperationException($"only 1/2 channels audio are supported by {nameof(AviWriter)} and you chose: {a_channels}");
}
wfex.Init(); wfex.Init();
wfex.nBlockAlign = (ushort)(bytes * a_channels); wfex.nBlockAlign = (ushort)(bytes * a_channels);
wfex.nChannels = (ushort)a_channels; wfex.nChannels = (ushort)a_channels;
@ -373,12 +377,10 @@ namespace BizHawk.Client.EmuHawk
/// </summary> /// </summary>
public void SetVideoParameters(int width, int height) public void SetVideoParameters(int width, int height)
{ {
bool change = false; var change = _parameters.width != width ||
_parameters.height != height;
change |= _parameters.width != width;
_parameters.width = width; _parameters.width = width;
change |= _parameters.height != height;
_parameters.height = height; _parameters.height = height;
if (change) if (change)
@ -392,18 +394,14 @@ namespace BizHawk.Client.EmuHawk
/// </summary> /// </summary>
public void SetAudioParameters(int sampleRate, int channels, int bits) public void SetAudioParameters(int sampleRate, int channels, int bits)
{ {
bool change = false; var change = _parameters.a_samplerate != sampleRate ||
_parameters.a_channels != channels ||
_parameters.a_bits != bits ||
_parameters.has_audio != true;
change |= _parameters.a_samplerate != sampleRate;
_parameters.a_samplerate = sampleRate; _parameters.a_samplerate = sampleRate;
change |= _parameters.a_channels != channels;
_parameters.a_channels = channels; _parameters.a_channels = channels;
change |= _parameters.a_bits != bits;
_parameters.a_bits = bits; _parameters.a_bits = bits;
change |= _parameters.has_audio != true;
_parameters.has_audio = true; _parameters.has_audio = true;
if (change) if (change)
@ -414,24 +412,31 @@ namespace BizHawk.Client.EmuHawk
public class CodecToken : IDisposable public class CodecToken : IDisposable
{ {
public void Dispose() { } public void Dispose()
private CodecToken() { } {
}
private CodecToken()
{
}
private AVIWriterImports.AVICOMPRESSOPTIONS _comprOptions; private AVIWriterImports.AVICOMPRESSOPTIONS _comprOptions;
public string codec; public string codec;
public byte[] Format = Array.Empty<byte>(); public byte[] Format = Array.Empty<byte>();
public byte[] Parms = Array.Empty<byte>(); public byte[] Parms = Array.Empty<byte>();
private static string Decode_mmioFOURCC(int code) private static unsafe string Decode_mmioFOURCC(int code)
{ {
char[] chs = new char[4]; var chs = stackalloc char[4];
for (int i = 0; i < 4; i++) for (var i = 0; i < 4; i++)
{ {
chs[i] = (char)(byte)((code >> (i << 3)) & 0xFF); chs[i] = (char)(byte)((code >> (i << 3)) & 0xFF);
if (!char.IsLetterOrDigit(chs[i])) if (!char.IsLetterOrDigit(chs[i]))
chs[i] = ' '; chs[i] = ' ';
} }
return new string(chs);
return new(chs, 0, 4);
} }
public static CodecToken CreateFromAVICOMPRESSOPTIONS(ref AVIWriterImports.AVICOMPRESSOPTIONS opts) public static CodecToken CreateFromAVICOMPRESSOPTIONS(ref AVIWriterImports.AVICOMPRESSOPTIONS opts)
@ -461,27 +466,36 @@ namespace BizHawk.Client.EmuHawk
{ {
#endif #endif
#if false // test: increase stability by never freeing anything, ever #if false // test: increase stability by never freeing anything, ever
if (opts.lpParms != IntPtr.Zero) Win32Imports.HeapFree(Win32Imports.GetProcessHeap(), 0, opts.lpParms); if (opts.lpParms != IntPtr.Zero)
if (opts.lpFormat != IntPtr.Zero) Win32Imports.HeapFree(Win32Imports.GetProcessHeap(), 0, opts.lpFormat); {
HeapFree(GetProcessHeap(), 0, opts.lpParms);
}
if (opts.lpFormat != IntPtr.Zero)
{
HeapFree(GetProcessHeap(), 0, opts.lpFormat);
}
#endif #endif
#if AVI_SUPPORT #if AVI_SUPPORT
opts.lpParms = IntPtr.Zero; opts.lpParms = IntPtr.Zero;
opts.lpFormat = IntPtr.Zero; opts.lpFormat = IntPtr.Zero;
} }
public void AllocateToAVICOMPRESSOPTIONS(ref AVIWriterImports.AVICOMPRESSOPTIONS opts) public void AllocateToAVICOMPRESSOPTIONS(out AVIWriterImports.AVICOMPRESSOPTIONS opts)
{ {
if (_comprOptions.cbParms != 0)
{
_comprOptions.lpParms = HeapAlloc(GetProcessHeap(), 0, _comprOptions.cbParms);
Marshal.Copy(Parms, 0, _comprOptions.lpParms, _comprOptions.cbParms);
}
if (_comprOptions.cbFormat != 0)
{
_comprOptions.lpFormat = HeapAlloc(GetProcessHeap(), 0, _comprOptions.cbFormat);
Marshal.Copy(Format, 0, _comprOptions.lpFormat, _comprOptions.cbFormat);
}
opts = _comprOptions; opts = _comprOptions;
if (opts.cbParms != 0)
{
opts.lpParms = Win32Imports.HeapAlloc(Win32Imports.GetProcessHeap(), 0, opts.cbParms);
Marshal.Copy(Parms, 0, opts.lpParms, opts.cbParms);
}
if (opts.cbFormat != 0)
{
opts.lpFormat = Win32Imports.HeapAlloc(Win32Imports.GetProcessHeap(), 0, opts.cbFormat);
Marshal.Copy(Format, 0, opts.lpFormat, opts.cbFormat);
}
} }
private byte[] SerializeToByteArray() private byte[] SerializeToByteArray()
@ -511,7 +525,7 @@ namespace BizHawk.Client.EmuHawk
var m = new MemoryStream(data, false); var m = new MemoryStream(data, false);
var b = new BinaryReader(m); var b = new BinaryReader(m);
AVIWriterImports.AVICOMPRESSOPTIONS comprOptions = new AVIWriterImports.AVICOMPRESSOPTIONS(); var comprOptions = default(AVIWriterImports.AVICOMPRESSOPTIONS);
byte[] format; byte[] format;
byte[] parms; byte[] parms;
@ -544,25 +558,20 @@ namespace BizHawk.Client.EmuHawk
b.Close(); b.Close();
} }
var ret = new CodecToken return new()
{ {
_comprOptions = comprOptions, _comprOptions = comprOptions,
Format = format, Format = format,
Parms = parms, Parms = parms,
codec = Decode_mmioFOURCC(comprOptions.fccHandler) codec = Decode_mmioFOURCC(comprOptions.fccHandler)
}; };
return ret;
} }
public string Serialize() public string Serialize()
{ => Convert.ToBase64String(SerializeToByteArray());
return Convert.ToBase64String(SerializeToByteArray());
}
public static CodecToken DeSerialize(string s) public static CodecToken DeSerialize(string s)
{ => DeSerializeFromByteArray(Convert.FromBase64String(s));
return DeSerializeFromByteArray(Convert.FromBase64String(s));
}
} }
/// <summary> /// <summary>
@ -573,21 +582,15 @@ namespace BizHawk.Client.EmuHawk
{ {
} }
private unsafe class AviWriterSegment : IDisposable private class AviWriterSegment : IDisposable
{ {
static AviWriterSegment() static AviWriterSegment()
{ {
AVIWriterImports.AVIFileInit(); AVIWriterImports.AVIFileInit();
} }
public AviWriterSegment()
{
}
public void Dispose() public void Dispose()
{ => CloseFile();
CloseFile();
}
private CodecToken _currVideoCodecToken; private CodecToken _currVideoCodecToken;
private bool _isOpen; private bool _isOpen;
@ -631,19 +634,15 @@ namespace BizHawk.Client.EmuHawk
private OutputStatus _outStatus; private OutputStatus _outStatus;
public long GetLengthApproximation() public long GetLengthApproximation()
{ => _outStatus.video_bytes + _outStatus.audio_bytes;
return _outStatus.video_bytes + _outStatus.audio_bytes;
}
private static bool FAILED(int hr) => hr < 0; private static unsafe int AVISaveOptions(IntPtr stream, ref AVIWriterImports.AVICOMPRESSOPTIONS opts, IntPtr owner)
private static int AVISaveOptions(IntPtr stream, ref AVIWriterImports.AVICOMPRESSOPTIONS opts, IntPtr owner)
{ {
fixed (AVIWriterImports.AVICOMPRESSOPTIONS* _popts = &opts) fixed (AVIWriterImports.AVICOMPRESSOPTIONS* _popts = &opts)
{ {
IntPtr* pStream = &stream; var pStream = &stream;
AVIWriterImports.AVICOMPRESSOPTIONS* popts = _popts; var popts = _popts;
AVIWriterImports.AVICOMPRESSOPTIONS** ppopts = &popts; var ppopts = &popts;
return AVIWriterImports.AVISaveOptions(owner, 0, 1, pStream, ppopts); return AVIWriterImports.AVISaveOptions(owner, 0, 1, pStream, ppopts);
} }
} }
@ -654,10 +653,10 @@ namespace BizHawk.Client.EmuHawk
public void OpenFile(string destPath, Parameters parameters, CodecToken videoCodecToken) public void OpenFile(string destPath, Parameters parameters, CodecToken videoCodecToken)
{ {
static int mmioFOURCC(string str) => ( static int mmioFOURCC(string str) => (
(byte)(str[0]) (byte)str[0] |
| ((byte)(str[1]) << 8) ((byte)str[1] << 8) |
| ((byte)(str[2]) << 16) ((byte)str[2] << 16) |
| ((byte)(str[3]) << 24) ((byte)str[3] << 24)
); );
this._parameters = parameters; this._parameters = parameters;
@ -671,28 +670,34 @@ namespace BizHawk.Client.EmuHawk
File.Delete(destPath); File.Delete(destPath);
} }
if (FAILED(AVIWriterImports.AVIFileOpenW(ref _pAviFile, destPath, AVIWriterImports.OpenFileStyle.OF_CREATE | AVIWriterImports.OpenFileStyle.OF_WRITE, 0))) var hr = AVIWriterImports.AVIFileOpenW(ref _pAviFile, destPath,
AVIWriterImports.OpenFileStyle.OF_CREATE | AVIWriterImports.OpenFileStyle.OF_WRITE, 0);
var hrEx = Marshal.GetExceptionForHR(hr);
if (hrEx != null)
{ {
throw new InvalidOperationException($"Couldnt open dest path for avi file: {destPath}"); throw new InvalidOperationException($"Couldnt open dest path for avi file: {destPath}", hrEx);
} }
// initialize the video stream // initialize the video stream
AVIWriterImports.AVISTREAMINFOW vidstream_header = new AVIWriterImports.AVISTREAMINFOW(); var vidstream_header = default(AVIWriterImports.AVISTREAMINFOW);
AVIWriterImports.BITMAPINFOHEADER bmih = new AVIWriterImports.BITMAPINFOHEADER(); var bmih = default(AVIWriterImports.BITMAPINFOHEADER);
parameters.PopulateBITMAPINFOHEADER24(ref bmih); parameters.PopulateBITMAPINFOHEADER24(ref bmih);
vidstream_header.fccType = mmioFOURCC("vids"); vidstream_header.fccType = mmioFOURCC("vids");
vidstream_header.dwRate = parameters.fps; vidstream_header.dwRate = parameters.fps;
vidstream_header.dwScale = parameters.fps_scale; vidstream_header.dwScale = parameters.fps_scale;
vidstream_header.dwSuggestedBufferSize = (int)bmih.biSizeImage; vidstream_header.dwSuggestedBufferSize = (int)bmih.biSizeImage;
if (FAILED(AVIWriterImports.AVIFileCreateStreamW(_pAviFile, out _pAviRawVideoStream, ref vidstream_header)))
hr = AVIWriterImports.AVIFileCreateStreamW(_pAviFile, out _pAviRawVideoStream, ref vidstream_header);
hrEx = Marshal.GetExceptionForHR(hr);
if (hrEx != null)
{ {
CloseFile(); CloseFile();
throw new InvalidOperationException("Failed opening raw video stream. Not sure how this could happen"); throw new InvalidOperationException("Failed opening raw video stream. Not sure how this could happen", hrEx);
} }
// initialize audio stream // initialize audio stream
AVIWriterImports.AVISTREAMINFOW audstream_header = new AVIWriterImports.AVISTREAMINFOW(); var audstream_header = default(AVIWriterImports.AVISTREAMINFOW);
AVIWriterImports.WAVEFORMATEX wfex = new AVIWriterImports.WAVEFORMATEX(); var wfex = default(AVIWriterImports.WAVEFORMATEX);
parameters.PopulateWAVEFORMATEX(ref wfex); parameters.PopulateWAVEFORMATEX(ref wfex);
audstream_header.fccType = mmioFOURCC("auds"); audstream_header.fccType = mmioFOURCC("auds");
audstream_header.dwQuality = -1; audstream_header.dwQuality = -1;
@ -700,13 +705,16 @@ namespace BizHawk.Client.EmuHawk
audstream_header.dwRate = (int)wfex.nAvgBytesPerSec; audstream_header.dwRate = (int)wfex.nAvgBytesPerSec;
audstream_header.dwSampleSize = wfex.nBlockAlign; audstream_header.dwSampleSize = wfex.nBlockAlign;
audstream_header.dwInitialFrames = 1; // ??? optimal value? audstream_header.dwInitialFrames = 1; // ??? optimal value?
if (FAILED(AVIWriterImports.AVIFileCreateStreamW(_pAviFile, out _pAviRawAudioStream, ref audstream_header)))
hr = AVIWriterImports.AVIFileCreateStreamW(_pAviFile, out _pAviRawAudioStream, ref audstream_header);
hrEx = Marshal.GetExceptionForHR(hr);
if (hrEx != null)
{ {
CloseFile(); CloseFile();
throw new InvalidOperationException("Failed opening raw audio stream. Not sure how this could happen"); throw new InvalidOperationException("Failed opening raw audio stream. Not sure how this could happen", hrEx);
} }
_outStatus = new OutputStatus(); _outStatus = new();
_isOpen = true; _isOpen = true;
} }
@ -726,11 +734,11 @@ namespace BizHawk.Client.EmuHawk
} }
// encoder params // encoder params
AVIWriterImports.AVICOMPRESSOPTIONS comprOptions = new AVIWriterImports.AVICOMPRESSOPTIONS(); var comprOptions = default(AVIWriterImports.AVICOMPRESSOPTIONS);
_currVideoCodecToken?.AllocateToAVICOMPRESSOPTIONS(ref comprOptions); _currVideoCodecToken?.AllocateToAVICOMPRESSOPTIONS(out comprOptions);
bool result = AVISaveOptions(_pAviRawVideoStream, ref comprOptions, hwnd) != 0; var result = AVISaveOptions(_pAviRawVideoStream, ref comprOptions, hwnd) != 0;
CodecToken ret = CodecToken.CreateFromAVICOMPRESSOPTIONS(ref comprOptions); var ret = CodecToken.CreateFromAVICOMPRESSOPTIONS(ref comprOptions);
// so, AVISaveOptions may have changed some of the pointers // so, AVISaveOptions may have changed some of the pointers
// if it changed the pointers, did it it free the old ones? we don't know // if it changed the pointers, did it it free the old ones? we don't know
@ -741,12 +749,7 @@ namespace BizHawk.Client.EmuHawk
// guess what? doesn't matter. We'll free them all ourselves. // guess what? doesn't matter. We'll free them all ourselves.
CodecToken.DeallocateAVICOMPRESSOPTIONS(ref comprOptions); CodecToken.DeallocateAVICOMPRESSOPTIONS(ref comprOptions);
if (result) return result ? ret : null;
{
return ret;
}
return null;
} }
/// <summary>begin recording</summary> /// <summary>begin recording</summary>
@ -759,19 +762,19 @@ namespace BizHawk.Client.EmuHawk
} }
// open compressed video stream // open compressed video stream
AVIWriterImports.AVICOMPRESSOPTIONS opts = new AVIWriterImports.AVICOMPRESSOPTIONS(); _currVideoCodecToken.AllocateToAVICOMPRESSOPTIONS(out var opts);
_currVideoCodecToken.AllocateToAVICOMPRESSOPTIONS(ref opts);
bool failed = FAILED(AVIWriterImports.AVIMakeCompressedStream(out _pAviCompressedVideoStream, _pAviRawVideoStream, ref opts, IntPtr.Zero));
CodecToken.DeallocateAVICOMPRESSOPTIONS(ref opts);
if (failed) var hr = AVIWriterImports.AVIMakeCompressedStream(out _pAviCompressedVideoStream, _pAviRawVideoStream, ref opts, IntPtr.Zero);
var hrEx = Marshal.GetExceptionForHR(hr);
CodecToken.DeallocateAVICOMPRESSOPTIONS(ref opts);
if (hrEx != null)
{ {
CloseStreams(); CloseStreams();
throw new InvalidOperationException("Failed making compressed video stream"); throw new InvalidOperationException("Failed making compressed video stream", hrEx);
} }
// set the compressed video stream input format // set the compressed video stream input format
AVIWriterImports.BITMAPINFOHEADER bmih = new AVIWriterImports.BITMAPINFOHEADER(); var bmih = default(AVIWriterImports.BITMAPINFOHEADER);
if (_bit32) if (_bit32)
{ {
_parameters.PopulateBITMAPINFOHEADER32(ref bmih); _parameters.PopulateBITMAPINFOHEADER32(ref bmih);
@ -781,17 +784,22 @@ namespace BizHawk.Client.EmuHawk
_parameters.PopulateBITMAPINFOHEADER24(ref bmih); _parameters.PopulateBITMAPINFOHEADER24(ref bmih);
} }
if (FAILED(AVIWriterImports.AVIStreamSetFormat(_pAviCompressedVideoStream, 0, ref bmih, Marshal.SizeOf(bmih)))) hr = AVIWriterImports.AVIStreamSetFormat(_pAviCompressedVideoStream, 0, ref bmih, Marshal.SizeOf(bmih));
hrEx = Marshal.GetExceptionForHR(hr);
if (hrEx != null)
{ {
_bit32 = true; // we'll try again _bit32 = true; // we'll try again
CloseStreams(); CloseStreams();
throw new InvalidOperationException("Failed setting compressed video stream input format"); throw new InvalidOperationException("Failed setting compressed video stream input format", hrEx);
} }
// set audio stream input format // set audio stream input format
AVIWriterImports.WAVEFORMATEX wfex = new AVIWriterImports.WAVEFORMATEX(); var wfex = default(AVIWriterImports.WAVEFORMATEX);
_parameters.PopulateWAVEFORMATEX(ref wfex); _parameters.PopulateWAVEFORMATEX(ref wfex);
if (FAILED(AVIWriterImports.AVIStreamSetFormat(_pAviRawAudioStream, 0, ref wfex, Marshal.SizeOf(wfex))))
hr = AVIWriterImports.AVIStreamSetFormat(_pAviRawAudioStream, 0, ref wfex, Marshal.SizeOf(wfex));
hrEx = Marshal.GetExceptionForHR(hr);
if (hrEx != null)
{ {
CloseStreams(); CloseStreams();
throw new InvalidOperationException("Failed setting raw audio stream input format"); throw new InvalidOperationException("Failed setting raw audio stream input format");
@ -833,7 +841,7 @@ namespace BizHawk.Client.EmuHawk
/// <summary> /// <summary>
/// end recording /// end recording
/// </summary> /// </summary>
public void CloseStreams() private void CloseStreams()
{ {
if (_pAviRawAudioStream != IntPtr.Zero) if (_pAviRawAudioStream != IntPtr.Zero)
{ {
@ -848,18 +856,20 @@ namespace BizHawk.Client.EmuHawk
} }
// todo - why couldnt this take an ISoundProvider? it could do the timekeeping as well.. hmm // todo - why couldnt this take an ISoundProvider? it could do the timekeeping as well.. hmm
public void AddSamples(short[] samples) public void AddSamples(IReadOnlyList<short> samples)
{ {
int todo = samples.Length; var todo = samples.Count;
int idx = 0; var idx = 0;
while (todo > 0) while (todo > 0)
{ {
int remain = OutputStatus.AUDIO_SEGMENT_SIZE - _outStatus.audio_buffered_shorts; var remain = OutputStatus.AUDIO_SEGMENT_SIZE - _outStatus.audio_buffered_shorts;
int chunk = Math.Min(remain, todo); var chunk = Math.Min(remain, todo);
for (int i = 0; i < chunk; i++)
for (var i = 0; i < chunk; i++)
{ {
_outStatus.BufferedShorts[_outStatus.audio_buffered_shorts++] = samples[idx++]; _outStatus.BufferedShorts[_outStatus.audio_buffered_shorts++] = samples[idx++];
} }
todo -= chunk; todo -= chunk;
if (_outStatus.audio_buffered_shorts == OutputStatus.AUDIO_SEGMENT_SIZE) if (_outStatus.audio_buffered_shorts == OutputStatus.AUDIO_SEGMENT_SIZE)
@ -869,27 +879,28 @@ namespace BizHawk.Client.EmuHawk
} }
} }
private void FlushBufferedAudio() private unsafe void FlushBufferedAudio()
{ {
int todo = _outStatus.audio_buffered_shorts; var todo = _outStatus.audio_buffered_shorts;
int todo_realsamples = todo / 2; var todo_realsamples = todo / 2;
IntPtr buf = GetStaticGlobalBuf(todo * 2); var buf = GetStaticGlobalBuf(todo * 2);
short* sptr = (short*)buf.ToPointer(); var sptr = (short*)buf.ToPointer();
for (int i = 0; i < todo; i++) for (var i = 0; i < todo; i++)
{ {
sptr[i] = _outStatus.BufferedShorts[i]; sptr[i] = _outStatus.BufferedShorts[i];
} }
// (TODO - inefficient- build directly in a buffer) // (TODO - inefficient- build directly in a buffer)
_ = AVIWriterImports.AVIStreamWrite(_pAviRawAudioStream, _outStatus.audio_samples, todo_realsamples, buf, todo_realsamples * 4, 0, IntPtr.Zero, out var bytes_written); _ = AVIWriterImports.AVIStreamWrite(_pAviRawAudioStream, _outStatus.audio_samples,
todo_realsamples, buf, todo_realsamples * 4, 0, IntPtr.Zero, out var bytes_written);
_outStatus.audio_samples += todo_realsamples; _outStatus.audio_samples += todo_realsamples;
_outStatus.audio_bytes += bytes_written; _outStatus.audio_bytes += bytes_written;
_outStatus.audio_buffered_shorts = 0; _outStatus.audio_buffered_shorts = 0;
} }
/// <exception cref="InvalidOperationException">attempted frame resize during encoding</exception> /// <exception cref="InvalidOperationException">attempted frame resize during encoding</exception>
public void AddFrame(IVideoProvider source) public unsafe void AddFrame(IVideoProvider source)
{ {
const int AVIIF_KEYFRAME = 0x00000010; const int AVIIF_KEYFRAME = 0x00000010;
@ -897,40 +908,42 @@ namespace BizHawk.Client.EmuHawk
|| _parameters.height != source.BufferHeight) || _parameters.height != source.BufferHeight)
throw new InvalidOperationException("video buffer changed between start and now"); throw new InvalidOperationException("video buffer changed between start and now");
int pitch_add = _parameters.pitch_add; var pitch_add = _parameters.pitch_add;
int todo = _parameters.pitch * _parameters.height; var todo = _parameters.pitch * _parameters.height;
int w = source.BufferWidth; var w = source.BufferWidth;
int h = source.BufferHeight; var h = source.BufferHeight;
if (!_bit32) if (!_bit32)
{ {
IntPtr buf = GetStaticGlobalBuf(todo); var buf = GetStaticGlobalBuf(todo);
// TODO - would using a byte* be faster? // TODO - would using a byte* be faster?
int[] buffer = source.GetVideoBuffer(); var buffer = source.GetVideoBuffer();
fixed (int* buffer_ptr = &buffer[0]) fixed (int* buffer_ptr = buffer)
{ {
byte* bytes_ptr = (byte*)buf.ToPointer(); var bytes_ptr = (byte*)buf.ToPointer();
{ {
byte* bp = bytes_ptr; var bp = bytes_ptr;
for (int idx = w * h - w, y = 0; y < h; y++) for (int idx = w * h - w, y = 0; y < h; y++)
{ {
for (int x = 0; x < w; x++, idx++) for (var x = 0; x < w; x++, idx++)
{ {
int r = (buffer[idx] >> 0) & 0xFF; var r = (buffer_ptr[idx] >> 0) & 0xFF;
int g = (buffer[idx] >> 8) & 0xFF; var g = (buffer_ptr[idx] >> 8) & 0xFF;
int b = (buffer[idx] >> 16) & 0xFF; var b = (buffer_ptr[idx] >> 16) & 0xFF;
*bp++ = (byte)r; *bp++ = (byte)r;
*bp++ = (byte)g; *bp++ = (byte)g;
*bp++ = (byte)b; *bp++ = (byte)b;
} }
idx -= w * 2; idx -= w * 2;
bp += pitch_add; bp += pitch_add;
} }
int ret = AVIWriterImports.AVIStreamWrite(_pAviCompressedVideoStream, _outStatus.video_frames, 1, new IntPtr(bytes_ptr), todo, AVIIF_KEYFRAME, IntPtr.Zero, out var bytes_written); _ = AVIWriterImports.AVIStreamWrite(_pAviCompressedVideoStream, _outStatus.video_frames,
1, new(bytes_ptr), todo, AVIIF_KEYFRAME, IntPtr.Zero, out var bytes_written);
_outStatus.video_bytes += bytes_written; _outStatus.video_bytes += bytes_written;
_outStatus.video_frames++; _outStatus.video_frames++;
} }
@ -938,30 +951,32 @@ namespace BizHawk.Client.EmuHawk
} }
else // 32 bit else // 32 bit
{ {
IntPtr buf = GetStaticGlobalBuf(todo * 4); var buf = GetStaticGlobalBuf(todo * 4);
int[] buffer = source.GetVideoBuffer(); var buffer = source.GetVideoBuffer();
fixed (int* buffer_ptr = &buffer[0]) fixed (int* buffer_ptr = buffer)
{ {
byte* bytes_ptr = (byte*)buf.ToPointer(); var bytes_ptr = (byte*)buf.ToPointer();
{ {
byte* bp = bytes_ptr; var bp = bytes_ptr;
for (int idx = w * h - w, y = 0; y < h; y++) for (int idx = w * h - w, y = 0; y < h; y++)
{ {
for (int x = 0; x < w; x++, idx++) for (var x = 0; x < w; x++, idx++)
{ {
int r = (buffer[idx] >> 0) & 0xFF; var r = (buffer_ptr[idx] >> 0) & 0xFF;
int g = (buffer[idx] >> 8) & 0xFF; var g = (buffer_ptr[idx] >> 8) & 0xFF;
int b = (buffer[idx] >> 16) & 0xFF; var b = (buffer_ptr[idx] >> 16) & 0xFF;
*bp++ = (byte)r; *bp++ = (byte)r;
*bp++ = (byte)g; *bp++ = (byte)g;
*bp++ = (byte)b; *bp++ = (byte)b;
*bp++ = 0; *bp++ = 0;
} }
idx -= w * 2; idx -= w * 2;
} }
int ret = AVIWriterImports.AVIStreamWrite(_pAviCompressedVideoStream, _outStatus.video_frames, 1, new IntPtr(bytes_ptr), todo * 3, AVIIF_KEYFRAME, IntPtr.Zero, out var bytes_written); _ = AVIWriterImports.AVIStreamWrite(_pAviCompressedVideoStream, _outStatus.video_frames,
1, new(bytes_ptr), todo * 3, AVIIF_KEYFRAME, IntPtr.Zero, out var bytes_written);
_outStatus.video_bytes += bytes_written; _outStatus.video_bytes += bytes_written;
_outStatus.video_frames++; _outStatus.video_frames++;
} }
@ -974,13 +989,11 @@ namespace BizHawk.Client.EmuHawk
public void SetDefaultVideoCodecToken(Config config) public void SetDefaultVideoCodecToken(Config config)
{ {
var ct = CodecToken.DeSerialize(config.AviCodecToken); var ct = CodecToken.DeSerialize(config.AviCodecToken);
_currVideoCodecToken = ct ?? throw new Exception($"No default {nameof(config.AviCodecToken)} in config!"); _currVideoCodecToken = ct ?? throw new($"No default {nameof(config.AviCodecToken)} in config!");
} }
public string DesiredExtension() public string DesiredExtension()
{ => "avi";
return "avi";
}
public bool UsesAudio => _parameters.has_audio; public bool UsesAudio => _parameters.has_audio;

View File

@ -7,6 +7,8 @@ using System.Windows.Forms;
using BizHawk.Common; using BizHawk.Common;
using static BizHawk.Common.ShlobjImports;
namespace BizHawk.Client.EmuHawk namespace BizHawk.Client.EmuHawk
{ {
/// <summary> /// <summary>
@ -21,8 +23,8 @@ namespace BizHawk.Client.EmuHawk
/// </remarks> /// </remarks>
public sealed class FolderBrowserEx : Component public sealed class FolderBrowserEx : Component
{ {
/// <remarks>is this supposed to be public? we're obviously not using it at callsites at the moment --yoshi</remarks> private const BROWSEINFO.FLAGS BrowseOptions = BROWSEINFO.FLAGS.RestrictToFilesystem | BROWSEINFO.FLAGS.RestrictToDomain |
private readonly Win32Imports.BROWSEINFO.FLAGS publicOptions = Win32Imports.BROWSEINFO.FLAGS.RestrictToFilesystem | Win32Imports.BROWSEINFO.FLAGS.RestrictToDomain; BROWSEINFO.FLAGS.NewDialogStyle | BROWSEINFO.FLAGS.ShowTextBox;
public string Description = "Please select a folder below:"; public string Description = "Please select a folder below:";
@ -31,54 +33,78 @@ namespace BizHawk.Client.EmuHawk
/// <summary>Shows the folder browser dialog box with the specified owner window.</summary> /// <summary>Shows the folder browser dialog box with the specified owner window.</summary>
public DialogResult ShowDialog(IWin32Window owner = null) public DialogResult ShowDialog(IWin32Window owner = null)
{ {
const Win32Imports.BROWSEINFO.FLAGS privateOptions = Win32Imports.BROWSEINFO.FLAGS.NewDialogStyle | Win32Imports.BROWSEINFO.FLAGS.ShowTextBox;
const int startLocation = 0; // = Desktop CSIDL const int startLocation = 0; // = Desktop CSIDL
int Callback(IntPtr hwnd, uint uMsg, IntPtr lParam, IntPtr lpData) int Callback(IntPtr hwnd, uint uMsg, IntPtr lParam, IntPtr lpData)
{ {
if (uMsg == 1) if (uMsg == BFFM_INITIALIZED)
{ {
var str = Marshal.StringToHGlobalUni(SelectedPath); var str = Marshal.StringToHGlobalUni(SelectedPath);
Win32Imports.SendMessage(hwnd, 0x400 + 103, (IntPtr) 1, str); try
{
WmImports.SendMessage(hwnd, BFFM_SETSELECTIONW, new(1), str);
}
finally
{
Marshal.FreeHGlobal(str); Marshal.FreeHGlobal(str);
} }
}
return 0; return 0;
} }
var hWndOwner = owner?.Handle ?? Win32Imports.GetActiveWindow(); var hWndOwner = owner?.Handle ?? WmImports.GetActiveWindow();
_ = Win32Imports.SHGetSpecialFolderLocation(hWndOwner, startLocation, out var pidlRoot); _ = SHGetSpecialFolderLocation(hWndOwner, startLocation, out var pidlRoot);
if (pidlRoot == IntPtr.Zero) return DialogResult.Cancel; if (pidlRoot == IntPtr.Zero)
var mergedOptions = publicOptions | privateOptions;
if ((mergedOptions & Win32Imports.BROWSEINFO.FLAGS.NewDialogStyle) != 0 && ApartmentState.MTA == Application.OleRequired())
{ {
mergedOptions &= ~Win32Imports.BROWSEINFO.FLAGS.NewDialogStyle; return DialogResult.Cancel;
} }
IntPtr pidlRet = default; var browseOptions = BrowseOptions;
if (ApartmentState.MTA == Application.OleRequired())
{
browseOptions &= ~BROWSEINFO.FLAGS.NewDialogStyle;
}
var pidlRet = IntPtr.Zero;
try try
{ {
var buffer = Marshal.AllocHGlobal(Win32Imports.MAX_PATH); var buffer = Marshal.AllocHGlobal(Win32Imports.MAX_PATH);
var bi = new Win32Imports.BROWSEINFO var bi = new BROWSEINFO
{ {
hwndOwner = hWndOwner, hwndOwner = hWndOwner,
pidlRoot = pidlRoot, pidlRoot = pidlRoot,
pszDisplayName = buffer, pszDisplayName = buffer,
lpszTitle = Description, lpszTitle = Description,
ulFlags = mergedOptions, ulFlags = browseOptions,
lpfn = Callback lpfn = Callback
}; };
pidlRet = Win32Imports.SHBrowseForFolder(ref bi);
pidlRet = SHBrowseForFolder(ref bi);
Marshal.FreeHGlobal(buffer); Marshal.FreeHGlobal(buffer);
if (pidlRet == IntPtr.Zero) return DialogResult.Cancel; // user clicked Cancel if (pidlRet == IntPtr.Zero)
var sb = new StringBuilder(Win32Imports.MAX_PATH); {
if (Win32Imports.SHGetPathFromIDList(pidlRet, sb) == 0) return DialogResult.Cancel; return DialogResult.Cancel; // user clicked Cancel
SelectedPath = sb.ToString(); }
var path = new StringBuilder(Win32Imports.MAX_PATH);
if (SHGetPathFromIDList(pidlRet, path) == 0)
{
return DialogResult.Cancel;
}
SelectedPath = path.ToString();
} }
finally finally
{ {
_ = Win32Imports.SHGetMalloc(out var malloc); _ = SHGetMalloc(out var malloc);
malloc.Free(pidlRoot); malloc.Free(pidlRoot);
if (pidlRet != IntPtr.Zero) malloc.Free(pidlRet);
if (pidlRet != IntPtr.Zero)
{
malloc.Free(pidlRet);
} }
}
return DialogResult.OK; return DialogResult.OK;
} }
} }

View File

@ -63,7 +63,11 @@ namespace BizHawk.Client.EmuHawk
protected override void OnMouseClick(MouseEventArgs e) protected override void OnMouseClick(MouseEventArgs e)
{ {
if (!OSTailoredCode.IsUnixHost) Win32Imports.HideCaret(Handle); if (!OSTailoredCode.IsUnixHost)
{
WmImports.HideCaret(Handle);
}
base.OnMouseClick(e); base.OnMouseClick(e);
} }
@ -253,7 +257,10 @@ namespace BizHawk.Client.EmuHawk
protected override void OnGotFocus(EventArgs e) protected override void OnGotFocus(EventArgs e)
{ {
if (!OSTailoredCode.IsUnixHost) Win32Imports.HideCaret(Handle); if (!OSTailoredCode.IsUnixHost)
{
WmImports.HideCaret(Handle);
}
} }
protected override bool ProcessCmdKey(ref Message msg, Keys keyData) protected override bool ProcessCmdKey(ref Message msg, Keys keyData)

View File

@ -10,11 +10,14 @@ using System.Linq;
using System.Reflection; using System.Reflection;
using System.Text; using System.Text;
using System.Windows.Forms; using System.Windows.Forms;
using BizHawk.Client.Common; using BizHawk.Client.Common;
using BizHawk.Common; using BizHawk.Common;
using BizHawk.Common.ReflectionExtensions; using BizHawk.Common.ReflectionExtensions;
using BizHawk.Emulation.Common; using BizHawk.Emulation.Common;
using static BizHawk.Common.CommctrlImports;
namespace BizHawk.Client.EmuHawk namespace BizHawk.Client.EmuHawk
{ {
public static class ControlExtensions public static class ControlExtensions
@ -183,32 +186,42 @@ namespace BizHawk.Client.EmuHawk
/// <exception cref="Win32Exception">unmanaged call failed</exception> /// <exception cref="Win32Exception">unmanaged call failed</exception>
public static void SetSortIcon(this ListView listViewControl, int columnIndex, SortOrder order) public static void SetSortIcon(this ListView listViewControl, int columnIndex, SortOrder order)
{ {
if (OSTailoredCode.IsUnixHost) return; if (OSTailoredCode.IsUnixHost)
{
return;
}
const int LVM_GETHEADER = 4127; var columnHeader = WmImports.SendMessage(listViewControl.Handle, LVM_GETHEADER, IntPtr.Zero, IntPtr.Zero);
const int HDM_GETITEM = 4619;
const int HDM_SETITEM = 4620;
var columnHeader = Win32Imports.SendMessage(listViewControl.Handle, LVM_GETHEADER, IntPtr.Zero, IntPtr.Zero);
for (int columnNumber = 0, l = listViewControl.Columns.Count; columnNumber < l; columnNumber++) for (int columnNumber = 0, l = listViewControl.Columns.Count; columnNumber < l; columnNumber++)
{ {
var columnPtr = new IntPtr(columnNumber); var columnPtr = new IntPtr(columnNumber);
var item = new Win32Imports.HDITEM { mask = Win32Imports.HDITEM.Mask.Format }; var item = new HDITEM { mask = HDITEM.Mask.Format };
if (Win32Imports.SendMessage(columnHeader, HDM_GETITEM, columnPtr, ref item) == IntPtr.Zero) throw new Win32Exception(); if (SendMessage(columnHeader, HDM_GETITEM, columnPtr, ref item) == IntPtr.Zero)
{
throw new Win32Exception();
}
if (columnNumber != columnIndex || order == SortOrder.None) if (columnNumber != columnIndex || order == SortOrder.None)
{ {
item.fmt &= ~Win32Imports.HDITEM.Format.SortDown & ~Win32Imports.HDITEM.Format.SortUp; item.fmt &= ~HDITEM.Format.SortDown & ~HDITEM.Format.SortUp;
} }
else if (order == SortOrder.Ascending) // ReSharper disable once SwitchStatementMissingSomeEnumCasesNoDefault
else switch (order)
{ {
item.fmt &= ~Win32Imports.HDITEM.Format.SortDown; case SortOrder.Ascending:
item.fmt |= Win32Imports.HDITEM.Format.SortUp; item.fmt &= ~HDITEM.Format.SortDown;
item.fmt |= HDITEM.Format.SortUp;
break;
case SortOrder.Descending:
item.fmt &= ~HDITEM.Format.SortUp;
item.fmt |= HDITEM.Format.SortDown;
break;
} }
else if (order == SortOrder.Descending)
if (SendMessage(columnHeader, HDM_SETITEM, columnPtr, ref item) == IntPtr.Zero)
{ {
item.fmt &= ~Win32Imports.HDITEM.Format.SortUp; throw new Win32Exception();
item.fmt |= Win32Imports.HDITEM.Format.SortDown;
} }
if (Win32Imports.SendMessage(columnHeader, HDM_SETITEM, columnPtr, ref item) == IntPtr.Zero) throw new Win32Exception();
} }
} }

View File

@ -868,7 +868,7 @@ namespace BizHawk.Client.EmuHawk
{ {
groupFreeze.SuspendLayout(); groupFreeze.SuspendLayout();
Win32Imports.SendMessage(groupFreeze.Handle, 11, (IntPtr)0, IntPtr.Zero); //WM_SETREDRAW false WmImports.SendMessage(groupFreeze.Handle, 11, (IntPtr)0, IntPtr.Zero); //WM_SETREDRAW false
var tp = tabctrlDetails.SelectedTab; var tp = tabctrlDetails.SelectedTab;
@ -886,7 +886,7 @@ namespace BizHawk.Client.EmuHawk
groupFreeze.ResumeLayout(); groupFreeze.ResumeLayout();
Win32Imports.SendMessage(groupFreeze.Handle, 11, (IntPtr)1, IntPtr.Zero); //WM_SETREDRAW true WmImports.SendMessage(groupFreeze.Handle, 11, (IntPtr)1, IntPtr.Zero); //WM_SETREDRAW true
groupFreeze.Refresh(); groupFreeze.Refresh();
} }

View File

@ -82,7 +82,7 @@ namespace BizHawk.Common.PathExtensions
if (File.Exists(path1.SubstringBefore('|'))) return FileAttributes.Normal; if (File.Exists(path1.SubstringBefore('|'))) return FileAttributes.Normal;
throw new FileNotFoundException(); throw new FileNotFoundException();
} }
var path = new StringBuilder(260 /* = MAX_PATH */); var path = new StringBuilder(Win32Imports.MAX_PATH);
return Win32Imports.PathRelativePathTo(path, fromPath, GetPathAttribute(fromPath), toPath, GetPathAttribute(toPath)) return Win32Imports.PathRelativePathTo(path, fromPath, GetPathAttribute(fromPath), toPath, GetPathAttribute(toPath))
? path.ToString() ? path.ToString()
: throw new ArgumentException(message: "Paths must have a common prefix", paramName: nameof(toPath)); : throw new ArgumentException(message: "Paths must have a common prefix", paramName: nameof(toPath));

View File

@ -1,6 +1,6 @@
using System; using System;
using static BizHawk.Common.Kernel32Imports; using static BizHawk.Common.MemoryApiImports;
using static BizHawk.Common.MemoryBlock; using static BizHawk.Common.MemoryBlock;
namespace BizHawk.Common namespace BizHawk.Common

View File

@ -4,6 +4,8 @@
using System; using System;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
// ReSharper disable FieldCanBeMadeReadOnly.Global
namespace BizHawk.Common namespace BizHawk.Common
{ {
public static class AVIWriterImports public static class AVIWriterImports
@ -35,7 +37,8 @@ namespace BizHawk.Common
public RECT rcFrame; public RECT rcFrame;
public int dwEditCount; public int dwEditCount;
public int dwFormatChangeCount; public int dwFormatChangeCount;
[MarshalAs(UnmanagedType.LPWStr, SizeConst=64)] public string szName; [MarshalAs(UnmanagedType.LPWStr, SizeConst = 64)]
public string szName;
[StructLayout(LayoutKind.Sequential, Pack = 1)] [StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct RECT public struct RECT
@ -63,9 +66,7 @@ namespace BizHawk.Common
public uint biClrImportant; public uint biClrImportant;
public void Init() public void Init()
{ => biSize = (uint)Marshal.SizeOf(this);
biSize = (uint)Marshal.SizeOf(this);
}
} }
[StructLayout(LayoutKind.Sequential)] [StructLayout(LayoutKind.Sequential)]
@ -96,9 +97,7 @@ namespace BizHawk.Common
public ushort cbSize; public ushort cbSize;
public void Init() public void Init()
{ => cbSize = (ushort)Marshal.SizeOf(this);
cbSize = (ushort)Marshal.SizeOf(this);
}
} }
/// <summary>Create a new stream in an existing file and creates an interface to the new stream</summary> /// <summary>Create a new stream in an existing file and creates an interface to the new stream</summary>

View File

@ -1,38 +1,60 @@
using System; using System;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text;
namespace BizHawk.Common namespace BizHawk.Common
{ {
/// <summary>Gets/Sets the current working directory while bypassing the security checks triggered by the public API (<see cref="Environment.CurrentDirectory"/>).</summary> /// <summary>Gets/Sets the current working directory while bypassing the security checks triggered by the public API (<see cref="Environment.CurrentDirectory"/>).</summary>
public static unsafe class CWDHacks public static class CWDHacks
{ {
private const uint BUFFER_LEN = 0x200U; [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true, ExactSpelling = true)]
private static extern unsafe int GetCurrentDirectoryW(int nBufferLength, char* lpBuffer);
private static readonly byte[] BUFFER = new byte[BUFFER_LEN]; [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true, ExactSpelling = true)]
private static extern bool SetCurrentDirectoryW(string lpPathName);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern uint GetCurrentDirectoryW(uint nBufferLength, byte* pBuffer);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool SetCurrentDirectoryW(byte* lpPathName);
public static bool Set(string newCWD) public static bool Set(string newCWD)
=> SetCurrentDirectoryW(newCWD);
public static unsafe string Get()
{ {
fixed (byte* pstr = &Encoding.Unicode.GetBytes($"{newCWD}\0")[0]) static Exception GetExceptionForFailure()
return SetCurrentDirectoryW(pstr); {
var ex = Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error())
?? new("Marshal.GetExceptionForHR returned null?");
return new InvalidOperationException("GetCurrentDirectoryW returned 0!", ex);
} }
public static string Get() const int STARTING_BUF_SIZE = Win32Imports.MAX_PATH + 1;
var startingBuffer = stackalloc char[STARTING_BUF_SIZE];
var ret = GetCurrentDirectoryW(STARTING_BUF_SIZE, startingBuffer);
switch (ret)
{ {
uint result; case 0:
fixed (byte* pBuf = &BUFFER[0]) result = GetCurrentDirectoryW(BUFFER_LEN, pBuf); throw GetExceptionForFailure();
if (result <= BUFFER_LEN && result is not 0U) return Encoding.Unicode.GetString(BUFFER, 0, (int) (2U * result)); case < STARTING_BUF_SIZE: // ret should be smaller than the buffer, as ret doesn't include null terminator
var buf = new byte[result]; return new(startingBuffer, 0, ret);
uint result1; }
fixed (byte* pBuf = &buf[0]) result1 = GetCurrentDirectoryW(BUFFER_LEN, pBuf);
if (result1 == result) return Encoding.Unicode.GetString(buf, 0, (int) (2U * result)); // since current directory could suddenly grow (due to it being global / modifiable by other threads), a while true loop is used here
throw new Exception(); // although it's fairly unlikely we'll even reach this point, MAX_PATH can only be bypassed under certain circumstances
while (true)
{
var bufSize = ret + 1;
var buffer = new char[bufSize];
fixed (char* p = buffer)
{
ret = GetCurrentDirectoryW(bufSize, p);
if (ret == 0)
{
throw GetExceptionForFailure();
}
if (ret < bufSize)
{
return new(p, 0, ret);
}
}
}
} }
} }
} }

View File

@ -0,0 +1,57 @@
#nullable disable
using System;
using System.Runtime.InteropServices;
// ReSharper disable FieldCanBeMadeReadOnly.Global
// ReSharper disable UnusedMember.Global
namespace BizHawk.Common
{
public static class CommctrlImports
{
public const int LVM_GETHEADER = 4127;
public const int HDM_GETITEM = 4619;
public const int HDM_SETITEM = 4620;
[StructLayout(LayoutKind.Sequential)]
public struct HDITEM
{
public Mask mask;
public int cxy;
[MarshalAs(UnmanagedType.LPTStr)]
public string pszText;
public IntPtr hbm;
public int cchTextMax;
public Format fmt;
public IntPtr lParam;
// _WIN32_IE >= 0x0300
public int iImage;
public int iOrder;
// _WIN32_IE >= 0x0500
public uint type;
public IntPtr pvFilter;
// _WIN32_WINNT >= 0x0600
public uint state;
[Flags]
public enum Mask
{
Format = 0x4
}
[Flags]
public enum Format
{
SortDown = 0x200,
SortUp = 0x400
}
}
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr SendMessage(IntPtr hWnd, uint msg, IntPtr wParam, ref HDITEM lParam);
}
}

View File

@ -0,0 +1,22 @@
using System;
using System.Runtime.InteropServices;
// ReSharper disable FieldCanBeMadeReadOnly.Global
// ReSharper disable UnusedMember.Global
namespace BizHawk.Common
{
public static class HeapApiImports
{
[DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr GetProcessHeap();
[DllImport("kernel32.dll", SetLastError = false)]
public static extern IntPtr HeapAlloc(IntPtr hHeap, uint dwFlags, int dwBytes);
/// <remarks>used in <c>#if false</c> code in <c>AviWriter.CodecToken.DeallocateAVICOMPRESSOPTIONS</c>, don't delete it</remarks>
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool HeapFree(IntPtr hHeap, uint dwFlags, IntPtr lpMem);
}
}

View File

@ -2,10 +2,11 @@
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
// ReSharper disable FieldCanBeMadeReadOnly.Global // ReSharper disable FieldCanBeMadeReadOnly.Global
// ReSharper disable UnusedMember.Global
namespace BizHawk.Common namespace BizHawk.Common
{ {
public static class Kernel32Imports public static class MemoryApiImports
{ {
[Flags] [Flags]
public enum AllocationType : uint public enum AllocationType : uint

View File

@ -3,6 +3,7 @@ namespace BizHawk.Common
/// <remarks>This code (and an import for <see cref="Win32Imports.DeleteFileW"/>) is duplicated in each executable project because it needs to be used before loading assemblies.</remarks> /// <remarks>This code (and an import for <see cref="Win32Imports.DeleteFileW"/>) is duplicated in each executable project because it needs to be used before loading assemblies.</remarks>
public static class MotWHack public static class MotWHack
{ {
public static void RemoveMOTW(string path) => Win32Imports.DeleteFileW($"{path}:Zone.Identifier"); public static void RemoveMOTW(string path)
=> Win32Imports.DeleteFileW($"{path}:Zone.Identifier");
} }
} }

View File

@ -4,6 +4,8 @@ using System;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text; using System.Text;
// ReSharper disable FieldCanBeMadeReadOnly.Global
namespace BizHawk.Common namespace BizHawk.Common
{ {
public static class ShellLinkImports public static class ShellLinkImports
@ -19,15 +21,21 @@ namespace BizHawk.Common
public uint nFileSizeLow; public uint nFileSizeLow;
public uint dwReserved0; public uint dwReserved0;
public uint dwReserved1; public uint dwReserved1;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)] public string cFileName; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 14)] public string cAlternateFileName; public string cFileName;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 14)]
public string cAlternateFileName;
} }
[Flags] [Flags]
public enum SLGP_FLAGS {} public enum SLGP_FLAGS
{
}
[Flags] [Flags]
public enum SLR_FLAGS {} public enum SLR_FLAGS
{
}
/// <summary>The IShellLink interface allows Shell links to be created, modified, and resolved</summary> /// <summary>The IShellLink interface allows Shell links to be created, modified, and resolved</summary>
[ComImport] [ComImport]
@ -108,6 +116,8 @@ namespace BizHawk.Common
/// <remarks>CLSID_ShellLink from ShlGuid.h</remarks> /// <remarks>CLSID_ShellLink from ShlGuid.h</remarks>
[ComImport] [ComImport]
[Guid("00021401-0000-0000-C000-000000000046")] [Guid("00021401-0000-0000-C000-000000000046")]
public class ShellLink /* : IPersistFile, IShellLinkW */ {} public class ShellLink /* : IPersistFile, IShellLinkW */
{
}
} }
} }

View File

@ -0,0 +1,93 @@
#nullable disable
using System;
using System.Runtime.InteropServices;
using System.Text;
// ReSharper disable FieldCanBeMadeReadOnly.Global
// ReSharper disable UnusedMember.Global
namespace BizHawk.Common
{
public static class ShlobjImports
{
public const int BFFM_INITIALIZED = 1;
public const int BFFM_SETSELECTIONW = 0x400 + 103;
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
public delegate int BFFCALLBACK(IntPtr hwnd, uint uMsg, IntPtr lParam, IntPtr lpData);
[StructLayout(LayoutKind.Sequential, Pack = 8)]
public struct BROWSEINFO
{
public IntPtr hwndOwner;
public IntPtr pidlRoot;
public IntPtr pszDisplayName;
[MarshalAs(UnmanagedType.LPTStr)]
public string lpszTitle;
public FLAGS ulFlags;
[MarshalAs(UnmanagedType.FunctionPtr)]
public BFFCALLBACK lpfn;
public IntPtr lParam;
public int iImage;
[Flags]
public enum FLAGS
{
/// <remarks>BIF_RETURNONLYFSDIRS</remarks>
RestrictToFilesystem = 0x0001,
/// <remarks>BIF_DONTGOBELOWDOMAIN</remarks>
RestrictToDomain = 0x0002,
/// <remarks>BIF_RETURNFSANCESTORS</remarks>
RestrictToSubfolders = 0x0008,
/// <remarks>BIF_EDITBOX</remarks>
ShowTextBox = 0x0010,
/// <remarks>BIF_VALIDATE</remarks>
ValidateSelection = 0x0020,
/// <remarks>BIF_NEWDIALOGSTYLE</remarks>
NewDialogStyle = 0x0040,
/// <remarks>BIF_BROWSEFORCOMPUTER</remarks>
BrowseForComputer = 0x1000,
/// <remarks>BIF_BROWSEFORPRINTER</remarks>
BrowseForPrinter = 0x2000,
/// <remarks>BIF_BROWSEINCLUDEFILES</remarks>
BrowseForEverything = 0x4000
}
}
[Guid("00000002-0000-0000-C000-000000000046")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IMalloc
{
[PreserveSig]
IntPtr Alloc([In] int cb);
[PreserveSig]
IntPtr Realloc([In] IntPtr pv, [In] int cb);
[PreserveSig]
void Free([In] IntPtr pv);
[PreserveSig]
int GetSize([In] IntPtr pv);
[PreserveSig]
int DidAlloc(IntPtr pv);
[PreserveSig]
void HeapMinimize();
}
[DllImport("shell32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr SHBrowseForFolder(ref BROWSEINFO bi);
[DllImport("shell32.dll")]
public static extern int SHGetMalloc(out IMalloc ppMalloc);
[DllImport("shell32.dll", CharSet = CharSet.Auto)]
public static extern int SHGetPathFromIDList(IntPtr pidl, StringBuilder pszPath);
[DllImport("shell32.dll")]
public static extern int SHGetSpecialFolderLocation(IntPtr hwndOwner, int nFolder, out IntPtr ppidl);
}
}

View File

@ -15,35 +15,11 @@ namespace BizHawk.Common
{ {
public const uint QS_ALLINPUT = 0x4FFU; public const uint QS_ALLINPUT = 0x4FFU;
public const uint MWMO_INPUTAVAILABLE = 0x0004U; public const uint MWMO_INPUTAVAILABLE = 0x0004U;
public const uint PM_REMOVE = 0x0001U;
[StructLayout(LayoutKind.Sequential)]
public struct MSG
{
public IntPtr hwnd;
public uint message;
public IntPtr wParam;
public IntPtr lParam;
public uint time;
public int x;
public int y;
}
[DllImport("user32.dll")]
public static extern IntPtr DispatchMessage([In] ref MSG lpMsg);
[DllImport("user32.dll", SetLastError = true)] [DllImport("user32.dll", SetLastError = true)]
public static extern uint MsgWaitForMultipleObjectsEx(uint nCount, IntPtr[] pHandles, uint dwMilliseconds, uint dwWakeMask, uint dwFlags); public static extern uint MsgWaitForMultipleObjectsEx(uint nCount, IntPtr[] pHandles, uint dwMilliseconds, uint dwWakeMask, uint dwFlags);
[DllImport("user32.dll")] [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool PeekMessage(out MSG lpMsg, IntPtr hWnd, uint wMsgFilterMin, uint wMsgFilterMax, uint wRemoveMsg);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool TranslateMessage([In] ref MSG lpMsg);
[DllImport("kernel32", SetLastError = true, ExactSpelling = true)]
public static extern int WaitForSingleObject(SafeWaitHandle handle, uint milliseconds); public static extern int WaitForSingleObject(SafeWaitHandle handle, uint milliseconds);
} }
} }

View File

@ -7,227 +7,29 @@ using System.Text;
namespace BizHawk.Common namespace BizHawk.Common
{ {
/// <summary>
/// This is more just an assorted bunch of Win32 functions
/// </summary>
public static class Win32Imports public static class Win32Imports
{ {
public const int MAX_PATH = 260; public const int MAX_PATH = 260;
public const uint PM_REMOVE = 0x0001U;
public static readonly IntPtr HWND_MESSAGE = new(-3);
public const int GWLP_USERDATA = -21;
public delegate int BFFCALLBACK(IntPtr hwnd, uint uMsg, IntPtr lParam, IntPtr lpData); [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode, ExactSpelling = true)]
[StructLayout(LayoutKind.Sequential, Pack = 8)]
public struct BROWSEINFO
{
public IntPtr hwndOwner;
public IntPtr pidlRoot;
public IntPtr pszDisplayName;
[MarshalAs(UnmanagedType.LPTStr)] public string lpszTitle;
public FLAGS ulFlags;
[MarshalAs(UnmanagedType.FunctionPtr)] public BFFCALLBACK lpfn;
public IntPtr lParam;
public int iImage;
[Flags]
public enum FLAGS
{
/// <remarks>BIF_RETURNONLYFSDIRS</remarks>
RestrictToFilesystem = 0x0001,
/// <remarks>BIF_DONTGOBELOWDOMAIN</remarks>
RestrictToDomain = 0x0002,
/// <remarks>BIF_RETURNFSANCESTORS</remarks>
RestrictToSubfolders = 0x0008,
/// <remarks>BIF_EDITBOX</remarks>
ShowTextBox = 0x0010,
/// <remarks>BIF_VALIDATE</remarks>
ValidateSelection = 0x0020,
/// <remarks>BIF_NEWDIALOGSTYLE</remarks>
NewDialogStyle = 0x0040,
/// <remarks>BIF_BROWSEFORCOMPUTER</remarks>
BrowseForComputer = 0x1000,
/// <remarks>BIF_BROWSEFORPRINTER</remarks>
BrowseForPrinter = 0x2000,
/// <remarks>BIF_BROWSEINCLUDEFILES</remarks>
BrowseForEverything = 0x4000
}
}
[StructLayout(LayoutKind.Sequential)]
public struct HDITEM
{
public Mask mask;
public int cxy;
[MarshalAs(UnmanagedType.LPTStr)] public string pszText;
public IntPtr hbm;
public int cchTextMax;
public Format fmt;
public IntPtr lParam;
// _WIN32_IE >= 0x0300
public int iImage;
public int iOrder;
// _WIN32_IE >= 0x0500
public uint type;
public IntPtr pvFilter;
// _WIN32_WINNT >= 0x0600
public uint state;
[Flags]
public enum Mask
{
Format = 0x4
}
[Flags]
public enum Format
{
SortDown = 0x200,
SortUp = 0x400
}
}
[StructLayout(LayoutKind.Sequential)]
public struct MSG
{
public IntPtr hwnd;
public uint message;
public IntPtr wParam;
public IntPtr lParam;
public uint time;
public int x;
public int y;
}
public delegate IntPtr WNDPROC(IntPtr hWnd, uint uMsg, IntPtr wParam, IntPtr lParam);
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct WNDCLASS
{
public uint style;
public WNDPROC lpfnWndProc;
public int cbClsExtra;
public int cbWndExtra;
public IntPtr hInstance;
public IntPtr hIcon;
public IntPtr hCursor;
public IntPtr hbrBackground;
public string lpszMenuName;
public string lpszClassName;
}
[Guid("00000002-0000-0000-C000-000000000046")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IMalloc
{
[PreserveSig] IntPtr Alloc([In] int cb);
[PreserveSig] IntPtr Realloc([In] IntPtr pv, [In] int cb);
[PreserveSig] void Free([In] IntPtr pv);
[PreserveSig] int GetSize([In] IntPtr pv);
[PreserveSig] int DidAlloc(IntPtr pv);
[PreserveSig] void HeapMinimize();
}
[DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern uint _control87(uint @new, uint mask);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr CreateWindowEx(int dwExStyle, IntPtr lpClassName, string lpWindowName,
int dwStyle, int X, int Y, int nWidth, int nHeight, IntPtr hWndParent, IntPtr hMenu, IntPtr hInstance, IntPtr lpParam);
[DllImport("kernel32.dll", EntryPoint = "DeleteFileW", SetLastError = true, CharSet = CharSet.Unicode, ExactSpelling = true)]
public static extern bool DeleteFileW([MarshalAs(UnmanagedType.LPWStr)] string lpFileName); public static extern bool DeleteFileW([MarshalAs(UnmanagedType.LPWStr)] string lpFileName);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr DefWindowProc(IntPtr hWnd, uint uMsg, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool DestroyWindow(IntPtr hWnd);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr DispatchMessage([In] ref MSG lpMsg);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr FindWindowEx(IntPtr parentHandle, IntPtr hWndChildAfter, string className, string windowTitle);
[DllImport("user32.dll")]
public static extern IntPtr GetActiveWindow();
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr GetModuleHandle(string lpModuleName); public static extern IntPtr GetModuleHandle(string lpModuleName);
[DllImport("kernel32", SetLastError = true, EntryPoint = "GetProcAddress")]
public static extern IntPtr GetProcAddressOrdinal(IntPtr hModule, IntPtr procName);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr GetProcessHeap();
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr GetWindowLongPtr(IntPtr hWnd, int nIndex);
[DllImport("kernel32.dll", SetLastError = false)]
public static extern IntPtr HeapAlloc(IntPtr hHeap, uint dwFlags, int dwBytes);
/// <remarks>used in <c>#if false</c> code in <c>AviWriter.CodecToken.DeallocateAVICOMPRESSOPTIONS</c>, don't delete it</remarks>
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool HeapFree(IntPtr hHeap, uint dwFlags, IntPtr lpMem);
[DllImport("user32")]
public static extern bool HideCaret(IntPtr hWnd);
[DllImport("kernel32.dll")]
public static extern bool IsDebuggerPresent();
[DllImport("user32.dll", CharSet = CharSet.Auto)] [DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern uint MapVirtualKey(uint uCode, uint uMapType); public static extern uint MapVirtualKey(uint uCode, uint uMapType);
[DllImport("msvcrt.dll", EntryPoint = "memset", CallingConvention = CallingConvention.Cdecl, SetLastError = false)]
public static extern IntPtr MemSet(IntPtr dest, int c, uint count);
[DllImport("shlwapi.dll", CharSet = CharSet.Auto)] [DllImport("shlwapi.dll", CharSet = CharSet.Auto)]
public static extern bool PathRelativePathTo([Out] StringBuilder pszPath, [In] string pszFrom, [In] FileAttributes dwAttrFrom, [In] string pszTo, [In] FileAttributes dwAttrTo); public static extern bool PathRelativePathTo([Out] StringBuilder pszPath, [In] string pszFrom, [In] FileAttributes dwAttrFrom, [In] string pszTo, [In] FileAttributes dwAttrTo);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool PeekMessage(out MSG lpMsg, IntPtr hWnd, uint wMsgFilterMin, uint wMsgFilterMax, uint wRemoveMsg);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr PostMessage(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr RegisterClass([In] ref WNDCLASS lpWndClass);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr SendMessage(IntPtr hWnd, uint msg, IntPtr wParam, ref HDITEM lParam);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr SendMessage(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr SetWindowLongPtr(IntPtr hWnd, int nIndex, IntPtr dwNewLong);
[DllImport("shell32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr SHBrowseForFolder(ref BROWSEINFO bi);
[DllImport("shell32.dll")]
public static extern int SHGetMalloc(out IMalloc ppMalloc);
[DllImport("shell32.dll")]
public static extern int SHGetPathFromIDList(IntPtr pidl, StringBuilder Path);
[DllImport("shell32.dll")]
public static extern int SHGetSpecialFolderLocation(IntPtr hwndOwner, int nFolder, out IntPtr ppidl);
[DllImport("user32.dll", CharSet = CharSet.Auto)] [DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern bool SystemParametersInfo(int uAction, int uParam, ref int lpvParam, int flags); public static extern bool SystemParametersInfo(int uAction, int uParam, ref int lpvParam, int flags);
[DllImport("winmm.dll", EntryPoint = "timeBeginPeriod")] [DllImport("winmm.dll")]
public static extern uint timeBeginPeriod(uint uMilliseconds); public static extern uint timeBeginPeriod(uint uMilliseconds);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool TranslateMessage([In] ref MSG lpMsg);
} }
} }

View File

@ -2,6 +2,9 @@
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text; using System.Text;
// ReSharper disable FieldCanBeMadeReadOnly.Global
// ReSharper disable UnusedMember.Global
namespace BizHawk.Common namespace BizHawk.Common
{ {
public class Win32ShellContextMenu public class Win32ShellContextMenu
@ -117,6 +120,7 @@ namespace BizHawk.Common
} }
} }
#pragma warning disable CA1069
[Flags] [Flags]
public enum TPM public enum TPM
{ {
@ -140,6 +144,7 @@ namespace BizHawk.Common
TPM_NOANIMATION = 0x4000, TPM_NOANIMATION = 0x4000,
TPM_LAYOUTRTL = 0x8000, TPM_LAYOUTRTL = 0x8000,
} }
#pragma warning restore CA1069
[Flags] [Flags]
public enum CMF : uint public enum CMF : uint

View File

@ -0,0 +1,90 @@
#nullable disable
using System;
using System.Runtime.InteropServices;
// ReSharper disable FieldCanBeMadeReadOnly.Global
namespace BizHawk.Common
{
public static class WmImports
{
public const uint PM_REMOVE = 0x0001U;
public static readonly IntPtr HWND_MESSAGE = new(-3);
public const int GWLP_USERDATA = -21;
[StructLayout(LayoutKind.Sequential)]
public struct MSG
{
public IntPtr hwnd;
public uint message;
public IntPtr wParam;
public IntPtr lParam;
public uint time;
public int x;
public int y;
}
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
public delegate IntPtr WNDPROC(IntPtr hWnd, uint uMsg, IntPtr wParam, IntPtr lParam);
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct WNDCLASS
{
public uint style;
public WNDPROC lpfnWndProc;
public int cbClsExtra;
public int cbWndExtra;
public IntPtr hInstance;
public IntPtr hIcon;
public IntPtr hCursor;
public IntPtr hbrBackground;
public string lpszMenuName;
public string lpszClassName;
}
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr CreateWindowEx(int dwExStyle, IntPtr lpClassName, string lpWindowName,
int dwStyle, int X, int Y, int nWidth, int nHeight, IntPtr hWndParent, IntPtr hMenu, IntPtr hInstance, IntPtr lpParam);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr DefWindowProc(IntPtr hWnd, uint uMsg, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool DestroyWindow(IntPtr hWnd);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr DispatchMessage([In] ref MSG lpMsg);
[DllImport("user32.dll")]
public static extern IntPtr GetActiveWindow();
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr GetWindowLongPtr(IntPtr hWnd, int nIndex);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool HideCaret(IntPtr hWnd);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool PeekMessage(out MSG lpMsg, IntPtr hWnd, uint wMsgFilterMin, uint wMsgFilterMax, uint wRemoveMsg);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr PostMessage(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr RegisterClass([In] ref WNDCLASS lpWndClass);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr SendMessage(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr SetWindowLongPtr(IntPtr hWnd, int nIndex, IntPtr dwNewLong);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool TranslateMessage([In] ref MSG lpMsg);
}
}

View File

@ -785,10 +785,10 @@ namespace BizHawk.Emulation.Cores.Nintendo.N64.NativeApi
{ {
IntPtr[] waitHandles = { handle.SafeWaitHandle.DangerousGetHandle() }; IntPtr[] waitHandles = { handle.SafeWaitHandle.DangerousGetHandle() };
const uint count = 1; const uint count = 1;
var QS_MASK = ThreadHacks.QS_ALLINPUT; // message queue status var QS_MASK = WmImports.QS_ALLINPUT; // message queue status
QS_MASK = 0; //bizhawk edit?? did we need any messages here?? apparently not??? QS_MASK = 0; //bizhawk edit?? did we need any messages here?? apparently not???
uint nativeResult; uint nativeResult;
ThreadHacks.MSG msg; WmImports.MSG msg;
while (true) while (true)
{ {
// MsgWaitForMultipleObjectsEx with MWMO_INPUTAVAILABLE returns, // MsgWaitForMultipleObjectsEx with MWMO_INPUTAVAILABLE returns,
@ -796,10 +796,10 @@ namespace BizHawk.Emulation.Cores.Nintendo.N64.NativeApi
nativeResult = ThreadHacks.MsgWaitForMultipleObjectsEx(count, waitHandles, 0xFFFFFFFF, QS_MASK, ThreadHacks.MWMO_INPUTAVAILABLE); nativeResult = ThreadHacks.MsgWaitForMultipleObjectsEx(count, waitHandles, 0xFFFFFFFF, QS_MASK, ThreadHacks.MWMO_INPUTAVAILABLE);
if (IsNativeWaitSuccessful(count, nativeResult, out int managedResult) || WaitHandle.WaitTimeout == managedResult) break; if (IsNativeWaitSuccessful(count, nativeResult, out int managedResult) || WaitHandle.WaitTimeout == managedResult) break;
// there is a message, pump and dispatch it // there is a message, pump and dispatch it
if (ThreadHacks.PeekMessage(out msg, IntPtr.Zero, 0, 0, ThreadHacks.PM_REMOVE)) if (WmImports.PeekMessage(out msg, IntPtr.Zero, 0, 0, WmImports.PM_REMOVE))
{ {
ThreadHacks.TranslateMessage(ref msg); WmImports.TranslateMessage(ref msg);
ThreadHacks.DispatchMessage(ref msg); WmImports.DispatchMessage(ref msg);
} }
} }
// handle.WaitOne(); // handle.WaitOne();