settings infra

This commit is contained in:
nattthebear 2020-05-24 17:56:43 -04:00
parent daaa28f451
commit 8c9f4e24d8
10 changed files with 477 additions and 70 deletions

View File

@ -526,7 +526,8 @@ namespace BizHawk.Client.Common
else
{
// TODO: pass disc in
nextEmulator = new TerboGrafix(game, null, nextComm, "dunno what to put here");
throw new NotImplementedException();
// nextEmulator = new TerboGrafix(game, null, nextComm, "dunno what to put here");
}
break;

View File

@ -10,6 +10,8 @@ namespace BizHawk.Common
/// </summary>
public static unsafe string PtrToStringUtf8(IntPtr p)
{
if (p == IntPtr.Zero)
return null;
byte* b = (byte*)p;
int len = 0;
while (*b++ != 0)

View File

@ -3,16 +3,13 @@ using BizHawk.Emulation.Cores.Waterbox;
namespace BizHawk.Emulation.Cores.Consoles.NEC.PCE
{
[Core(CoreNames.TurboNyma, "Mednafen Team", true, false, "", "", false)]
[Core(CoreNames.TurboNyma, "Mednafen Team", true, false, "1.24.3", "", false)]
public class TerboGrafix : NymaCore, IRegionable
{
[CoreConstructor("PCE")]
public TerboGrafix(GameInfo game, byte[] rom, CoreComm comm, string extension)
: base(game, rom, comm, new Configuration
{
SystemId = "PCE" // whatever
// TODO: This stuff isn't used so much
})
public TerboGrafix(GameInfo game, byte[] rom, CoreComm comm, string extension,
NymaSettings settings, NymaSyncSettings syncSettings)
: base(game, rom, comm, "PCE", "PC Engine Controller", settings, syncSettings)
{
DoInit<LibNymaCore>(game, rom, "pce.wbx", extension);
}

View File

@ -25,6 +25,15 @@ namespace BizHawk.Emulation.Cores.Waterbox
public string FileNameFull;
}
/// <summary>
/// Do this before calling anything, even settings queries
/// </summary>
[BizImport(CC, Compatibility = true)]
public abstract void PreInit();
/// <summary>
/// Load a ROM
/// </summary>
[BizImport(CC, Compatibility = true)]
public abstract bool Init([In]InitData data);
@ -270,5 +279,16 @@ namespace BizHawk.Emulation.Cores.Waterbox
[BizImport(CC, Compatibility = true)]
public abstract SystemInfo* GetSystemInfo();
[BizImport(CC, Compatibility = true)]
public abstract void IterateSettings(int index, [In, Out]NymaCore.NymaSettingsInfo.MednaSettingS s);
[BizImport(CC, Compatibility = true)]
public abstract void IterateSettingEnums(int index, int enumIndex,[In, Out]NymaCore.NymaSettingsInfo.MednaSettingS.EnumValueS e);
public delegate void FrontendSettingQuery(string setting, IntPtr dest);
[BizImport(CC)]
public abstract void SetFrontendSettingQuery(FrontendSettingQuery q);
}
}

View File

