mirror of
https://port.numenaute.org/aleajactaest/khanat-code-old.git
synced 2024-11-25 08:16:16 +00:00
486 lines
13 KiB
C++
486 lines
13 KiB
C++
|
// NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
|
||
|
// Copyright (C) 2010 Winch Gate Property Limited
|
||
|
//
|
||
|
// This program is free software: you can redistribute it and/or modify
|
||
|
// it under the terms of the GNU Affero General Public License as
|
||
|
// published by the Free Software Foundation, either version 3 of the
|
||
|
// License, or (at your option) any later version.
|
||
|
//
|
||
|
// This program is distributed in the hope that it will be useful,
|
||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
// GNU Affero General Public License for more details.
|
||
|
//
|
||
|
// You should have received a copy of the GNU Affero General Public License
|
||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||
|
|
||
|
#include "tile_utility.h"
|
||
|
|
||
|
extern HINSTANCE hInstance;
|
||
|
TCHAR *GetString(int id);
|
||
|
|
||
|
#define NSUBTEX 2 // number of texture map slots
|
||
|
#define NCOLS 2
|
||
|
|
||
|
static int subTexId[NSUBTEX] = { IDC_MULT_TEX1, IDC_MULT_TEX2 };
|
||
|
|
||
|
#define ALPHA_FROM_1 0
|
||
|
#define ALPHA_FROM_2 1
|
||
|
#define ALPHA_FROM_MULT 2
|
||
|
|
||
|
//--------------------------------------------------------------
|
||
|
// RGBAdd: A Composite texture map
|
||
|
//--------------------------------------------------------------
|
||
|
class RGBAdd: public Texmap {
|
||
|
public:
|
||
|
BOOL Param1;
|
||
|
IParamBlock2 *pblock; // ref #0
|
||
|
Texmap* subTex[NSUBTEX]; // refs 1,2;
|
||
|
BOOL mapOn[NSUBTEX];
|
||
|
Color col[NCOLS];
|
||
|
Interval ivalid;
|
||
|
int alphaFrom;
|
||
|
BOOL rollScroll;
|
||
|
BOOL loadingOld;
|
||
|
RGBAdd();
|
||
|
ParamDlg* CreateParamDlg(HWND hwMtlEdit, IMtlParams *imp);
|
||
|
void Update(TimeValue t, Interval& valid);
|
||
|
void Init();
|
||
|
void Reset();
|
||
|
Interval Validity(TimeValue t) { Interval v; Update(t,v); return ivalid; }
|
||
|
|
||
|
void NotifyChanged();
|
||
|
|
||
|
void SetColor(int i, Color c, TimeValue t);
|
||
|
|
||
|
// Evaluate the color of map for the context.
|
||
|
AColor EvalColor(ShadeContext& sc);
|
||
|
float EvalMono(ShadeContext& sc);
|
||
|
AColor EvalFunction(ShadeContext& sc, float u, float v, float du, float dv);
|
||
|
|
||
|
// For Bump mapping, need a perturbation to apply to a normal.
|
||
|
// Leave it up to the Texmap to determine how to do this.
|
||
|
Point3 EvalNormalPerturb(ShadeContext& sc);
|
||
|
|
||
|
// Methods to access texture maps of material
|
||
|
int NumSubTexmaps() { return NSUBTEX; }
|
||
|
Texmap* GetSubTexmap(int i) { return subTex[i]; }
|
||
|
void SetSubTexmap(int i, Texmap *m);
|
||
|
TSTR GetSubTexmapSlotName(int i);
|
||
|
|
||
|
Class_ID ClassID() { return RGBAddClassID; }
|
||
|
SClass_ID SuperClassID() { return TEXMAP_CLASS_ID; }
|
||
|
void GetClassName(TSTR& s) { s= "RGB Additive"; }
|
||
|
void DeleteThis() { delete this; }
|
||
|
|
||
|
int NumSubs() { return NSUBTEX+1; }
|
||
|
Animatable* SubAnim(int i);
|
||
|
TSTR SubAnimName(int i);
|
||
|
int SubNumToRefNum(int subNum) { return subNum; }
|
||
|
|
||
|
// From ref
|
||
|
int NumRefs() { return NSUBTEX+1; }
|
||
|
RefTargetHandle GetReference(int i);
|
||
|
void SetReference(int i, RefTargetHandle rtarg);
|
||
|
int RemapRefOnLoad(int iref);
|
||
|
|
||
|
RefTargetHandle Clone(RemapDir &remap = DefaultRemapDir());
|
||
|
RefResult NotifyRefChanged( Interval changeInt, RefTargetHandle hTarget,
|
||
|
PartID& partID, RefMessage message );
|
||
|
|
||
|
// IO
|
||
|
IOResult Save(ISave *isave);
|
||
|
IOResult Load(ILoad *iload);
|
||
|
|
||
|
// JBW: direct ParamBlock access is added
|
||
|
int NumParamBlocks() { return 1; } // return number of ParamBlocks in this instance
|
||
|
IParamBlock2* GetParamBlock(int i) { return pblock; } // return i'th ParamBlock
|
||
|
IParamBlock2* GetParamBlockByID(BlockID id) { return (pblock->ID() == id) ? pblock : NULL; } // return id'd ParamBlock
|
||
|
|
||
|
|
||
|
};
|
||
|
|
||
|
class RGBAddClassDesc:public ClassDesc2 {
|
||
|
public:
|
||
|
int IsPublic() { return 1; }
|
||
|
void * Create(BOOL loading) { return new RGBAdd; }
|
||
|
const TCHAR * ClassName() { return GetString(IDS_DS_RGBMULT_CDESC); } // mjm - 2.3.99
|
||
|
SClass_ID SuperClassID() { return TEXMAP_CLASS_ID; }
|
||
|
Class_ID ClassID() { return RGBAddClassID; }
|
||
|
const TCHAR* Category() { return TEXMAP_CAT_COMP; }
|
||
|
// JBW: new descriptor data accessors added. Note that the
|
||
|
// internal name is hardwired since it must not be localized.
|
||
|
const TCHAR* InternalName() { return _T("RGBAdd"); } // returns fixed parsable name (scripter-visible name)
|
||
|
HINSTANCE HInstance() { return hInstance; } // returns owning module handle
|
||
|
};
|
||
|
|
||
|
static RGBAddClassDesc maskCD;
|
||
|
|
||
|
ClassDesc* GetRGBAddDesc() { return &maskCD; }
|
||
|
|
||
|
enum { RGBAdd_params }; // pblock ID
|
||
|
// RGBAdd_params param IDs
|
||
|
enum
|
||
|
{
|
||
|
RGBAdd_color1, RGBAdd_color2,
|
||
|
RGBAdd_map1, RGBAdd_map2,
|
||
|
RGBAdd_map1_on, RGBAdd_map2_on, // main grad params
|
||
|
RGBAdd_type,
|
||
|
};
|
||
|
|
||
|
|
||
|
static ParamBlockDesc2 RGBAdd_param_blk ( RGBAdd_params, _T("parameters"), 0, &maskCD, P_AUTO_CONSTRUCT + P_AUTO_UI, 0,
|
||
|
//rollout
|
||
|
IDD_RGBMULT, "RGB Additif Parameters", 0, 0, NULL,
|
||
|
// params
|
||
|
RGBAdd_color1, _T("color1"), TYPE_RGBA, P_ANIMATABLE, IDS_DS_COLOR1,
|
||
|
p_default, Color(0,0,0),
|
||
|
p_ui, TYPE_COLORSWATCH, IDC_MULT_COL1,
|
||
|
end,
|
||
|
RGBAdd_color2, _T("color2"), TYPE_RGBA, P_ANIMATABLE, IDS_DS_COLOR2,
|
||
|
p_default, Color(0.5,0.5,0.5),
|
||
|
p_ui, TYPE_COLORSWATCH, IDC_MULT_COL2,
|
||
|
end,
|
||
|
RGBAdd_map1, _T("map1"), TYPE_TEXMAP, P_OWNERS_REF, IDS_JW_MAP1,
|
||
|
p_refno, 1,
|
||
|
p_subtexno, 0,
|
||
|
p_ui, TYPE_TEXMAPBUTTON, IDC_MULT_TEX1,
|
||
|
end,
|
||
|
RGBAdd_map2, _T("map2"), TYPE_TEXMAP, P_OWNERS_REF, IDS_JW_MAP2,
|
||
|
p_refno, 2,
|
||
|
p_subtexno, 1,
|
||
|
p_ui, TYPE_TEXMAPBUTTON, IDC_MULT_TEX2,
|
||
|
end,
|
||
|
RGBAdd_map1_on, _T("map1Enabled"), TYPE_BOOL, 0, IDS_JW_MAP1ENABLE,
|
||
|
p_default, TRUE,
|
||
|
p_ui, TYPE_SINGLECHEKBOX, IDC_MAPON1,
|
||
|
end,
|
||
|
RGBAdd_map2_on, _T("map2Enabled"), TYPE_BOOL, 0, IDS_JW_MAP2ENABLE,
|
||
|
p_default, TRUE,
|
||
|
p_ui, TYPE_SINGLECHEKBOX, IDC_MAPON2,
|
||
|
end,
|
||
|
RGBAdd_type, _T("alphaFrom"), TYPE_INT, 0, IDS_PW_ALPHAFROM,
|
||
|
p_default, 2,
|
||
|
p_range, 0, 2,
|
||
|
p_ui, TYPE_RADIO, 3, IDC_MULT_ALPHA1, IDC_MULT_ALPHA2, IDC_MULT_ALPHA3,
|
||
|
end,
|
||
|
|
||
|
end
|
||
|
);
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// RGBAdd
|
||
|
//-----------------------------------------------------------------------------
|
||
|
#define NPARAMS 2
|
||
|
|
||
|
static int name_id[NPARAMS] = {IDS_DS_COLOR1, IDS_DS_COLOR2};
|
||
|
|
||
|
#define RGBAdd_VERSION 2
|
||
|
static ParamBlockDescID pbdesc[NPARAMS] = {
|
||
|
{ TYPE_RGBA, NULL, TRUE,RGBAdd_color1 }, // col1
|
||
|
{ TYPE_RGBA, NULL, TRUE,RGBAdd_color2 } // col2
|
||
|
};
|
||
|
|
||
|
static ParamVersionDesc versions[] = {
|
||
|
ParamVersionDesc(pbdesc,2,1), // Version 1 params
|
||
|
};
|
||
|
|
||
|
|
||
|
void RGBAdd::Init() {
|
||
|
ivalid.SetEmpty();
|
||
|
alphaFrom = ALPHA_FROM_MULT;
|
||
|
SetColor(0, Color(0.0f,0.0f,0.0f), TimeValue(0));
|
||
|
SetColor(1, Color(0.0f,0.0f,0.0f), TimeValue(0));
|
||
|
mapOn[0] = mapOn[1] = 1;
|
||
|
}
|
||
|
|
||
|
void RGBAdd::Reset() {
|
||
|
maskCD.Reset(this, TRUE); // reset all pb2's
|
||
|
DeleteReference(1); // get rid of maps
|
||
|
DeleteReference(2); // get rid of maps
|
||
|
Init();
|
||
|
}
|
||
|
|
||
|
void RGBAdd::NotifyChanged() {
|
||
|
NotifyDependents(FOREVER, PART_ALL, REFMSG_CHANGE);
|
||
|
}
|
||
|
|
||
|
RGBAdd::RGBAdd()
|
||
|
{
|
||
|
for (int i=0; i<NSUBTEX; i++) subTex[i] = NULL;
|
||
|
pblock = NULL;
|
||
|
maskCD.MakeAutoParamBlocks(this); // make and intialize paramblock2
|
||
|
Init();
|
||
|
rollScroll=0;
|
||
|
}
|
||
|
|
||
|
|
||
|
static AColor white(1.0f,1.0f,1.0f,1.0f);
|
||
|
|
||
|
AColor RGBAdd::EvalColor(ShadeContext& sc)
|
||
|
{
|
||
|
if (gbufID) sc.SetGBufferID(gbufID);
|
||
|
AColor c0 = (subTex[0]&&mapOn[0])? subTex[0]->EvalColor(sc): col[0];
|
||
|
AColor c1 = (subTex[1]&&mapOn[1])? subTex[1]->EvalColor(sc): col[1];
|
||
|
AColor c;
|
||
|
c.r = c0.r+c1.r;
|
||
|
c.r = c.r > 1.f ? 1.f : c.r;
|
||
|
c.g = c0.g+c1.g;
|
||
|
c.g = c.g > 1.f ? 1.f : c.g;
|
||
|
c.b = c0.b+c1.b;
|
||
|
c.b = c.b > 1.f ? 1.f : c.b;
|
||
|
switch(alphaFrom) {
|
||
|
case ALPHA_FROM_1: c.a = c0.a; break;
|
||
|
case ALPHA_FROM_2: c.a = c1.a; break;
|
||
|
case ALPHA_FROM_MULT: c.a = c0.a*c1.a; break;
|
||
|
}
|
||
|
return c;
|
||
|
}
|
||
|
|
||
|
float RGBAdd::EvalMono(ShadeContext& sc)
|
||
|
{
|
||
|
if (gbufID) sc.SetGBufferID(gbufID);
|
||
|
float m = (subTex[1]&&mapOn[1])? subTex[1]->EvalMono(sc): Intens(col[0]);
|
||
|
float c0 = (subTex[0]&&mapOn[0])? subTex[0]->EvalMono(sc): Intens(col[1]);
|
||
|
return (m+c0) > 1.f ? 1.f : m+c0;
|
||
|
}
|
||
|
|
||
|
Point3 RGBAdd::EvalNormalPerturb(ShadeContext& sc) {
|
||
|
if (gbufID) sc.SetGBufferID(gbufID);
|
||
|
Point3 p0 = subTex[0]&&mapOn[0]? subTex[0]->EvalNormalPerturb(sc): Point3(0.0f,0.0f,0.0f);
|
||
|
Point3 p1 = subTex[1]&&mapOn[1]? subTex[1]->EvalNormalPerturb(sc): Point3(0.0f,0.0f,0.0f);
|
||
|
return p0+p1;
|
||
|
}
|
||
|
|
||
|
RefTargetHandle RGBAdd::Clone(RemapDir &remap) {
|
||
|
RGBAdd *mnew = new RGBAdd();
|
||
|
*((MtlBase*)mnew) = *((MtlBase*)this); // copy superclass stuff
|
||
|
mnew->ReplaceReference(0,remap.CloneRef(pblock));
|
||
|
mnew->ivalid.SetEmpty();
|
||
|
for (int i = 0; i<NSUBTEX; i++) {
|
||
|
mnew->subTex[i] = NULL;
|
||
|
if (subTex[i])
|
||
|
mnew->ReplaceReference(i+1,remap.CloneRef(subTex[i]));
|
||
|
mnew->mapOn[i] = mapOn[i];
|
||
|
}
|
||
|
mnew->alphaFrom = alphaFrom;
|
||
|
return (RefTargetHandle)mnew;
|
||
|
}
|
||
|
|
||
|
ParamDlg* RGBAdd::CreateParamDlg(HWND hwMtlEdit, IMtlParams *imp) {
|
||
|
IAutoMParamDlg* masterDlg = maskCD.CreateParamDlgs(hwMtlEdit, imp, this);
|
||
|
return masterDlg;
|
||
|
|
||
|
}
|
||
|
|
||
|
void RGBAdd::Update(TimeValue t, Interval& valid) {
|
||
|
|
||
|
if (Param1)
|
||
|
{
|
||
|
pblock->SetValue( RGBAdd_map1_on, 0, mapOn[0]);
|
||
|
pblock->SetValue( RGBAdd_map2_on, 0, mapOn[1]);
|
||
|
pblock->SetValue( RGBAdd_type, 0, alphaFrom);
|
||
|
Param1 = FALSE;
|
||
|
}
|
||
|
|
||
|
if (!ivalid.InInterval(t)) {
|
||
|
ivalid.SetInfinite();
|
||
|
pblock->GetValue( RGBAdd_color1, t, col[0], ivalid );
|
||
|
col[0].ClampMinMax();
|
||
|
pblock->GetValue( RGBAdd_color2, t, col[1], ivalid );
|
||
|
|
||
|
pblock->GetValue( RGBAdd_map1_on, t, mapOn[0], ivalid );
|
||
|
pblock->GetValue( RGBAdd_map2_on, t, mapOn[1], ivalid );
|
||
|
pblock->GetValue( RGBAdd_type, t, alphaFrom, ivalid );
|
||
|
|
||
|
|
||
|
col[1].ClampMinMax();
|
||
|
for (int i=0; i<NSUBTEX; i++) {
|
||
|
if (subTex[i])
|
||
|
subTex[i]->Update(t,ivalid);
|
||
|
}
|
||
|
}
|
||
|
valid &= ivalid;
|
||
|
}
|
||
|
|
||
|
void RGBAdd::SetColor(int i, Color c, TimeValue t) {
|
||
|
col[i] = c;
|
||
|
int id = i==0?RGBAdd_color1:RGBAdd_color2;
|
||
|
pblock->SetValue( id, t, c);
|
||
|
}
|
||
|
|
||
|
RefTargetHandle RGBAdd::GetReference(int i) {
|
||
|
if (i==0) return pblock;
|
||
|
else return subTex[i-1];
|
||
|
}
|
||
|
|
||
|
int RGBAdd::RemapRefOnLoad(int iref) {
|
||
|
if (loadingOld)
|
||
|
return iref+1;
|
||
|
else return iref;
|
||
|
}
|
||
|
|
||
|
void RGBAdd::SetReference(int i, RefTargetHandle rtarg) {
|
||
|
switch(i) {
|
||
|
case 0: pblock = (IParamBlock2 *)rtarg; break;
|
||
|
default: subTex[i-1] = (Texmap *)rtarg; break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void RGBAdd::SetSubTexmap(int i, Texmap *m) {
|
||
|
ReplaceReference(i+1,m);
|
||
|
if (i==0)
|
||
|
{
|
||
|
RGBAdd_param_blk.InvalidateUI(RGBAdd_map1);
|
||
|
ivalid.SetEmpty();
|
||
|
}
|
||
|
else if (i==1)
|
||
|
{
|
||
|
RGBAdd_param_blk.InvalidateUI(RGBAdd_map2);
|
||
|
ivalid.SetEmpty();
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
TSTR RGBAdd::GetSubTexmapSlotName(int i) {
|
||
|
switch(i) {
|
||
|
case 0: return TSTR(GetString(IDS_DS_COLOR1));
|
||
|
case 1: return TSTR(GetString(IDS_DS_COLOR2));
|
||
|
default: return TSTR(_T(""));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Animatable* RGBAdd::SubAnim(int i) {
|
||
|
switch (i) {
|
||
|
case 0: return pblock;
|
||
|
default: return subTex[i-1];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
TSTR RGBAdd::SubAnimName(int i) {
|
||
|
switch (i) {
|
||
|
case 0: return TSTR(GetString(IDS_DS_PARAMETERS));
|
||
|
default: return GetSubTexmapTVName(i-1);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
RefResult RGBAdd::NotifyRefChanged(Interval changeInt, RefTargetHandle hTarget,
|
||
|
PartID& partID, RefMessage message ) {
|
||
|
switch (message) {
|
||
|
case REFMSG_CHANGE:
|
||
|
ivalid.SetEmpty();
|
||
|
if (hTarget == pblock)
|
||
|
{
|
||
|
// see if this message came from a changing parameter in the pblock,
|
||
|
// if so, limit rollout update to the changing item and update any active viewport texture
|
||
|
ParamID changing_param = pblock->LastNotifyParamID();
|
||
|
RGBAdd_param_blk.InvalidateUI(changing_param);
|
||
|
// notify our dependents that we've changed
|
||
|
// NotifyChanged(); //DS this is redundant
|
||
|
}
|
||
|
|
||
|
break;
|
||
|
}
|
||
|
return(REF_SUCCEED);
|
||
|
}
|
||
|
|
||
|
|
||
|
#define MTL_HDR_CHUNK 0x4000
|
||
|
#define MAPOFF_CHUNK 0x1000
|
||
|
#define INVERT_RGBMULT_CHUNK 0x2000
|
||
|
#define ALPHA_FROM_CHUNK 0x2010
|
||
|
#define RGBAdd_VERSION_CHUNK 0x2020
|
||
|
#define PARAM2_CHUNK 0x2030
|
||
|
|
||
|
IOResult RGBAdd::Save(ISave *isave) {
|
||
|
IOResult res;
|
||
|
ULONG nb;
|
||
|
|
||
|
// Save common stuff
|
||
|
isave->BeginChunk(MTL_HDR_CHUNK);
|
||
|
res = MtlBase::Save(isave);
|
||
|
if (res!=IO_OK) return res;
|
||
|
isave->EndChunk();
|
||
|
|
||
|
int vers = RGBAdd_VERSION;
|
||
|
isave->BeginChunk(RGBAdd_VERSION_CHUNK);
|
||
|
isave->Write(&vers,sizeof(vers),&nb);
|
||
|
isave->EndChunk();
|
||
|
|
||
|
isave->BeginChunk(PARAM2_CHUNK);
|
||
|
isave->EndChunk();
|
||
|
return IO_OK;
|
||
|
}
|
||
|
|
||
|
class RGBAddPostLoad : public PostLoadCallback {
|
||
|
public:
|
||
|
RGBAdd *tm;
|
||
|
RGBAddPostLoad(RGBAdd *b) {tm=b;}
|
||
|
void proc(ILoad *iload) { tm->loadingOld = FALSE; delete this; }
|
||
|
};
|
||
|
|
||
|
class RGBAdd2PostLoad : public PostLoadCallback {
|
||
|
public:
|
||
|
RGBAdd *n;
|
||
|
RGBAdd2PostLoad(RGBAdd *ns) {n = ns;}
|
||
|
void proc(ILoad *iload) {
|
||
|
if (n->Param1)
|
||
|
{
|
||
|
n->pblock->SetValue( RGBAdd_map1_on, 0, n->mapOn[0]);
|
||
|
n->pblock->SetValue( RGBAdd_map2_on, 0, n->mapOn[1]);
|
||
|
n->pblock->SetValue( RGBAdd_type, 0, n->alphaFrom);
|
||
|
}
|
||
|
delete this;
|
||
|
|
||
|
|
||
|
}
|
||
|
};
|
||
|
|
||
|
|
||
|
|
||
|
IOResult RGBAdd::Load(ILoad *iload) {
|
||
|
ULONG nb;
|
||
|
IOResult res;
|
||
|
int id;
|
||
|
loadingOld = TRUE;
|
||
|
Param1 = TRUE;
|
||
|
while (IO_OK==(res=iload->OpenChunk())) {
|
||
|
switch(id = iload->CurChunkID()) {
|
||
|
case MTL_HDR_CHUNK:
|
||
|
res = MtlBase::Load(iload);
|
||
|
break;
|
||
|
case PARAM2_CHUNK:
|
||
|
Param1= FALSE;
|
||
|
break;
|
||
|
case RGBAdd_VERSION_CHUNK:
|
||
|
loadingOld = FALSE;
|
||
|
break;
|
||
|
case MAPOFF_CHUNK+0:
|
||
|
case MAPOFF_CHUNK+1:
|
||
|
mapOn[id-MAPOFF_CHUNK] = 0;
|
||
|
break;
|
||
|
case ALPHA_FROM_CHUNK:
|
||
|
res = iload->Read(&alphaFrom,sizeof(alphaFrom), &nb);
|
||
|
break;
|
||
|
}
|
||
|
iload->CloseChunk();
|
||
|
if (res!=IO_OK)
|
||
|
return res;
|
||
|
}
|
||
|
if (loadingOld)
|
||
|
iload->RegisterPostLoadCallback(new RGBAddPostLoad(this));
|
||
|
|
||
|
// JBW: register old version ParamBlock to ParamBlock2 converter
|
||
|
ParamBlock2PLCB* plcb = new ParamBlock2PLCB(versions, 1, &RGBAdd_param_blk, this, 0);
|
||
|
iload->RegisterPostLoadCallback(plcb);
|
||
|
|
||
|
// iload->RegisterPostLoadCallback(new RGBAdd2PostLoad(this));
|
||
|
|
||
|
|
||
|
|
||
|
return IO_OK;
|
||
|
}
|
||
|
|
||
|
|