Move git root from Client/ to src/ to track all source code: - Client: Game client source (moved to Client/Client/) - Server: Game server source - GameTools: Development tools - CryptoSource: Encryption utilities - database: Database scripts - Script: Game scripts - rylCoder_16.02.2008_src: Legacy coder tools - GMFont, Game: Additional resources 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
311 lines
16 KiB
C++
311 lines
16 KiB
C++
#include "stdafx.h"
|
|
#include "RYL_AdminManagerServer.h"
|
|
#include "RYL_AdminWindow.h"
|
|
#include "RYL_AdminMgrDispatch.h"
|
|
#include "RYL_AgentServerTable.h"
|
|
|
|
#include <Network/IOCP/IOCPNet.h>
|
|
#include <Network/Session/CreatePolicy.h>
|
|
#include <Network/Protocol/RYL_AdminMgrProtocol.h>
|
|
|
|
#include <DBComponent/RYL_AdminMgrDB.h>
|
|
#include <DBComponent/BillingDB.h>
|
|
#include <DBComponent/AuthDB.h>
|
|
|
|
#include <Utility/Time/Pulse/Pulse.h>
|
|
#include <Utility/Setup/ServerSetup.h>
|
|
|
|
#include <Item/ItemMgr.h>
|
|
#include <Skill/SkillMgr.h>
|
|
#include <Quest/QuestMgr.h>
|
|
|
|
#include <Log/ServerLog.h>
|
|
#include <Parser/ServerInfo.h>
|
|
|
|
#include <mmsystem.h>
|
|
|
|
#include "RYL_AdminServerCommand.h"
|
|
|
|
CRylAdminManagerServer CRylAdminManagerServer::ms_this;
|
|
unsigned char PktAdminMgr::AuthorityMask::AdminAuthority[PktAdminMgr::PktCMD::PktEnd];
|
|
|
|
// ServerFramework Process 상속.. ( Thread )
|
|
class CAdminToolProcessThread : public CProcessThread
|
|
{
|
|
public:
|
|
|
|
enum Const
|
|
{
|
|
PROCESS_TPP = 100, // 200ms(0.2초) 에 1틱.
|
|
CONNECT_CHECK = 10, // 10틱 - 3초마다 연결 체크
|
|
INTERESTED_USER_CHK = 10 // 3초마다 관심유저 접속 체크
|
|
};
|
|
|
|
CAdminToolProcessThread(CRylAdminManagerServer& AdminServer)
|
|
: CProcessThread(AdminServer, PROCESS_TPP)
|
|
, m_AdminServer(AdminServer)
|
|
{
|
|
}
|
|
|
|
~CAdminToolProcessThread()
|
|
{
|
|
}
|
|
|
|
private:
|
|
virtual void Cleanup(CPulse& Pulse)
|
|
{
|
|
}
|
|
|
|
virtual void InternalRun(CPulse& Pulse)
|
|
{
|
|
unsigned long dwCurrentPulse = Pulse.GetCurrentPulse();
|
|
|
|
if(0 == (dwCurrentPulse % CONNECT_CHECK))
|
|
{
|
|
m_AdminServer.PrintServerInfo();
|
|
//CRylAdminManagerServer.PrintStatistics();
|
|
}
|
|
|
|
// 10초에 한번 테스트
|
|
if(Pulse.ProcessBySecond(10))
|
|
{
|
|
if((CAgentServerTable::GetInstance().GetBillingDB() && CAgentServerTable::GetInstance().GetBillingDB()->GetQueryErrorCount() >= 1) ||
|
|
(CAgentServerTable::GetInstance().GetAuthDB() && CAgentServerTable::GetInstance().GetAuthDB()->GetQueryErrorCount() >= 1) )
|
|
{
|
|
CAgentServerTable::GetInstance().InitAgentServerTable(*m_AdminServer.GetIOCPNet());
|
|
}
|
|
|
|
|
|
// DB테스트
|
|
// edith 2009.10.1 DB 자동 커넥트 추가기능
|
|
// 10초에 한번씩 네트워크 오류를 검사한다.
|
|
// 쿼리 에러가 연속으로 10번이상 일어나면 DB에 먼가 문제가 있다.
|
|
if(CDBAdminTool::GetInstance().GetQueryErrorCount() >= 1)
|
|
{
|
|
// DB에 새로 커넥트를 시도한다.
|
|
CDBAdminTool::GetInstance().ConnectAdminToolDB();
|
|
}
|
|
}
|
|
}
|
|
|
|
CRylAdminManagerServer& m_AdminServer;
|
|
};
|
|
|
|
CRylAdminManagerServer& CRylAdminManagerServer::GetInstance()
|
|
{
|
|
static CRylAdminManagerServer ServerInstance;
|
|
return ServerInstance;
|
|
}
|
|
|
|
CRylAdminManagerServer::CRylAdminManagerServer()
|
|
: m_bInitializedClientListener(FALSE)
|
|
, m_lpClientSessionPolicy(SessionPolicy::CreateTCPPolicy<CRylAdminMgrDispatch >())
|
|
{
|
|
}
|
|
|
|
CRylAdminManagerServer::~CRylAdminManagerServer()
|
|
{
|
|
if(0 != m_lpClientSessionPolicy)
|
|
{
|
|
m_lpClientSessionPolicy->Release();
|
|
m_lpClientSessionPolicy = 0;
|
|
}
|
|
}
|
|
|
|
bool CRylAdminManagerServer::ApplicationSpecificInit(const TCHAR* szCmdLine)
|
|
{
|
|
const int MAX_BUFFER = 255;
|
|
CServerInfo& InfoScript = CServerInfo::GetInstance();
|
|
|
|
// 서버 커맨드 실행
|
|
if(!InitializeCommand())
|
|
{
|
|
ERRLOG0(g_Log, "Command lnit failed");
|
|
return false;
|
|
}
|
|
// 서버 스크립트 파일 로드
|
|
if(!InfoScript.Reload())
|
|
{
|
|
ERRLOG0(g_Log, "ServerInfo file load failed");
|
|
return false;
|
|
}
|
|
// 아이템 스크립트 파일 로드
|
|
if(!Item::CItemMgr::GetInstance().LoadItemProtoType("./Script/Game/ItemScript.txt"))
|
|
{
|
|
ERRLOG0(g_Log, "ItemScript load failed");
|
|
return false;
|
|
}
|
|
// 스킬 스크립트 로드
|
|
if(!CSkillMgr::GetInstance().LoadSkillsFromFile())
|
|
{
|
|
ERRLOG0(g_Log, "SkillScript load failed");
|
|
return false;
|
|
}
|
|
// 퀘스트 매니터 초기화
|
|
if(!CQuestMgr::GetInstance().LoadQuestInfo())
|
|
{
|
|
ERRLOG0(g_Log, "QuestScript load failed");
|
|
return false;
|
|
}
|
|
// 사용자 정의 윈도우 메세지 등록
|
|
if(!InitializeMsgProc())
|
|
{
|
|
ERRLOG0(g_Log, "Window message registing failed");
|
|
}
|
|
// AdminToolDB 접속
|
|
if(!CDBAdminTool::GetInstance().ConnectAdminToolDB())
|
|
{
|
|
ERRLOG0(g_Log, "Connecting failed: AdminTool DB");
|
|
return false;
|
|
}
|
|
// Listener 생성
|
|
if(!GetIOCPNet()->AddListener(m_lpClientSessionPolicy, 0, CServerSetup::P2AdminToolServerClientListen))
|
|
{
|
|
ERRLOG0(g_Log, "Add listener failed");
|
|
return false;
|
|
}
|
|
// CAdminToolProcessThread 시작및 Thread관리자에 등록...
|
|
if(!AddProcessThread(new CAdminToolProcessThread(*this)))
|
|
{
|
|
ERRLOG0(g_Log, "Add process thread failed");
|
|
return false;
|
|
}
|
|
|
|
// 패킷 권한 설정
|
|
InitializeAuthority();
|
|
|
|
// 중계서버 테이블 초기화 (과금 디비도 포함된 테이블)
|
|
CAgentServerTable::GetInstance().InitAgentServerTable(*GetIOCPNet());
|
|
|
|
return true;
|
|
}
|
|
|
|
void CRylAdminManagerServer::InitializeAuthority()
|
|
{
|
|
using namespace PktAdminMgr;
|
|
|
|
// 권한 없음
|
|
AuthorityMask::AdminAuthority[PktCMD::PktLogin] = AuthorityMask::QUEST_MODE;
|
|
AuthorityMask::AdminAuthority[PktCMD::PktLogUseMessage] = AuthorityMask::FREE_MODE;
|
|
AuthorityMask::AdminAuthority[PktCMD::PktSetZoneList] = AuthorityMask::FREE_MODE;
|
|
AuthorityMask::AdminAuthority[PktCMD::PktCloseCharacter] = AuthorityMask::FREE_MODE;
|
|
|
|
// 개발자
|
|
AuthorityMask::AdminAuthority[PktCMD::PktItemDistribute] = AuthorityMask::DEVELOPER;
|
|
|
|
// 마스터
|
|
AuthorityMask::AdminAuthority[PktCMD::PktNewAdmin] |= AuthorityMask::DEVELOPER | AuthorityMask::MASTER; // MASTER 권한 계정 생성은 DEVELOPER 만 가능
|
|
AuthorityMask::AdminAuthority[PktCMD::PktAdminList] |= AuthorityMask::DEVELOPER | AuthorityMask::MASTER;
|
|
AuthorityMask::AdminAuthority[PktCMD::PktDelAdmin] |= AuthorityMask::DEVELOPER | AuthorityMask::MASTER;
|
|
AuthorityMask::AdminAuthority[PktCMD::PktUpdateLevel] |= AuthorityMask::DEVELOPER | AuthorityMask::MASTER;
|
|
AuthorityMask::AdminAuthority[PktCMD::PktUpdateIP] |= AuthorityMask::DEVELOPER | AuthorityMask::MASTER;
|
|
AuthorityMask::AdminAuthority[PktCMD::PktUpdatePasswd] |= AuthorityMask::DEVELOPER | AuthorityMask::MASTER;
|
|
AuthorityMask::AdminAuthority[PktCMD::PktGameAdmin] |= AuthorityMask::DEVELOPER | AuthorityMask::MASTER;
|
|
AuthorityMask::AdminAuthority[PktCMD::PktLogList] |= AuthorityMask::DEVELOPER | AuthorityMask::MASTER;
|
|
AuthorityMask::AdminAuthority[PktCMD::PktDetailLog] |= AuthorityMask::DEVELOPER | AuthorityMask::MASTER;
|
|
AuthorityMask::AdminAuthority[PktCMD::PktBeforeAfter] |= AuthorityMask::DEVELOPER | AuthorityMask::MASTER;
|
|
AuthorityMask::AdminAuthority[PktCMD::PktItemQtyControl] |= AuthorityMask::DEVELOPER | AuthorityMask::MASTER;
|
|
|
|
// 게임 마스터
|
|
AuthorityMask::AdminAuthority[PktCMD::PktCheckName] |= AuthorityMask::DEVELOPER | AuthorityMask::MASTER | AuthorityMask::GM;
|
|
AuthorityMask::AdminAuthority[PktCMD::PktChangeName] |= AuthorityMask::DEVELOPER | AuthorityMask::MASTER | AuthorityMask::GM;
|
|
AuthorityMask::AdminAuthority[PktCMD::PktDelCharList] |= AuthorityMask::DEVELOPER | AuthorityMask::MASTER | AuthorityMask::GM;
|
|
AuthorityMask::AdminAuthority[PktCMD::PktCharRestore] |= AuthorityMask::DEVELOPER | AuthorityMask::MASTER | AuthorityMask::GM;
|
|
AuthorityMask::AdminAuthority[PktCMD::PktUpdateStatus] |= AuthorityMask::DEVELOPER | AuthorityMask::MASTER | AuthorityMask::GM;
|
|
AuthorityMask::AdminAuthority[PktCMD::PktCreateItem] |= AuthorityMask::DEVELOPER | AuthorityMask::MASTER | AuthorityMask::GM;
|
|
AuthorityMask::AdminAuthority[PktCMD::PktUpdateItem] |= AuthorityMask::DEVELOPER | AuthorityMask::MASTER | AuthorityMask::GM;
|
|
AuthorityMask::AdminAuthority[PktCMD::PktRemoveItem] |= AuthorityMask::DEVELOPER | AuthorityMask::MASTER | AuthorityMask::GM;
|
|
AuthorityMask::AdminAuthority[PktCMD::PktItemReset] |= AuthorityMask::DEVELOPER | AuthorityMask::MASTER | AuthorityMask::GM;
|
|
AuthorityMask::AdminAuthority[PktCMD::PktInvenGold] |= AuthorityMask::DEVELOPER | AuthorityMask::MASTER | AuthorityMask::GM;
|
|
AuthorityMask::AdminAuthority[PktCMD::PktSkillEdit] |= AuthorityMask::DEVELOPER | AuthorityMask::MASTER | AuthorityMask::GM;
|
|
AuthorityMask::AdminAuthority[PktCMD::PktDepositPasswd] |= AuthorityMask::DEVELOPER | AuthorityMask::MASTER | AuthorityMask::GM;
|
|
AuthorityMask::AdminAuthority[PktCMD::PktAdminStoreInsert] |= AuthorityMask::DEVELOPER | AuthorityMask::MASTER | AuthorityMask::GM;
|
|
AuthorityMask::AdminAuthority[PktCMD::PktAdminStoreDelete] |= AuthorityMask::DEVELOPER | AuthorityMask::MASTER | AuthorityMask::GM;
|
|
AuthorityMask::AdminAuthority[PktCMD::PktGetAdminStoreItem] |= AuthorityMask::DEVELOPER | AuthorityMask::MASTER | AuthorityMask::GM;
|
|
AuthorityMask::AdminAuthority[PktCMD::PktUpdateAdminStore] |= AuthorityMask::DEVELOPER | AuthorityMask::MASTER | AuthorityMask::GM;
|
|
AuthorityMask::AdminAuthority[PktCMD::PktDelCharacter] |= AuthorityMask::DEVELOPER | AuthorityMask::MASTER | AuthorityMask::GM;
|
|
AuthorityMask::AdminAuthority[PktCMD::PktStoreGoldUpdate] |= AuthorityMask::DEVELOPER | AuthorityMask::MASTER | AuthorityMask::GM;
|
|
AuthorityMask::AdminAuthority[PktCMD::PktDuplicatedItem] |= AuthorityMask::DEVELOPER | AuthorityMask::MASTER | AuthorityMask::GM;
|
|
AuthorityMask::AdminAuthority[PktCMD::PktCancelExecuteQuest] |= AuthorityMask::DEVELOPER | AuthorityMask::MASTER | AuthorityMask::GM;
|
|
AuthorityMask::AdminAuthority[PktCMD::PktDeleteHistoryQuest] |= AuthorityMask::DEVELOPER | AuthorityMask::MASTER | AuthorityMask::GM;
|
|
AuthorityMask::AdminAuthority[PktCMD::PktGMLogMsg] |= AuthorityMask::DEVELOPER | AuthorityMask::MASTER | AuthorityMask::GM;
|
|
AuthorityMask::AdminAuthority[PktCMD::PktGuildMemberEdit] |= AuthorityMask::DEVELOPER | AuthorityMask::MASTER | AuthorityMask::GM;
|
|
|
|
// 서포터
|
|
AuthorityMask::AdminAuthority[PktCMD::PktSearchBlock] |= AuthorityMask::DEVELOPER | AuthorityMask::MASTER | AuthorityMask::GM | AuthorityMask::SUPPORTER;
|
|
AuthorityMask::AdminAuthority[PktCMD::PktAddBlockUser] |= AuthorityMask::DEVELOPER | AuthorityMask::MASTER | AuthorityMask::GM | AuthorityMask::SUPPORTER;
|
|
AuthorityMask::AdminAuthority[PktCMD::PktDelBlockUser] |= AuthorityMask::DEVELOPER | AuthorityMask::MASTER | AuthorityMask::GM | AuthorityMask::SUPPORTER;
|
|
AuthorityMask::AdminAuthority[PktCMD::PktSearchID] |= AuthorityMask::DEVELOPER | AuthorityMask::MASTER | AuthorityMask::GM | AuthorityMask::SUPPORTER;
|
|
AuthorityMask::AdminAuthority[PktCMD::PktSearchName] |= AuthorityMask::DEVELOPER | AuthorityMask::MASTER | AuthorityMask::GM | AuthorityMask::SUPPORTER;
|
|
AuthorityMask::AdminAuthority[PktCMD::PktSetCharacter] |= AuthorityMask::DEVELOPER | AuthorityMask::MASTER | AuthorityMask::GM | AuthorityMask::SUPPORTER;
|
|
AuthorityMask::AdminAuthority[PktCMD::PktGetCharacter] |= AuthorityMask::DEVELOPER | AuthorityMask::MASTER | AuthorityMask::GM | AuthorityMask::SUPPORTER;
|
|
AuthorityMask::AdminAuthority[PktCMD::PktForcedDis] |= AuthorityMask::DEVELOPER | AuthorityMask::MASTER | AuthorityMask::GM | AuthorityMask::SUPPORTER;
|
|
AuthorityMask::AdminAuthority[PktCMD::PktUserBillingLog] |= AuthorityMask::DEVELOPER | AuthorityMask::MASTER | AuthorityMask::GM | AuthorityMask::SUPPORTER;
|
|
AuthorityMask::AdminAuthority[PktCMD::PktGuildSearch] |= AuthorityMask::DEVELOPER | AuthorityMask::MASTER | AuthorityMask::GM | AuthorityMask::SUPPORTER;
|
|
AuthorityMask::AdminAuthority[PktCMD::PktGuildRight] |= AuthorityMask::DEVELOPER | AuthorityMask::MASTER | AuthorityMask::GM | AuthorityMask::SUPPORTER;
|
|
AuthorityMask::AdminAuthority[PktCMD::PktRequestGuildMemberList] |= AuthorityMask::DEVELOPER | AuthorityMask::MASTER | AuthorityMask::GM | AuthorityMask::SUPPORTER;
|
|
AuthorityMask::AdminAuthority[PktCMD::PktGuildRestoreDataList] |= AuthorityMask::DEVELOPER | AuthorityMask::MASTER | AuthorityMask::GM | AuthorityMask::SUPPORTER;
|
|
AuthorityMask::AdminAuthority[PktCMD::PktFortSearch] |= AuthorityMask::DEVELOPER | AuthorityMask::MASTER | AuthorityMask::GM | AuthorityMask::SUPPORTER;
|
|
AuthorityMask::AdminAuthority[PktCMD::PktMiningCamp] |= AuthorityMask::DEVELOPER | AuthorityMask::MASTER | AuthorityMask::GM | AuthorityMask::SUPPORTER;
|
|
AuthorityMask::AdminAuthority[PktCMD::PktShopCamp] |= AuthorityMask::DEVELOPER | AuthorityMask::MASTER | AuthorityMask::GM | AuthorityMask::SUPPORTER;
|
|
AuthorityMask::AdminAuthority[PktCMD::PktShopCampGold] |= AuthorityMask::DEVELOPER | AuthorityMask::MASTER | AuthorityMask::GM | AuthorityMask::SUPPORTER;
|
|
AuthorityMask::AdminAuthority[PktCMD::PktShopCampTax] |= AuthorityMask::DEVELOPER | AuthorityMask::MASTER | AuthorityMask::GM | AuthorityMask::SUPPORTER;
|
|
AuthorityMask::AdminAuthority[PktCMD::PktCastleSearch] |= AuthorityMask::DEVELOPER | AuthorityMask::MASTER | AuthorityMask::GM | AuthorityMask::SUPPORTER;
|
|
AuthorityMask::AdminAuthority[PktCMD::PktCastleCreature] |= AuthorityMask::DEVELOPER | AuthorityMask::MASTER | AuthorityMask::GM | AuthorityMask::SUPPORTER;
|
|
AuthorityMask::AdminAuthority[PktCMD::PktChangePos] |= AuthorityMask::DEVELOPER | AuthorityMask::MASTER | AuthorityMask::GM | AuthorityMask::SUPPORTER;
|
|
AuthorityMask::AdminAuthority[PktCMD::PktConnectAllServerz] |= AuthorityMask::DEVELOPER | AuthorityMask::MASTER | AuthorityMask::GM | AuthorityMask::SUPPORTER;
|
|
AuthorityMask::AdminAuthority[PktCMD::PktRefreshConnectedList] |= AuthorityMask::DEVELOPER | AuthorityMask::MASTER | AuthorityMask::GM | AuthorityMask::SUPPORTER;
|
|
AuthorityMask::AdminAuthority[PktCMD::PktInterestedUser] |= AuthorityMask::DEVELOPER | AuthorityMask::MASTER | AuthorityMask::GM | AuthorityMask::SUPPORTER;
|
|
AuthorityMask::AdminAuthority[PktCMD::PktConnectionChk] |= AuthorityMask::DEVELOPER | AuthorityMask::MASTER | AuthorityMask::GM | AuthorityMask::SUPPORTER;
|
|
AuthorityMask::AdminAuthority[PktCMD::PktConnectedUserList] |= AuthorityMask::DEVELOPER | AuthorityMask::MASTER | AuthorityMask::GM | AuthorityMask::SUPPORTER;
|
|
AuthorityMask::AdminAuthority[PktCMD::PktUnifiedCharInfo] |= AuthorityMask::DEVELOPER | AuthorityMask::MASTER | AuthorityMask::GM | AuthorityMask::SUPPORTER;
|
|
AuthorityMask::AdminAuthority[PktCMD::PktUnifiedGuildInfo] |= AuthorityMask::DEVELOPER | AuthorityMask::MASTER | AuthorityMask::GM | AuthorityMask::SUPPORTER;
|
|
AuthorityMask::AdminAuthority[PktCMD::PktDetailCharInfo] |= AuthorityMask::DEVELOPER | AuthorityMask::MASTER | AuthorityMask::GM | AuthorityMask::SUPPORTER;
|
|
AuthorityMask::AdminAuthority[PktCMD::PktUserNation] |= AuthorityMask::DEVELOPER | AuthorityMask::MASTER | AuthorityMask::GM | AuthorityMask::SUPPORTER;
|
|
AuthorityMask::AdminAuthority[PktCMD::PktShiftToUID] |= AuthorityMask::DEVELOPER | AuthorityMask::MASTER | AuthorityMask::GM | AuthorityMask::SUPPORTER;
|
|
AuthorityMask::AdminAuthority[PktCMD::PktStoreTabEdit] |= AuthorityMask::DEVELOPER | AuthorityMask::MASTER | AuthorityMask::GM | AuthorityMask::SUPPORTER;
|
|
AuthorityMask::AdminAuthority[PktCMD::PktAmountOfGold] |= AuthorityMask::DEVELOPER | AuthorityMask::MASTER | AuthorityMask::GM | AuthorityMask::SUPPORTER;
|
|
AuthorityMask::AdminAuthority[PktCMD::PktNameChangeCount] |= AuthorityMask::DEVELOPER | AuthorityMask::MASTER | AuthorityMask::GM | AuthorityMask::SUPPORTER;
|
|
AuthorityMask::AdminAuthority[PktCMD::PktGetCharInfoTime] |= AuthorityMask::DEVELOPER | AuthorityMask::MASTER | AuthorityMask::GM | AuthorityMask::SUPPORTER;
|
|
}
|
|
|
|
void CRylAdminManagerServer::PrintServerInfo()
|
|
{
|
|
char szServInfo[4096];
|
|
|
|
CAgentServerTable& ServerTable = CAgentServerTable::GetInstance();
|
|
|
|
ServerTable.PrintServerState(szServInfo, sizeof(szServInfo));
|
|
PrintInfo(szServInfo, sizeof(szServInfo));
|
|
}
|
|
|
|
bool CRylAdminManagerServer::InitializeCommand()
|
|
{
|
|
CConsoleCMDFactory& CMDFactory = *GetCommandFactory();
|
|
|
|
if(!CMDFactory.AddCommand("flush", new CCMDFlush))
|
|
{
|
|
ERRLOG0(g_Log, "Command failed : flush");
|
|
return false;
|
|
}
|
|
|
|
if(!CMDFactory.AddCommand("connect_all", new CCMDConnectAll))
|
|
{
|
|
ERRLOG0(g_Log, "Command failed : connect_all");
|
|
return false;
|
|
}
|
|
|
|
if(!CMDFactory.AddCommand("reload_script", new CCMDReload))
|
|
{
|
|
ERRLOG0(g_Log, "Command failed : reload");
|
|
return false;
|
|
}
|
|
|
|
if(!CMDFactory.AddCommand("connect_index", new CCMDConnect))
|
|
{
|
|
ERRLOG0(g_Log, "Command failed : connect");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
} |