@ -9,10 +9,11 @@ namespace BizHawk.Emulation.Cores.Waterbox
{
private ControllerAdapter _controllerAdapter;
private readonly byte[] _inputPortData = new byte[16 * 16];
private readonly string _controllerDeckName;
private void InitControls()
{
_controllerAdapter = new ControllerAdapter(_nyma, new string[0]);
_controllerAdapter = new ControllerAdapter(_nyma, _syncSettingsActual.PortDevices);
_nyma.SetInputDevices(_controllerAdapter.Devices);
ControllerDefinition = _controllerAdapter.Definition;
}
@ -34,7 +35,7 @@ namespace BizHawk.Emulation.Cores.Waterbox
/// </summary>
public string[] Devices { get; }
public ControllerDefinition Definition { get; }
public ControllerAdapter(LibNymaCore core, string[] config)
public ControllerAdapter(LibNymaCore core, IList<string> config)
{
var ret = new ControllerDefinition
{
@ -49,7 +50,7 @@ namespace BizHawk.Emulation.Cores.Waterbox
for (uint port = 0, devByteStart = 0; port < numPorts; port++, devByteStart += MAX_PORT_DATA)
{
var portInfo = *core.GetPort(port);
var deviceName = port < config.Length ? config[port] : portInfo.DefaultDeviceShortName;
var deviceName = port < config.Count ? config[(int)port] : portInfo.DefaultDeviceShortName;
finalDevices.Add(deviceName);
var devices = Enumerable.Range(0, (int)portInfo.NumDevices)
@ -91,7 +92,7 @@ namespace BizHawk.Emulation.Cores.Waterbox
case LibNymaCore.InputType.BUTTON:
case LibNymaCore.InputType.BUTTON_CAN_RAPID:
{
var data = *core.GetButton(port, device.Index, (uint)input.Index);
// var data = *core.GetButton(port, device.Index, (uint)input.Index);
// TODO: Wire up data.ExcludeName
ret.BoolButtons.Add(name);
_thunks.Add((c, b) =>
@ -104,21 +105,23 @@ namespace BizHawk.Emulation.Cores.Waterbox
case LibNymaCore.InputType.SWITCH:
{
var data = *core.GetSwitch(port, device.Index, (uint)input.Index);
var zzhacky = (int)data.DefaultPosition;
// TODO: Possibly bulebutton for 2 states?
ret.AxisControls.Add(name);
ret.AxisRanges.Add(new ControllerDefinition.AxisRange(
0, (int)data.DefaultPosition, (int)data.NumPositions - 1));
// HACK: Silently discard this until bizhawk fixes its shit
// _thunks.Add((c, b) =>
// {
// var val = (int)Math.Round(c.AxisValue(name));
// b[byteStart] |= (byte)(1 << bitOffset);
// });
_thunks.Add((c, b) =>
{
// HACK: Silently discard this until bizhawk fixes its shit
// var val = (int)Math.Round(c.AxisValue(name));
var val = zzhacky;
b[byteStart] |= (byte)(val << bitOffset);
});
break;
}
case LibNymaCore.InputType.AXIS:
{
var data = core.GetAxis(port, device.Index, (uint)input.Index);
// var data = core.GetAxis(port, device.Index, (uint)input.Index);
ret.AxisControls.Add(name);
ret.AxisRanges.Add(new ControllerDefinition.AxisRange(
0, 0x8000, 0xffff, (inputInfo.Flags & LibNymaCore.AxisFlags.INVERT_CO) != 0
@ -133,7 +136,7 @@ namespace BizHawk.Emulation.Cores.Waterbox
}
case LibNymaCore.InputType.AXIS_REL:
{
var data = core.GetAxis(port, device.Index, (uint)input.Index);
// var data = core.GetAxis(port, device.Index, (uint)input.Index);
ret.AxisControls.Add(name);
ret.AxisRanges.Add(new ControllerDefinition.AxisRange(
-0x8000, 0, 0x7fff, (inputInfo.Flags & LibNymaCore.AxisFlags.INVERT_CO) != 0

View File

@ -0,0 +1,296 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using BizHawk.Common;
using BizHawk.Emulation.Common;
namespace BizHawk.Emulation.Cores.Waterbox
{
unsafe partial class NymaCore : ISettable<NymaCore.NymaSettings, NymaCore.NymaSyncSettings>
{
public NymaSettingsInfo SettingsInfo { get; private set; }
private NymaSettings _settings;
private NymaSyncSettings _syncSettings;
/// <summary>
/// What this core was actually started with
/// </summary>
private NymaSyncSettings _syncSettingsActual;
public NymaSettings GetSettings() => _settings.Clone();
public NymaSyncSettings GetSyncSettings() => _syncSettings.Clone();
public PutSettingsDirtyBits PutSettings(NymaSettings o)
{
_settings = o.Clone();
if (SettingsInfo.LayerNames.Count > 0)
{
ulong layers = ~0ul;
for (int i = 0; i < 64 && i < SettingsInfo.LayerNames.Count; i++)
{
if (_settings.DisabledLayers.Contains(SettingsInfo.LayerNames[i]))
layers &= ~(1ul << i);
}
_nyma.SetLayers(layers);
}
return PutSettingsDirtyBits.None;
}
public PutSettingsDirtyBits PutSyncSettings(NymaSyncSettings o)
{
_syncSettings = o.Clone();
return _syncSettings.Equals(_syncSettingsActual)
? PutSettingsDirtyBits.None
: PutSettingsDirtyBits.RebootCore;
}
public class NymaSettings
{
public HashSet<string> DisabledLayers { get; set; } = new HashSet<string>();
public NymaSettings Clone()
{
return new NymaSettings { DisabledLayers = new HashSet<string>(DisabledLayers) };
}
}
public class NymaSyncSettings
{
public Dictionary<string, string> MednafenValues { get; set; } = new Dictionary<string, string>();
public List<string> PortDevices { get; set; } = new List<string>();
public NymaSyncSettings Clone()
{
return new NymaSyncSettings
{
MednafenValues = new Dictionary<string, string>(MednafenValues),
PortDevices = new List<string>(PortDevices)
};
}
public override bool Equals(object obj)
{
if (!(obj is NymaSyncSettings x))
return false;
return PortDevices.SequenceEqual(x.PortDevices)
&& new HashSet<KeyValuePair<string, string>>(MednafenValues).SetEquals(x.MednafenValues);
}
public override int GetHashCode()
{
return 0; // some other time, maybe
}
}
private void SettingsQuery(string name, IntPtr dest)
{
if (!_syncSettingsActual.MednafenValues.TryGetValue(name, out var val))
{
if (SettingsInfo.SettingsByKey.TryGetValue(name, out var info))
{
val = info.DefaultValue;
}
else
{
throw new InvalidOperationException($"Core asked for setting {name} which was not found in the defaults");
}
}
var bytes = Encoding.UTF8.GetBytes(val);
if (bytes.Length > 255)
throw new InvalidOperationException($"Value {val} for setting {name} was too long");
WaterboxUtils.ZeroMemory(dest, 256);
Marshal.Copy(bytes, 0, dest, bytes.Length);
}
private LibNymaCore.FrontendSettingQuery _settingsQueryDelegate;
public class NymaSettingsInfo
{
/// <summary>
/// What layers are available to toggle. If empty, layers cannot be set on this core.
/// </summary>
public List<string> LayerNames { get; set; }
public class Device
{
public string Name { get; set; }
public string Description { get; set; }
public string SettingdValue { get; set; }
}
public class Port
{
public string Name { get; set; }
public List<Device> AllowedDevices { get; set; } = new List<Device>();
public string DefaultSettingsValue { get; set; }
}
/// <summary>
/// What devices can be plugged into each port
/// </summary>
public List<Port> Ports { get; set; } = new List<Port>();
public class MednaSetting
{
public string Name;
public string Description;
public string SettingsKey;
public string DefaultValue;
public string Min;
public string Max;
[Flags]
public enum SettingFlags : uint
{
NOFLAGS = 0U, // Always 0, makes setting definitions prettier...maybe.
// TODO(cats)
CAT_INPUT = (1U << 8),
CAT_SOUND = (1U << 9),
CAT_VIDEO = (1U << 10),
CAT_INPUT_MAPPING = (1U << 11), // User-configurable physical->virtual button/axes and hotkey mappings(driver-side code category mainly).
// Setting is used as a path or filename(mostly intended for automatic charset conversion of 0.9.x settings on MS Windows).
CAT_PATH = (1U << 12),
EMU_STATE = (1U << 17), // If the setting affects emulation from the point of view of the emulated program
UNTRUSTED_SAFE = (1U << 18), // If it's safe for an untrusted source to modify it, probably only used in conjunction with
// MDFNST_EX_EMU_STATE and network play
SUPPRESS_DOC = (1U << 19), // Suppress documentation generation for this setting.
COMMON_TEMPLATE = (1U << 20), // Auto-generated common template setting(like nes.xscale, pce.xscale, vb.xscale, nes.enable, pce.enable, vb.enable)
NONPERSISTENT = (1U << 21), // Don't save setting in settings file.
// TODO:
// WILL_BREAK_GAMES (1U << ) // If changing the value of the setting from the default value will break games/programs that would otherwise work.
// TODO(in progress):
REQUIRES_RELOAD = (1U << 24), // If a game reload is required for the setting to take effect.
REQUIRES_RESTART = (1U << 25), // If Mednafen restart is required for the setting to take effect.
}
public SettingFlags Flags;
public enum SettingType : int
{
INT = 0, // (signed), int8, int16, int32, int64(saved as)
UINT, // uint8, uint16, uint32, uint64(saved as)
/// <summary>
/// 0 or 1
/// </summary>
BOOL,
/// <summary>
/// float64
/// </summary>
FLOAT,
STRING,
/// <summary>
/// string value from a list of potential strings
/// </summary>
ENUM,
/// <summary>
/// TODO: How do these work
/// </summary>
MULTI_ENUM,
/// <summary>
/// Shouldn't see any of these
/// </summary>
ALIAS
}
public SettingType Type;
public class EnumValue
{
public string Name;
public string Description;
public string Value;
public EnumValue(MednaSettingS.EnumValueS s)
{
Name = Mershul.PtrToStringUtf8(s.Name);
Description = Mershul.PtrToStringUtf8(s.Description);
Value = Mershul.PtrToStringUtf8(s.Value);
}
}
public MednaSetting(MednaSettingS s)
{
Name = Mershul.PtrToStringUtf8(s.Name);
Description = Mershul.PtrToStringUtf8(s.Description);
SettingsKey = Mershul.PtrToStringUtf8(s.SettingsKey);
DefaultValue = Mershul.PtrToStringUtf8(s.DefaultValue);
Min = Mershul.PtrToStringUtf8(s.Min);
Max = Mershul.PtrToStringUtf8(s.Max);
Flags = (SettingFlags)s.Flags;
Type = (SettingType)s.Type;
}
}
[StructLayout(LayoutKind.Sequential)]
public class MednaSettingS
{
public IntPtr Name;
public IntPtr Description;
public IntPtr SettingsKey;
public IntPtr DefaultValue;
public IntPtr Min;
public IntPtr Max;
public uint Flags;
public int Type;
[StructLayout(LayoutKind.Sequential)]
public class EnumValueS
{
public IntPtr Name;
public IntPtr Description;
public IntPtr Value;
}
}
public List<MednaSetting> Settings { get; set; } = new List<MednaSetting>();
public Dictionary<string, List<MednaSetting.EnumValue>> SettingEnums { get; set; } = new Dictionary<string, List<MednaSetting.EnumValue>>();
public Dictionary<string, MednaSetting> SettingsByKey { get; set; } = new Dictionary<string, MednaSetting>();
}
private void InitSyncSettingsInfo()
{
// TODO: Some shared logic in ControllerAdapter. Avoidable?
var s = new NymaSettingsInfo();
var numPorts = _nyma.GetNumPorts();
for (uint port = 0; port < numPorts; port++)
{
var portInfo = *_nyma.GetPort(port);
s.Ports.Add(new NymaSettingsInfo.Port
{
Name = portInfo.FullName,
DefaultSettingsValue = portInfo.DefaultDeviceShortName,
AllowedDevices = Enumerable.Range(0, (int)portInfo.NumDevices)
.Select(i =>
{
var dev = *_nyma.GetDevice(port, (uint)i);
return new NymaSettingsInfo.Device
{
Name = dev.FullName,
Description = dev.Description,
SettingdValue = dev.ShortName
};
})
.ToList()
});
}
for (var i = 0;; i++)
{
var tt = new NymaSettingsInfo.MednaSettingS();
_nyma.IterateSettings(i, tt);
if (tt.SettingsKey == IntPtr.Zero)
break;
var ss = new NymaSettingsInfo.MednaSetting(tt);
s.Settings.Add(ss);
s.SettingsByKey.Add(ss.SettingsKey, ss);
if (ss.Type == NymaSettingsInfo.MednaSetting.SettingType.ENUM)
{
var l = new List<NymaSettingsInfo.MednaSetting.EnumValue>();
for (var j = 0;; j++)
{
var ff = new NymaSettingsInfo.MednaSettingS.EnumValueS();
_nyma.IterateSettingEnums(i, j, ff);
if (ff.Value == IntPtr.Zero)
break;
var ee = new NymaSettingsInfo.MednaSetting.EnumValue(ff);
l.Add(ee);
}
s.SettingEnums.Add(ss.SettingsKey, l);
}
}
SettingsInfo = s;
}
}
}

View File

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using BizHawk.Common;
@ -9,35 +10,42 @@ namespace BizHawk.Emulation.Cores.Waterbox
{
public unsafe abstract partial class NymaCore : WaterboxCore
{
protected NymaCore(GameInfo game, byte[] rom, CoreComm comm, Configuration c)
: base(comm, c)
protected NymaCore(GameInfo game, byte[] rom, CoreComm comm, string systemId, string controllerDeckName,
NymaSettings settings, NymaSyncSettings syncSettings)
: base(comm, new Configuration { SystemId = systemId })
{
_settings = settings ?? new NymaSettings();
_syncSettings = syncSettings ?? new NymaSyncSettings();
_syncSettingsActual = _syncSettings;
_controllerDeckName = controllerDeckName;
}
private LibNymaCore _nyma;
protected T DoInit<T>(GameInfo game, byte[] rom, string filename, string extension)
protected T DoInit<T>(GameInfo game, byte[] rom, string wbxFilename, string extension)
where T : LibNymaCore
{
var t = PreInit<T>(new WaterboxOptions
{
// TODO cfg and stuff
Filename = filename,
// TODO fix these up
Filename = wbxFilename,
SbrkHeapSizeKB = 1024 * 16,
SealedHeapSizeKB = 1024 * 16,
InvisibleHeapSizeKB = 1024 * 16,
PlainHeapSizeKB = 1024 * 16,
MmapHeapSizeKB = 1024 * 16,
StartAddress = WaterboxHost.CanonicalStart,
SkipCoreConsistencyCheck = CoreComm.CorePreferences.HasFlag(CoreComm.CorePreferencesFlags.WaterboxCoreConsistencyCheck),
SkipMemoryConsistencyCheck = CoreComm.CorePreferences.HasFlag(CoreComm.CorePreferencesFlags.WaterboxMemoryConsistencyCheck),
});
_nyma = t;
_settingsQueryDelegate = new LibNymaCore.FrontendSettingQuery(SettingsQuery);
var fn = game.FilesystemSafeName();
using (_exe.EnterExit())
{
var fn = game.FilesystemSafeName();
_nyma.PreInit();
InitSyncSettingsInfo();
_exe.AddReadonlyFile(rom, fn);
_nyma.SetFrontendSettingQuery(_settingsQueryDelegate);
var didInit = _nyma.Init(new LibNymaCore.InitData
{
@ -72,17 +80,24 @@ namespace BizHawk.Emulation.Cores.Waterbox
}
VsyncNumerator = info.FpsFixed;
VsyncDenominator = 1 << 24;
_soundBuffer = new short[22050 * 2];
InitControls();
_nyma.SetFrontendSettingQuery(null);
PostInit();
SettingsInfo.LayerNames = GetLayerData();
_nyma.SetFrontendSettingQuery(_settingsQueryDelegate);
PutSettings(_settings);
}
return t;
}
protected override void LoadStateBinaryInternal(BinaryReader reader)
{
_nyma.SetFrontendSettingQuery(_settingsQueryDelegate);
}
// todo: bleh
private GCHandle _frameAdvanceInputLock;
@ -107,31 +122,28 @@ namespace BizHawk.Emulation.Cores.Waterbox
public DisplayType Region { get; protected set; }
/// <summary>
/// Gets a string array of valid layers to pass to SetLayers, or null if that method should not be called
/// Gets a string array of valid layers to pass to SetLayers, or an empty list if that method should not be called
/// </summary>
private string[] GetLayerData()
private List<string> GetLayerData()
{
using (_exe.EnterExit())
var ret = new List<string>();
var p = _nyma.GetLayerData();
if (p == null)
return ret;
var q = p;
while (true)
{
var p = _nyma.GetLayerData();
if (p == null)
return null;
var ret = new List<string>();
var q = p;
while (true)
if (*q == 0)
{
if (*q == 0)
{
if (q > p)
ret.Add(Mershul.PtrToStringUtf8((IntPtr)p));
else
break;
p = ++q;
}
q++;
if (q > p)
ret.Add(Mershul.PtrToStringUtf8((IntPtr)p));
else
break;
p = q + 1;
}
return ret.ToArray();
q++;
}
return ret;
}
}
}

6
waterbox/nyma/.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,6 @@
{
"files.associations": {
"*.mak": "makefile",
"typeinfo": "cpp"
}
}

View File

@ -8,6 +8,15 @@
#include <stdio.h>
#include <stdarg.h>
#include <emulibc.h>
enum { SETTING_VALUE_MAX_LENGTH = 256 };
static void (*FrontendSettingQuery)(const char* setting, char* dest);
ECL_EXPORT void SetFrontendSettingQuery(void (*q)(const char* setting, char* dest))
{
FrontendSettingQuery = q;
}
namespace Mednafen
{
@ -76,37 +85,50 @@ namespace Mednafen
uint64 MDFN_GetSettingUI(const char *name)
{
auto s = GetSetting(name);
if (s)
return strtoul(s->default_value, nullptr, 10);
return 0;
char tmp[SETTING_VALUE_MAX_LENGTH];
FrontendSettingQuery(name, tmp);
if (s && s->type == MDFNST_ENUM)
{
for (int i = 0; s->enum_list[i].string; i++)
{
if (strcmp(s->enum_list[i].string, tmp) == 0)
return s->enum_list[i].number;
}
for (int i = 0; s->enum_list[i].string; i++)
{
if (strcmp(s->enum_list[i].string, s->default_value) == 0)
return s->enum_list[i].number;
}
return 0;
}
else
{
return strtoul(tmp, nullptr, 10);
}
}
int64 MDFN_GetSettingI(const char *name)
{
auto s = GetSetting(name);
if (s)
return strtol(s->default_value, nullptr, 10);
return 0;
char tmp[SETTING_VALUE_MAX_LENGTH];
FrontendSettingQuery(name, tmp);
return strtol(tmp, nullptr, 10);
}
double MDFN_GetSettingF(const char *name)
{
auto s = GetSetting(name);
if (s)
return strtod(s->default_value, nullptr);
return 0;
char tmp[SETTING_VALUE_MAX_LENGTH];
FrontendSettingQuery(name, tmp);
return strtod(tmp, nullptr);
}
bool MDFN_GetSettingB(const char *name)
{
auto s = GetSetting(name);
if (s)
return strtol(s->default_value, nullptr, 10) != 0;
return 0;
char tmp[SETTING_VALUE_MAX_LENGTH];
FrontendSettingQuery(name, tmp);
return strtol(tmp, nullptr, 10) != 0;
}
std::string MDFN_GetSettingS(const char *name)
{
auto s = GetSetting(name);
if (s)
return s->default_value;
return "";
char tmp[SETTING_VALUE_MAX_LENGTH];
FrontendSettingQuery(name, tmp);
return std::string(tmp);
}
void MDFNMP_Init(uint32 ps, uint32 numpages)

View File

@ -26,12 +26,15 @@ enum { MAX_PORTS = 16 };
enum { MAX_PORT_DATA = 16 };
static uint8_t InputPortData[MAX_PORTS * MAX_PORT_DATA];
ECL_EXPORT void PreInit()
{
SetupMDFNGameInfo();
}
ECL_EXPORT bool Init(const InitData& data)
{
try
{
SetupMDFNGameInfo();
pixels = new uint32_t[Game->fb_width * Game->fb_height];
samples = new int16_t[22050 * 2];
Surf = new MDFN_Surface(
@ -335,3 +338,48 @@ ECL_EXPORT void SetInputDevices(const char** devices)
Game->SetInput(port, dev.c_str(), &InputPortData[port * MAX_PORT_DATA]);
}
}
struct NSetting
{
const char* Name;
const char* Description;
const char* SettingsKey;
const char* DefaultValue;
const char* Min;
const char* Max;
uint32_t Flags;
uint32_t Type;
};
struct NEnumValue
{
const char* Name;
const char* Description;
const char* Value;
};
ECL_EXPORT void IterateSettings(int index, NSetting& s)
{
auto& a = Game->Settings[index];
if (a.name)
{
s.Name = a.description;
s.Description = a.description_extra;
s.SettingsKey = a.name;
s.DefaultValue = a.default_value;
s.Min = a.minimum;
s.Max = a.maximum;
s.Flags = a.flags;
s.Type = a.type;
}
}
ECL_EXPORT void IterateSettingEnums(int index, int enumIndex, NEnumValue& e)
{
auto& a = Game->Settings[index].enum_list[enumIndex];
if (a.string)
{
e.Name = a.description;
e.Description = a.description_extra;
e.Value = a.string;
}
}