Melon DSi (#3114)
* update melon * frontend work * make this work (i think?) * fucking merge conflicts * bleh * hack up DSi support, very hardcoded atm, but at least states are reasonable * add IS_DSI load flag * frontend work bleh * oops forgot to push * ok fine apparently that doesnt display right * oops * prevent zealous release screen calls, DSi firmware seems to not like it? * support for loading DSiWare title * dsiware * oh right this bullshit * oops * will this work * why the fuck was this signed * 0 out these hashes, these are also unique per console, no hope verifying these
This commit is contained in:
parent
096f24e7c6
commit
33a4dda6b7
Binary file not shown.
|
@ -114,6 +114,8 @@ namespace BizHawk.Emulation.Common
|
|||
|
||||
FirmwareAndOption("24F67BDEA115A2C847C8813A262502EE1607B7DF", 16384, "NDS", "bios7", "NDS_Bios7.bin", "ARM7 BIOS");
|
||||
FirmwareAndOption("BFAAC75F101C135E32E2AAF541DE6B1BE4C8C62D", 4096, "NDS", "bios9", "NDS_Bios9.bin", "ARM9 BIOS");
|
||||
FirmwareAndOption("0000000000000000000000000000000000000000", 65536, "NDS", "bios7i", "NDS_Bios7i.bin", "ARM7i BIOS");
|
||||
FirmwareAndOption("0000000000000000000000000000000000000000", 65536, "NDS", "bios9i", "NDS_Bios9i.bin", "ARM9i BIOS");
|
||||
Firmware("NDS", "firmware", "NDS Firmware");
|
||||
// throwing a ton of hashes from the interwebs
|
||||
var knownhack1 = File("22A7547DBC302BCBFB4005CFB5A2D426D3F85AC6", 262144, "NDS_Firmware [b1].bin", "NDS Firmware", "known hack", true);
|
||||
|
@ -129,6 +131,9 @@ namespace BizHawk.Emulation.Common
|
|||
Option("NDS", "firmware", in likelygood2);
|
||||
Option("NDS", "firmware", in likelygood3);
|
||||
|
||||
FirmwareAndOption("0000000000000000000000000000000000000000", 131072, "NDS", "firmwarei", "DSi_Firmware.bin", "DSi Firmware");
|
||||
FirmwareAndOption("0000000000000000000000000000000000000000", 251658304, "NDS", "nand", "DSi_Nand.bin", "DSi NAND");
|
||||
|
||||
FirmwareAndOption("E4ED47FAE31693E016B081C6BDA48DA5B70D7CCB", 512, "Lynx", "Boot", "LYNX_boot.img", "Boot Rom");
|
||||
|
||||
FirmwareAndOption("5A65B922B562CB1F57DAB51B73151283F0E20C7A", 8192, "INTV", "EROM", "INTV_EROM.bin", "Executive Rom");
|
||||
|
|
|
@ -49,6 +49,8 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.NDS
|
|||
GBA_CART_PRESENT = 0x04,
|
||||
ACCURATE_AUDIO_BITRATE = 0x08,
|
||||
FIRMWARE_OVERRIDE = 0x10,
|
||||
IS_DSI = 0x20,
|
||||
LOAD_DSIWARE = 0x40,
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
|
@ -60,6 +62,9 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.NDS
|
|||
public int GbaRomLength;
|
||||
public IntPtr GbaRamData;
|
||||
public int GbaRamLength;
|
||||
public IntPtr NandData;
|
||||
public int NandLength;
|
||||
public IntPtr TmdData;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
|
|
|
@ -103,8 +103,18 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.NDS
|
|||
[DefaultValue(false)]
|
||||
public bool UseRealTime { get; set; }
|
||||
|
||||
[DisplayName("DSi Mode")]
|
||||
[Description("If true, DSi mode will be used.")]
|
||||
[DefaultValue(false)]
|
||||
public bool UseDSi { get; set; }
|
||||
|
||||
[DisplayName("Load DSiWare")]
|
||||
[Description("")]
|
||||
[DefaultValue(false)]
|
||||
public bool LoadDSiWare { get; set; }
|
||||
|
||||
[DisplayName("Use Real BIOS")]
|
||||
[Description("If true, real BIOS files will be used.")]
|
||||
[Description("If true, real BIOS files will be used. Forced true for DSi.")]
|
||||
[DefaultValue(false)]
|
||||
public bool UseRealBIOS { get; set; }
|
||||
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
using BizHawk.Common;
|
||||
using BizHawk.Emulation.Common;
|
||||
using BizHawk.Emulation.Cores.Properties;
|
||||
using BizHawk.Emulation.Cores.Waterbox;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Consoles.Nintendo.NDS
|
||||
|
@ -30,9 +33,12 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.NDS
|
|||
SystemId = VSystemID.Raw.NDS,
|
||||
})
|
||||
{
|
||||
_syncSettings = lp.SyncSettings ?? new NDSSyncSettings();
|
||||
_settings = lp.Settings ?? new NDSSettings();
|
||||
|
||||
var roms = lp.Roms.Select(r => r.RomData).ToList();
|
||||
|
||||
if (roms.Count > 3)
|
||||
if (roms.Count > (_syncSettings.UseDSi ? 1 : 3))
|
||||
{
|
||||
throw new InvalidOperationException("Wrong number of ROMs!");
|
||||
}
|
||||
|
@ -54,26 +60,41 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.NDS
|
|||
SkipMemoryConsistencyCheck = CoreComm.CorePreferences.HasFlag(CoreComm.CorePreferencesFlags.WaterboxMemoryConsistencyCheck),
|
||||
}, new Delegate[] { _tracecb });
|
||||
|
||||
_syncSettings = lp.SyncSettings ?? new NDSSyncSettings();
|
||||
_settings = lp.Settings ?? new NDSSettings();
|
||||
|
||||
var bios7 = _syncSettings.UseRealBIOS
|
||||
var bios7 = _syncSettings.UseDSi || _syncSettings.UseRealBIOS
|
||||
? CoreComm.CoreFileProvider.GetFirmwareOrThrow(new("NDS", "bios7"))
|
||||
: null;
|
||||
|
||||
var bios9 = _syncSettings.UseRealBIOS
|
||||
var bios9 = _syncSettings.UseDSi || _syncSettings.UseRealBIOS
|
||||
? CoreComm.CoreFileProvider.GetFirmwareOrThrow(new("NDS", "bios9"))
|
||||
: null;
|
||||
|
||||
var fw = CoreComm.CoreFileProvider.GetFirmware(new("NDS", "firmware"));
|
||||
var bios7i = _syncSettings.UseDSi
|
||||
? CoreComm.CoreFileProvider.GetFirmwareOrThrow(new("NDS", "bios7i"))
|
||||
: null;
|
||||
|
||||
var bios9i = _syncSettings.UseDSi
|
||||
? CoreComm.CoreFileProvider.GetFirmwareOrThrow(new("NDS", "bios9i"))
|
||||
: null;
|
||||
|
||||
var nand = _syncSettings.UseDSi
|
||||
? CoreComm.CoreFileProvider.GetFirmwareOrThrow(new("NDS", "nand"))
|
||||
: null;
|
||||
|
||||
var fw = _syncSettings.UseDSi
|
||||
? CoreComm.CoreFileProvider.GetFirmwareOrThrow(new("NDS", "firmwarei"))
|
||||
: CoreComm.CoreFileProvider.GetFirmware(new("NDS", "firmware"));
|
||||
|
||||
var tmd = _syncSettings.UseDSi && _syncSettings.LoadDSiWare
|
||||
? GetTMDData(roms[0])
|
||||
: null;
|
||||
|
||||
bool skipfw = _syncSettings.SkipFirmware || !_syncSettings.UseRealBIOS || fw == null;
|
||||
|
||||
LibMelonDS.LoadFlags flags = LibMelonDS.LoadFlags.NONE;
|
||||
|
||||
if (_syncSettings.UseRealBIOS)
|
||||
if (_syncSettings.UseRealBIOS || _syncSettings.UseDSi)
|
||||
flags |= LibMelonDS.LoadFlags.USE_REAL_BIOS;
|
||||
if (skipfw)
|
||||
if (skipfw && !_syncSettings.UseDSi)
|
||||
flags |= LibMelonDS.LoadFlags.SKIP_FIRMWARE;
|
||||
if (gbacartpresent)
|
||||
flags |= LibMelonDS.LoadFlags.GBA_CART_PRESENT;
|
||||
|
@ -81,6 +102,10 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.NDS
|
|||
flags |= LibMelonDS.LoadFlags.ACCURATE_AUDIO_BITRATE;
|
||||
if (_syncSettings.FirmwareOverride || lp.DeterministicEmulationRequested)
|
||||
flags |= LibMelonDS.LoadFlags.FIRMWARE_OVERRIDE;
|
||||
if (_syncSettings.UseDSi)
|
||||
flags |= LibMelonDS.LoadFlags.IS_DSI;
|
||||
if (_syncSettings.UseDSi && _syncSettings.LoadDSiWare)
|
||||
flags |= LibMelonDS.LoadFlags.LOAD_DSIWARE;
|
||||
|
||||
var fwSettings = new LibMelonDS.FirmwareSettings();
|
||||
var name = Encoding.UTF8.GetBytes(_syncSettings.FirmwareUsername);
|
||||
|
@ -98,22 +123,32 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.NDS
|
|||
DsRomLength = roms[0].Length,
|
||||
GbaRomLength = gbacartpresent ? roms[1].Length : 0,
|
||||
GbaRamLength = gbasrampresent ? roms[2].Length : 0,
|
||||
NandLength = nand?.Length ?? 0,
|
||||
};
|
||||
if (_syncSettings.UseRealBIOS)
|
||||
if (_syncSettings.UseRealBIOS || _syncSettings.UseDSi)
|
||||
{
|
||||
_exe.AddReadonlyFile(bios7, "bios7.rom");
|
||||
_exe.AddReadonlyFile(bios9, "bios9.rom");
|
||||
}
|
||||
if (_syncSettings.UseDSi)
|
||||
{
|
||||
_exe.AddReadonlyFile(bios7i, "bios7i.rom");
|
||||
_exe.AddReadonlyFile(bios9i, "bios9i.rom");
|
||||
if (_syncSettings.LoadDSiWare)
|
||||
{
|
||||
_exe.AddReadonlyFile(roms[0], "dsiware.rom");
|
||||
}
|
||||
}
|
||||
if (fw != null)
|
||||
{
|
||||
if (NDSFirmware.MaybeWarnIfBadFw(fw, CoreComm))
|
||||
if (_syncSettings.UseDSi || NDSFirmware.MaybeWarnIfBadFw(fw, CoreComm))
|
||||
{
|
||||
if (_syncSettings.FirmwareOverride || lp.DeterministicEmulationRequested)
|
||||
{
|
||||
NDSFirmware.SanitizeFw(fw);
|
||||
}
|
||||
}
|
||||
_exe.AddReadonlyFile(fw, "firmware.bin");
|
||||
_exe.AddReadonlyFile(fw, _syncSettings.UseDSi ? "firmwarei.bin" : "firmware.bin");
|
||||
}
|
||||
|
||||
unsafe
|
||||
|
@ -122,12 +157,16 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.NDS
|
|||
dsRomPtr = roms[0],
|
||||
gbaRomPtr = gbacartpresent ? roms[1] : null,
|
||||
gbaRamPtr = gbasrampresent ? roms[2] : null,
|
||||
namePtr = &name[0],
|
||||
messagePtr = &message[0])
|
||||
nandPtr = nand,
|
||||
tmdPtr = tmd,
|
||||
namePtr = name,
|
||||
messagePtr = message)
|
||||
{
|
||||
loadData.DsRomData = (IntPtr)dsRomPtr;
|
||||
loadData.GbaRomData = (IntPtr)gbaRomPtr;
|
||||
loadData.GbaRamData = (IntPtr)gbaRamPtr;
|
||||
loadData.NandData = (IntPtr)nandPtr;
|
||||
loadData.TmdData = (IntPtr)tmdPtr;
|
||||
fwSettings.FirmwareUsername = (IntPtr)namePtr;
|
||||
fwSettings.FirmwareMessage = (IntPtr)messagePtr;
|
||||
if (!_core.Init(flags, loadData, fwSettings))
|
||||
|
@ -137,14 +176,14 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.NDS
|
|||
}
|
||||
}
|
||||
|
||||
/*if (_syncSettings.UseRealBIOS)
|
||||
{
|
||||
_exe.RemoveReadonlyFile("bios7.rom");
|
||||
_exe.RemoveReadonlyFile("bios9.rom");
|
||||
}*/
|
||||
if (fw != null)
|
||||
{
|
||||
_exe.RemoveReadonlyFile("firmware.bin");
|
||||
_exe.RemoveReadonlyFile(_syncSettings.UseDSi ? "firmwarei.bin" : "firmware.bin");
|
||||
}
|
||||
|
||||
if (_syncSettings.UseDSi && _syncSettings.LoadDSiWare)
|
||||
{
|
||||
_exe.RemoveReadonlyFile("dsiware.rom");
|
||||
}
|
||||
|
||||
PostInit();
|
||||
|
@ -162,7 +201,7 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.NDS
|
|||
|
||||
const string TRACE_HEADER = "ARM9+ARM7: PC, opcode, registers (r0, r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, r12, r13, r14, r15, Cy, CpuMode)";
|
||||
Tracer = new TraceBuffer(TRACE_HEADER);
|
||||
_serviceProvider.Register<ITraceable>(Tracer);
|
||||
_serviceProvider.Register(Tracer);
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
|
@ -175,6 +214,21 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.NDS
|
|||
}
|
||||
}
|
||||
|
||||
private static byte[] GetTMDData(byte[] ware)
|
||||
{
|
||||
ulong titleId = 0;
|
||||
for (int i = 0; i < 16; i++)
|
||||
{
|
||||
titleId <<= 8;
|
||||
titleId |= ware[0x23F - i];
|
||||
}
|
||||
using var zip = new ZipArchive(new MemoryStream(Util.DecompressGzipFile(new MemoryStream(Resources.TMDS.Value))), ZipArchiveMode.Read, false);
|
||||
using var tmd = zip.GetEntry($"{titleId:x16}.tmd").Open();
|
||||
var ret = new byte[tmd.Length];
|
||||
tmd.Read(ret, 0, (int)tmd.Length);
|
||||
return ret;
|
||||
}
|
||||
|
||||
public override ControllerDefinition ControllerDefinition => NDSController;
|
||||
|
||||
public static readonly ControllerDefinition NDSController = new ControllerDefinition("NDS Controller")
|
||||
|
|
|
@ -23,5 +23,6 @@ namespace BizHawk.Emulation.Cores.Properties {
|
|||
internal static readonly Lazy<byte[]> ZX_48_ROM = new Lazy<byte[]>(() => ReadEmbeddedByteArray("48.ROM.gz"));
|
||||
internal static readonly Lazy<byte[]> ZX_plus2_rom = new Lazy<byte[]>(() => ReadEmbeddedByteArray("plus2.rom.gz"));
|
||||
internal static readonly Lazy<byte[]> ZX_plus2a_rom = new Lazy<byte[]>(() => ReadEmbeddedByteArray("plus2a.rom.gz"));
|
||||
internal static readonly Lazy<byte[]> TMDS = new Lazy<byte[]>(() => ReadEmbeddedByteArray("tmds.zip.gz"));
|
||||
}
|
||||
}
|
||||
|
|
Binary file not shown.
|
@ -5,6 +5,7 @@
|
|||
#include "ARM.h"
|
||||
#include "NDSCart.h"
|
||||
#include "GBACart.h"
|
||||
#include "DSi_NAND.h"
|
||||
#include "Platform.h"
|
||||
#include "BizConfig.h"
|
||||
#include "types.h"
|
||||
|
@ -17,6 +18,8 @@
|
|||
#include <algorithm>
|
||||
#include <time.h>
|
||||
|
||||
#include <sstream>
|
||||
|
||||
#define EXPORT extern "C" ECL_EXPORT
|
||||
|
||||
static GPU::RenderSettings biz_render_settings { false, 1, false };
|
||||
|
@ -36,6 +39,8 @@ typedef enum
|
|||
GBA_CART_PRESENT = 0x04,
|
||||
ACCURATE_AUDIO_BITRATE = 0x08,
|
||||
FIRMWARE_OVERRIDE = 0x10,
|
||||
IS_DSI = 0x20,
|
||||
LOAD_DSIWARE = 0x40,
|
||||
} LoadFlags;
|
||||
|
||||
typedef struct
|
||||
|
@ -46,14 +51,11 @@ typedef struct
|
|||
u32 GbaRomLen;
|
||||
u8* GbaRamData;
|
||||
u32 GbaRamLen;
|
||||
char* NandData;
|
||||
u32 NandLen;
|
||||
u8* TmdData;
|
||||
} LoadData;
|
||||
|
||||
static const char* rom_path = "game.rom";
|
||||
static const char* sram_path = "save.ram";
|
||||
static const char* gba_rom_path = "gba.rom";
|
||||
static const char* gba_sram_path = "gba.ram";
|
||||
static const char* no_path = "";
|
||||
|
||||
typedef struct
|
||||
{
|
||||
char* FirmwareUsername; // max 10 length (then terminator)
|
||||
|
@ -66,14 +68,41 @@ typedef struct
|
|||
s32 FirmwareMessageLength;
|
||||
} FirmwareSettings;
|
||||
|
||||
extern std::stringstream* NANDFilePtr;
|
||||
|
||||
static bool LoadDSiWare(u8* TmdData)
|
||||
{
|
||||
FILE* bios7i = Platform::OpenLocalFile(Config::DSiBIOS7Path, "rb");
|
||||
if (!bios7i)
|
||||
return false;
|
||||
|
||||
u8 es_keyY[16];
|
||||
fseek(bios7i, 0x8308, SEEK_SET);
|
||||
fread(es_keyY, 16, 1, bios7i);
|
||||
fclose(bios7i);
|
||||
|
||||
FILE* curNAND = Platform::OpenLocalFile(Config::DSiNANDPath, "r+b");
|
||||
if (!curNAND)
|
||||
return false;
|
||||
|
||||
if (!DSi_NAND::Init(curNAND, es_keyY))
|
||||
return false;
|
||||
|
||||
bool ret = DSi_NAND::ImportTitle("dsiware.rom", TmdData, false);
|
||||
|
||||
DSi_NAND::DeInit();
|
||||
return ret;
|
||||
}
|
||||
|
||||
EXPORT bool Init(LoadFlags flags, LoadData* loadData, FirmwareSettings* fwSettings)
|
||||
{
|
||||
Config::ExternalBIOSEnable = !!(flags & USE_REAL_BIOS);
|
||||
Config::AudioBitrate = !!(flags & ACCURATE_AUDIO_BITRATE) ? 1 : 2;
|
||||
Config::FirmwareOverrideSettings = !!(flags & FIRMWARE_OVERRIDE);
|
||||
biz_skip_fw = !!(flags & SKIP_FIRMWARE);
|
||||
bool isDsi = !!(flags & IS_DSI);
|
||||
|
||||
NDS::SetConsoleType(0);
|
||||
NDS::SetConsoleType(isDsi);
|
||||
// time calls are deterministic under wbx, so this will force the mac address to a constant value instead of relying on whatever is in the firmware
|
||||
// fixme: might want to allow the user to specify mac address?
|
||||
srand(time(NULL));
|
||||
|
@ -95,16 +124,28 @@ EXPORT bool Init(LoadFlags flags, LoadData* loadData, FirmwareSettings* fwSettin
|
|||
Config::FirmwareMessage = fwMessage;
|
||||
}
|
||||
|
||||
NANDFilePtr = isDsi ? new std::stringstream(std::string(loadData->NandData, loadData->NandLen), std::ios_base::in | std::ios_base::out | std::ios_base::binary) : nullptr;
|
||||
|
||||
if (isDsi && (flags & LOAD_DSIWARE))
|
||||
{
|
||||
if (!LoadDSiWare(loadData->TmdData))
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!NDS::Init()) return false;
|
||||
GPU::InitRenderer(false);
|
||||
GPU::SetRenderSettings(false, biz_render_settings);
|
||||
if (!NDS::LoadCart(loadData->DsRomData, loadData->DsRomLen, nullptr, 0)) return false;
|
||||
if (flags & GBA_CART_PRESENT)
|
||||
NDS::LoadBIOS();
|
||||
if (!isDsi || !(flags & LOAD_DSIWARE))
|
||||
{
|
||||
if (!NDS::LoadCart(loadData->DsRomData, loadData->DsRomLen, nullptr, 0))
|
||||
return false;
|
||||
}
|
||||
if (!isDsi && (flags & GBA_CART_PRESENT))
|
||||
{
|
||||
if (!NDS::LoadGBACart(loadData->GbaRomData, loadData->GbaRomLen, loadData->GbaRamData, loadData->GbaRamLen))
|
||||
return false;
|
||||
}
|
||||
NDS::LoadBIOS();
|
||||
if (biz_skip_fw) NDS::SetupDirectBoot("");
|
||||
NDS::Start();
|
||||
Config::FirmwareOverrideSettings = false;
|
||||
|
@ -269,8 +310,8 @@ struct MyFrameInfo : public FrameInfo
|
|||
{
|
||||
s64 Time;
|
||||
u32 Keys;
|
||||
s8 TouchX;
|
||||
s8 TouchY;
|
||||
u8 TouchX;
|
||||
u8 TouchY;
|
||||
s8 MicVolume;
|
||||
s8 GBALightSensor;
|
||||
};
|
||||
|
@ -301,14 +342,19 @@ EXPORT void FrameAdvance(MyFrameInfo* f)
|
|||
{
|
||||
NDS::LoadBIOS();
|
||||
if (biz_skip_fw) NDS::SetupDirectBoot("");
|
||||
NDS::Start();
|
||||
}
|
||||
|
||||
NDS::SetKeyMask(~f->Keys & 0xFFF);
|
||||
|
||||
if (f->Keys & 0x1000)
|
||||
{
|
||||
NDS::TouchScreen(f->TouchX, f->TouchY);
|
||||
}
|
||||
else
|
||||
{
|
||||
NDS::ReleaseScreen();
|
||||
}
|
||||
|
||||
if (f->Keys & 0x2000)
|
||||
NDS::SetLidClosed(false);
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#include "BizConfig.h"
|
||||
|
||||
bool NdsSaveRamIsDirty = false;
|
||||
std::stringstream* NANDFilePtr = NULL;
|
||||
|
||||
namespace Platform
|
||||
{
|
||||
|
@ -155,11 +156,15 @@ bool GetConfigArray(ConfigEntry entry, void* data)
|
|||
|
||||
FILE* OpenFile(std::string path, std::string mode, bool mustexist)
|
||||
{
|
||||
if (path == Config::DSiNANDPath) return reinterpret_cast<FILE*>(NANDFilePtr);
|
||||
|
||||
return fopen(path.c_str(), mode.c_str());
|
||||
}
|
||||
|
||||
FILE* OpenLocalFile(std::string path, std::string mode)
|
||||
{
|
||||
if (path == Config::DSiNANDPath) return reinterpret_cast<FILE*>(NANDFilePtr);
|
||||
|
||||
return fopen(path.c_str(), mode.c_str());
|
||||
}
|
||||
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit e59387b3fcc8dde8e8df1e1a1c1251c53720c2bc
|
||||
Subproject commit 4faf7b4dd4e3ffbe9f1fe72df3bf8448276d660b
|
Loading…
Reference in New Issue