ISO639-1 to ISO639-2 conversion. TV series automatic version choice. Stream selection during playback - only transcoding working.
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
@@ -18,6 +18,8 @@ using Microsoft.Extensions.Primitives;
|
||||
using Cinema.Webshare;
|
||||
using CinemaLib.API;
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Controller.MediaEncoding;
|
||||
using MediaBrowser.Model.Dlna;
|
||||
|
||||
namespace Jellyfin.Plugin.Cinema;
|
||||
|
||||
@@ -27,6 +29,10 @@ public sealed class CinemaMediaSourceManager : IMediaSourceManager
|
||||
private const double BitrateMargin = 0.1; // 10 %
|
||||
private const double WebshareFreeBitrate = 300000 * 8;
|
||||
|
||||
private const int FirstRelativeVideoIndex = -2;
|
||||
private const int FirstRelativeAudioIndex = -5;
|
||||
private const int FirstRelativeSubtitleIndex = -15;
|
||||
|
||||
private static readonly TimeSpan VersionValidityTimeout = TimeSpan.FromMinutes(180);
|
||||
private static readonly TimeSpan LinkValidityTimeout = VersionValidityTimeout;
|
||||
internal static readonly TimeSpan SubfolderValidityTimeout = VersionValidityTimeout;
|
||||
@@ -194,6 +200,8 @@ public sealed class CinemaMediaSourceManager : IMediaSourceManager
|
||||
result.Add(GetVersionInfo(i, ver.Versions[idx++].Meta, thisServerBaseUri));
|
||||
|
||||
// For episodes we must choose the audio/subtitle automatically as there is no UI for it
|
||||
// and MediaInfoHelper.SetDeviceSpecificData and deeper StreamBuilder.BuildVideoItem does
|
||||
// a poor job at selecting the proper streams
|
||||
if (video is CinemaEpisode)
|
||||
foreach (MediaSourceInfo i in result)
|
||||
ForceByPreferences(user, i);
|
||||
@@ -237,8 +245,8 @@ public sealed class CinemaMediaSourceManager : IMediaSourceManager
|
||||
return items;
|
||||
|
||||
double? bitrateLimit = IsWebshareFreeAccount ? WebshareFreeBitrate : null;
|
||||
string? audioLang = FixLangToIso(user.AudioLanguagePreference);
|
||||
string? subtitleLang = FixLangToIso(user.SubtitleLanguagePreference);
|
||||
string? audioLang = user.AudioLanguagePreference;
|
||||
string? subtitleLang = user.SubtitleLanguagePreference;
|
||||
int[] scores = new int[items.Length];
|
||||
for (int i = 0; i < items.Length; i++)
|
||||
{
|
||||
@@ -254,7 +262,7 @@ public sealed class CinemaMediaSourceManager : IMediaSourceManager
|
||||
|
||||
if (m.subtitles != null && subtitleLang != null)
|
||||
foreach (StreamSubtitle j in m.subtitles)
|
||||
if (j.language == subtitleLang)
|
||||
if (ISO639_1ToISO639_2(j.language) == subtitleLang)
|
||||
{
|
||||
score += 4; // proper subtitles are preferred but much better resulution still rules
|
||||
break;
|
||||
@@ -262,7 +270,7 @@ public sealed class CinemaMediaSourceManager : IMediaSourceManager
|
||||
|
||||
if (m.audio != null && audioLang != null)
|
||||
foreach (StreamAudio j in m.audio)
|
||||
if (j.language == audioLang)
|
||||
if (ISO639_1ToISO639_2(j.language) == audioLang)
|
||||
{
|
||||
score += 25; // proper audio channel overrides resolution and other minor bonuses
|
||||
break;
|
||||
@@ -282,29 +290,27 @@ public sealed class CinemaMediaSourceManager : IMediaSourceManager
|
||||
return items;
|
||||
}
|
||||
|
||||
private static void ForceByPreferences(User user, MediaSourceInfo item) {
|
||||
private static void ForceByPreferences(User user, MediaSourceInfo item)
|
||||
{
|
||||
if (user == null)
|
||||
// We have no info about the user so nothing to force
|
||||
return;
|
||||
|
||||
string? audioLang = FixLangToIso(user.AudioLanguagePreference);
|
||||
string? subtitleLang = FixLangToIso(user.SubtitleLanguagePreference);
|
||||
string? audioLang = user.AudioLanguagePreference;
|
||||
// Free account prevents seeking and thus subtitle exctraction
|
||||
string? subtitleLang = IsWebshareFreeAccount ? null : user.SubtitleLanguagePreference;
|
||||
|
||||
foreach (var i in item.MediaStreams) {
|
||||
if (i.Type == MediaStreamType.Audio && audioLang != null)
|
||||
i.IsForced = audioLang == i.Language;
|
||||
else if (i.Type == MediaStreamType.Subtitle && subtitleLang != null)
|
||||
i.IsForced = subtitleLang == i.Language;
|
||||
}
|
||||
}
|
||||
|
||||
private static string? FixLangToIso(string? nonIso)
|
||||
{
|
||||
switch (nonIso)
|
||||
foreach (MediaStream i in item.MediaStreams)
|
||||
{
|
||||
case "cze": return "cs";
|
||||
case "eng": return "en";
|
||||
default: return nonIso;
|
||||
// We cannot use DefaultAudioStreamIndex and DefaultSubtitleStreamIndex as we do not
|
||||
// have absolute stream index values in i.Index. Thankfully MediaSourceInfo.GetDefaultAudioStream
|
||||
// uses IsDefault as a fallback.
|
||||
if (i.Type == MediaStreamType.Audio && audioLang != null && audioLang == i.Language)
|
||||
i.IsDefault = true;
|
||||
else if (i.Type == MediaStreamType.Subtitle && subtitleLang != null && subtitleLang == i.Language)
|
||||
i.IsDefault = true;
|
||||
else
|
||||
i.IsDefault = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -548,12 +554,17 @@ public sealed class CinemaMediaSourceManager : IMediaSourceManager
|
||||
//stream.media
|
||||
//stream.date_added
|
||||
|
||||
// We must assign MediaStream.Index to something at least unique as this
|
||||
// values is also passed from client (ie. to identify transcoding channel).
|
||||
// Therefore use clearly insane values. Also -1 seems to be used on many places
|
||||
// as "no channel" so start with -2.
|
||||
if (ver.video != null)
|
||||
{
|
||||
int uniqueId = FirstRelativeVideoIndex;
|
||||
foreach (StreamVideo j in ver.video)
|
||||
{
|
||||
MediaStream a = new MediaStream();
|
||||
result.Add(a);
|
||||
a.Index = uniqueId--;
|
||||
a.Type = MediaStreamType.Video;
|
||||
a.Width = j.width;
|
||||
a.Height = j.height;
|
||||
@@ -566,38 +577,137 @@ public sealed class CinemaMediaSourceManager : IMediaSourceManager
|
||||
if (j.is3d)
|
||||
// Just a probability guess
|
||||
item.Video3DFormat = Video3DFormat.HalfSideBySide;
|
||||
|
||||
result.Add(a);
|
||||
}
|
||||
}
|
||||
|
||||
if (ver.audio != null)
|
||||
{
|
||||
int uniqueId = FirstRelativeAudioIndex;
|
||||
foreach (StreamAudio j in ver.audio)
|
||||
{
|
||||
MediaStream a = new MediaStream();
|
||||
result.Add(a);
|
||||
a.Index = uniqueId--;
|
||||
a.Type = MediaStreamType.Audio;
|
||||
a.Language = j.language;
|
||||
a.Language = ISO639_1ToISO639_2(j.language);
|
||||
a.Codec = j.codec;
|
||||
a.Channels = j.channels;
|
||||
|
||||
result.Add(a);
|
||||
}
|
||||
}
|
||||
|
||||
if (ver.subtitles != null)
|
||||
{
|
||||
int uniqueId = FirstRelativeSubtitleIndex;
|
||||
foreach (StreamSubtitle j in ver.subtitles)
|
||||
{
|
||||
MediaStream a = new MediaStream();
|
||||
result.Add(a);
|
||||
a.Index = uniqueId--;
|
||||
a.Type = MediaStreamType.Subtitle;
|
||||
a.Language = j.language;
|
||||
a.Language = ISO639_1ToISO639_2(j.language);
|
||||
a.IsForced = j.forced;
|
||||
a.Path = j.src;
|
||||
|
||||
result.Add(a);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
internal static string EncodingHelper_GetMapArgs_New(EncodingHelper @this, EncodingJobInfo state)
|
||||
{
|
||||
if ((state.VideoStream == null && state.AudioStream == null)
|
||||
|| (state.VideoStream != null && state.VideoStream.Index == -1)
|
||||
|| (state.AudioStream != null && state.AudioStream.Index == -1))
|
||||
// Use original implementation
|
||||
// Note: The condition reflects the beginning of the original method
|
||||
return EncodingHelper_GetMapArgs_Orig(@this, state);
|
||||
|
||||
// HACK: Stream indicies below FirstRelativeVideoIndex are misused by VersionToMediaStreams to
|
||||
// indicate relative indexing for ffmpeg
|
||||
if ((state.VideoStream?.Index ?? 0) > FirstRelativeVideoIndex
|
||||
&& (state.AudioStream?.Index ?? 0) > FirstRelativeAudioIndex
|
||||
&& (state.SubtitleStream?.Index ?? 0) > FirstRelativeSubtitleIndex)
|
||||
// Use original implementation
|
||||
return EncodingHelper_GetMapArgs_Orig(@this, state);
|
||||
|
||||
// Video stream
|
||||
string args = "";
|
||||
if (state.VideoStream == null)
|
||||
{
|
||||
// No known video stream
|
||||
args += "-vn";
|
||||
}
|
||||
else if (state.VideoStream.Index <= FirstRelativeVideoIndex)
|
||||
{
|
||||
args += string.Format(CultureInfo.InvariantCulture, "-map 0:v:{0}", -(state.VideoStream.Index - FirstRelativeVideoIndex));
|
||||
}
|
||||
else
|
||||
{
|
||||
int videoStreamIndex = EncodingHelper.FindIndex(state.MediaSource.MediaStreams, state.VideoStream);
|
||||
args += string.Format(CultureInfo.InvariantCulture, "-map 0:{0}", videoStreamIndex);
|
||||
}
|
||||
|
||||
// Audio stream
|
||||
if (state.AudioStream == null)
|
||||
{
|
||||
args += " -map -0:a";
|
||||
}
|
||||
else if (state.AudioStream.Index <= FirstRelativeAudioIndex)
|
||||
{
|
||||
args += string.Format(CultureInfo.InvariantCulture, " -map 0:a:{0}", -(state.AudioStream.Index - FirstRelativeAudioIndex));
|
||||
}
|
||||
else
|
||||
{
|
||||
int audioStreamIndex = EncodingHelper.FindIndex(state.MediaSource.MediaStreams, state.AudioStream);
|
||||
if (state.AudioStream.IsExternal)
|
||||
{
|
||||
throw new InvalidOperationException("Cinema shall not have external audio streams.");
|
||||
}
|
||||
else
|
||||
{
|
||||
args += string.Format(CultureInfo.InvariantCulture, " -map 0:{0}", audioStreamIndex);
|
||||
}
|
||||
}
|
||||
|
||||
// Subtitle stream
|
||||
var subtitleMethod = state.SubtitleDeliveryMethod;
|
||||
if (state.SubtitleStream == null || subtitleMethod == SubtitleDeliveryMethod.Hls)
|
||||
{
|
||||
args += " -map -0:s";
|
||||
}
|
||||
else if (subtitleMethod == SubtitleDeliveryMethod.Embed)
|
||||
{
|
||||
if (state.SubtitleStream.Index <= FirstRelativeSubtitleIndex)
|
||||
{
|
||||
args += string.Format(CultureInfo.InvariantCulture, " -map 0:s:{0}", -(state.SubtitleStream.Index - FirstRelativeSubtitleIndex));
|
||||
}
|
||||
else
|
||||
{
|
||||
int subtitleStreamIndex = EncodingHelper.FindIndex(state.MediaSource.MediaStreams, state.SubtitleStream);
|
||||
|
||||
args += string.Format(CultureInfo.InvariantCulture, " -map 0:{0}", subtitleStreamIndex);
|
||||
}
|
||||
}
|
||||
else if (state.SubtitleStream.IsExternal && !state.SubtitleStream.IsTextSubtitleStream)
|
||||
{
|
||||
int externalSubtitleStreamIndex = EncodingHelper.FindIndex(state.MediaSource.MediaStreams, state.SubtitleStream);
|
||||
|
||||
args += string.Format(CultureInfo.InvariantCulture, " -map 1:{0} -sn", externalSubtitleStreamIndex);
|
||||
}
|
||||
|
||||
// Do not call the original
|
||||
return args;
|
||||
}
|
||||
|
||||
internal static string EncodingHelper_GetMapArgs_Orig(EncodingHelper @this, EncodingJobInfo state)
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
private static void ConvertHdr(bool isHdr, string? hdr, MediaStream dst)
|
||||
{
|
||||
if (!isHdr)
|
||||
@@ -673,6 +783,201 @@ public sealed class CinemaMediaSourceManager : IMediaSourceManager
|
||||
return entry.Link;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts two-letter 639-1 language code to three letter 639-2.
|
||||
/// </summary>
|
||||
private static string? ISO639_1ToISO639_2(string? twoLetter639_1)
|
||||
{
|
||||
// Based on https://www.loc.gov/standards/iso639-2/php/code_list-utf8.php
|
||||
switch (twoLetter639_1)
|
||||
{
|
||||
case "aa": return "aar";
|
||||
case "ab": return "abk";
|
||||
case "af": return "afr";
|
||||
case "ak": return "aka";
|
||||
case "sq": return "alb";
|
||||
case "am": return "amh";
|
||||
case "ar": return "ara";
|
||||
case "an": return "arg";
|
||||
case "hy": return "arm";
|
||||
case "as": return "asm";
|
||||
case "av": return "ava";
|
||||
case "ae": return "ave";
|
||||
case "ay": return "aym";
|
||||
case "az": return "aze";
|
||||
case "ba": return "bak";
|
||||
case "bm": return "bam";
|
||||
case "eu": return "baq";
|
||||
case "be": return "bel";
|
||||
case "bn": return "ben";
|
||||
case "bi": return "bis";
|
||||
case "bo": return "tib";
|
||||
case "bs": return "bos";
|
||||
case "br": return "bre";
|
||||
case "bg": return "bul";
|
||||
case "my": return "bur";
|
||||
case "ca": return "cat";
|
||||
case "cs": return "cze";
|
||||
case "ch": return "cha";
|
||||
case "ce": return "che";
|
||||
case "zh": return "chi";
|
||||
case "cu": return "chu";
|
||||
case "cv": return "chv";
|
||||
case "kw": return "cor";
|
||||
case "co": return "cos";
|
||||
case "cr": return "cre";
|
||||
case "cy": return "wel";
|
||||
case "da": return "dan";
|
||||
case "de": return "ger";
|
||||
case "dv": return "div";
|
||||
case "nl": return "dut";
|
||||
case "dz": return "dzo";
|
||||
case "el": return "gre";
|
||||
case "en": return "eng";
|
||||
case "eo": return "epo";
|
||||
case "et": return "est";
|
||||
case "ee": return "ewe";
|
||||
case "fo": return "fao";
|
||||
case "fa": return "per";
|
||||
case "fj": return "fij";
|
||||
case "fi": return "fin";
|
||||
case "fr": return "fre";
|
||||
case "fy": return "fry";
|
||||
case "ff": return "ful";
|
||||
case "ka": return "geo";
|
||||
case "gd": return "gla";
|
||||
case "ga": return "gle";
|
||||
case "gl": return "glg";
|
||||
case "gv": return "glv";
|
||||
case "gn": return "grn";
|
||||
case "gu": return "guj";
|
||||
case "ht": return "hat";
|
||||
case "ha": return "hau";
|
||||
case "he": return "heb";
|
||||
case "hz": return "her";
|
||||
case "hi": return "hin";
|
||||
case "ho": return "hmo";
|
||||
case "hr": return "hrv";
|
||||
case "hu": return "hun";
|
||||
case "ig": return "ibo";
|
||||
case "is": return "ice";
|
||||
case "io": return "ido";
|
||||
case "ii": return "iii";
|
||||
case "iu": return "iku";
|
||||
case "ie": return "ile";
|
||||
case "ia": return "ina";
|
||||
case "id": return "ind";
|
||||
case "ik": return "ipk";
|
||||
case "it": return "ita";
|
||||
case "jv": return "jav";
|
||||
case "ja": return "jpn";
|
||||
case "kl": return "kal";
|
||||
case "kn": return "kan";
|
||||
case "ks": return "kas";
|
||||
case "kr": return "kau";
|
||||
case "kk": return "kaz";
|
||||
case "km": return "khm";
|
||||
case "ki": return "kik";
|
||||
case "rw": return "kin";
|
||||
case "ky": return "kir";
|
||||
case "kv": return "kom";
|
||||
case "kg": return "kon";
|
||||
case "ko": return "kor";
|
||||
case "kj": return "kua";
|
||||
case "ku": return "kur";
|
||||
case "lo": return "lao";
|
||||
case "la": return "lat";
|
||||
case "lv": return "lav";
|
||||
case "li": return "lim";
|
||||
case "ln": return "lin";
|
||||
case "lt": return "lit";
|
||||
case "lb": return "ltz";
|
||||
case "lu": return "lub";
|
||||
case "lg": return "lug";
|
||||
case "mk": return "mac";
|
||||
case "mh": return "mah";
|
||||
case "ml": return "mal";
|
||||
case "mi": return "mao";
|
||||
case "mr": return "mar";
|
||||
case "ms": return "may";
|
||||
case "mg": return "mlg";
|
||||
case "mt": return "mlt";
|
||||
case "mn": return "mon";
|
||||
case "na": return "nau";
|
||||
case "nv": return "nav";
|
||||
case "nr": return "nbl";
|
||||
case "nd": return "nde";
|
||||
case "ng": return "ndo";
|
||||
case "ne": return "nep";
|
||||
case "nn": return "nno";
|
||||
case "nb": return "nob";
|
||||
case "no": return "nor";
|
||||
case "ny": return "nya";
|
||||
case "oc": return "oci";
|
||||
case "oj": return "oji";
|
||||
case "or": return "ori";
|
||||
case "om": return "orm";
|
||||
case "os": return "oss";
|
||||
case "pa": return "pan";
|
||||
case "pi": return "pli";
|
||||
case "pl": return "pol";
|
||||
case "pt": return "por";
|
||||
case "ps": return "pus";
|
||||
case "qu": return "que";
|
||||
case "rm": return "roh";
|
||||
case "ro": return "rum";
|
||||
case "rn": return "run";
|
||||
case "ru": return "rus";
|
||||
case "sg": return "sag";
|
||||
case "sa": return "san";
|
||||
case "si": return "sin";
|
||||
case "sk": return "slo";
|
||||
case "sl": return "slv";
|
||||
case "se": return "sme";
|
||||
case "sm": return "smo";
|
||||
case "sn": return "sna";
|
||||
case "sd": return "snd";
|
||||
case "so": return "som";
|
||||
case "st": return "sot";
|
||||
case "es": return "spa";
|
||||
case "sc": return "srd";
|
||||
case "sr": return "srp";
|
||||
case "ss": return "ssw";
|
||||
case "su": return "sun";
|
||||
case "sw": return "swa";
|
||||
case "sv": return "swe";
|
||||
case "ty": return "tah";
|
||||
case "ta": return "tam";
|
||||
case "tt": return "tat";
|
||||
case "te": return "tel";
|
||||
case "tg": return "tgk";
|
||||
case "tl": return "tgl";
|
||||
case "th": return "tha";
|
||||
case "ti": return "tir";
|
||||
case "to": return "ton";
|
||||
case "tn": return "tsn";
|
||||
case "ts": return "tso";
|
||||
case "tk": return "tuk";
|
||||
case "tr": return "tur";
|
||||
case "tw": return "twi";
|
||||
case "ug": return "uig";
|
||||
case "uk": return "ukr";
|
||||
case "ur": return "urd";
|
||||
case "uz": return "uzb";
|
||||
case "ve": return "ven";
|
||||
case "vi": return "vie";
|
||||
case "vo": return "vol";
|
||||
case "wa": return "wln";
|
||||
case "wo": return "wol";
|
||||
case "xh": return "xho";
|
||||
case "yi": return "yid";
|
||||
case "yo": return "yor";
|
||||
case "za": return "zha";
|
||||
case "zu": return "zul";
|
||||
default: return twoLetter639_1;
|
||||
}
|
||||
}
|
||||
|
||||
struct VersionSetEntry
|
||||
{
|
||||
internal DateTime ValidUntil;
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.Plugins;
|
||||
using MediaBrowser.Model.Plugins;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using MediaBrowser.Controller.MediaEncoding;
|
||||
|
||||
using Jellyfin.Plugin.Cinema.Configuration;
|
||||
using System.Linq.Expressions;
|
||||
|
||||
namespace Jellyfin.Plugin.Cinema;
|
||||
|
||||
@@ -21,6 +25,14 @@ public class CinemaPlugin : BasePlugin<CinemaPluginConfiguration>, IHasWebPages
|
||||
: base(applicationPaths, xmlSerializer)
|
||||
{
|
||||
Instance = this;
|
||||
|
||||
// Perform patching
|
||||
MethodInfo encodingGetMapArgs_orig = ((Func<EncodingJobInfo, string>)new EncodingHelper(null, null, null, null, null).GetMapArgs).Method;
|
||||
MethodInfo encodingGetMapArgs_new = ((Func<EncodingHelper, EncodingJobInfo, string>)CinemaMediaSourceManager.EncodingHelper_GetMapArgs_New).Method;
|
||||
MethodInfo encodingGetMapArgs_proxy = ((Func<EncodingHelper, EncodingJobInfo, string>)CinemaMediaSourceManager.EncodingHelper_GetMapArgs_Orig).Method;
|
||||
|
||||
RedirectTo(encodingGetMapArgs_proxy, encodingGetMapArgs_orig);
|
||||
RedirectTo(encodingGetMapArgs_orig, encodingGetMapArgs_new);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -49,4 +61,64 @@ public class CinemaPlugin : BasePlugin<CinemaPluginConfiguration>, IHasWebPages
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
private static void RedirectTo(MethodInfo origin, MethodInfo target)
|
||||
{
|
||||
IntPtr ori = GetMethodAddress(origin);
|
||||
IntPtr tar = GetMethodAddress(target);
|
||||
|
||||
Marshal.Copy(new IntPtr[] { Marshal.ReadIntPtr(tar) }, 0, ori, 1);
|
||||
}
|
||||
|
||||
private static IntPtr GetMethodAddress(MethodInfo mi)
|
||||
{
|
||||
const ushort SLOT_NUMBER_MASK = 0xffff; // 2 bytes mask
|
||||
const int MT_OFFSET_32BIT = 0x28; // 40 bytes offset
|
||||
const int MT_OFFSET_64BIT = 0x40; // 64 bytes offset
|
||||
|
||||
IntPtr address;
|
||||
|
||||
// JIT compilation of the method
|
||||
RuntimeHelpers.PrepareMethod(mi.MethodHandle);
|
||||
|
||||
IntPtr md = mi.MethodHandle.Value; // MethodDescriptor address
|
||||
IntPtr mt = mi.DeclaringType!.TypeHandle.Value; // MethodTable address
|
||||
|
||||
bool isNetFramework = RuntimeInformation.FrameworkDescription.StartsWith(".NET Framework");
|
||||
|
||||
if (mi.IsVirtual)
|
||||
{
|
||||
// The fixed-size portion of the MethodTable structure depends on the process type
|
||||
int offset = IntPtr.Size == 4 ? MT_OFFSET_32BIT : MT_OFFSET_64BIT;
|
||||
|
||||
// First method slot = MethodTable address + fixed-size offset
|
||||
// This is the address of the first method of any type (i.e. ToString)
|
||||
IntPtr ms = Marshal.ReadIntPtr(mt + offset);
|
||||
|
||||
// Get the slot number of the virtual method entry from the MethodDesc data structure
|
||||
long shift = Marshal.ReadInt64(md) >> 32;
|
||||
int slot = (int)(shift & SLOT_NUMBER_MASK);
|
||||
|
||||
// Get the virtual method address relative to the first method slot
|
||||
address = ms + (slot * IntPtr.Size);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Bypass default MethodDescriptor padding (8 bytes)
|
||||
// Reach the CodeOrIL field which contains the address of the JIT-compiled code
|
||||
if (isNetFramework)
|
||||
address = md + 8;
|
||||
else
|
||||
address = md + 16;
|
||||
}
|
||||
|
||||
nint a = mi.MethodHandle.GetFunctionPointer();
|
||||
nint b = Marshal.ReadIntPtr(address);
|
||||
if (b != a)
|
||||
throw new InvalidOperationException("Method patching primitives are broken.");
|
||||
|
||||
return address;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user