mirror of https://github.com/AxioDL/metaforce.git
CResFactory bug fixes
This commit is contained in:
parent
2f4cddd3d2
commit
bb10aa9844
|
@ -1131,7 +1131,7 @@ bool ANCS::Cook(const hecl::ProjectPath& outPath,
|
||||||
{
|
{
|
||||||
ch.cmdlOverlay = sub.overlayMeshes[0].second;
|
ch.cmdlOverlay = sub.overlayMeshes[0].second;
|
||||||
ch.cskrOverlay = inPath.ensureAuxInfo(chSysName.sys_str() + _S('.') +
|
ch.cskrOverlay = inPath.ensureAuxInfo(chSysName.sys_str() + _S('.') +
|
||||||
sub.overlayMeshes[0].first + _S(".CSKR"));
|
sub.overlayMeshes[0].first + _S(".CSKR"));
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -341,6 +341,7 @@ void SpecBase::flattenDependencies(const hecl::ProjectPath& path,
|
||||||
hecl::BlenderToken& btok)
|
hecl::BlenderToken& btok)
|
||||||
{
|
{
|
||||||
DataSpec::g_curSpec.reset(this);
|
DataSpec::g_curSpec.reset(this);
|
||||||
|
g_ThreadBlenderToken.reset(&btok);
|
||||||
|
|
||||||
hecl::ProjectPath asBlend;
|
hecl::ProjectPath asBlend;
|
||||||
if (path.getPathType() == hecl::ProjectPath::Type::Glob)
|
if (path.getPathType() == hecl::ProjectPath::Type::Glob)
|
||||||
|
|
|
@ -32,6 +32,7 @@ public:
|
||||||
bool IsComplete() {return m_complete;}
|
bool IsComplete() {return m_complete;}
|
||||||
void PostCancelRequest()
|
void PostCancelRequest()
|
||||||
{
|
{
|
||||||
|
std::unique_lock<std::mutex> waitlk(CDvdFile::m_WaitMutex);
|
||||||
m_cancel = true;
|
m_cancel = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,15 +16,16 @@ CFactoryFnReturn CResFactory::BuildSync(const SObjectTag& tag, const CVParamTran
|
||||||
if (x5c_factoryMgr.CanMakeMemory(tag))
|
if (x5c_factoryMgr.CanMakeMemory(tag))
|
||||||
{
|
{
|
||||||
std::unique_ptr<uint8_t[]> data;
|
std::unique_ptr<uint8_t[]> data;
|
||||||
int size;
|
int size = 0;
|
||||||
x4_loader.LoadMemResourceSync(tag, data, &size);
|
x4_loader.LoadMemResourceSync(tag, data, &size);
|
||||||
ret = x5c_factoryMgr.MakeObjectFromMemory(tag, std::move(data), size,
|
if (size)
|
||||||
x4_loader.GetResourceCompression(tag), xfer, selfRef);
|
ret = x5c_factoryMgr.MakeObjectFromMemory(tag, std::move(data), size,
|
||||||
|
x4_loader.GetResourceCompression(tag), xfer, selfRef);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
auto rp = x4_loader.LoadNewResourceSync(tag, nullptr);
|
if (auto rp = x4_loader.LoadNewResourceSync(tag, nullptr))
|
||||||
ret = x5c_factoryMgr.MakeObject(tag, *rp, xfer, selfRef);
|
ret = x5c_factoryMgr.MakeObject(tag, *rp, xfer, selfRef);
|
||||||
}
|
}
|
||||||
Log.report(logvisor::Warning, "sync-built %.4s %08X", tag.type.getChars(), tag.id.Value());
|
Log.report(logvisor::Warning, "sync-built %.4s %08X", tag.type.getChars(), tag.id.Value());
|
||||||
return ret;
|
return ret;
|
||||||
|
@ -50,9 +51,10 @@ std::unique_ptr<IObj> CResFactory::Build(const SObjectTag& tag, const CVParamTra
|
||||||
if (search != m_loadMap.end())
|
if (search != m_loadMap.end())
|
||||||
{
|
{
|
||||||
while (!PumpResource(*search->second) || !search->second->xc_targetPtr) {}
|
while (!PumpResource(*search->second) || !search->second->xc_targetPtr) {}
|
||||||
|
std::unique_ptr<IObj> ret = std::move(*search->second->xc_targetPtr);
|
||||||
m_loadList.erase(search->second);
|
m_loadList.erase(search->second);
|
||||||
m_loadMap.erase(search);
|
m_loadMap.erase(search);
|
||||||
return std::move(*search->second->xc_targetPtr);
|
return ret;
|
||||||
}
|
}
|
||||||
return BuildSync(tag, xfer, selfRef);
|
return BuildSync(tag, xfer, selfRef);
|
||||||
}
|
}
|
||||||
|
@ -61,12 +63,16 @@ void CResFactory::BuildAsync(const SObjectTag& tag, const CVParamTransfer& xfer,
|
||||||
CObjectReference* selfRef)
|
CObjectReference* selfRef)
|
||||||
{
|
{
|
||||||
auto search = m_loadMap.find(tag);
|
auto search = m_loadMap.find(tag);
|
||||||
if (search != m_loadMap.end())
|
if (search == m_loadMap.end())
|
||||||
{
|
{
|
||||||
SLoadingData data(tag, target, xfer, x4_loader.GetResourceCompression(tag), selfRef);
|
SLoadingData data(tag, target, xfer, x4_loader.GetResourceCompression(tag), selfRef);
|
||||||
data.x10_loadBuffer = std::unique_ptr<u8[]>(new u8[x4_loader.ResourceSize(tag)]);
|
data.x14_resSize = x4_loader.ResourceSize(tag);
|
||||||
data.x8_dvdReq = x4_loader.LoadResourceAsync(tag, data.x10_loadBuffer.get());
|
if (data.x14_resSize)
|
||||||
AddToLoadList(std::move(data));
|
{
|
||||||
|
data.x10_loadBuffer = std::unique_ptr<u8[]>(new u8[data.x14_resSize]);
|
||||||
|
data.x8_dvdReq = x4_loader.LoadResourceAsync(tag, data.x10_loadBuffer.get());
|
||||||
|
AddToLoadList(std::move(data));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,6 +89,8 @@ void CResFactory::AsyncIdle()
|
||||||
{
|
{
|
||||||
m_loadMap.erase(task.x0_tag);
|
m_loadMap.erase(task.x0_tag);
|
||||||
m_loadList.pop_front();
|
m_loadList.pop_front();
|
||||||
|
if (m_loadList.empty())
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -69,6 +69,11 @@ public:
|
||||||
return x4_loader.LoadResourcePartSync(tag, size, off);
|
return x4_loader.LoadResourcePartSync(tag, size, off);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GetTagListForFile(const char* pakName, std::vector<SObjectTag>& out) const
|
||||||
|
{
|
||||||
|
return x4_loader.GetTagListForFile(pakName, out);
|
||||||
|
}
|
||||||
|
|
||||||
std::shared_ptr<IDvdRequest> LoadResourceAsync(const urde::SObjectTag& tag, void* target)
|
std::shared_ptr<IDvdRequest> LoadResourceAsync(const urde::SObjectTag& tag, void* target)
|
||||||
{
|
{
|
||||||
return x4_loader.LoadResourceAsync(tag, target);
|
return x4_loader.LoadResourceAsync(tag, target);
|
||||||
|
|
|
@ -53,11 +53,13 @@ std::unique_ptr<CInputStream> CResLoader::LoadNewResourcePartSync(const SObjectT
|
||||||
|
|
||||||
void CResLoader::LoadMemResourceSync(const SObjectTag& tag, std::unique_ptr<u8[]>& bufOut, int* sizeOut)
|
void CResLoader::LoadMemResourceSync(const SObjectTag& tag, std::unique_ptr<u8[]>& bufOut, int* sizeOut)
|
||||||
{
|
{
|
||||||
CPakFile* file = FindResourceForLoad(tag);
|
if (CPakFile* file = FindResourceForLoad(tag))
|
||||||
bufOut = std::unique_ptr<u8[]>(new u8[x50_cachedResInfo->GetSize()]);
|
{
|
||||||
file->SyncSeekRead(bufOut.get(), x50_cachedResInfo->GetSize(), ESeekOrigin::Begin,
|
bufOut = std::unique_ptr<u8[]>(new u8[x50_cachedResInfo->GetSize()]);
|
||||||
x50_cachedResInfo->GetOffset());
|
file->SyncSeekRead(bufOut.get(), x50_cachedResInfo->GetSize(), ESeekOrigin::Begin,
|
||||||
*sizeOut = x50_cachedResInfo->GetSize();
|
x50_cachedResInfo->GetOffset());
|
||||||
|
*sizeOut = x50_cachedResInfo->GetSize();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<CInputStream> CResLoader::LoadResourceFromMemorySync(const SObjectTag& tag, const void* buf)
|
std::unique_ptr<CInputStream> CResLoader::LoadResourceFromMemorySync(const SObjectTag& tag, const void* buf)
|
||||||
|
@ -75,18 +77,21 @@ std::unique_ptr<CInputStream> CResLoader::LoadResourceFromMemorySync(const SObje
|
||||||
std::unique_ptr<CInputStream> CResLoader::LoadNewResourceSync(const SObjectTag& tag, void* extBuf)
|
std::unique_ptr<CInputStream> CResLoader::LoadNewResourceSync(const SObjectTag& tag, void* extBuf)
|
||||||
{
|
{
|
||||||
void* buf = extBuf;
|
void* buf = extBuf;
|
||||||
CPakFile* file = FindResourceForLoad(tag);
|
if (CPakFile* file = FindResourceForLoad(tag))
|
||||||
size_t resSz = ROUND_UP_32(x50_cachedResInfo->GetSize());
|
|
||||||
if (!buf)
|
|
||||||
buf = new u8[resSz];
|
|
||||||
file->SyncSeekRead(buf, resSz, ESeekOrigin::Begin, x50_cachedResInfo->GetOffset());
|
|
||||||
CInputStream* newStrm = new athena::io::MemoryReader((atUint8*)buf, resSz, !extBuf);
|
|
||||||
if (x50_cachedResInfo->IsCompressed())
|
|
||||||
{
|
{
|
||||||
newStrm->readUint32Big();
|
size_t resSz = ROUND_UP_32(x50_cachedResInfo->GetSize());
|
||||||
newStrm = new CZipInputStream(std::unique_ptr<CInputStream>(newStrm));
|
if (!buf)
|
||||||
|
buf = new u8[resSz];
|
||||||
|
file->SyncSeekRead(buf, resSz, ESeekOrigin::Begin, x50_cachedResInfo->GetOffset());
|
||||||
|
CInputStream* newStrm = new athena::io::MemoryReader((atUint8*) buf, resSz, !extBuf);
|
||||||
|
if (x50_cachedResInfo->IsCompressed())
|
||||||
|
{
|
||||||
|
newStrm->readUint32Big();
|
||||||
|
newStrm = new CZipInputStream(std::unique_ptr<CInputStream>(newStrm));
|
||||||
|
}
|
||||||
|
return std::unique_ptr<CInputStream>(newStrm);
|
||||||
}
|
}
|
||||||
return std::unique_ptr<CInputStream>(newStrm);
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<IDvdRequest> CResLoader::LoadResourcePartAsync(const SObjectTag& tag, u32 length, u32 offset, void* buf)
|
std::shared_ptr<IDvdRequest> CResLoader::LoadResourcePartAsync(const SObjectTag& tag, u32 length, u32 offset, void* buf)
|
||||||
|
@ -118,6 +123,25 @@ std::unique_ptr<u8[]> CResLoader::LoadResourcePartSync(const urde::SObjectTag& t
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CResLoader::GetTagListForFile(const char* pakName, std::vector<SObjectTag>& out) const
|
||||||
|
{
|
||||||
|
std::string path = std::string(pakName) + ".upak";
|
||||||
|
for (const std::unique_ptr<CPakFile>& file : x18_pakLoadedList)
|
||||||
|
{
|
||||||
|
if (!CStringExtras::CompareCaseInsensitive(file->GetPath(), path))
|
||||||
|
{
|
||||||
|
auto& depList = file->GetDepList();
|
||||||
|
out.reserve(depList.size());
|
||||||
|
for (const auto& dep : depList)
|
||||||
|
{
|
||||||
|
auto resInfo = file->GetResInfo(dep);
|
||||||
|
out.emplace_back(resInfo->GetType(), dep);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool CResLoader::GetResourceCompression(const SObjectTag& tag)
|
bool CResLoader::GetResourceCompression(const SObjectTag& tag)
|
||||||
{
|
{
|
||||||
if (FindResource(tag.id))
|
if (FindResource(tag.id))
|
||||||
|
@ -193,7 +217,7 @@ bool CResLoader::FindResource(CAssetId id) const
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.report(logvisor::Fatal, "Unable to find asset %08X", id);
|
Log.report(logvisor::Error, "Unable to find asset %08X", id);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -214,7 +238,7 @@ CPakFile* CResLoader::FindResourceForLoad(CAssetId id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.report(logvisor::Fatal, "Unable to find asset %08X", id);
|
Log.report(logvisor::Error, "Unable to find asset %08X", id);
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -37,6 +37,7 @@ public:
|
||||||
std::shared_ptr<IDvdRequest> LoadResourceAsync(const SObjectTag& tag, void* buf);
|
std::shared_ptr<IDvdRequest> LoadResourceAsync(const SObjectTag& tag, void* buf);
|
||||||
std::unique_ptr<u8[]> LoadResourceSync(const urde::SObjectTag& tag);
|
std::unique_ptr<u8[]> LoadResourceSync(const urde::SObjectTag& tag);
|
||||||
std::unique_ptr<u8[]> LoadResourcePartSync(const urde::SObjectTag& tag, u32 size, u32 off);
|
std::unique_ptr<u8[]> LoadResourcePartSync(const urde::SObjectTag& tag, u32 size, u32 off);
|
||||||
|
void GetTagListForFile(const char* pakName, std::vector<SObjectTag>& out) const;
|
||||||
bool GetResourceCompression(const SObjectTag& tag);
|
bool GetResourceCompression(const SObjectTag& tag);
|
||||||
u32 ResourceSize(const SObjectTag& tag);
|
u32 ResourceSize(const SObjectTag& tag);
|
||||||
bool ResourceExists(const SObjectTag& tag);
|
bool ResourceExists(const SObjectTag& tag);
|
||||||
|
|
|
@ -86,7 +86,7 @@ bool CCollisionResponseData::CheckAndAddDecalToResponse(FourCC clsId, CInputStre
|
||||||
if (cls == SBIG('NONE'))
|
if (cls == SBIG('NONE'))
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
CAssetId id = u64(CPF::GetInt(in));
|
CAssetId id = u64(in.readUint32Big());
|
||||||
if (!id.IsValid())
|
if (!id.IsValid())
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
|
|
@ -48,7 +48,6 @@ public:
|
||||||
virtual std::shared_ptr<IDvdRequest> LoadResourcePartAsync(const urde::SObjectTag& tag, u32 size, u32 off, void* target)=0;
|
virtual std::shared_ptr<IDvdRequest> LoadResourcePartAsync(const urde::SObjectTag& tag, u32 size, u32 off, void* target)=0;
|
||||||
virtual std::unique_ptr<u8[]> LoadResourceSync(const urde::SObjectTag& tag)=0;
|
virtual std::unique_ptr<u8[]> LoadResourceSync(const urde::SObjectTag& tag)=0;
|
||||||
virtual std::unique_ptr<u8[]> LoadResourcePartSync(const urde::SObjectTag& tag, u32 size, u32 off)=0;
|
virtual std::unique_ptr<u8[]> LoadResourcePartSync(const urde::SObjectTag& tag, u32 size, u32 off)=0;
|
||||||
|
|
||||||
virtual void GetTagListForFile(const char* pakName, std::vector<SObjectTag>& out) const {}
|
virtual void GetTagListForFile(const char* pakName, std::vector<SObjectTag>& out) const {}
|
||||||
|
|
||||||
virtual CAssetId TranslateOriginalToNew(CAssetId id) const { return -1; }
|
virtual CAssetId TranslateOriginalToNew(CAssetId id) const { return -1; }
|
||||||
|
|
|
@ -149,11 +149,15 @@ atUint64 CZipInputStream::readUBytesToBuf(void *buf, atUint64 len)
|
||||||
{
|
{
|
||||||
x30_zstrm.next_out = (Bytef*)buf;
|
x30_zstrm.next_out = (Bytef*)buf;
|
||||||
x30_zstrm.avail_out = len;
|
x30_zstrm.avail_out = len;
|
||||||
|
x30_zstrm.total_out = 0;
|
||||||
while (x30_zstrm.avail_out != 0)
|
while (x30_zstrm.avail_out != 0)
|
||||||
{
|
{
|
||||||
atUint64 readSz = x28_strm->readUBytesToBuf(x24_compBuf.get(), 4096);
|
if (x30_zstrm.avail_in == 0)
|
||||||
x30_zstrm.avail_in = readSz;
|
{
|
||||||
x30_zstrm.next_in = x24_compBuf.get();
|
atUint64 readSz = x28_strm->readUBytesToBuf(x24_compBuf.get(), 4096);
|
||||||
|
x30_zstrm.avail_in = readSz;
|
||||||
|
x30_zstrm.next_in = x24_compBuf.get();
|
||||||
|
}
|
||||||
if (inflate(&x30_zstrm, Z_NO_FLUSH) != Z_OK)
|
if (inflate(&x30_zstrm, Z_NO_FLUSH) != Z_OK)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,10 +51,10 @@ class CGameGlobalObjects
|
||||||
{
|
{
|
||||||
friend class CMain;
|
friend class CMain;
|
||||||
|
|
||||||
|
std::unique_ptr<CSimplePool> m_gameSimplePool;
|
||||||
|
std::unique_ptr<CResFactory> m_gameResFactory;
|
||||||
std::unique_ptr<CMemoryCardSys> x0_memoryCardSys;
|
std::unique_ptr<CMemoryCardSys> x0_memoryCardSys;
|
||||||
std::unique_ptr<CResFactory> x4_gameResFactory;
|
|
||||||
IFactory* x4_resFactory;
|
IFactory* x4_resFactory;
|
||||||
std::unique_ptr<CSimplePool> x4_gameSimplePool;
|
|
||||||
CSimplePool* xcc_simplePool;
|
CSimplePool* xcc_simplePool;
|
||||||
CCharacterFactoryBuilder xec_charFactoryBuilder;
|
CCharacterFactoryBuilder xec_charFactoryBuilder;
|
||||||
CAiFuncMap x110_aiFuncMap;
|
CAiFuncMap x110_aiFuncMap;
|
||||||
|
@ -83,13 +83,13 @@ public:
|
||||||
{
|
{
|
||||||
if (!x4_resFactory)
|
if (!x4_resFactory)
|
||||||
{
|
{
|
||||||
x4_gameResFactory.reset(new CResFactory());
|
m_gameResFactory.reset(new CResFactory());
|
||||||
x4_resFactory = x4_gameResFactory.get();
|
x4_resFactory = m_gameResFactory.get();
|
||||||
}
|
}
|
||||||
if (!xcc_simplePool)
|
if (!xcc_simplePool)
|
||||||
{
|
{
|
||||||
x4_gameSimplePool.reset(new CSimplePool(*x4_resFactory));
|
m_gameSimplePool.reset(new CSimplePool(*x4_resFactory));
|
||||||
xcc_simplePool = x4_gameSimplePool.get();
|
xcc_simplePool = m_gameSimplePool.get();
|
||||||
}
|
}
|
||||||
g_ResFactory = x4_resFactory;
|
g_ResFactory = x4_resFactory;
|
||||||
g_SimplePool = xcc_simplePool;
|
g_SimplePool = xcc_simplePool;
|
||||||
|
|
|
@ -17,7 +17,6 @@ using FourCC = hecl::FourCC;
|
||||||
|
|
||||||
class CAssetId
|
class CAssetId
|
||||||
{
|
{
|
||||||
private:
|
|
||||||
u64 id = UINT64_MAX;
|
u64 id = UINT64_MAX;
|
||||||
public:
|
public:
|
||||||
CAssetId() = default;
|
CAssetId() = default;
|
||||||
|
|
|
@ -19,7 +19,7 @@ CDummyWorld::CDummyWorld(CAssetId mlvlId, bool loadMap) : x4_loadMap(loadMap), x
|
||||||
{
|
{
|
||||||
SObjectTag tag{FOURCC('MLVL'), mlvlId};
|
SObjectTag tag{FOURCC('MLVL'), mlvlId};
|
||||||
x34_loadBuf.reset(new u8[g_ResFactory->ResourceSize(tag)]);
|
x34_loadBuf.reset(new u8[g_ResFactory->ResourceSize(tag)]);
|
||||||
g_ResFactory->LoadResourceAsync(tag, x34_loadBuf.get());
|
x30_loadToken = g_ResFactory->LoadResourceAsync(tag, x34_loadBuf.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
CAssetId CDummyWorld::IGetWorldAssetId() const { return xc_mlvlId; }
|
CAssetId CDummyWorld::IGetWorldAssetId() const { return xc_mlvlId; }
|
||||||
|
@ -103,8 +103,9 @@ bool CDummyWorld::ICheckWorldComplete()
|
||||||
{
|
{
|
||||||
case Phase::Loading:
|
case Phase::Loading:
|
||||||
{
|
{
|
||||||
if (!x34_loadBuf)
|
if (x30_loadToken && !x30_loadToken->IsComplete())
|
||||||
return false;
|
return false;
|
||||||
|
x30_loadToken.reset();
|
||||||
athena::io::MemoryReader r(x34_loadBuf.get(), UINT32_MAX, false);
|
athena::io::MemoryReader r(x34_loadBuf.get(), UINT32_MAX, false);
|
||||||
r.readUint32Big();
|
r.readUint32Big();
|
||||||
int version = r.readUint32Big();
|
int version = r.readUint32Big();
|
||||||
|
|
|
@ -50,7 +50,7 @@ class CDummyWorld : public IWorld
|
||||||
std::vector<CDummyGameArea> x18_areas;
|
std::vector<CDummyGameArea> x18_areas;
|
||||||
CAssetId x28_mapWorldId = -1;
|
CAssetId x28_mapWorldId = -1;
|
||||||
TLockedToken<CMapWorld> x2c_mapWorld;
|
TLockedToken<CMapWorld> x2c_mapWorld;
|
||||||
//AsyncTask x30_loadToken;
|
std::shared_ptr<IDvdRequest> x30_loadToken;
|
||||||
std::unique_ptr<uint8_t[]> x34_loadBuf;
|
std::unique_ptr<uint8_t[]> x34_loadBuf;
|
||||||
//u32 x38_bufSz;
|
//u32 x38_bufSz;
|
||||||
TAreaId x3c_curAreaId = kInvalidAreaId;
|
TAreaId x3c_curAreaId = kInvalidAreaId;
|
||||||
|
|
2
hecl
2
hecl
|
@ -1 +1 @@
|
||||||
Subproject commit e6f5ea29c23008f5eb4aa353b4adc8a82b4a0332
|
Subproject commit 30b0ef6851cc25f9912f8ba094ff253f6f389c0b
|
Loading…
Reference in New Issue