mirror of
https://github.com/ZDoom/gzdoom-gles.git
synced 2024-11-11 07:12:16 +00:00
- refactored the GUS/Timidity player's path building code so that it can also be used by WildMidi.
- fixed crash during sound reset - in this case I_ShutdownMusic should not close the WildMidi player.
This commit is contained in:
parent
0634205d7f
commit
fe2dcfd588
11 changed files with 198 additions and 121 deletions
|
@ -915,6 +915,7 @@ add_executable( zdoom WIN32 MACOSX_BUNDLE
|
||||||
nodebuild_extract.cpp
|
nodebuild_extract.cpp
|
||||||
nodebuild_gl.cpp
|
nodebuild_gl.cpp
|
||||||
nodebuild_utility.cpp
|
nodebuild_utility.cpp
|
||||||
|
pathexpander.cpp
|
||||||
p_3dfloors.cpp
|
p_3dfloors.cpp
|
||||||
p_3dmidtex.cpp
|
p_3dmidtex.cpp
|
||||||
p_acs.cpp
|
p_acs.cpp
|
||||||
|
|
134
src/pathexpander.cpp
Normal file
134
src/pathexpander.cpp
Normal file
|
@ -0,0 +1,134 @@
|
||||||
|
/*
|
||||||
|
** pathexpander.cpp
|
||||||
|
** Utility class for expanding a given path with a range of directories
|
||||||
|
**
|
||||||
|
**---------------------------------------------------------------------------
|
||||||
|
** Copyright 2015 Christoph Oelckers
|
||||||
|
** All rights reserved.
|
||||||
|
**
|
||||||
|
** Redistribution and use in source and binary forms, with or without
|
||||||
|
** modification, are permitted provided that the following conditions
|
||||||
|
** are met:
|
||||||
|
**
|
||||||
|
** 1. Redistributions of source code must retain the above copyright
|
||||||
|
** notice, this list of conditions and the following disclaimer.
|
||||||
|
** 2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
** notice, this list of conditions and the following disclaimer in the
|
||||||
|
** documentation and/or other materials provided with the distribution.
|
||||||
|
** 3. The name of the author may not be used to endorse or promote products
|
||||||
|
** derived from this software without specific prior written permission.
|
||||||
|
**
|
||||||
|
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||||
|
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||||
|
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||||
|
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||||
|
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||||
|
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||||
|
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
**---------------------------------------------------------------------------
|
||||||
|
**
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "pathexpander.h"
|
||||||
|
#include "cmdlib.h"
|
||||||
|
#include "w_wad.h"
|
||||||
|
|
||||||
|
//============================================================================
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//============================================================================
|
||||||
|
|
||||||
|
static FString BuildPath(const FString &base, const char *name)
|
||||||
|
{
|
||||||
|
FString current;
|
||||||
|
if (base.IsNotEmpty())
|
||||||
|
{
|
||||||
|
current = base;
|
||||||
|
if (current[current.Len() - 1] != '/') current += '/';
|
||||||
|
}
|
||||||
|
current += name;
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
|
||||||
|
//============================================================================
|
||||||
|
//
|
||||||
|
// This is meant to find and open files for reading.
|
||||||
|
//
|
||||||
|
//============================================================================
|
||||||
|
|
||||||
|
FileReader *PathExpander::openFileReader(const char *name, int *plumpnum)
|
||||||
|
{
|
||||||
|
FileReader *fp;
|
||||||
|
FString current_filename;
|
||||||
|
|
||||||
|
if (!name || !(*name))
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* First try the given name */
|
||||||
|
current_filename = name;
|
||||||
|
FixPathSeperator(current_filename);
|
||||||
|
|
||||||
|
int lumpnum = Wads.CheckNumForFullName(current_filename);
|
||||||
|
|
||||||
|
if (openmode != OM_FILE)
|
||||||
|
{
|
||||||
|
if (lumpnum >= 0)
|
||||||
|
{
|
||||||
|
fp = Wads.ReopenLumpNum(lumpnum);
|
||||||
|
if (plumpnum) *plumpnum = lumpnum;
|
||||||
|
return fp;
|
||||||
|
}
|
||||||
|
if (openmode == OM_LUMP) // search the path list when not loading the main config
|
||||||
|
{
|
||||||
|
for (unsigned int plp = PathList.Size(); plp-- != 0; )
|
||||||
|
{ /* Try along the path then */
|
||||||
|
current_filename = BuildPath(PathList[plp], name);
|
||||||
|
lumpnum = Wads.CheckNumForFullName(current_filename);
|
||||||
|
if (lumpnum >= 0)
|
||||||
|
{
|
||||||
|
fp = Wads.ReopenLumpNum(lumpnum);
|
||||||
|
if (plumpnum) *plumpnum = lumpnum;
|
||||||
|
return fp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (plumpnum) *plumpnum = -1;
|
||||||
|
|
||||||
|
|
||||||
|
fp = new FileReader;
|
||||||
|
if (fp->Open(current_filename)) return fp;
|
||||||
|
|
||||||
|
if (name[0] != '/')
|
||||||
|
{
|
||||||
|
for (unsigned int plp = PathList.Size(); plp-- != 0; )
|
||||||
|
{ /* Try along the path then */
|
||||||
|
current_filename = BuildPath(PathList[plp], name);
|
||||||
|
if (fp->Open(current_filename)) return fp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delete fp;
|
||||||
|
|
||||||
|
/* Nothing could be opened. */
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* This adds a directory to the path list */
|
||||||
|
void PathExpander::addToPathlist(const char *s)
|
||||||
|
{
|
||||||
|
FString copy = s;
|
||||||
|
FixPathSeperator(copy);
|
||||||
|
PathList.Push(copy);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PathExpander::clearPathlist()
|
||||||
|
{
|
||||||
|
PathList.Clear();
|
||||||
|
}
|
31
src/pathexpander.h
Normal file
31
src/pathexpander.h
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
#ifndef __PATHEXPANDER_H
|
||||||
|
#define __PATHEXPANDER_H
|
||||||
|
|
||||||
|
#include "tarray.h"
|
||||||
|
#include "zstring.h"
|
||||||
|
#include "files.h"
|
||||||
|
|
||||||
|
class PathExpander
|
||||||
|
{
|
||||||
|
TArray<FString> PathList;
|
||||||
|
|
||||||
|
public:
|
||||||
|
int openmode;
|
||||||
|
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
OM_FILEORLUMP = 0,
|
||||||
|
OM_LUMP,
|
||||||
|
OM_FILE
|
||||||
|
};
|
||||||
|
|
||||||
|
PathExpander(int om = OM_FILEORLUMP)
|
||||||
|
{
|
||||||
|
openmode = om;
|
||||||
|
}
|
||||||
|
void addToPathlist(const char *s);
|
||||||
|
void clearPathlist();
|
||||||
|
FileReader *openFileReader(const char *name, int *plumpnum);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
|
@ -162,7 +162,7 @@ void I_InitMusic (void)
|
||||||
if (!setatterm)
|
if (!setatterm)
|
||||||
{
|
{
|
||||||
setatterm = true;
|
setatterm = true;
|
||||||
atterm (I_ShutdownMusic);
|
atterm (I_ShutdownMusicExit);
|
||||||
|
|
||||||
#ifndef _WIN32
|
#ifndef _WIN32
|
||||||
signal (SIGCHLD, ChildSigHandler);
|
signal (SIGCHLD, ChildSigHandler);
|
||||||
|
@ -178,7 +178,7 @@ void I_InitMusic (void)
|
||||||
//
|
//
|
||||||
//==========================================================================
|
//==========================================================================
|
||||||
|
|
||||||
void I_ShutdownMusic(void)
|
void I_ShutdownMusic(bool onexit)
|
||||||
{
|
{
|
||||||
if (MusicDown)
|
if (MusicDown)
|
||||||
return;
|
return;
|
||||||
|
@ -189,12 +189,17 @@ void I_ShutdownMusic(void)
|
||||||
assert (currSong == NULL);
|
assert (currSong == NULL);
|
||||||
}
|
}
|
||||||
Timidity::FreeAll();
|
Timidity::FreeAll();
|
||||||
WildMidi_Shutdown();
|
if (onexit) WildMidi_Shutdown();
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
I_ShutdownMusicWin32();
|
I_ShutdownMusicWin32();
|
||||||
#endif // _WIN32
|
#endif // _WIN32
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void I_ShutdownMusicExit()
|
||||||
|
{
|
||||||
|
I_ShutdownMusic(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
//==========================================================================
|
//==========================================================================
|
||||||
//
|
//
|
||||||
|
|
|
@ -43,7 +43,8 @@ struct FOptionValues;
|
||||||
// MUSIC I/O
|
// MUSIC I/O
|
||||||
//
|
//
|
||||||
void I_InitMusic ();
|
void I_InitMusic ();
|
||||||
void I_ShutdownMusic ();
|
void I_ShutdownMusic (bool onexit = false);
|
||||||
|
void I_ShutdownMusicExit ();
|
||||||
void I_BuildMIDIMenuList (FOptionValues *);
|
void I_BuildMIDIMenuList (FOptionValues *);
|
||||||
void I_UpdateMusic ();
|
void I_UpdateMusic ();
|
||||||
|
|
||||||
|
|
|
@ -35,82 +35,6 @@
|
||||||
namespace Timidity
|
namespace Timidity
|
||||||
{
|
{
|
||||||
|
|
||||||
static TArray<FString> PathList;
|
|
||||||
|
|
||||||
FString BuildPath(FString base, const char *name)
|
|
||||||
{
|
|
||||||
FString current;
|
|
||||||
if (base.IsNotEmpty())
|
|
||||||
{
|
|
||||||
current = base;
|
|
||||||
if (current[current.Len() - 1] != '/') current += '/';
|
|
||||||
}
|
|
||||||
current += name;
|
|
||||||
return current;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* This is meant to find and open files for reading. */
|
|
||||||
FileReader *open_filereader(const char *name, int open, int *plumpnum)
|
|
||||||
{
|
|
||||||
FileReader *fp;
|
|
||||||
FString current_filename;
|
|
||||||
|
|
||||||
if (!name || !(*name))
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* First try the given name */
|
|
||||||
current_filename = name;
|
|
||||||
FixPathSeperator(current_filename);
|
|
||||||
|
|
||||||
int lumpnum = Wads.CheckNumForFullName(current_filename);
|
|
||||||
|
|
||||||
if (open != OM_FILE)
|
|
||||||
{
|
|
||||||
if (lumpnum >= 0)
|
|
||||||
{
|
|
||||||
fp = Wads.ReopenLumpNum(lumpnum);
|
|
||||||
if (plumpnum) *plumpnum = lumpnum;
|
|
||||||
return fp;
|
|
||||||
}
|
|
||||||
if (open == OM_LUMP) // search the path list when not loading the main config
|
|
||||||
{
|
|
||||||
for (unsigned int plp = PathList.Size(); plp-- != 0; )
|
|
||||||
{ /* Try along the path then */
|
|
||||||
current_filename = BuildPath(PathList[plp], name);
|
|
||||||
lumpnum = Wads.CheckNumForFullName(current_filename);
|
|
||||||
if (lumpnum >= 0)
|
|
||||||
{
|
|
||||||
fp = Wads.ReopenLumpNum(lumpnum);
|
|
||||||
if (plumpnum) *plumpnum = lumpnum;
|
|
||||||
return fp;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (plumpnum) *plumpnum = -1;
|
|
||||||
|
|
||||||
|
|
||||||
fp = new FileReader;
|
|
||||||
if (fp->Open(current_filename)) return fp;
|
|
||||||
|
|
||||||
if (name[0] != '/')
|
|
||||||
{
|
|
||||||
for (unsigned int plp = PathList.Size(); plp-- != 0; )
|
|
||||||
{ /* Try along the path then */
|
|
||||||
current_filename = BuildPath(PathList[plp], name);
|
|
||||||
if (fp->Open(current_filename)) return fp;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
delete fp;
|
|
||||||
|
|
||||||
/* Nothing could be opened. */
|
|
||||||
current_filename = "";
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* This'll allocate memory or die. */
|
/* This'll allocate memory or die. */
|
||||||
|
@ -132,17 +56,5 @@ void *safe_malloc(size_t count)
|
||||||
return 0; // Unreachable.
|
return 0; // Unreachable.
|
||||||
}
|
}
|
||||||
|
|
||||||
/* This adds a directory to the path list */
|
|
||||||
void add_to_pathlist(const char *s)
|
|
||||||
{
|
|
||||||
FString copy = s;
|
|
||||||
FixPathSeperator(copy);
|
|
||||||
PathList.Push(copy);
|
|
||||||
}
|
|
||||||
|
|
||||||
void clear_pathlist()
|
|
||||||
{
|
|
||||||
PathList.Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -159,12 +159,12 @@ static Instrument *load_instrument(Renderer *song, const char *name, int percuss
|
||||||
if (!name) return 0;
|
if (!name) return 0;
|
||||||
|
|
||||||
/* Open patch file */
|
/* Open patch file */
|
||||||
if ((fp = open_filereader(name, openmode, NULL)) == NULL)
|
if ((fp = pathExpander.openFileReader(name, NULL)) == NULL)
|
||||||
{
|
{
|
||||||
/* Try with various extensions */
|
/* Try with various extensions */
|
||||||
FString tmp = name;
|
FString tmp = name;
|
||||||
tmp += ".pat";
|
tmp += ".pat";
|
||||||
if ((fp = open_filereader(tmp, openmode, NULL)) == NULL)
|
if ((fp = pathExpander.openFileReader(tmp, NULL)) == NULL)
|
||||||
{
|
{
|
||||||
#ifdef __unix__ // Windows isn't case-sensitive.
|
#ifdef __unix__ // Windows isn't case-sensitive.
|
||||||
tmp.ToUpper();
|
tmp.ToUpper();
|
||||||
|
|
|
@ -54,7 +54,7 @@ void font_add(const char *filename, int load_order)
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
FileReader *fp = open_filereader(filename, openmode, NULL);
|
FileReader *fp = pathExpander.openFileReader(filename, NULL);
|
||||||
|
|
||||||
if (fp != NULL)
|
if (fp != NULL)
|
||||||
{
|
{
|
||||||
|
|
|
@ -1502,7 +1502,7 @@ void SFFile::ApplyGeneratorsToRegion(SFGenComposite *gen, SFSample *sfsamp, Rend
|
||||||
|
|
||||||
void SFFile::LoadSample(SFSample *sample)
|
void SFFile::LoadSample(SFSample *sample)
|
||||||
{
|
{
|
||||||
FileReader *fp = open_filereader(Filename, openmode, NULL);
|
FileReader *fp = pathExpander.openFileReader(Filename, NULL);
|
||||||
DWORD i;
|
DWORD i;
|
||||||
|
|
||||||
if (fp == NULL)
|
if (fp == NULL)
|
||||||
|
|
|
@ -42,10 +42,10 @@ CVAR(Int, gus_memsize, 0, CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
|
||||||
namespace Timidity
|
namespace Timidity
|
||||||
{
|
{
|
||||||
|
|
||||||
|
PathExpander pathExpander;
|
||||||
ToneBank *tonebank[MAXBANK], *drumset[MAXBANK];
|
ToneBank *tonebank[MAXBANK], *drumset[MAXBANK];
|
||||||
|
|
||||||
static FString def_instr_name;
|
static FString def_instr_name;
|
||||||
int openmode = OM_FILEORLUMP;
|
|
||||||
|
|
||||||
static int read_config_file(const char *name, bool ismain)
|
static int read_config_file(const char *name, bool ismain)
|
||||||
{
|
{
|
||||||
|
@ -62,21 +62,21 @@ static int read_config_file(const char *name, bool ismain)
|
||||||
return (-1);
|
return (-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ismain) openmode = OM_FILEORLUMP;
|
if (ismain) pathExpander.openmode = PathExpander::OM_FILEORLUMP;
|
||||||
|
|
||||||
if (!(fp = open_filereader(name, openmode, &lumpnum)))
|
if (!(fp = pathExpander.openFileReader(name, &lumpnum)))
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
if (ismain)
|
if (ismain)
|
||||||
{
|
{
|
||||||
if (lumpnum > 0)
|
if (lumpnum > 0)
|
||||||
{
|
{
|
||||||
openmode = OM_LUMP;
|
pathExpander.openmode = PathExpander::OM_LUMP;
|
||||||
clear_pathlist(); // when reading from a PK3 we won't want to use any external path
|
pathExpander.clearPathlist(); // when reading from a PK3 we don't want to use any external path
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
openmode = OM_FILE;
|
pathExpander.openmode = PathExpander::OM_FILE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -288,7 +288,7 @@ static int read_config_file(const char *name, bool ismain)
|
||||||
return -2;
|
return -2;
|
||||||
}
|
}
|
||||||
for (i = 1; i < words; i++)
|
for (i = 1; i < words; i++)
|
||||||
add_to_pathlist(w[i]);
|
pathExpander.addToPathlist(w[i]);
|
||||||
}
|
}
|
||||||
else if (!strcmp(w[0], "source"))
|
else if (!strcmp(w[0], "source"))
|
||||||
{
|
{
|
||||||
|
@ -532,15 +532,15 @@ int LoadConfig(const char *filename)
|
||||||
* file itself since that file should contain any other directory
|
* file itself since that file should contain any other directory
|
||||||
* that needs to be added to the search path.
|
* that needs to be added to the search path.
|
||||||
*/
|
*/
|
||||||
clear_pathlist();
|
pathExpander.clearPathlist();
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
add_to_pathlist("C:\\TIMIDITY");
|
pathExpander.addToPathlist("C:\\TIMIDITY");
|
||||||
add_to_pathlist("\\TIMIDITY");
|
pathExpander.addToPathlist("\\TIMIDITY");
|
||||||
add_to_pathlist(progdir);
|
pathExpander.addToPathlist(progdir);
|
||||||
#else
|
#else
|
||||||
add_to_pathlist("/usr/local/lib/timidity");
|
pathExpander.addToPathlist("/usr/local/lib/timidity");
|
||||||
add_to_pathlist("/etc/timidity");
|
pathExpander.addToPathlist("/etc/timidity");
|
||||||
add_to_pathlist("/etc");
|
pathExpander.addToPathlist("/etc");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* Some functions get aggravated if not even the standard banks are available. */
|
/* Some functions get aggravated if not even the standard banks are available. */
|
||||||
|
@ -579,10 +579,10 @@ int LoadDMXGUS()
|
||||||
if (ultradir.IsNotEmpty())
|
if (ultradir.IsNotEmpty())
|
||||||
{
|
{
|
||||||
ultradir += "/midi";
|
ultradir += "/midi";
|
||||||
add_to_pathlist(ultradir.GetChars());
|
pathExpander.addToPathlist(ultradir.GetChars());
|
||||||
}
|
}
|
||||||
// Load DMXGUS lump and patches from gus_patchdir
|
// Load DMXGUS lump and patches from gus_patchdir
|
||||||
add_to_pathlist(gus_patchdir);
|
pathExpander.addToPathlist(gus_patchdir);
|
||||||
|
|
||||||
char readbuffer[1024];
|
char readbuffer[1024];
|
||||||
long size = data.GetLength();
|
long size = data.GetLength();
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
#define TIMIDITY_H
|
#define TIMIDITY_H
|
||||||
|
|
||||||
#include "doomtype.h"
|
#include "doomtype.h"
|
||||||
|
#include "pathexpander.h"
|
||||||
|
|
||||||
class FileReader;
|
class FileReader;
|
||||||
|
|
||||||
|
@ -172,17 +173,8 @@ extern __inline__ double pow_x87_inline(double x,double y)
|
||||||
common.h
|
common.h
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#define OM_FILEORLUMP 0
|
|
||||||
#define OM_LUMP 1
|
|
||||||
#define OM_FILE 2
|
|
||||||
|
|
||||||
extern void add_to_pathlist(const char *s);
|
|
||||||
extern void clear_pathlist();
|
|
||||||
extern void *safe_malloc(size_t count);
|
extern void *safe_malloc(size_t count);
|
||||||
|
|
||||||
FileReader *open_filereader(const char *name, int open, int *plumpnum);
|
|
||||||
extern int openmode;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
controls.h
|
controls.h
|
||||||
*/
|
*/
|
||||||
|
@ -627,6 +619,7 @@ int LoadConfig(const char *filename);
|
||||||
int LoadDMXGUS();
|
int LoadDMXGUS();
|
||||||
extern int LoadConfig();
|
extern int LoadConfig();
|
||||||
extern void FreeAll();
|
extern void FreeAll();
|
||||||
|
extern PathExpander pathExpander;
|
||||||
|
|
||||||
extern ToneBank *tonebank[MAXBANK];
|
extern ToneBank *tonebank[MAXBANK];
|
||||||
extern ToneBank *drumset[MAXBANK];
|
extern ToneBank *drumset[MAXBANK];
|
||||||
|
|
Loading…
Reference in a new issue