Welcome!

Join our community of MMO enthusiasts and game developers! By registering, you'll gain access to discussions on the latest developments in MMO server files and collaborate with like-minded individuals. Join us today and unlock the potential of MMO server development!

Join Today!

Periachronicles

Junior Spellweaver
Joined
Sep 15, 2009
Messages
135
Reaction score
49
复制这段内容后打开百度网盘App,操作更方便哦。
链接:
提取码:9F1w --来自百度网盘超级会员V3的分享
 
Last edited by a moderator:
Junior Spellweaver
Joined
Dec 29, 2016
Messages
180
Reaction score
101
Re: Peria chronicles

Well, It's about time.
I hope you enjoy this release as I did a long time ago
If you like Python, this is for you. 90% of the entire game is coded in Python which is pretty much what caused the downfall of this project that led to development hell and lead to cancellation.

I don't have tutorials, nor do I have any interest. But I just want to warm that this game is very incomplete and contains a lot of bugs. You can only have fun if you know Python.

I just need that link to be in Gdrive since I lost the client a long time ago and I just want to play with the assets models
 
Joined
Mar 29, 2019
Messages
1,011
Reaction score
1,144
Re: Peria chronicles

Would anyone be able to re-upload to google drive/mega/mediafire?
Would take some time, but yeah









GM Commands

Code:
/notice
/summon
/bookset  
/teleport
/transfer
/enterHome
/leaveHome
/run
/startQuest
/cancelQuest
/addToFinishedQuests
/removeFromFinishedQuests
/questStates
/test_menu_selection_marker
/testload
/move
/whereami
/action
/getProperty
/setProperty
/resetCutscene
/item
/uncarve
/batch
/forcetransfer
/whereis
/playerlist
/checkmemory
/behaviortree
/kickout
/kickoutAll
/stamina
/refreshkiranaconditionz
/refreshkc
/time
/dump
/resetZone
/resetTerrain
/controlZoneEnter
/getRelationship
/setRelationship
/level
/emptyinven
/loot
/hottimez
/ho
/spmaxz
/sp
/kimaxz
/ki
/giveItem
/ban
/account
/character
/server
/debugAI
/dumpuser
/resetNPC
 
Junior Spellweaver
Joined
Dec 29, 2016
Messages
180
Reaction score
101
Re: Peria chronicles

J1HpSek - Periachronicles - RaGEZONE Forums
vkaxLQT - Periachronicles - RaGEZONE Forums

Just some extra info about the files themselves.
Hope this helps out.
 

Attachments

You must be registered for see attachments list
Junior Spellweaver
Joined
Nov 30, 2013
Messages
144
Reaction score
14
Re: Peria chronicles

Would take some time, but yeah







GM Commands

Code:
/notice
/summon
/bookset  
/teleport
/transfer
/enterHome
/leaveHome
/run
/startQuest
/cancelQuest
/addToFinishedQuests
/removeFromFinishedQuests
/questStates
/test_menu_selection_marker
/testload
/move
/whereami
/action
/getProperty
/setProperty
/resetCutscene
/item
/uncarve
/batch
/forcetransfer
/whereis
/playerlist
/checkmemory
/behaviortree
/kickout
/kickoutAll
/stamina
/refreshkiranaconditionz
/refreshkc
/time
/dump
/resetZone
/resetTerrain
/controlZoneEnter
/getRelationship
/setRelationship
/level
/emptyinven
/loot
/hottimez
/ho
/spmaxz
/sp
/kimaxz
/ki
/giveItem
/ban
/account
/character
/server
/debugAI
/dumpuser
/resetNPC

Thanks for the re-upload.

It seems that the client files and server are similar to each other.
 
Experienced Elementalist
Joined
Apr 10, 2011
Messages
203
Reaction score
23
Re: Peria chronicles

Client - not work...
Server[Maybe, it is test client...] - work
(However, because of the high system requirements of this game,
I have a hard time playing this game.
Too slow. :*:)
 
Skilled Illusionist
Joined
Dec 21, 2013
Messages
392
Reaction score
181
Re: Peria chronicles

Sp4KCG9 - Periachronicles - RaGEZONE Forums

Server and client works using the pre-registered account Tac. I think server settings are compiled directly into the pyc files.
 

Attachments

You must be registered for see attachments list
Junior Spellweaver
Joined
Dec 29, 2016
Messages
180
Reaction score
101
Re: Peria chronicles

I don't think anyone has the src according to my connections.

Have you determine if it's a codepage issue? I can probably write something quickly if that's the issue
 
Junior Spellweaver
Joined
Dec 29, 2016
Messages
180
Reaction score
101
Re: Peria chronicles

I never really got a hold of the server files myself. So I was just going based on what I was told in the past. I really thought it was Python which it is, but I wouldn't think it would be compiled Python code I guess that makes things a bit difficult huh.

If the usual LC Emu trick doesn't work, we'll have to dig deeper. I'm mostly busy with other projects however and this is the lowest of my priorities, I can probably look into it in a week or two.
Does the client CTD or has a MessageBox? Those are usually easy to detect with a debugger.
 
Joined
Aug 14, 2009
Messages
2,304
Reaction score
1,189
Re: Peria chronicles

It does have a messagebox yes, I also found it in decompiled app.py

Code:
             if currentLocale != 'cp949':
                 if self.Renderer.NativeWindowHandle:
                     text = 'Unsupported system locale : {}'.format(currentLocale)
                     text += '\n\nTo execute the client, change system locale to cp949 (Korean)'
                     self.showWindowsMessageBox(text)
                     self.nxlog = None
                     self.quit(0)
                     return

issue is that the decompilation failed half way down the file :S and its not possible to just replace the app.pyc with app.py because of this.
 
Junior Spellweaver
Joined
Dec 29, 2016
Messages
180
Reaction score
101
Re: Peria chronicles

Hey so, I'm just not going to have time to look into this since I have too many going on. I'll just provide some additional steps from this point on.


1). With your favorite debugger, add a BP on the last RETN to both MessageBoxA and MessageBoxW
2). Wait for the MsgBox to appear, then click on OK.
3). The debugger should now capture the BP. Step into and follow the on-screen assembly instructions. Eventually you're going to see a familiar condition toggle in the disassembly. (Based on the code provided above, looks like it's going to be a JNZ, the edit is probably going to be a JMP)
4). Implement the modification and pray that it'll work.

If you find it, please provide the AoB.
If you can't figure it out within a month, I'll handle it.
 
Junior Spellweaver
Joined
Nov 23, 2008
Messages
146
Reaction score
11
Re: Peria chronicles

It does have a messagebox yes, I also found it in decompiled app.py

Code:
             if currentLocale != 'cp949':
                 if self.Renderer.NativeWindowHandle:
                     text = 'Unsupported system locale : {}'.format(currentLocale)
                     text += '\n\nTo execute the client, change system locale to cp949 (Korean)'
                     self.showWindowsMessageBox(text)
                     self.nxlog = None
                     self.quit(0)
                     return

issue is that the decompilation failed half way down the file :S and its not possible to just replace the app.pyc with app.py because of this.

Where did it failed ?
Mine failed @ Line 1247

Found the same part of code, i'll try to decompile it again and see what happens.

If i get to decompile it, i'll post it there
 
Joined
Aug 14, 2009
Messages
2,304
Reaction score
1,189
Re: Peria chronicles

Line 314 to 402

Line 1247 doesnt even exist on mine :p.

Output from uncompyle6
Code:
# uncompyle6 version 3.7.4
# Python bytecode 3.7 (3394)
# Decompiled from: Python 2.7.17 (default, Sep 30 2020, 13:38:04) 
# [GCC 7.5.0]
# Warning: this version of Python has problems handling the Python 3 "byte" type in constants properly.

# Embedded file name: C:\NT_GPM_WORKSPACE\peria_server_stage\..\PYC\sources\python\natuum\client\app.py
# Compiled at: 2019-05-10 19:51:18
# Size of source mod 2**32: 69315 bytes
import collections, logging, os, sys, weakref, time, datetime, platform, enum, locale, thing.accessor
import thing.asyncio as asyncio
import thing.system
from natuum.activeobjects import ApartmentActiveObject
import natuum.apartment, natuum.app, natuum.builder, natuum.renderer, natuum.jobQueue, natuum.configuration, natuum.client.action, natuum.client.pager, natuum.client.resource, natuum.client.craft, natuum.client.logic, natuum.util, natuum.variation, natuum.indexBook, natuum.reward, natuum.growth, natuum.emotionCutscene, natuum.client.synergy, natuum.client.contract, natuum.client.relationship, natuum._entity.apartmentEvents, natuum._entity.framework.cluster
import natuum._entity.messageRPC as messageRPC
from natuum.nexon import NXLogClient
from natuum._entity.interfaces.clientSubscriber import IGatewaySubscriber
import natuum.gameService.wordFilter
if __thing_can_get_zcom_objects__:
    import multiprocessing
if not __final__:
    import natuum.objects
DEFAULT_CLIENT_RESOLUTION = (1920, 1080)
if not __final__:
    LOG_DEBUG = natuum.getLogger('client.app').debug
    LOG_WARNING = natuum.getLogger('client.app').warning
    import socket

def callbackBeforeDump():
    if natuum.client.app.Instance:
        natuum.client.app.Instance.nxlog.stageLog(1020, 'crash')
    natuum.flushLog()


def callbackAfterDump():
    pass


TEXT_FORMAT_TO_PRELOAD = ((0, '2002', 24.0, 3, 0, 5, 1.0), (0, 'KoPubDotum', 12.0, 3, 0, 5, 1.0),
                          (0, 'KoPubDotum', 13.0, 3, 0, 5, 1.0), (0, 'KoPubDotum', 13.0, 5, 0, 5, 1.0),
                          (0, 'KoPubDotum', 14.0, 3, 0, 5, 1.0), (0, 'KoPubDotum', 14.0, 5, 0, 5, 1.0),
                          (0, 'KoPubDotum', 15.0, 3, 0, 5, 1.0), (0, 'KoPubDotum', 15.0, 5, 0, 5, 1.0),
                          (0, 'KoPubDotum', 17.0, 3, 0, 5, 1.0), (0, 'KoPubDotum', 17.0, 5, 0, 5, 1.0),
                          (0, 'KoPubDotum', 18.0, 3, 0, 5, 1.0), (0, 'KoPubDotum', 18.0, 5, 0, 5, 1.0),
                          (0, 'KoPubDotum', 21.0, 5, 0, 5, 1.0), (0, 'KoPubDotum', 22.0, 5, 0, 5, 1.0),
                          (0, 'KoPubDotum', 23.0, 5, 0, 5, 1.0), (0, 'KoPubDotum', 24.0, 3, 0, 5, 1.0),
                          (0, 'KoPubDotum', 26.0, 3, 0, 5, 1.0), (0, 'KoPubDotum', 27.36, 3, 0, 5, 1.0),
                          (0, 'KoPubDotum', 27.6, 5, 0, 5, 1.0), (0, 'KoPubDotum', 33.0, 5, 0, 5, 1.0),
                          (0, 'KoPubDotum', 34.0, 3, 0, 5, 1.0), (0, 'KoPubDotum', 50.0, 5, 0, 5, 1.0),
                          (0, 'NanumSquare', 12.0, 5, 0, 5, 1.0), (0, 'NanumSquare', 14.0, 3, 0, 5, 1.0),
                          (0, 'NanumSquare', 14.0, 5, 0, 5, 1.0), (0, 'NanumSquare', 15.0, 5, 0, 5, 1.0),
                          (0, 'NanumSquare', 16.0, 5, 2, 5, 1.0), (0, 'NanumSquare', 17.0, 5, 0, 5, 1.0),
                          (0, 'NanumSquare', 40.0, 5, 0, 5, 1.0))
SIDEMENU_HTMLIVEWS = ('sideMenu_page_collection', 'sideMenu_page_map', 'sideMenu_page_present',
                      'sideMenuAvatar_editor')
Instance = None

class ListenerType(enum.IntEnum):
    ZONE = 0
    PERSON = 1


class App(natuum.app.App):
    _App__LONG_TIME_PLAY_WARNING_INTERVAL = 3600000

    def __init__(self):
        global Instance
        super().__init__()
        Instance = self
        self.RealTimeClock = thing.Value.RealTimeClock()
        self.RunOptions = self._getRunOptions('Natuum Client', '', (self._App__RunOptionHandler(),), sys.argv[1:])
        now = time.clock()
        self._App__clientTime = now
        self._App__serverTime = now
        self._App__fps = 60
        self.nxlog = None

    [USER=1333341624]Property[/USER]
    def FPS(self):
        return self._App__fps

    @FPS.setter
    def FPS(self, fps):
        self._App__fps = fps

    def __sendPacket(self, blob):
        pass

    Resolution = thing.accessor.ri(doc=u'\ud074\ub77c\uc774\uc5b8\ud2b8 \ud574\uc0c1\ub3c4')
    Renderer = thing.accessor.ri(doc=u'\ub80c\ub354\ub7ec \uac1d\uccb4')
    RPCManager = thing.accessor.ri(doc=u'RPC \uad00\ub9ac\uc790 \uac1d\uccb4')
    SoundManager = thing.accessor.ri(None, doc=u'\uc74c\ud5a5 \uad00\ub9ac\uc790 \uac1d\uccb4')
    Apartment = thing.accessor.ri(doc=u'\ud074\ub77c\uc774\uc5b8\ud2b8 \uc544\ud30c\ud2b8\uba3c\ud2b8')
    EventLoop = thing.accessor.ri(None, doc=u'\ud074\ub77c\uc774\uc5b8\ud2b8 \uc774\ubca4\ud2b8\ub8e8\ud504')
    ClusterGuestNode = thing.accessor.ri(doc=u'\ud074\ub77c\uc774\uc5b8\ud2b8 \ud074\ub7ec\uc2a4\ud130 \ub178\ub4dc')
    ClientZone = thing.accessor.rw(None)
    QuitOnRendererShutdown = thing.accessor.ri(True, doc=u'\ub80c\ub354\ub7ec \uc167\ub2e4\uc6b4\uc2dc \ud504\ub85c\uadf8\ub7a8 \uc885\ub8cc')
    Platform = thing.accessor.ri(doc=u'\ud50c\ub7ab\ud3fc \uac1d\uccb4')
    GatewayEntityID = thing.accessor.rw(None, doc=u'\uac8c\uc774\ud2b8\uc6e8\uc774\uc5d4\ud2f0\ud2f0 ID')
    ChatManagerEntityID = thing.accessor.rw(None, doc=u'\ucc44\ud305\uad00\ub9ac\uc790\uc5d4\ud2f0\ud2f0 ID')
    ItemDesignLibraryEntityID = thing.accessor.rw(None, doc=u'\uc544\uc774\ud15c\uc124\uacc4\uad00\ub9ac\uc790\uc5d4\ud2f0\ud2f0 ID')
    DocumentContentLibraryEntityID = thing.accessor.rw(None, doc=u'\ubb38\uc11c\ub0b4\uc6a9\uad00\ub9ac\uc790\uc5d4\ud2f0\ud2f0 ID')
    ServerHostAddress = thing.accessor.ri(None, doc=u'\uac8c\uc784 \uc11c\ubc84 \uc8fc\uc18c')
    LoaderThreadCount = thing.accessor.ri(1, doc=u'\ub85c\ub354 \uc2a4\ub808\ub4dc \uac2f\uc218')
    RealTimeClock = thing.accessor.ri(None, doc='RealTimeClock')
    EngineConfig = thing.accessor.rw({}, doc='EngineConfig')
    ServerConfig = thing.accessor.rw({}, doc='ServerConfig')
    ControlModeParams = thing.accessor.ri({}, doc='ControlModeParams')
    PlayerInitConfig = thing.accessor.ri({}, doc='PlayerInitConfig')
    MaxFPS = thing.accessor.rw(None, doc='MaxFPS')
    AccountSession = thing.accessor.ri(None, doc='AccountSession')
    ClientSpace = thing.accessor.ri(None, doc='ClientSpace')
    GameLogic = thing.accessor.ri(None, doc='GameLogic')
    Context = thing.accessor.ri(None, doc='Context')
    DummyAvatarSceneObjectData = thing.accessor.ri(None, doc='DummyAvatarSceneObjectData')
    IsPreloading = thing.accessor.ri(True, doc=u'\uc120\ub85c\ub529 \uc5ec\ubd80')
    UserConfig = thing.accessor.ri(None, doc='UserConfig')
    InitialSplashLoadTask = thing.accessor.ri(None, doc='InitialSplashLoadTask')
    _App__SERVER_EPOCH = datetime.datetime(2018, 1, 1)

    def __setup(self) -> 'int, exit code':
        if not __final__:
            thing.system.setQuietMode(self.RunOptions.Quiet)
        DebugLogCategories = '*,-action,-builder,-cutscene,' if self.RunOptions.SSOAuthToken is not None else ''
        DebugLogCategories += os.environ.get('NT_DEBUG_LOG', '*') + ',' + self.RunOptions.DebugLogCategories
        for category in DebugLogCategories.split(','):
            if not category:
                continue
            if category.startswith('-'):
                logger = natuum.getLogger(category[1:])
                logger.setLevel(logging.INFO)
                LOG_INFO('Log category [{}] level=debug was disabled'.format(logger.name))

        from thing.changeset import changeset, NexonCrashReporterVersion
        LOG_INFO('Changeset for Code:{}'.format(changeset))
        LOG_INFO('BuildVersion:{}'.format(NexonCrashReporterVersion))
        LOG_INFO('LauncherVer:{}, NGM Param:{}, NexonSN:{}, NexonSID:{}'.format(self.RunOptions.LauncherBuildVersion, self.RunOptions.LauncherOptionalParam, self.RunOptions.NexonSN, self.RunOptions.NexonSID))
        self.Platform = platform = thing.Platform.Platform()
        self.ETWSession_findObjects = platform.createETWSession('findObjects')
        if self.RunOptions.SendCrashReport:
            installed = platform.installCrashReporter(self.RunOptions.ProjectID)
            installed or LOG_WARNING('exception handler is not installed')
        else:
            platform.setCrashReporterInformation('ProcessType', self.RunOptions.ProductName)
            from thing.changeset import NexonCrashReporterVersion
            platform.setCrashReporterInformation('ProjectVersion', NexonCrashReporterVersion)
            if natuum.m_logFilePath:
                relPath = os.path.relpath(natuum.m_logFilePath, os.path.dirname(sys.executable))
                platform.setCrashReporterAuxFile(relPath)
            if __thing_supports_gc__:
                if natuum.objects.m_refCycleFilePath:
                    relPath = os.path.relpath(natuum.objects.m_refCycleFilePath, os.path.dirname(sys.executable))
                    platform.setCrashReporterAuxFile(relPath)
                platform.registerCrashReporterCallbackBeforeDump(callbackBeforeDump)
                thing.system._installExceptHook(fromInit=False)
            else:
                __final__ or self._App__initConsoleCtrlHandler()
            if not __final__:
                platform.listenEasyProfileGUI(28077)
            else:
                self._App__thMan, self.RPCManager, self._App__chMan, self._App__chManThread = self._App__setupRPCManager()
                self._App__clientLoaderApartments = self._App__createLoaderApartments()
                rootFS = thing.FileSystem.FileSystem(natuum.getRootPath())
                dataFS = rootFS['data']
                try:
                    resourcesFS = rootFS['resources']
                except Exception:
                    resourcesFS = rootFS.createDirectory('resources')

                rm = natuum.client.resource.Manager(dataFS, resourcesFS)
                rm.IsPreloading = self.IsPreloading
                self._App__clientLoaderRegisterTokens = self._App__setupResourceManager(rm, self._App__clientLoaderApartments)
                self.JobQueue = natuum.jobQueue.JobQueue()
                self.EngineConfig = engineConfig = rm.load(natuum.builder.combineID('_', 'EngineConfig')).lockForReading().Data
                self.ServerConfig = rm.load(natuum.builder.combineID('_', 'ServerConfig')).lockForReading().Data[self.RunOptions.ClusterID]
                self.GatewayEntityID = self.ServerConfig['GatewayEntityID']
                self.ChatManagerEntityID = self.ServerConfig['ChatManagerEntityID']
                self.ItemDesignLibraryEntityID = self.ServerConfig['ItemDesignLibraryEntityID']
                self.DocumentContentLibraryEntityID = self.ServerConfig['DocumentContentLibraryEntityID']
                self.ServerHostAddress = self.RunOptions.Host
                self.ControlModeParams = rm.ensureResource('controlModeParams@GameSetting')
                self.PlayerInitConfig = rm.ensureResource('playerInitialization@GameSetting')
                fullScreen = self.RunOptions.FullScreen
                if fullScreen:
                    import ctypes
                    user32 = ctypes.windll.user32
                    screenWidth = user32.GetSystemMetrics(0)
                    screenHeight = user32.GetSystemMetrics(1)
                    screenRatio = screenWidth / screenHeight
                    if screenRatio > 1.7777777777777777:
                        width, height = (2400, 1080)
                    else:
                        if screenRatio > 1.6:
                            if screenWidth == 2048 and screenHeight == 1152:
                                width, height = screenWidth, screenHeight
                            else:
                                width, height = (1920, 1080)
                        else:
                            if screenRatio > 1.3333333333333333:
                                width, height = (1920, 1200)
                            else:
                                width, height = (1600, 1200)
                else:
                    width, height = DEFAULT_CLIENT_RESOLUTION
                self.AspectRatio = thing.Dirp.Dirp_Static(thing.system.vec(width / height, 1))
                self.MaxFPS = max(int(engineConfig.Config.get('maxFPS', 60)), 1)
                rendererMode = thing.Renderer.RendererManager().createRendererMode(fullScreen, width, height, 32)
                self.Renderer = self._App__Renderer(rendererMode, engineConfig.Config)
                self.Renderer.TerrainTessellationQueueLength = 3
                self.Renderer.HangDetector = thing.ToolHelpers.HangDetector(self._App__onHangDetected)
                self.PropCanvasMipHeadCutCountOnLoad = 0
                userConfig = self._loadUserConfig()
                if userConfig is not None:
                    self.UserConfig = userConfig
                    self._refreshCanvasLoadQuality(userConfig)
                    self.Renderer.UserConfig = userConfig
                else:
                    self.UserConfig = self.Renderer.UserConfigValue['']
            currentLocale = locale.getdefaultlocale()[1]
            # if currentLocale != 'cp949':
                # if self.Renderer.NativeWindowHandle:
                    # text = 'Unsupported system locale : {}'.format(currentLocale)
                    # text += '\n\nTo execute the client, change system locale to cp949 (Korean)'
                    # self.showWindowsMessageBox(text)
                    # self.nxlog = None
                    # self.quit(0)
                    # return
            self._App__rendererModeWatchToken = self.Renderer.ModeVersion.addWatcher(self._App__onRendererModeChangedEntry, '__call__', weakref.ref(self))
            self.nxlog = NXLogClient(self.RunOptions.NexonSN, self.RunOptions.NexonSID)
            self.nxlog.stageLog(420, '__setup')
            self._App__rendererReadyAtom = rendererReadyAtom = thing.Thread.Atom()
            self._App__pagerThread = thread = self._App__thMan.createThread(thing.system.nativeThreadEntry, '__call__', self._App__pagerThreadEntry, rendererReadyAtom)
            thread.Name = 'Main Updater'
            thread.run()

    _App__settingsDirName = 'settings'
    _App__userConfigFileName = 'userConfig.nothing'

    def _loadUserConfig(self):
        settingsDirPath = os.path.join(natuum.getRootPath(), 'storage', __class__._App__settingsDirName)
        try:
            if os.path.exists(settingsDirPath):
                fs = thing.FileSystem.FileSystem(settingsDirPath)
            else:
                raise KeyError
            f = fs[__class__._App__userConfigFileName]
            ra = thing.Archive.ReadingArchive_FromFile(f)
            userConfig = thing.Collection.Dict()
            userConfig.load(ra)
            LOG_INFO('user config loaded')
        except KeyError:
            LOG_WARNING('cannot load user config')
            userConfig = None

        return userConfig

    def _saveUserConfig(self, userConfig):
        settingsDirPath = os.path.join(natuum.getRootPath(), 'storage', __class__._App__settingsDirName)
        try:
            os.makedirs(settingsDirPath)
        except OSError:
            pass

        try:
            fs = thing.FileSystem.FileSystem(settingsDirPath)
            f = fs.createFile(__class__._App__userConfigFileName, True)
            wa = thing.Archive.WritingArchive_FromFile(f)
            userConfig.save(wa)
            wa.flush()
            LOG_INFO('user config saved')
        except:
            LOG_WARNING('cannot save user config')

    def _refreshCanvasLoadQuality(self, userConfig):
        propTexQuality = userConfig.get('PropTextureQuality', -1)
        if propTexQuality >= 0:
            propTexQuality = min(2, propTexQuality)
            oldValue = self.PropCanvasMipHeadCutCountOnLoad
            newValue = (2, 1, 0)[propTexQuality]
            if oldValue != newValue:
                self.PropCanvasMipHeadCutCountOnLoad = newValue
                return True
        return False

    def __preload(self):
        markerResourceIDs = self._App__collectMarkerResources()
        effectPreloadList = tuple(markerResourceIDs.get('Effect', set()) | set(self._App__PRELOAD_EFFECTS))
        rm = natuum.client.resource.Manager.Instance
        rm.preload('Avatar', preloadList=('AT099800(man_default)', 'AT099900(woman_default)'))
        rm.preload('Effect', preloadList=effectPreloadList)
        rm.preload('CameraAngle')
        rm.preload('CameraAngleBlender')
        rm.preload('CameraAngleBlenderSet')
        rm.preload('HTMLView', preloadList=SIDEMENU_HTMLIVEWS)
        rm.preload('HTML', preloadList=('sideMenu', 'sideMenuAvatar'))
        rm.preload('ItemDesign')
        rm.preload('ItemIO')
        rm.preload('MeshSet', preloadList=(self._App__DUMMY_AVATAR_MESHSETS))
        rm.preload('PropPart', preloadList=(self._App__PRELOAD_PROPPARTS))
        rm.preload('Quest')
        rm.preload('VueApp', preloadList=('app', ))
        rm.preload('Zone', preloadList=('ZR0000(carveKiranaArea)', ))
        for typeName, resourceIDs in markerResourceIDs.items():
            if typeName in ('Effect', 'ItemDesign'):
                continue
            rm.preload(typeName, preloadList=(tuple(resourceIDs)))

        rm.preload('GameSetting')

    def __collectMarkerResources(self):

        def collectResourceIDs--- This code section failed: ---

 L. 433         0  LOAD_GLOBAL              isinstance
                2  LOAD_FAST                'value'
                4  LOAD_GLOBAL              str
                6  CALL_FUNCTION_2       2  '2 positional arguments'
                8  POP_JUMP_IF_FALSE    56  'to 56'
               10  LOAD_GLOBAL              natuum
               12  LOAD_ATTR                builder
               14  LOAD_METHOD              isResourceID
               16  LOAD_FAST                'value'
               18  CALL_METHOD_1         1  '1 positional argument'
               20  POP_JUMP_IF_FALSE    56  'to 56'

 L. 434        22  LOAD_GLOBAL              natuum
               24  LOAD_ATTR                builder
               26  LOAD_METHOD              splitID
               28  LOAD_FAST                'value'
               30  CALL_METHOD_1         1  '1 positional argument'
               32  UNPACK_SEQUENCE_3     3 
               34  STORE_FAST               'name'
               36  STORE_FAST               'typeName'
               38  STORE_FAST               '_'

 L. 435        40  LOAD_FAST                'resourceIDs_'
               42  LOAD_FAST                'typeName'
               44  BINARY_SUBSCR    
               46  LOAD_METHOD              add
               48  LOAD_FAST                'name'
               50  CALL_METHOD_1         1  '1 positional argument'
               52  POP_TOP          
               54  JUMP_FORWARD        144  'to 144'
             56_0  COME_FROM            20  '20'
             56_1  COME_FROM             8  '8'

 L. 436        56  LOAD_GLOBAL              isinstance
               58  LOAD_FAST                'value'
               60  LOAD_GLOBAL              str
               62  CALL_FUNCTION_2       2  '2 positional arguments'
               64  POP_JUMP_IF_TRUE    104  'to 104'
               66  LOAD_GLOBAL              isinstance
               68  LOAD_FAST                'value'
               70  LOAD_GLOBAL              collections
               72  LOAD_ATTR                Sequence
               74  CALL_FUNCTION_2       2  '2 positional arguments'
               76  POP_JUMP_IF_FALSE   104  'to 104'

 L. 437        78  SETUP_LOOP          144  'to 144'
               80  LOAD_FAST                'value'
               82  GET_ITER         
               84  FOR_ITER            100  'to 100'
               86  STORE_FAST               'value_'

 L. 438        88  LOAD_DEREF               'collectResourceIDs'
               90  LOAD_FAST                'value_'
               92  LOAD_FAST                'resourceIDs_'
               94  CALL_FUNCTION_2       2  '2 positional arguments'
               96  POP_TOP          
               98  JUMP_BACK            84  'to 84'
              100  POP_BLOCK        
              102  JUMP_FORWARD        144  'to 144'
            104_0  COME_FROM            76  '76'
            104_1  COME_FROM            64  '64'

 L. 439       104  LOAD_GLOBAL              isinstance
              106  LOAD_FAST                'value'
              108  LOAD_GLOBAL              collections
              110  LOAD_ATTR                Mapping
              112  CALL_FUNCTION_2       2  '2 positional arguments'
              114  POP_JUMP_IF_FALSE   144  'to 144'

 L. 440       116  SETUP_LOOP          144  'to 144'
              118  LOAD_FAST                'value'
              120  LOAD_METHOD              values
              122  CALL_METHOD_0         0  '0 positional arguments'
              124  GET_ITER         
              126  FOR_ITER            142  'to 142'
              128  STORE_FAST               'value_'

 L. 441       130  LOAD_DEREF               'collectResourceIDs'
              132  LOAD_FAST                'value_'
              134  LOAD_FAST                'resourceIDs_'
              136  CALL_FUNCTION_2       2  '2 positional arguments'
              138  POP_TOP          
              140  JUMP_BACK           126  'to 126'
              142  POP_BLOCK        
            144_0  COME_FROM_LOOP      116  '116'
            144_1  COME_FROM           114  '114'
            144_2  COME_FROM           102  '102'
            144_3  COME_FROM_LOOP       78  '78'
            144_4  COME_FROM            54  '54'

Parse error at or near `COME_FROM_LOOP' instruction at offset 144_3

        def collectMarkerClasses(marker, markerClasses_):
            markerClasses.add(marker)
            for marker_ in marker.__subclasses__():
                collectMarkerClasses(marker_, markerClasses_)

        markerResourceIDs = collections.defaultdict(set)
        markerClasses = set()
        collectMarkerClasses(natuum.client.clientSpace.marker.Marker, markerClasses)
        for arranger in natuum.client.clientSpace.arranger.__dict__.values():
            if isinstance(arranger, type):
                collectMarkerClasses(arranger, markerClasses)

        for marker in markerClasses:
            for attribute in marker.__dict__.values():
                collectResourceIDs(attribute, markerResourceIDs)

        collectMarkerClasses = None
        collectResourceIDs = None
        return markerResourceIDs

    def __run(self) -> 'int, exit code':
        if not __final__:
            if not self.RunOptions.NoUsageReport:
                self._App__sendUsageReport()
        windowTitle = 'Peria Chronicles{}'.format(natuum.util.getBuildVersion())
        rendererExitCode = self.Renderer.start(windowTitle, thing.system.ICanvas.PIXEL_FORMAT_R8G8B8A8, self._App__rendererReadyAtom, self.QuitOnRendererShutdown)
        return self._App__exitCode

    def __cleanup(self) -> None:
        global Instance
        if not __final__:
            for i in range(5):
                if self._App__pagerThread.wait(1):
                    break
                (LOG_DEBUG if i <= 2 else LOG_WARNING)(u'Pager \uc2a4\ub808\ub4dc \uc885\ub8cc \ub300\uae30\uc911 (%d\ucd08)', i + 1)

        else:
            self._App__pagerThread.wait(5)
        del self._App__pagerThread
        self.JobQueue.shutdown()
        self._App__clientLoaderRegisterTokens.clear()
        for apartment in self._App__clientLoaderApartments.values():
            apartment.shutdown()

        self._App__shutdownTableManagers()
        self._App__cleanupResourceManager()
        if self._App__chMan is not None:
            self._App__chMan.shutdown()
            VERIFY(self._App__chManThread.wait(5))
            del self._App__chManThread
        del self._App__mainApartment
        self.RPCManager.shutdown()
        Instance = None

    [USER=186209]Thing[/USER].overrides(natuum.app.App)
    def run(self) -> 'int, exit code':
        self._App__setup()
        ret = self._App__run()
        self._App__cleanup()
        return ret

    [USER=186209]Thing[/USER].overrides(natuum.app.App)
    def quit(self, exitCode):
        LOG_INFO('client quit called')
        self.Renderer.shutdown()
        curPage = natuum.client.pager.CurPage
        if curPage is not None:
            if not natuum.client.pager.command((curPage.canQuitApp), wait=True):
                return
        super().quit()
        self._App__exitCode = exitCode
        if self.RunOptions.SendCrashReport:
            self.Platform.setCrashReporterClientExit(exitCode)
        if natuum.client.app.Instance.nxlog is not None:
            natuum.client.app.Instance.nxlog.stageLog(1010, str(exitCode))
        self.Renderer.quit(True)

    def isInRendererThreadGroup(self, threadID):
        return threadID == self.Renderer.ThreadID

    def collectZcomObjects(self, showBackrefsOfIDs=False):
        if not __thing_can_get_zcom_objects__:
            return
        createdZcomObjects = self._App__createdZcomObjects
        if createdZcomObjects is None:
            self._App__createdZcomObjects = thing.ToolHelpers.CreatedObjects()
        else:
            self._App__createdZcomObjects = None
            objs = None
            try:
                backrefsDir = os.path.join(natuum.getLogPath(), 'backrefs')
                os.makedirs(backrefsDir, exist_ok=True)
                today = datetime.datetime.today()
                timestamp = '%4d%02d%02d_%02d%02d%02d' % (today.year, today.month, today.day, today.hour, today.minute, today.second)
                graphFilePath = os.path.join(backrefsDir, 'graph_c{}.txt'.format(timestamp))
                with open(graphFilePath, 'w', encoding='utf_8-sig') as (file):
                    natuum.objects.Graph.collectToFile(file)
                objs = list(createdZcomObjects)
                objectIDsFilePath = os.path.join(backrefsDir, 'objects_c{}.txt'.format(timestamp))
                with open(objectIDsFilePath, 'w') as (file):
                    natuum.objects.saveObjectIDsToFile(file, objs)
                if showBackrefsOfIDs:
                    multiprocessing.Process(target=(natuum.objects.showBackrefsOfIDs),
                      args=(
                     backrefsDir, graphFilePath, objectIDsFilePath)).start()
                thing.system._debugBreak()
                objs.clear()
            finally:
                if objs is not None:
                    objs.clear()

    def checkMemory(self):
        if __final__ or self._App__memoryReclaimer is None:
            return
        for i in range(10):
            self._App__memoryReclaimer.reclaimAll()

        natuum.objects.GarbageCollector().collect(-1, True)
        trendsCollector = self._App__objectCountTrendsCollector
        if trendsCollector is None:
            self._App__objectCountTrendsCollector = trendsCollector = natuum.objects.CountTrendsCollector()
        trendsCollector.collect()
        trends = [(v, trendsCollector.getCount(k), k) for k, v in trendsCollector.items()]
        trends.sort(reverse=True)
        tracker = self._App__memoryTracker
        lines = []
        lines.append('**** object count trend log start ****')
        for trend, count, typeName in trends:
            countList = tracker.get(typeName, None)
            if countList is None:
                countList = list()
                tracker[typeName] = countList
            countList.append(count)
            lines.append('{:G}\t{}\t{}'.format(trend, typeName, '\t'.join([str(i) for i in countList])))

        lines.append('**** object count trend log end ****')

    def getRoughServerTime(self) -> 'int. milliseconds':
        passed = int(time.time() - self._App__clientTime) * 1000
        return (self._App__serverTime + passed) % 2147483647

    def setListener(self, flag, position) -> None:
        soundManager = self.SoundManager
        if soundManager is not None:
            configuration = self.ControlModeParams.get('sound')
            if configuration is None:
                if ListenerType.ZONE == flag:
                    soundManager.Listener = position
            elif configuration['listener'] == flag:
                soundManager.Listener = thing.Dirp.Dirp_FromLocal(configuration['local'], thing.math.ROT_IDENTITY, position)

    def _setServerTime(self, serverNow):
        self._App__clientTime = time.time()
        self._App__serverTime = int(serverNow * 1000)

    class __Renderer(natuum.renderer.Renderer):

        [USER=186209]Thing[/USER].overrides(natuum.renderer.Renderer)
        def onRendererCloseButton(self, renderer: 'thing.system.IRenderer', private) -> None:
            Instance.quit(0)

        [USER=186209]Thing[/USER].overrides(natuum.renderer.Renderer)
        def onRendererConfigChange(self, renderer: 'thing.system.IRenderer', private) -> None:
            if isinstance(private, thing.system.IMutableMapping):
                userConfig = private
                natuum.client.app.Instance._saveUserConfig(userConfig)
                if natuum.client.app.Instance._refreshCanvasLoadQuality(userConfig):
                    natuum.client.pager.command(natuum.client.app.Instance.GameLogic.onCanvasLoadQualityChange)

        def _registerLayerCategories(self) -> None:
            self.registerLayerCategory('UI').forget()

    class __RunOptionHandler(natuum.runOptions.IHandler):

        def fillDefaults(self, options):
            options.Host = None
            options.Quiet = False
            options.InfuseActor = None
            options.CrashReportURL = 'http://socorro.thingsoft.com:8882/submit'
            options.ProductName = 'client'
            options.ProductVersion = '0.8.1'
            options.NoUsageReport = False
            options.NoEffect = False
            options.BatchJob = None
            options.Locale = None
            options.ClusterID = 'default'
            options.SSOAuthToken = None
            options.FullScreen = False
            options.DebugLogCategories = '*,-action,-builder,-cutscene' if __final__ else ''
            options.ProjectID = 'peria_dev'
            options.LauncherBuildVersion = -1
            options.LauncherOptionalParam = ''
            options.NexonSN = 0
            options.NexonSID = None
            options.disableNXLog = False
            crashReport = os.environ.get('NT_NO_CRASH_REPORT', '0')
            options.SendCrashReport = crashReport == '0'

        def buildParser(self, parser: 'optparse.OptionParser', options) -> None:
            parser.add_option('-a', '--address', dest='Host', help='server address')
            parser.add_option('-q', '--quiet', dest='Quiet', default=False, action='store_true', help='quiet mode')
            parser.add_option('-i', '--infuseActor', dest='InfuseActor', help='auto infuse actor')
            parser.add_option('-b', '--batch', dest='BatchJob', help='batch job file')
            parser.add_option('-l', '--locale', dest='Locale', help='locale')
            parser.add_option('-C', '--cluster', dest='ClusterID', help='cluster id')
            parser.add_option('--crashreport-url', dest='CrashReportURL', help='crash report url')
            parser.add_option('--product-name', dest='ProductName', help='product name')
            parser.add_option('--product-version', dest='ProductVersion', help='product version')
            parser.add_option('--nousagereport', dest='NoUsageReport', default=False, action='store_true', help="don't send usage report")
            parser.add_option('-t', '--auth', dest='SSOAuthToken', help='auth token')
            parser.add_option('-f', '--fullScreen', dest='FullScreen', default=False, action='store_true', help='full screen mode')
            parser.add_option('--debugLog', type='str', dest='DebugLogCategories', help='comma seperated log categories')
            parser.add_option('--projectid', dest='ProjectID', help='crash reporter project id')
            parser.add_option('--launcherBuildVersion', dest='LauncherBuildVersion', help='launcher build version')
            parser.add_option('--launcherParam', dest='LauncherOptionalParam', default='', help='argument from NexonGameManager')
            parser.add_option('--nexonsn', dest='NexonSN', default=0, help='argument from NexonGameManager')
            parser.add_option('--nexonsid', dest='NexonSID', default='', help='argument from NexonGameManager')
            parser.add_option('--disableNXLog', dest='disableNXLog', default=False, action='store_true', help='disable NXLOG')

        def onParsed(self, parser: 'optparse.OptionParser', options, args: tuple) -> None:
            if options.BatchJob is not None:
                batchJobPath = os.path.join(natuum.getRootPath(), 'settings', options.BatchJob)
                options.BatchJob = natuum.util.readXDF(batchJobPath)
            if options.Locale is not None:
                if options.Locale in ('ko', 'ja', 'zh', 'en'):
                    natuum.util.DefaultLocale = options.Locale

    def __createLoaderApartments(self) -> '{ str: thing.system.IApartment }':
        apartments = {}
        for i in range(self.LoaderThreadCount):
            name = 'Loader{}'.format(i)
            apartmentObject = ApartmentActiveObject(self._App__thMan, self.RPCManager)
            apartmentObject.start(name, -0.13333333333333333)
            apartments[name] = apartmentObject.Apartment

        return apartments

    if not __final__:

        def __initConsoleCtrlHandler(self):

            def _terminateProcess(ctrlType):
                thing.system._TerminateProcess()

            thing.system.setConsoleCtrlHandler(_terminateProcess)

    def __setupRPCManager(self):
        thMan = thing.Thread.ThreadManager()
        rpcManager = thing.RPC.RPCManager(thMan)
        self._App__mainApartment = natuum.apartment.Apartment()
        return (
         thMan, rpcManager, None, None)

    def __setupResourceManager(self, rm, loaderApartments) -> '{ str : thing.system.IForgettableToken }':
        tokens = {}
        for name, apartment in loaderApartments.items():
            token = rm.CacheManager.registerDelayedLoaderApartment(name, apartment.Inner)
            tokens[name] = token

        return tokens

    def __cleanupResourceManager(self) -> None:
        natuum.client.resource.Manager.Instance.shutdown()

    @staticmethod
    def __onRendererModeChangedEntry(watched, weakSelf, hint, funcName) -> None:
        self = weakSelf()
        if self is not None:
            clientSpace = self.ClientSpace
            if clientSpace is not None:
                natuum.client.pager.command(clientSpace.onRendererModeChanged)

    def __pagerThreadEntry(self, rendererReadyAtom: 'thing.system.IAtom') -> None:
        self.Context = thing.Common.Sites()
        self.EventLoop = loop = natuum._entity.apartmentEvents.EventLoop()
        asyncio.set_event_loop(loop)
        self.Apartment = loop.Apartment
        thing.system.setSwitchIntervalForCurrentThread(1)
        rendererReadyAtom.wait()
        inputManager = natuum.client.input.Manager()
        self._App__initFontManager()
        self.showInitialSplash(True)
        if self.InitialSplashLoadTask:
            loop.run_until_complete(self.InitialSplashLoadTask)
        _ = self.Platform.disableAccessibility()
        self.GUI = guiSystem = thing.Renderer.GUISystem()
        self.Renderer.GUISystem = guiSystem
        self.SoundManager = soundManager = self._App__createSoundManager(parameters=(self.ControlModeParams))
        try:
            soundManager.setVolume('music', self.UserConfig['BGMVolume'])
            soundFXVol = self.UserConfig['SoundEffectVolume']
            for bus in ('effect', 'voice', 'environment', 'kiranaContract'):
                soundManager.setVolume(bus, soundFXVol)

        except KeyError:
            pass

        natuum.configuration.Configuration.initialize()
        self.ClusterGuestNode = node = natuum._entity.framework.cluster.setup_guest(self.ServerConfig['GatewayServers'])
        if self.ServerHostAddress is not None:
            node.overrideConnectingtHost(self.ServerHostAddress)
        self.AccountSession = natuum.client.app.AccountSession()
        self.ZoneChanged = natuum.signal.Signal(natuum.client.zone.Zone)
        self._App__initClassTables()
        if self.IsPreloading:
            self._App__preload()
        self._App__initTableManagers()
        clientSpace = natuum.client.clientSpace.ClientSpace()
        clientSpace.onRendererModeChanged()
        clientSpace.addDesktop()
        clientSpace.addDesktop()
        clientSpace.addInventory()
        self.ClientSpace = clientSpace
        inputManager.setEventHandler(clientSpace)
        self.GameLogic = gameLogic = natuum.client.logic.GameLogic(clientSpace)
        gameLogic.SyncClock.defer(App._App__LONG_TIME_PLAY_WARNING_INTERVAL, App._App__warnLongTimePlay, 1).forget()
        self._App__setupIDE()
        self._App__buildDummyAvatar()
        self._App__processorToken = thing.Thread.Processor().addTask(0, self.MaxFPS)
        self._App__memoryReclaimer = memoryReclaimer = thing.Common.MemoryReclaimer()
        memoryReclaimer.clearPolicies()
        memoryReclaimer.appendPolicy(memoryReclaimer.MEMORY_RECLAIMER_CRITERION_AVAILABLE_PHYSICAL_MB_LESS_THAN, 100, 209715200, 5)
        memoryReclaimer.appendPolicy(memoryReclaimer.MEMORY_RECLAIMER_CRITERION_AVAILABLE_PHYSICAL_MB_LESS_THAN, 500, 104857600, 10)
        memoryReclaimer.appendPolicy(memoryReclaimer.MEMORY_RECLAIMER_CRITERION_AVAILABLE_PHYSICAL_MB_LESS_THAN, 1000, 1, 15)
        memoryReclaimer.appendPolicy(memoryReclaimer.MEMORY_RECLAIMER_CRITERION_PAGE_FILE_USAGE_MB_GREATER_THAN, 7168, 102400, 15)
        natuum.apartment.getCurrentApartment().Alarm.postTask(self._App__reclaimMemory, 0).forget()
        if not __final__:
            natuum.apartment.getCurrentApartment().Alarm.postTask(self._App__logMemoryReclaimer, 60000).forget()
        self._App__memoryTracker = {}
        loop.call_soon(natuum.client.pager.enter)
        loop.run_forever()
        loop.run_until_complete(node.shutdown())
        loop.close()
        gameLogic.shutdown()
        thing.Client.EffectManager().terminate()
        soundManager.shutdown()
        guiSystem.shutdown()
        self.Renderer.GUISystem = None

    def __initClassTables(self) -> None:
        rm = natuum.client.resource.Manager.Instance
        natuum.client.action.ActionTableManager.ensureInstance()
        rawClassTables = rm.load(natuum.gameClass.CLASS_TABLE).lockForReading().Data
        natuum.gameClass.initManager(rawClassTables)
        if self.IsPreloading:
            natuum.gameClass.getManagerInstance().preloadClasses()

    def __initTableManagers(self) -> None:
        rm = natuum.client.resource.Manager.Instance
        natuum.emotionCutscene.initManager(rm)
        natuum.client.craft.initManager(rm)
        natuum.variation.initManager(rm)
        natuum.indexBook.initManager(rm)
        natuum.client.contract.initManager(rm)
        natuum.reward.initManager(rm)
        natuum.growth.initManager(rm)
        natuum.actor.initManager(rm)
        natuum.client.quest.initManager(rm)
        natuum.town.initManager(rm)
        natuum.client.relationship.initManager(rm)
        natuum.client.synergy.initManager(rm)
        natuum.gameService.wordFilter.initManager(rm)

    def __initFontManager(self) -> None:
        fontManager = thing.Renderer.FontManager()
        rm = natuum.client.resource.Manager.Instance
        rm.preload('Font', preloadList=(natuum.util.FontNames))
        for fontName in natuum.util.FontNames:
            fontID = natuum.builder.combineID(fontName, 'Font')
            fontData = rm.ensureResource(fontID)
            fontManager.registerFontData(fontData[0], fontData[1])

        for info in TEXT_FORMAT_TO_PRELOAD:
            (fontManager.createRasterTextFormat)(self.Renderer, *info)

    def __setupIDE(self) -> None:
        pass

    def __buildDummyAvatar(self) -> None:
        rm = natuum.client.resource.Manager.Instance
        meshSet = rm.ensureResource('AP7250@MeshSet')
        sceneObjectData = thing.Scene.SceneObjectData_Mesh()
        sceneObjectData.ShaderFlags = thing.system.IShader.SHADER_SHADOW_MAP | thing.system.IShader.SHADER_REALISTIC
        for meshName, meshAndBone in meshSet.items():
            sceneObjectMeshElementData = thing.Scene.SceneObjectMeshElementData()
            sceneObjectMeshElementData.Mesh = meshAndBone['mesh']
            sceneObjectData.Elements[meshName] = sceneObjectMeshElementData

        self.Renderer.warm(sceneObjectData, 0)
        self.DummyAvatarSceneObjectData = sceneObjectData

    def __shutdownTableManagers(self) -> None:
        natuum.client.craft.shutdownManager()
        natuum.client.action.ActionTableManager.shutdown()
        natuum.gameClass.shutdownManager()

    def __reclaimMemory(self) -> None:
        if self.IsQuitting:
            return
        waitDuration = self._App__memoryReclaimer.reclaim()
        natuum.apartment.getCurrentApartment().Alarm.postTask(self._App__reclaimMemory, waitDuration).forget()

    if not __final__:

        def __logMemoryReclaimer(self) -> None:
            if self.IsQuitting:
                return
            memoryReclaimer = self._App__memoryReclaimer
            LOG_INFO('available physical memory: {} GB'.format(memoryReclaimer.AvailablePhysicalMemoryGB))
            LOG_INFO('page file usage: {} GB'.format(memoryReclaimer.PageFileUsageGB))
            natuum.apartment.getCurrentApartment().Alarm.postTask(self._App__logMemoryReclaimer, 60000).forget()

    def __sendUsageReport(self) -> None:

        def sendReport(info: 'sequence of ( str, str )') -> str:
            tokenDelim = '_TDELIM_'
            emptyToken = '_EMPTY'
            stream = tokenDelim.join([value if value != '' else emptyToken for value in info])
            try:
                sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
                sock.sendto(stream.encode('utf-8'), ('wild.thingsoft.com', 8025))
                LOG_INFO('Usage report sent..')
            except socket.error:
                pass

            sock.close()

        rendererManager = thing.Renderer.RendererManager()
        usageInfo = (
         '{}'.format(platform.node()),
         '{} {}'.format(platform.system(), platform.version()),
         '{}'.format(platform.processor()),
         '{}'.format(self.Platform.MemorySize),
         '{}'.format(self.Platform.HardDiskSize),
         '{}'.format(rendererManager.DeviceDescription.split('\x00')[0].strip()),
         '{}'.format(natuum.util.getBuildVersion()))
        sendReport(usageInfo)
        sendReport = None

    [USER=437263]clas[/USER]smethod
    def __createSoundManager(cls, parameters=None, liveUpdate=False) -> 'thing.system.ISoundManager':
        rm = natuum.client.resource.Manager.Instance
        masterBank = rm.load(cls._App__masterBankResourceID)
        masterBank = masterBank.lockForReading().Data
        try:
            masterBank = masterBank.SoundData
        except KeyError:
            LOG_WARNING(u'{}\ub97c \uc77d\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4. \uc774\ubca4\ud2b8 \uacbd\ub85c\ub97c \uc77d\uc9c0 \ubabb\ud574 \uc18c\ub9ac\uac00 \ub098\uc624\uc9c0 ' + (u'\uc54a\uc2b5\ub2c8\ub2e4.').format(cls._App__masterBankResourceID))
            return
        else:
            masterStringBank = rm.load(cls._App__masterStringBankResourceID)
            try:
                masterStringBank = masterStringBank.lockForReading().Data.SoundData
            except KeyError:
                LOG_WARNING(u'{}\ub97c \uc77d\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4. \uc774\ubca4\ud2b8 \uacbd\ub85c\ub97c \uc77d\uc9c0 \ubabb\ud574 \uc18c\ub9ac\uac00 \ub098\uc624\uc9c0 ' + (u'\uc54a\uc2b5\ub2c8\ub2e4.').format(cls._App__masterStringBankResourceID))
                return
            else:
                busGroups = cls._App__getBusGroups(parameters)
                return thing.Sound.SoundManager(masterBank, masterStringBank, liveUpdate, natuum.getRootPath(), busGroups)

    def __closeSplashZoneViewerMarker(self):
        self._App__splashZoneViewerMarker.close()
        self._App__splashZoneViewerMarker = None

    def showInitialSplash(self, show):
        self._App__splashNoticeLayer = None
        self._App__splashLayer = None
        if self._App__splashZoneViewerMarker:
            self.Renderer.Timer.defer(0, self._App__closeSplashZoneViewerMarker).forget()
        if show:
            rm = natuum.client.resource.Manager.Instance
            canvas = rm.load('ui_title_booting@Canvas').lockForReading().Data
            self._App__splashLayer = self.Renderer.createOverlayLayer(thing.Dirp.Dirp_Static(), thing.Dirp.Dirp_Static(self.AspectRatio), canvas)
            self.Renderer.updateLayers()

    @staticmethod
    def __onSplashZoneLoaded(fut_) -> None:
        app = natuum.client.app.Instance
        canvas = app._App__splashZoneViewerMarker.getViewCanvas()
        app._App__splashLayer = app.Renderer.createOverlayLayer(thing.Dirp.Dirp_Static(), thing.Dirp.Dirp_Static(app.AspectRatio), canvas)
        text = natuum.client.text.Text(u'\ub85c\ub529 \uc911\uc785\ub2c8\ub2e4. \uae30\ub2e4\ub9ac\ub294 \ub3d9\uc548 \uc9c0\ud615 \ud3b8\uc9d1\uc744 \uc5f0\uc2b5\ud574 \ubcf4\uc138\uc694.', 22, 'NanumSquare', thing.system.vec(xyzw=1), thing.system.vec(a=1), 'bold', 0.0)
        app._App__splashNoticeLayer = app.Renderer.createOverlayLayer(thing.Dirp.Dirp_Static(thing.system.vec(0.5, 0.8)), thing.Dirp.Dirp_Static(thing.system.vec(text.getWidth(), text.getHeight()) / text.getHeight() * 0.04), text.getCanvas())
        app.Renderer.updateLayers()

    def showWindowsMessageBox(self, text, title='Peria Chronicles', uType=0, quit=False):
        import ctypes
        MessageBox = ctypes.windll.user32.MessageBoxW
        ret = None
        if natuum.client.app.Instance.Renderer.NativeWindowHandle:
            ret = MessageBox(natuum.client.app.Instance.Renderer.NativeWindowHandle, text, 'Peria Chronicles', uType)
        if quit:
            self.GameLogic.turnOffGame()
        return ret

    @staticmethod
    def __warnLongTimePlay(count):
        app = Instance
        if app is None:
            return
        gameLogic = app.GameLogic
        if gameLogic is None:
            return
        getLocalString = natuum.action.ActionTableManager.getInstance().getLocalString
        author = getLocalString('NoticeAuthor')
        longTimePlayWarning = getLocalString('LongTimePlayWarning').format(N=count)
        ageRatingInformation = getLocalString('AgeRatingInformation')
        gameLogic.createMarker('message', ((u'(\uc0c9\uc0c1:\ube68\uac15)(\ud06c\uae30:\ud06c\uac8c){}(/\ud06c\uae30)(/\uc0c9\uc0c1)').format(longTimePlayWarning)), forced=True)
        gameLogic.SyncClock.addWork(5000, (gameLogic.createMarker), 'message', ((u'(\uc0c9\uc0c1:\ube68\uac15)(\ud06c\uae30:\ud06c\uac8c){}(/\ud06c\uae30)(/\uc0c9\uc0c1)').format(ageRatingInformation)), forced=True).forget()
        gameLogic.vueUiController.ChattingView.addChatText('notice', author, longTimePlayWarning)
        gameLogic.vueUiController.ChattingView.addChatText('notice', author, ageRatingInformation)
        gameLogic.SyncClock.defer(App._App__LONG_TIME_PLAY_WARNING_INTERVAL, App._App__warnLongTimePlay, count + 1).forget()

    @staticmethod
    def __getBusGroups(parameters) -> '{ str : var }':
        if parameters is None:
            return {}
        try:
            return parameters['sound']['bus']['groups']
        except KeyError:
            return {}

    @staticmethod
    def __onHangDetected(detected: 'thing.system.IHangDetector') -> None:
        if not __final__:
            import thing.debugger
            if thing.debugger.checkAttached():
                LOG_WARNING('hang detected')
                thing.debugger.breakIfAttached()
                return
        try:
            raise RuntimeError('hang detected')
        except Exception as e:
            try:
                sys.excepthook(type(e), e, e.__traceback__)
            finally:
                e = None
                del e

    _App__thMan = None
    _App__chMan = None
    _App__clientLoaderApartments = None
    _App__clientLoaderRegisterTokens = None
    _App__chManThread = None
    _App__pagerThread = None
    _App__rendererReadyAtom = None
    _App__exitCode = 0
    _App__serverPort = 9999
    _App__server = None
    _App__memoryReclaimer = None
    _App__memoryChecker = None
    if not __final__:
        _App__objectCountTrendsCollector = None
    if __thing_can_get_zcom_objects__:
        _App__createdZcomObjects = None
    _App__actionTableManager = None
    _App__masterBankResourceID = natuum.builder.combineID('MasterBank', 'Sound')
    _App__masterStringBankResourceID = natuum.builder.combineID('MasterBankstrings', 'Sound')
    _App__splashLayer = None
    _App__splashNoticeLayer = None
    _App__splashZoneViewerMarker = None
    _App__PRELOAD_EFFECTS = ('EF7000', 'EF7000_lumi', 'EF7001', 'EF7002', 'EF7003',
                             'EF7004', 'EF7006', 'EF7007', 'EF7010', 'EF7012', 'EF7013',
                             'EF7016', 'EF7019', 'EF7020', 'EF7022', 'EF7024', 'EF7033',
                             'EF7050', 'EF7051', 'EF7052', 'EF7053', 'EF7054', 'EF7058',
                             'EF7074', 'EF7075', 'EF7080', 'EF7081', 'EF7082', 'EF7083',
                             'EF7084', 'EF7093', 'EF7094', 'EF7095', 'EF7096', 'EF7098',
                             'EF7099', 'EF8000', 'EF8001', 'EF8002', 'EF8003', 'EF8003',
                             'EF8004', 'EF8008', 'EF8010', 'EF8011', 'EF8012', 'EF8013',
                             'EF8015', 'EF8016', 'EF8017', 'EF8020', 'EF8021', 'EF8022',
                             'EF8030', 'EF8031', 'EF8040', 'EF8041', 'EF8050', 'EF8051',
                             'EF8100', 'EF8100a', 'EF8100b', 'EF8100d', 'EF8107',
                             'EF8130', 'EF8131', 'EF8132', 'EF8133', 'EF8134', 'EF8135',
                             'EF8136', 'EF8137', 'EF8139', 'EF8142', 'EF8142', 'EF8143',
                             'EF8144', 'EF8145', 'EF8146', 'EF8147', 'EF8148', 'EF8149',
                             'EF8149', 'EF8149', 'EF8150', 'EF8151', 'EF8152', 'EF8153',
                             'EF8154', 'EF8156', 'EF8159', 'EF8160', 'EF8162', 'EF8165',
                             'EF8167', 'EF8168', 'EF8170', 'EF8171', 'EF8172', 'EF8173',
                             'EF8174', 'EF8175', 'EF8176', 'EF8179', 'EF8182', 'EF8184',
                             'EF8186', 'EF8187', 'EF8188', 'EF8189', 'EF8191', 'EF8192',
                             'EF8194', 'EF8196', 'EF8197', 'EF8198', 'EF8199', 'EF8200',
                             'EF8201', 'EF8205', 'EF8207', 'EF8210', 'EF8211', 'EF8212',
                             'EF8213', 'EF8216', 'EF8217', 'EF8218', 'EF8219', 'EF8220',
                             'EF8158a', 'EF8158b', 'EF8158c', 'EF8224', 'EF8233',
                             'EF8234', 'EF8236', 'EF8237', 'SEF(warning_01)', 'SEF1018(world_map)',
                             'SEF1020(marker_open)', 'SEF1023(guardian_qattack_ready)',
                             'SEF1025(guardian_qattack_fire)', 'SEF1026(guardian_qattack_targeting)',
                             'SEF1028(actionmaker_click)', 'SEF1029(actionmaker_hover)',
                             'SEF1030(actionmaker_open)', 'SEF1031(actionmaker_release)',
                             'SEF1032(conversation_click)', 'SEF1033(conversation_hover)',
                             'SEF1034(f_key_to_continue_conversation)', 'SEF1035(f_key_to_start_conversation)',
                             'SEF1036(general_button_click)', 'SEF1037(general_button_release)',
                             'SEF1038(changing_tab)', 'SEF1043(list_open)', 'SEF1044(list_close)',
                             'SEF1045(Xbutton)', 'SEF1046(popup_window)', 'SEF1047(conversation_release)',
                             'SEF1048(f_key_hover)', 'SEF1049(general_button_hover)',
                             'SEF1050(tab_hover)', 'SEF1051(small_button_click)',
                             'SEF1052(small_button_hover)', 'SEF1053(small_button_release)',
                             'SEF1054(kirana_listup_hover)', 'SEF1055(kirana_listup_click)',
                             'SEF1057(kirana_listup_release)', 'SEF1058(kirana_listup_release_cancel)',
                             'SEF1063(f_key_click)', 'SEF1065(battle_book_use)',
                             'SEF1066(inventory_hover)', 'SEF1067(inventory_click)',
                             'SEF1068(inventory_block)', 'SEF1069(inventory_release)',
                             'SEF1070(tab_release)', 'SEF1071(battle_book_hover)',
                             'SEF1075(battle_book_appearance)', 'SEF1076(battle_book_disappearance)',
                             'UI_deliveryRangeSphere', 'balloon_marker', 'button_marker',
                             'caption_marker', 'close_marker', 'coordinates_marker',
                             'gauge_marker', 'label', 'list_marker', 'list_marker3',
                             'text_balloon_marker', 'text_edit_marker', 'tooltip_marker',
                             'window', 'window2')
    _App__PRELOAD_PROPPARTS = ('PP6954', 'PP6366(blue)', 'PP6366(green)', 'PP6366(purple)',
                               'PP6366(red)', 'PP6366(white)', 'PP6366(yellow)',
                               'PP6851', 'PP8200', 'PP8201', 'PP8202', 'PP8203',
                               'PP8204', 'PP8205', 'PP8206', 'PP8207', 'PP8208',
                               'PP8209', 'PP8210', 'PP8211', 'PP8212', 'PP8213',
                               'PP8214')
    _App__DUMMY_AVATAR_MESHSETS = ('AP7250', )


class AccountSession(natuum._entity.IEntitySubscriber, IGatewaySubscriber):

    def __init__(self):
        self._AccountSession__connected = False
        self._AccountSession__gatewaySubscription = None
        self._AccountSession__NGSClient = None
        self._AccountSession__JYPClient = None
        self._AccountSession__accountID = None
        self._AccountSession__kickoutMessage = None
        self._AccountSession__weakOnConntionQueueChangedCallback = None
        self._AccountSession__weakOnAccountAuthenticatedAfterWaitingQueueCallback = None
        self.OnDisconnected = natuum.signal.Signal(bool, str)
        self.OnBeginTransferMessage = natuum.signal.Signal(bool)
        self.OnPrepareTransferMessage = natuum.signal.Signal(str)
        self.OnFinishTransferMessage = natuum.signal.Signal(thing.system.uuid, str, bool)

    async def connect(self, callback=None, maxTryCount=-1):
        if self._AccountSession__connected:
            return
        await natuum.client.app.Instance.ClusterGuestNode.start()
        apartment = asyncio.get_event_loop().Apartment
        tryCount = 0
        entity = None
        while maxTryCount < 0 or maxTryCount > tryCount:
            try:
                entity = await apartment.EntityManager.findEntity(natuum.client.app.Instance.GatewayEntityID)
                break
            except natuum._entity.error.RemoteEntityServerNotFound:
                LOG_WARNING('RemoteEntityServer not found')
                return
            except natuum._entity.error.EntityNotExist:
                LOG_WARNING('GatewayEntity not exist')
                return
            except natuum._entity.error.EntityNotRestored:
                LOG_WARNING('GatewayEntity not restored. try after 5 seconds. try count : {}'.format(tryCount))
                tryCount += 1
                await asyncio.sleep(5)
            except natuum._entity.error.SubscriptionChannelClosed:
                tryCount += 1
                await asyncio.sleep(5)
            except asyncio.TimeoutError:
                return
            except asyncio.CancelledError:
                return

        if entity is None:
            return
        try:
            subscription = await natuum._entity.subscribeEntity(entity, 'hello', None, self)
        except natuum._entity.error.SubscribeFailed:
            return
        else:
            self._AccountSession__gatewaySubscription = subscription
            self._AccountSession__connected = True
            LOG_INFO('Connection to game server established')

    async def disconnect(self, forced=True):
        if not self._AccountSession__connected:
            return
        LOG_INFO('Connection to game server closed')
        self._AccountSession__connected = False
        self._AccountSession__gatewaySubscription = None
        self._AccountSession__NGSClient = None
        self._AccountSession__JYPClient = None
        await natuum.client.app.Instance.ClusterGuestNode.shutdown()
        self.OnDisconnected.emit(forced, self.KickOutMessage)
        self.KickOutMessage = None

    def setAuthenticationWaitingCallback(self, onConntionQueueChanged, onAccountAuthenticatedAfterWaitingQueue):
        self._AccountSession__weakOnConntionQueueChangedCallback = weakref.WeakMethod(onConntionQueueChanged) if onConntionQueueChanged is not None else None
        self._AccountSession__weakOnAccountAuthenticatedAfterWaitingQueueCallback = weakref.WeakMethod(onAccountAuthenticatedAfterWaitingQueue) if onAccountAuthenticatedAfterWaitingQueue is not None else None

    [USER=1333341624]Property[/USER]
    def Connected(self) -> 'bool':
        return self._AccountSession__connected

    [USER=1333341624]Property[/USER]
    def AccountID(self) -> 'str':
        return self._AccountSession__accountID

    [USER=162874]account[/USER]ID.setter
    def AccountID(self, accountID: 'str'):
        self._AccountSession__accountID = accountID

    [USER=1333341624]Property[/USER]
    def KickOutMessage(self) -> 'str':
        return self._AccountSession__kickoutMessage

    [USER=2000007488]KickOut[/USER]Message.setter
    def KickOutMessage(self, msg: 'str'):
        self._AccountSession__kickoutMessage = msg

    [USER=1333341624]Property[/USER]
    def GatewaySubscription(self):
        return self._AccountSession__gatewaySubscription

    def setupNGS(self):
        LOG_INFO('NGS:setupNGS')
        if self._AccountSession__NGSClient is not None:
            return
        natuum.client.app.Instance.nxlog.stageLog(510, 'setupNGS')

        def _sendMessage(weakSubscription, data):
            subscription = weakSubscription()
            if subscription is not None:
                _data = data.tobytes()
                subscription.MessageProxy.onNGSMessageReceived(_data, nowait=True)

        def _callSendMessage(loop, weakSelf, data):
            loop.call_soon_threadsafe(_sendMessage, weakSelf, data)

        callableFactory = thing.Common.CallableFactory()
        args = thing.Collection.Array()
        args.append(asyncio.get_event_loop())
        args.append(weakref.ref(self._AccountSession__gatewaySubscription))
        args.append(callableFactory.PlaceHolder[1])
        sendNGSMessage = callableFactory.bind(_callSendMessage, '__call__', args.iterate())
        self._AccountSession__NGSClient = thing.NGSClient.NGSClient(sendNGSMessage)
        if not self._AccountSession__NGSClient.isInitialized():
            natuum.client.app.Instance.showWindowsMessageBox(u'NGS \ucd08\uae30\ud654\uc5d0 \uc2e4\ud328\ud558\uc5ec \ud504\ub85c\uadf8\ub7a8\uc744 \uc885\ub8cc\ud569\ub2c8\ub2e4.')
            natuum.client.app.Instance.GameLogic.turnOffGame()
        natuum.client.app.Instance.nxlog.stageLog(520, 'setupNGS')

    def setupJYP(self, nexonID):
        LOG_INFO('NGS:setupJYP')
        if self._AccountSession__JYPClient is not None:
            return
        self._AccountSession__JYPClient = thing.JYP.JYPClient(nexonID)
        self._AccountSession__JYPClient.collectThisThread()

    def onFrameRenderForJYP(self):
        if self._AccountSession__JYPClient is not None:
            self._AccountSession__JYPClient.onFrameRender()

    def onZoneEnterForJYP(self, zoneID):
        if self._AccountSession__JYPClient is not None:
            self._AccountSession__JYPClient.onSetZone(zoneID)

    [USER=215285]mess[/USER]ageRPC.impl(IGatewaySubscriber)
    def doAnything(self, *args, **kwds):
        pass

    [USER=215285]mess[/USER]ageRPC.impl(IGatewaySubscriber)
    def onBeginTransfer(self, targetZoneName: str):
        self.OnBeginTransferMessage.emit(targetZoneName)

    [USER=215285]mess[/USER]ageRPC.impl(IGatewaySubscriber)
    def onPrepareTransfer(self, zoneStyle: str):
        self.OnPrepareTransferMessage.emit(zoneStyle)

    [USER=215285]mess[/USER]ageRPC.impl(IGatewaySubscriber)
    async def onFinishTransfer(self, zoneEntityID: 'thing.system.uuid or None', reason: str, isTimeout: bool):
        self.OnFinishTransferMessage.emit(zoneEntityID, reason, isTimeout)

    [USER=215285]mess[/USER]ageRPC.impl(IGatewaySubscriber)
    def onNGSMessageReceivedFromServer(self, data):
        if self._AccountSession__NGSClient is not None:
            self._AccountSession__NGSClient.onReceive(data)

    [USER=215285]mess[/USER]ageRPC.impl(IGatewaySubscriber)
    async def notifyKickOut(self, type, errorCode):
        LOG_INFO((u'\uc11c\ubc84\uc5d0 \uc758\ud574 \uac8c\uc784\uc744 \uac15\uc81c \uc885\ub8cc:{},{}.').format(type, errorCode))
        text = (u'\uc11c\ubc84\uc5d0 \uc758\ud574 \ud504\ub85c\uadf8\ub7a8\uc774 \uc885\ub8cc\ub429\ub2c8\ub2e4.\n\n\uc0ac\uc720 : {}\n\uc5d0\ub7ec \ucf54\ub4dc :{}').format(type, errorCode)
        self._AccountSession__kickoutMessage = text

    [USER=215285]mess[/USER]ageRPC.impl(IGatewaySubscriber)
    async def notifyServerClock(self, now):
        Instance._setServerTime(now)

    [USER=186209]Thing[/USER].overrides(natuum._entity.IEntitySubscriber)
    def onMessage(self, serial: 'int', message: 'var'):
        pass

    [USER=215285]mess[/USER]ageRPC.impl(IGatewaySubscriber)
    async def checkAlive(self, channelID=None):
        pass

    [USER=186209]Thing[/USER].overrides(natuum._entity.IEntitySubscriber)
    def onChannelStatusChanged(self, status: 'int', detailedStatus: 'str'):
        asyncio.ensure_future_and_forget(self.disconnect())

    [USER=215285]mess[/USER]ageRPC.impl(IGatewaySubscriber)
    def onConnectionQueueChanged(self, left: 'int'):
        callback = self._AccountSession__weakOnConntionQueueChangedCallback()
        if callback is not None:
            callback(left)

    [USER=215285]mess[/USER]ageRPC.impl(IGatewaySubscriber)
    def onAccountAuthenticatedAfterWaitingQueue(self, userID: 'str', actors: 'collections.Sequence'):
        callback = self._AccountSession__weakOnAccountAuthenticatedAfterWaitingQueueCallback()
        if callback is not None:
            callback(userID, actors)

    _AccountSession__connected = None


class EngineConfig:

    def __init__(self, id: 'str', config: 'collisions.Mapping'):
        self._EngineConfig__id = id
        self._EngineConfig__config = config

    [USER=1333341624]Property[/USER]
    def ID(self):
        return self._EngineConfig__id

    [USER=1333341624]Property[/USER]
    def Config(self):
        return self._EngineConfig__config

    def apply(self):
        pass

    def onReload(self, newFactory):
        self._EngineConfig__config = newFactory._EngineConfig__config
        self.apply()

    _EngineConfig__id = None
    _EngineConfig__config = None
 
Junior Spellweaver
Joined
Nov 23, 2008
Messages
146
Reaction score
11
Re: Peria chronicles

Tried to post something but it's just the same as you

Code:
import collections, logging, os, sys, weakref, time, datetime, platform, enum, locale, thing.accessor
import thing.asyncio as asyncio
import thing.system
from natuum.activeobjects import ApartmentActiveObject
import natuum.apartment, natuum.app, natuum.builder, natuum.renderer, natuum.jobQueue, natuum.configuration, natuum.client.action, natuum.client.pager, natuum.client.resource, natuum.client.craft, natuum.client.logic, natuum.util,
 natuum.variation, natuum.indexBook, natuum.reward, natuum.growth, natuum.emotionCutscene, natuum.client.synergy, natuum.client.contract, natuum.client.relationship, natuum._entity.apartmentEvents, natuum._entity.framework.cluster

import natuum._entity.messageRPC as messageRPC
from natuum.nexon import NXLogClient
from natuum._entity.interfaces.clientSubscriber import IGatewaySubscriber
import natuum.gameService.wordFilter
if __thing_can_get_zcom_objects__:
    import multiprocessing
if not __final__:
    import natuum.objects
DEFAULT_CLIENT_RESOLUTION = (1920, 1080)
if not __final__:
    LOG_DEBUG = natuum.getLogger('client.app').debug
    LOG_WARNING = natuum.getLogger('client.app').warning
    import socket

def callbackBeforeDump():
    if natuum.client.app.Instance:
        natuum.client.app.Instance.nxlog.stageLog(1020, 'crash')
    natuum.flushLog()


def callbackAfterDump():
    pass


TEXT_FORMAT_TO_PRELOAD = ((0, '2002', 24.0, 3, 0, 5, 1.0), (0, 'KoPubDotum', 12.0, 3, 0, 5, 1.0),
                          (0, 'KoPubDotum', 13.0, 3, 0, 5, 1.0), (0, 'KoPubDotum', 13.0, 5, 0, 5, 1.0),
                          (0, 'KoPubDotum', 14.0, 3, 0, 5, 1.0), (0, 'KoPubDotum', 14.0, 5, 0, 5, 1.0),
                          (0, 'KoPubDotum', 15.0, 3, 0, 5, 1.0), (0, 'KoPubDotum', 15.0, 5, 0, 5, 1.0),
                          (0, 'KoPubDotum', 17.0, 3, 0, 5, 1.0), (0, 'KoPubDotum', 17.0, 5, 0, 5, 1.0),
                          (0, 'KoPubDotum', 18.0, 3, 0, 5, 1.0), (0, 'KoPubDotum', 18.0, 5, 0, 5, 1.0),
                          (0, 'KoPubDotum', 21.0, 5, 0, 5, 1.0), (0, 'KoPubDotum', 22.0, 5, 0, 5, 1.0),
                          (0, 'KoPubDotum', 23.0, 5, 0, 5, 1.0), (0, 'KoPubDotum', 24.0, 3, 0, 5, 1.0),
                          (0, 'KoPubDotum', 26.0, 3, 0, 5, 1.0), (0, 'KoPubDotum', 27.36, 3, 0, 5, 1.0),
                          (0, 'KoPubDotum', 27.6, 5, 0, 5, 1.0), (0, 'KoPubDotum', 33.0, 5, 0, 5, 1.0),
                          (0, 'KoPubDotum', 34.0, 3, 0, 5, 1.0), (0, 'KoPubDotum', 50.0, 5, 0, 5, 1.0),
                          (0, 'NanumSquare', 12.0, 5, 0, 5, 1.0), (0, 'NanumSquare', 14.0, 3, 0, 5, 1.0),
                          (0, 'NanumSquare', 14.0, 5, 0, 5, 1.0), (0, 'NanumSquare', 15.0, 5, 0, 5, 1.0),
                          (0, 'NanumSquare', 16.0, 5, 2, 5, 1.0), (0, 'NanumSquare', 17.0, 5, 0, 5, 1.0),
                          (0, 'NanumSquare', 40.0, 5, 0, 5, 1.0))
SIDEMENU_HTMLIVEWS = ('sideMenu_page_collection', 'sideMenu_page_map', 'sideMenu_page_present',
                      'sideMenuAvatar_editor')
Instance = None

class ListenerType(enum.IntEnum):
    ZONE = 0
    PERSON = 1


class App(natuum.app.App):
    _App__LONG_TIME_PLAY_WARNING_INTERVAL = 3600000

    def __init__(self):
        global Instance
        super().__init__()
        Instance = self
        self.RealTimeClock = thing.Value.RealTimeClock()
        self.RunOptions = self._getRunOptions('Natuum Client', '', (self._App__RunOptionHandler(),), sys.argv[1:])
        now = time.clock()
        self._App__clientTime = now
        self._App__serverTime = now
        self._App__fps = 60
        self.nxlog = None

     [USER=1333341624]Property[/USER]
    def FPS(self):
        return self._App__fps

    @FPS.setter
    def FPS(self, fps):
        self._App__fps = fps

    def __sendPacket(self, blob):
        pass

    Resolution = thing.accessor.ri(doc='클라이언트 해상도')
    Renderer = thing.accessor.ri(doc='렌더러 객체')
    RPCManager = thing.accessor.ri(doc='RPC 관리자 객체')
    SoundManager = thing.accessor.ri(None, doc='음향 관리자 객체')
    Apartment = thing.accessor.ri(doc='클라이언트 아파트먼트')
    EventLoop = thing.accessor.ri(None, doc='클라이언트 이벤트루프')
    ClusterGuestNode = thing.accessor.ri(doc='클라이언트 클러스터 노드')
    ClientZone = thing.accessor.rw(None)
    QuitOnRendererShutdown = thing.accessor.ri(True, doc='렌더러 셧다운시 프로그램 종료')
    Platform = thing.accessor.ri(doc='플랫폼 객체')
    GatewayEntityID = thing.accessor.rw(None, doc='게이트웨이엔티티 ID')
    ChatManagerEntityID = thing.accessor.rw(None, doc='채팅관리자엔티티 ID')
    ItemDesignLibraryEntityID = thing.accessor.rw(None, doc='아이템설계관리자엔티티 ID')
    DocumentContentLibraryEntityID = thing.accessor.rw(None, doc='문서내용관리자엔티티 ID')
    ServerHostAddress = thing.accessor.ri(None, doc='게임 서버 주소')
    LoaderThreadCount = thing.accessor.ri(1, doc='로더 스레드 갯수')
    RealTimeClock = thing.accessor.ri(None, doc='RealTimeClock')
    EngineConfig = thing.accessor.rw({}, doc='EngineConfig')
    ServerConfig = thing.accessor.rw({}, doc='ServerConfig')
    ControlModeParams = thing.accessor.ri({}, doc='ControlModeParams')
    PlayerInitConfig = thing.accessor.ri({}, doc='PlayerInitConfig')
    MaxFPS = thing.accessor.rw(None, doc='MaxFPS')
    AccountSession = thing.accessor.ri(None, doc='AccountSession')
    ClientSpace = thing.accessor.ri(None, doc='ClientSpace')
    GameLogic = thing.accessor.ri(None, doc='GameLogic')
    Context = thing.accessor.ri(None, doc='Context')
    DummyAvatarSceneObjectData = thing.accessor.ri(None, doc='DummyAvatarSceneObjectData')
    IsPreloading = thing.accessor.ri(True, doc='선로딩 여부')
    UserConfig = thing.accessor.ri(None, doc='UserConfig')
    InitialSplashLoadTask = thing.accessor.ri(None, doc='InitialSplashLoadTask')
    _App__SERVER_EPOCH = datetime.datetime(2018, 1, 1)

    def __setup(self) -> 'int, exit code':
        if not __final__:
            thing.system.setQuietMode(self.RunOptions.Quiet)
        DebugLogCategories = '*,-action,-builder,-cutscene,' if self.RunOptions.SSOAuthToken is not None else ''
        DebugLogCategories += os.environ.get('NT_DEBUG_LOG', '*') + ',' + self.RunOptions.DebugLogCategories
        for category in DebugLogCategories.split(','):
            if not category:
                continue
            if category.startswith('-'):
                logger = natuum.getLogger(category[1:])
                logger.setLevel(logging.INFO)
                LOG_INFO('Log category [{}] level=debug was disabled'.format(logger.name))

        from thing.changeset import changeset, NexonCrashReporterVersion
        LOG_INFO('Changeset for Code:{}'.format(changeset))
        LOG_INFO('BuildVersion:{}'.format(NexonCrashReporterVersion))
        LOG_INFO('LauncherVer:{}, NGM Param:{}, NexonSN:{}, NexonSID:{}'.format(self.RunOptions.LauncherBuildVersion, self.RunOptions.LauncherOptionalParam, self.RunOptions.NexonSN, self.RunOptions.NexonSID))
        self.Platform = platform = thing.Platform.Platform()
        self.ETWSession_findObjects = platform.createETWSession('findObjects')
        if self.RunOptions.SendCrashReport:
            installed = platform.installCrashReporter(self.RunOptions.ProjectID)
            if not installed:
                LOG_WARNING('exception handler is not installed')
            else:
                platform.setCrashReporterInformation('ProcessType', self.RunOptions.ProductName)
                from thing.changeset import NexonCrashReporterVersion
                platform.setCrashReporterInformation('ProjectVersion', NexonCrashReporterVersion)
                if natuum.m_logFilePath:
                    relPath = os.path.relpath(natuum.m_logFilePath, os.path.dirname(sys.executable))
                    platform.setCrashReporterAuxFile(relPath)
                if __thing_supports_gc__:
                    if natuum.objects.m_refCycleFilePath:
                        relPath = os.path.relpath(natuum.objects.m_refCycleFilePath, os.path.dirname(sys.executable))
                        platform.setCrashReporterAuxFile(relPath)
                platform.registerCrashReporterCallbackBeforeDump(callbackBeforeDump)
                thing.system._installExceptHook(fromInit=False)
        if not __final__:
            self._App__initConsoleCtrlHandler()
        if not __final__:
            platform.listenEasyProfileGUI(28077)
        self._App__thMan, self.RPCManager, self._App__chMan, self._App__chManThread = self._App__setupRPCManager()
        self._App__clientLoaderApartments = self._App__createLoaderApartments()
        rootFS = thing.FileSystem.FileSystem(natuum.getRootPath())
        dataFS = rootFS['data']
        try:
            resourcesFS = rootFS['resources']
        except Exception:
            resourcesFS = rootFS.createDirectory('resources')

        rm = natuum.client.resource.Manager(dataFS, resourcesFS)
        rm.IsPreloading = self.IsPreloading
        self._App__clientLoaderRegisterTokens = self._App__setupResourceManager(rm, self._App__clientLoaderApartments)
        self.JobQueue = natuum.jobQueue.JobQueue()
        self.EngineConfig = engineConfig = rm.load(natuum.builder.combineID('_', 'EngineConfig')).lockForReading().Data
        self.ServerConfig = rm.load(natuum.builder.combineID('_', 'ServerConfig')).lockForReading().Data[self.RunOptions.ClusterID]
        self.GatewayEntityID = self.ServerConfig['GatewayEntityID']
        self.ChatManagerEntityID = self.ServerConfig['ChatManagerEntityID']
        self.ItemDesignLibraryEntityID = self.ServerConfig['ItemDesignLibraryEntityID']
        self.DocumentContentLibraryEntityID = self.ServerConfig['DocumentContentLibraryEntityID']
        self.ServerHostAddress = self.RunOptions.Host
        self.ControlModeParams = rm.ensureResource('controlModeParams@GameSetting')
        self.PlayerInitConfig = rm.ensureResource('playerInitialization@GameSetting')
        fullScreen = self.RunOptions.FullScreen
        if fullScreen:
            import ctypes
            user32 = ctypes.windll.user32
            screenWidth = user32.GetSystemMetrics(0)
            screenHeight = user32.GetSystemMetrics(1)
            screenRatio = screenWidth / screenHeight
            if screenRatio > 1.7777777777777777:
                width, height = (2400, 1080)
            elif screenRatio > 1.6:
                if screenWidth == 2048 and screenHeight == 1152:
                    width, height = screenWidth, screenHeight
                else:
                    width, height = (1920, 1080)
            elif screenRatio > 1.3333333333333333:
                width, height = (1920, 1200)
            else:
                width, height = (1600, 1200)
        else:
            width, height = DEFAULT_CLIENT_RESOLUTION
        self.AspectRatio = thing.Dirp.Dirp_Static(thing.system.vec(width / height, 1))
        self.MaxFPS = max(int(engineConfig.Config.get('maxFPS', 60)), 1)
        rendererMode = thing.Renderer.RendererManager().createRendererMode(fullScreen, width, height, 32)
        self.Renderer = self._App__Renderer(rendererMode, engineConfig.Config)
        self.Renderer.TerrainTessellationQueueLength = 3
        self.Renderer.HangDetector = thing.ToolHelpers.HangDetector(self._App__onHangDetected)
        self.PropCanvasMipHeadCutCountOnLoad = 0
        userConfig = self._loadUserConfig()
        if userConfig is not None:
            self.UserConfig = userConfig
            self._refreshCanvasLoadQuality(userConfig)
            self.Renderer.UserConfig = userConfig
        else:
            self.UserConfig = self.Renderer.UserConfigValue['']
        currentLocale = locale.getdefaultlocale()[1]
        if currentLocale != 'cp949':
            if self.Renderer.NativeWindowHandle:
                text = 'Unsupported system locale : {}'.format(currentLocale)
                text += '\n\nTo execute the client, change system locale to cp949 (Korean)'
                self.showWindowsMessageBox(text)
                self.nxlog = None
                self.quit(0)
                return
        self._App__rendererModeWatchToken = self.Renderer.ModeVersion.addWatcher(self._App__onRendererModeChangedEntry, '__call__', weakref.ref(self))
        self.nxlog = NXLogClient(self.RunOptions.NexonSN, self.RunOptions.NexonSID)
        self.nxlog.stageLog(420, '__setup')
        self._App__rendererReadyAtom = rendererReadyAtom = thing.Thread.Atom()
        self._App__pagerThread = thread = self._App__thMan.createThread(thing.system.nativeThreadEntry, '__call__', self._App__pagerThreadEntry, rendererReadyAtom)
        thread.Name = 'Main Updater'
        thread.run()

    _App__settingsDirName = 'settings'
    _App__userConfigFileName = 'userConfig.nothing'

    def _loadUserConfig(self):
        settingsDirPath = os.path.join(natuum.getRootPath(), 'storage', __class__._App__settingsDirName)
        try:
            if os.path.exists(settingsDirPath):
                fs = thing.FileSystem.FileSystem(settingsDirPath)
            else:
                raise KeyError
            f = fs[__class__._App__userConfigFileName]
            ra = thing.Archive.ReadingArchive_FromFile(f)
            userConfig = thing.Collection.Dict()
            userConfig.load(ra)
            LOG_INFO('user config loaded')
        except KeyError:
            LOG_WARNING('cannot load user config')
            userConfig = None

        return userConfig

    def _saveUserConfig(self, userConfig):
        settingsDirPath = os.path.join(natuum.getRootPath(), 'storage', __class__._App__settingsDirName)
        try:
            os.makedirs(settingsDirPath)
        except OSError:
            pass

        try:
            fs = thing.FileSystem.FileSystem(settingsDirPath)
            f = fs.createFile(__class__._App__userConfigFileName, True)
            wa = thing.Archive.WritingArchive_FromFile(f)
            userConfig.save(wa)
            wa.flush()
            LOG_INFO('user config saved')
        except:
            LOG_WARNING('cannot save user config')

    def _refreshCanvasLoadQuality(self, userConfig):
        propTexQuality = userConfig.get('PropTextureQuality', -1)
        if propTexQuality >= 0:
            propTexQuality = min(2, propTexQuality)
            oldValue = self.PropCanvasMipHeadCutCountOnLoad
            newValue = (2, 1, 0)[propTexQuality]
            if oldValue != newValue:
                self.PropCanvasMipHeadCutCountOnLoad = newValue
                return True
        return False

    def __preload(self):
        markerResourceIDs = self._App__collectMarkerResources()
        effectPreloadList = tuple(markerResourceIDs.get('Effect', set()) | set(self._App__PRELOAD_EFFECTS))
        rm = natuum.client.resource.Manager.Instance
        rm.preload('Avatar', preloadList=('AT099800(man_default)', 'AT099900(woman_default)'))
        rm.preload('Effect', preloadList=effectPreloadList)
        rm.preload('CameraAngle')
        rm.preload('CameraAngleBlender')
        rm.preload('CameraAngleBlenderSet')
        rm.preload('HTMLView', preloadList=SIDEMENU_HTMLIVEWS)
        rm.preload('HTML', preloadList=('sideMenu', 'sideMenuAvatar'))
        rm.preload('ItemDesign')
        rm.preload('ItemIO')
        rm.preload('MeshSet', preloadList=(self._App__DUMMY_AVATAR_MESHSETS))
        rm.preload('PropPart', preloadList=(self._App__PRELOAD_PROPPARTS))
        rm.preload('Quest')
        rm.preload('VueApp', preloadList=('app', ))
        rm.preload('Zone', preloadList=('ZR0000(carveKiranaArea)', ))
        for typeName, resourceIDs in markerResourceIDs.items():
            if typeName in ('Effect', 'ItemDesign'):
                continue
            else:
                rm.preload(typeName, preloadList=(tuple(resourceIDs)))

        rm.preload('GameSetting')

    def __collectMarkerResources(self):

        def collectResourceIDs(value, resourceIDs_):
            if isinstance(value, str) and natuum.builder.isResourceID(value):
                name, typeName, _ = natuum.builder.splitID(value)
                resourceIDs_[typeName].add(name)
            elif not isinstance(value, str) or isinstance(value, collections.Sequence):
                for value_ in value:
                    collectResourceIDs(value_, resourceIDs_)

            elif isinstance(value, collections.Mapping):
                for value_ in value.values():
                    collectResourceIDs(value_, resourceIDs_)

        def collectMarkerClasses(marker, markerClasses_):
            markerClasses.add(marker)
            for marker_ in marker.__subclasses__():
                collectMarkerClasses(marker_, markerClasses_)

        markerResourceIDs = collections.defaultdict(set)
        markerClasses = set()
        collectMarkerClasses(natuum.client.clientSpace.marker.Marker, markerClasses)
        for arranger in natuum.client.clientSpace.arranger.__dict__.values():
            if isinstance(arranger, type):
                collectMarkerClasses(arranger, markerClasses)

        for marker in markerClasses:
            for attribute in marker.__dict__.values():
                collectResourceIDs(attribute, markerResourceIDs)

        collectMarkerClasses = None
        collectResourceIDs = None
        return markerResourceIDs

    def __run(self) -> 'int, exit code':
        if not __final__:
            if not self.RunOptions.NoUsageReport:
                self._App__sendUsageReport()
            windowTitle = 'Peria Chronicles{}'.format(natuum.util.getBuildVersion())
            rendererExitCode = self.Renderer.start(windowTitle, thing.system.ICanvas.PIXEL_FORMAT_R8G8B8A8, self._App__rendererReadyAtom, self.QuitOnRendererShutdown)
            return self._App__exitCode

    def __cleanup(self) -> None:
        global Instance
        if not __final__:
            for i in range(5):
                if self._App__pagerThread.wait(1):
                    break
                else:
                    (LOG_DEBUG if i <= 2 else LOG_WARNING)('Pager 스레드 종료 대기중 (%d초)', i + 1)

        else:
            self._App__pagerThread.wait(5)
        del self._App__pagerThread
        self.JobQueue.shutdown()
        self._App__clientLoaderRegisterTokens.clear()
        for apartment in self._App__clientLoaderApartments.values():
            apartment.shutdown()

        self._App__shutdownTableManagers()
        self._App__cleanupResourceManager()
        if self._App__chMan is not None:
            self._App__chMan.shutdown()
            VERIFY(self._App__chManThread.wait(5))
            del self._App__chManThread
        del self._App__mainApartment
        self.RPCManager.shutdown()
        Instance = None

     [USER=186209]Thing[/USER].overrides(natuum.app.App)
    def run(self) -> 'int, exit code':
        self._App__setup()
        ret = self._App__run()
        self._App__cleanup()
        return ret

     [USER=186209]Thing[/USER].overrides(natuum.app.App)
    def quit(self, exitCode):
        LOG_INFO('client quit called')
        self.Renderer.shutdown()
        curPage = natuum.client.pager.CurPage
        if curPage is not None:
            if not natuum.client.pager.command((curPage.canQuitApp), wait=True):
                return
            super().quit()
            self._App__exitCode = exitCode
            if self.RunOptions.SendCrashReport:
                self.Platform.setCrashReporterClientExit(exitCode)
            if natuum.client.app.Instance.nxlog is not None:
                natuum.client.app.Instance.nxlog.stageLog(1010, str(exitCode))
        self.Renderer.quit(True)

    def isInRendererThreadGroup(self, threadID):
        return threadID == self.Renderer.ThreadID

    def collectZcomObjects(self, showBackrefsOfIDs=False):
        if not __thing_can_get_zcom_objects__:
            return
        createdZcomObjects = self._App__createdZcomObjects
        if createdZcomObjects is None:
            self._App__createdZcomObjects = thing.ToolHelpers.CreatedObjects()
        else:
            self._App__createdZcomObjects = None
            objs = None
            try:
                backrefsDir = os.path.join(natuum.getLogPath(), 'backrefs')
                os.makedirs(backrefsDir, exist_ok=True)
                today = datetime.datetime.today()
                timestamp = '%4d%02d%02d_%02d%02d%02d' % (today.year, today.month, today.day, today.hour, today.minute, today.second)
                graphFilePath = os.path.join(backrefsDir, 'graph_c{}.txt'.format(timestamp))
                with open(graphFilePath, 'w', encoding='utf_8-sig') as file:
                    natuum.objects.Graph.collectToFile(file)
                objs = list(createdZcomObjects)
                objectIDsFilePath = os.path.join(backrefsDir, 'objects_c{}.txt'.format(timestamp))
                with open(objectIDsFilePath, 'w') as file:
                    natuum.objects.saveObjectIDsToFile(file, objs)
                if showBackrefsOfIDs:
                    multiprocessing.Process(target=(natuum.objects.showBackrefsOfIDs),
                      args=(
                     backrefsDir, graphFilePath, objectIDsFilePath)).start()
                thing.system._debugBreak()
                objs.clear()
            finally:
                if objs is not None:
                    objs.clear()

    def checkMemory(self):
        if __final__ or (self._App__memoryReclaimer is None):
            return
        for i in range(10):
            self._App__memoryReclaimer.reclaimAll()

        natuum.objects.GarbageCollector().collect(-1, True)
        trendsCollector = self._App__objectCountTrendsCollector
        if trendsCollector is None:
            self._App__objectCountTrendsCollector = trendsCollector = natuum.objects.CountTrendsCollector()
        trendsCollector.collect()
        trends = [(v, trendsCollector.getCount(k), k) for k, v in trendsCollector.items()]
        trends.sort(reverse=True)
        tracker = self._App__memoryTracker
        lines = []
        lines.append('**** object count trend log start ****')
        for trend, count, typeName in trends:
            countList = tracker.get(typeName, None)
            if countList is None:
                countList = list()
                tracker[typeName] = countList
            else:
                countList.append(count)
                lines.append('{:G}\t{}\t{}'.format(trend, typeName, '\t'.join([str(i) for i in countList])))

        lines.append('**** object count trend log end ****')

    def getRoughServerTime(self) -> 'int. milliseconds':
        passed = int(time.time() - self._App__clientTime) * 1000
        return (self._App__serverTime + passed) % 2147483647

    def setListener(self, flag, position) -> None:
        soundManager = self.SoundManager
        if soundManager is not None:
            configuration = self.ControlModeParams.get('sound')
            if configuration is None:
                if ListenerType.ZONE == flag:
                    soundManager.Listener = position
            elif configuration['listener'] == flag:
                soundManager.Listener = thing.Dirp.Dirp_FromLocal(configuration['local'], thing.math.ROT_IDENTITY, position)

    def _setServerTime(self, serverNow):
        self._App__clientTime = time.time()
        self._App__serverTime = int(serverNow * 1000)

    class __Renderer(natuum.renderer.Renderer):

         [USER=186209]Thing[/USER].overrides(natuum.renderer.Renderer)
        def onRendererCloseButton(self, renderer: 'thing.system.IRenderer', private) -> None:
            Instance.quit(0)

         [USER=186209]Thing[/USER].overrides(natuum.renderer.Renderer)
        def onRendererConfigChange(self, renderer: 'thing.system.IRenderer', private) -> None:
            if isinstance(private, thing.system.IMutableMapping):
                userConfig = private
                natuum.client.app.Instance._saveUserConfig(userConfig)
                if natuum.client.app.Instance._refreshCanvasLoadQuality(userConfig):
                    natuum.client.pager.command(natuum.client.app.Instance.GameLogic.onCanvasLoadQualityChange)

        def _registerLayerCategories(self) -> None:
            self.registerLayerCategory('UI').forget()

    class __RunOptionHandler(natuum.runOptions.IHandler):

        def fillDefaults(self, options):
            options.Host = None
            options.Quiet = False
            options.InfuseActor = None
            options.CrashReportURL = 'http://socorro.thingsoft.com:8882/submit'
            options.ProductName = 'client'
            options.ProductVersion = '0.8.1'
            options.NoUsageReport = False
            options.NoEffect = False
            options.BatchJob = None
            options.Locale = None
            options.ClusterID = 'default'
            options.SSOAuthToken = None
            options.FullScreen = False
            options.DebugLogCategories = '*,-action,-builder,-cutscene' if __final__ else ''
            options.ProjectID = 'peria_dev'
            options.LauncherBuildVersion = -1
            options.LauncherOptionalParam = ''
            options.NexonSN = 0
            options.NexonSID = None
            options.disableNXLog = False
            crashReport = os.environ.get('NT_NO_CRASH_REPORT', '0')
            options.SendCrashReport = crashReport == '0'

        def buildParser(self, parser: 'optparse.OptionParser', options) -> None:
            parser.add_option('-a', '--address', dest='Host', help='server address')
            parser.add_option('-q', '--quiet', dest='Quiet', default=False, action='store_true', help='quiet mode')
            parser.add_option('-i', '--infuseActor', dest='InfuseActor', help='auto infuse actor')
            parser.add_option('-b', '--batch', dest='BatchJob', help='batch job file')
            parser.add_option('-l', '--locale', dest='Locale', help='locale')
            parser.add_option('-C', '--cluster', dest='ClusterID', help='cluster id')
            parser.add_option('--crashreport-url', dest='CrashReportURL', help='crash report url')
            parser.add_option('--product-name', dest='ProductName', help='product name')
            parser.add_option('--product-version', dest='ProductVersion', help='product version')
            parser.add_option('--nousagereport', dest='NoUsageReport', default=False, action='store_true', help="don't send usage report")
            parser.add_option('-t', '--auth', dest='SSOAuthToken', help='auth token')
            parser.add_option('-f', '--fullScreen', dest='FullScreen', default=False, action='store_true', help='full screen mode')
            parser.add_option('--debugLog', type='str', dest='DebugLogCategories', help='comma seperated log categories')
            parser.add_option('--projectid', dest='ProjectID', help='crash reporter project id')
            parser.add_option('--launcherBuildVersion', dest='LauncherBuildVersion', help='launcher build version')
            parser.add_option('--launcherParam', dest='LauncherOptionalParam', default='', help='argument from NexonGameManager')
            parser.add_option('--nexonsn', dest='NexonSN', default=0, help='argument from NexonGameManager')
            parser.add_option('--nexonsid', dest='NexonSID', default='', help='argument from NexonGameManager')
            parser.add_option('--disableNXLog', dest='disableNXLog', default=False, action='store_true', help='disable NXLOG')

        def onParsed(self, parser: 'optparse.OptionParser', options, args: tuple) -> None:
            if options.BatchJob is not None:
                batchJobPath = os.path.join(natuum.getRootPath(), 'settings', options.BatchJob)
                options.BatchJob = natuum.util.readXDF(batchJobPath)
            if options.Locale is not None:
                if options.Locale in ('ko', 'ja', 'zh', 'en'):
                    natuum.util.DefaultLocale = options.Locale

    def __createLoaderApartments(self) -> '{ str: thing.system.IApartment }':
        apartments = {}
        for i in range(self.LoaderThreadCount):
            name = 'Loader{}'.format(i)
            apartmentObject = ApartmentActiveObject(self._App__thMan, self.RPCManager)
            apartmentObject.start(name, -0.13333333333333333)
            apartments[name] = apartmentObject.Apartment

        return apartments

    if not __final__:

        def __initConsoleCtrlHandler(self):

            def _terminateProcess(ctrlType):
                thing.system._TerminateProcess()

            thing.system.setConsoleCtrlHandler(_terminateProcess)

    def __setupRPCManager(self):
        thMan = thing.Thread.ThreadManager()
        rpcManager = thing.RPC.RPCManager(thMan)
        self._App__mainApartment = natuum.apartment.Apartment()
        return (
         thMan, rpcManager, None, None)

    def __setupResourceManager(self, rm, loaderApartments) -> '{ str : thing.system.IForgettableToken }':
        tokens = {}
        for name, apartment in loaderApartments.items():
            token = rm.CacheManager.registerDelayedLoaderApartment(name, apartment.Inner)
            tokens[name] = token

        return tokens

    def __cleanupResourceManager(self) -> None:
        natuum.client.resource.Manager.Instance.shutdown()

    @staticmethod
    def __onRendererModeChangedEntry(watched, weakSelf, hint, funcName) -> None:
        self = weakSelf()
        if self is not None:
            clientSpace = self.ClientSpace
            if clientSpace is not None:
                natuum.client.pager.command(clientSpace.onRendererModeChanged)

    def __pagerThreadEntry(self, rendererReadyAtom: 'thing.system.IAtom') -> None:
        self.Context = thing.Common.Sites()
        self.EventLoop = loop = natuum._entity.apartmentEvents.EventLoop()
        asyncio.set_event_loop(loop)
        self.Apartment = loop.Apartment
        thing.system.setSwitchIntervalForCurrentThread(1)
        rendererReadyAtom.wait()
        inputManager = natuum.client.input.Manager()
        self._App__initFontManager()
        self.showInitialSplash(True)
        if self.InitialSplashLoadTask:
            loop.run_until_complete(self.InitialSplashLoadTask)
        _ = self.Platform.disableAccessibility()
        self.GUI = guiSystem = thing.Renderer.GUISystem()
        self.Renderer.GUISystem = guiSystem
        self.SoundManager = soundManager = self._App__createSoundManager(parameters=(self.ControlModeParams))
        try:
            soundManager.setVolume('music', self.UserConfig['BGMVolume'])
            soundFXVol = self.UserConfig['SoundEffectVolume']
            for bus in ('effect', 'voice', 'environment', 'kiranaContract'):
                soundManager.setVolume(bus, soundFXVol)

        except KeyError:
            pass

        natuum.configuration.Configuration.initialize()
        self.ClusterGuestNode = node = natuum._entity.framework.cluster.setup_guest(self.ServerConfig['GatewayServers'])
        if self.ServerHostAddress is not None:
            node.overrideConnectingtHost(self.ServerHostAddress)
        self.AccountSession = natuum.client.app.AccountSession()
        self.ZoneChanged = natuum.signal.Signal(natuum.client.zone.Zone)
        self._App__initClassTables()
        if self.IsPreloading:
            self._App__preload()
        self._App__initTableManagers()
        clientSpace = natuum.client.clientSpace.ClientSpace()
        clientSpace.onRendererModeChanged()
        clientSpace.addDesktop()
        clientSpace.addDesktop()
        clientSpace.addInventory()
        self.ClientSpace = clientSpace
        inputManager.setEventHandler(clientSpace)
        self.GameLogic = gameLogic = natuum.client.logic.GameLogic(clientSpace)
        gameLogic.SyncClock.defer(App._App__LONG_TIME_PLAY_WARNING_INTERVAL, App._App__warnLongTimePlay, 1).forget()
        self._App__setupIDE()
        self._App__buildDummyAvatar()
        self._App__processorToken = thing.Thread.Processor().addTask(0, self.MaxFPS)
        self._App__memoryReclaimer = memoryReclaimer = thing.Common.MemoryReclaimer()
        memoryReclaimer.clearPolicies()
        memoryReclaimer.appendPolicy(memoryReclaimer.MEMORY_RECLAIMER_CRITERION_AVAILABLE_PHYSICAL_MB_LESS_THAN, 100, 209715200, 5)
        memoryReclaimer.appendPolicy(memoryReclaimer.MEMORY_RECLAIMER_CRITERION_AVAILABLE_PHYSICAL_MB_LESS_THAN, 500, 104857600, 10)
        memoryReclaimer.appendPolicy(memoryReclaimer.MEMORY_RECLAIMER_CRITERION_AVAILABLE_PHYSICAL_MB_LESS_THAN, 1000, 1, 15)
        memoryReclaimer.appendPolicy(memoryReclaimer.MEMORY_RECLAIMER_CRITERION_PAGE_FILE_USAGE_MB_GREATER_THAN, 7168, 102400, 15)
        natuum.apartment.getCurrentApartment().Alarm.postTask(self._App__reclaimMemory, 0).forget()
        if not __final__:
            natuum.apartment.getCurrentApartment().Alarm.postTask(self._App__logMemoryReclaimer, 60000).forget()
        self._App__memoryTracker = {}
        loop.call_soon(natuum.client.pager.enter)
        loop.run_forever()
        loop.run_until_complete(node.shutdown())
        loop.close()
        gameLogic.shutdown()
        thing.Client.EffectManager().terminate()
        soundManager.shutdown()
        guiSystem.shutdown()
        self.Renderer.GUISystem = None

    def __initClassTables(self) -> None:
        rm = natuum.client.resource.Manager.Instance
        natuum.client.action.ActionTableManager.ensureInstance()
        rawClassTables = rm.load(natuum.gameClass.CLASS_TABLE).lockForReading().Data
        natuum.gameClass.initManager(rawClassTables)
        if self.IsPreloading:
            natuum.gameClass.getManagerInstance().preloadClasses()

    def __initTableManagers(self) -> None:
        rm = natuum.client.resource.Manager.Instance
        natuum.emotionCutscene.initManager(rm)
        natuum.client.craft.initManager(rm)
        natuum.variation.initManager(rm)
        natuum.indexBook.initManager(rm)
        natuum.client.contract.initManager(rm)
        natuum.reward.initManager(rm)
        natuum.growth.initManager(rm)
        natuum.actor.initManager(rm)
        natuum.client.quest.initManager(rm)
        natuum.town.initManager(rm)
        natuum.client.relationship.initManager(rm)
        natuum.client.synergy.initManager(rm)
        natuum.gameService.wordFilter.initManager(rm)

    def __initFontManager(self) -> None:
        fontManager = thing.Renderer.FontManager()
        rm = natuum.client.resource.Manager.Instance
        rm.preload('Font', preloadList=(natuum.util.FontNames))
        for fontName in natuum.util.FontNames:
            fontID = natuum.builder.combineID(fontName, 'Font')
            fontData = rm.ensureResource(fontID)
            fontManager.registerFontData(fontData[0], fontData[1])

        for info in TEXT_FORMAT_TO_PRELOAD:
            (fontManager.createRasterTextFormat)(self.Renderer, *info)

    def __setupIDE(self) -> None:
        pass

    def __buildDummyAvatar(self) -> None:
        rm = natuum.client.resource.Manager.Instance
        meshSet = rm.ensureResource('AP7250@MeshSet')
        sceneObjectData = thing.Scene.SceneObjectData_Mesh()
        sceneObjectData.ShaderFlags = thing.system.IShader.SHADER_SHADOW_MAP | thing.system.IShader.SHADER_REALISTIC
        for meshName, meshAndBone in meshSet.items():
            sceneObjectMeshElementData = thing.Scene.SceneObjectMeshElementData()
            sceneObjectMeshElementData.Mesh = meshAndBone['mesh']
            sceneObjectData.Elements[meshName] = sceneObjectMeshElementData

        self.Renderer.warm(sceneObjectData, 0)
        self.DummyAvatarSceneObjectData = sceneObjectData

    def __shutdownTableManagers(self) -> None:
        natuum.client.craft.shutdownManager()
        natuum.client.action.ActionTableManager.shutdown()
        natuum.gameClass.shutdownManager()

    def __reclaimMemory(self) -> None:
        if self.IsQuitting:
            return
        waitDuration = self._App__memoryReclaimer.reclaim()
        natuum.apartment.getCurrentApartment().Alarm.postTask(self._App__reclaimMemory, waitDuration).forget()

    if not __final__:

        def __logMemoryReclaimer(self) -> None:
            if self.IsQuitting:
                return
            memoryReclaimer = self._App__memoryReclaimer
            LOG_INFO('available physical memory: {} GB'.format(memoryReclaimer.AvailablePhysicalMemoryGB))
            LOG_INFO('page file usage: {} GB'.format(memoryReclaimer.PageFileUsageGB))
            natuum.apartment.getCurrentApartment().Alarm.postTask(self._App__logMemoryReclaimer, 60000).forget()

    def __sendUsageReport(self) -> None:

        def sendReport(info: 'sequence of ( str, str )') -> str:
            tokenDelim = '_TDELIM_'
            emptyToken = '_EMPTY'
            stream = tokenDelim.join([value if value != '' else emptyToken for value in info])
            try:
                sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
                sock.sendto(stream.encode('utf-8'), ('wild.thingsoft.com', 8025))
                LOG_INFO('Usage report sent..')
            except socket.error:
                pass

            sock.close()

        rendererManager = thing.Renderer.RendererManager()
        usageInfo = (
         '{}'.format(platform.node()),
         '{} {}'.format(platform.system(), platform.version()),
         '{}'.format(platform.processor()),
         '{}'.format(self.Platform.MemorySize),
         '{}'.format(self.Platform.HardDiskSize),
         '{}'.format(rendererManager.DeviceDescription.split('\x00')[0].strip()),
         '{}'.format(natuum.util.getBuildVersion()))
        sendReport(usageInfo)
        sendReport = None

     [USER=437263]clas[/USER]smethod
    def __createSoundManager(cls, parameters=None, liveUpdate=False) -> 'thing.system.ISoundManager':
        rm = natuum.client.resource.Manager.Instance
        masterBank = rm.load(cls._App__masterBankResourceID)
        masterBank = masterBank.lockForReading().Data
        try:
            masterBank = masterBank.SoundData
        except KeyError:
            LOG_WARNING('{}를 읽지 못했습니다. 이벤트 경로를 읽지 못해 소리가 나오지 ' + '않습니다.'.format(cls._App__masterBankResourceID))
            return
        else:
            masterStringBank = rm.load(cls._App__masterStringBankResourceID)
            try:
                masterStringBank = masterStringBank.lockForReading().Data.SoundData
            except KeyError:
                LOG_WARNING('{}를 읽지 못했습니다. 이벤트 경로를 읽지 못해 소리가 나오지 ' + '않습니다.'.format(cls._App__masterStringBankResourceID))
                return
            else:
                busGroups = cls._App__getBusGroups(parameters)
                return thing.Sound.SoundManager(masterBank, masterStringBank, liveUpdate, natuum.getRootPath(), busGroups)

    def __closeSplashZoneViewerMarker(self):
        self._App__splashZoneViewerMarker.close()
        self._App__splashZoneViewerMarker = None

    def showInitialSplash(self, show):
        self._App__splashNoticeLayer = None
        self._App__splashLayer = None
        if self._App__splashZoneViewerMarker:
            self.Renderer.Timer.defer(0, self._App__closeSplashZoneViewerMarker).forget()
        if show:
            rm = natuum.client.resource.Manager.Instance
            canvas = rm.load('ui_title_booting@Canvas').lockForReading().Data
            self._App__splashLayer = self.Renderer.createOverlayLayer(thing.Dirp.Dirp_Static(), thing.Dirp.Dirp_Static(self.AspectRatio), canvas)
            self.Renderer.updateLayers()

    @staticmethod
    def __onSplashZoneLoaded(fut_) -> None:
        app = natuum.client.app.Instance
        canvas = app._App__splashZoneViewerMarker.getViewCanvas()
        app._App__splashLayer = app.Renderer.createOverlayLayer(thing.Dirp.Dirp_Static(), thing.Dirp.Dirp_Static(app.AspectRatio), canvas)
        text = natuum.client.text.Text('로딩 중입니다. 기다리는 동안 지형 편집을 연습해 보세요.', 22, 'NanumSquare', thing.system.vec(xyzw=1), thing.system.vec(a=1), 'bold', 0.0)
        app._App__splashNoticeLayer = app.Renderer.createOverlayLayer(thing.Dirp.Dirp_Static(thing.system.vec(0.5, 0.8)), thing.Dirp.Dirp_Static(thing.system.vec(text.getWidth(), text.getHeight()) / text.getHeight() * 0.04), text.
getCanvas())
        app.Renderer.updateLayers()

    def showWindowsMessageBox(self, text, title='Peria Chronicles', uType=0, quit=False):
        import ctypes
        MessageBox = ctypes.windll.user32.MessageBoxW
        ret = None
        if natuum.client.app.Instance.Renderer.NativeWindowHandle:
            ret = MessageBox(natuum.client.app.Instance.Renderer.NativeWindowHandle, text, 'Peria Chronicles', uType)
        if quit:
            self.GameLogic.turnOffGame()
        return ret

    @staticmethod
    def __warnLongTimePlay(count):
        app = Instance
        if app is None:
            return
        gameLogic = app.GameLogic
        if gameLogic is None:
            return
        getLocalString = natuum.action.ActionTableManager.getInstance().getLocalString
        author = getLocalString('NoticeAuthor')
        longTimePlayWarning = getLocalString('LongTimePlayWarning').format(N=count)
        ageRatingInformation = getLocalString('AgeRatingInformation')
        gameLogic.createMarker('message', ('(색상:빨강)(크기:크게){}(/크기)(/색상)'.format(longTimePlayWarning)), forced=True)
        gameLogic.SyncClock.addWork(5000, (gameLogic.createMarker), 'message', ('(색상:빨강)(크기:크게){}(/크기)(/색상)'.format(ageRatingInformation)), forced=True).forget()
        gameLogic.vueUiController.ChattingView.addChatText('notice', author, longTimePlayWarning)
        gameLogic.vueUiController.ChattingView.addChatText('notice', author, ageRatingInformation)
        gameLogic.SyncClock.defer(App._App__LONG_TIME_PLAY_WARNING_INTERVAL, App._App__warnLongTimePlay, count + 1).forget()

    @staticmethod
    def __getBusGroups(parameters) -> '{ str : var }':
        if parameters is None:
            return {}
        try:
            return parameters['sound']['bus']['groups']
        except KeyError:
            return {}

    @staticmethod
    def __onHangDetected(detected: 'thing.system.IHangDetector') -> None:
        if not __final__:
            import thing.debugger
            if thing.debugger.checkAttached():
                LOG_WARNING('hang detected')
                thing.debugger.breakIfAttached()
                return
        try:
            raise RuntimeError('hang detected')
        except Exception as e:
            try:
                sys.excepthook(type(e), e, e.__traceback__)
            finally:
                e = None
                del e

    _App__thMan = None
    _App__chMan = None
    _App__clientLoaderApartments = None
    _App__clientLoaderRegisterTokens = None
    _App__chManThread = None
    _App__pagerThread = None
    _App__rendererReadyAtom = None
    _App__exitCode = 0
    _App__serverPort = 9999
    _App__server = None
    _App__memoryReclaimer = None
    _App__memoryChecker = None
    if not __final__:
        _App__objectCountTrendsCollector = None
    if __thing_can_get_zcom_objects__:
        _App__createdZcomObjects = None
    _App__actionTableManager = None
    _App__masterBankResourceID = natuum.builder.combineID('MasterBank', 'Sound')
    _App__masterStringBankResourceID = natuum.builder.combineID('MasterBankstrings', 'Sound')
    _App__splashLayer = None
    _App__splashNoticeLayer = None
    _App__splashZoneViewerMarker = None
    _App__PRELOAD_EFFECTS = ('EF7000', 'EF7000_lumi', 'EF7001', 'EF7002', 'EF7003',
                             'EF7004', 'EF7006', 'EF7007', 'EF7010', 'EF7012', 'EF7013',
                             'EF7016', 'EF7019', 'EF7020', 'EF7022', 'EF7024', 'EF7033',
                             'EF7050', 'EF7051', 'EF7052', 'EF7053', 'EF7054', 'EF7058',
                             'EF7074', 'EF7075', 'EF7080', 'EF7081', 'EF7082', 'EF7083',
                             'EF7084', 'EF7093', 'EF7094', 'EF7095', 'EF7096', 'EF7098',
                             'EF7099', 'EF8000', 'EF8001', 'EF8002', 'EF8003', 'EF8003',
                             'EF8004', 'EF8008', 'EF8010', 'EF8011', 'EF8012', 'EF8013',
                             'EF8015', 'EF8016', 'EF8017', 'EF8020', 'EF8021', 'EF8022',
                             'EF8030', 'EF8031', 'EF8040', 'EF8041', 'EF8050', 'EF8051',
                             'EF8100', 'EF8100a', 'EF8100b', 'EF8100d', 'EF8107',
                             'EF8130', 'EF8131', 'EF8132', 'EF8133', 'EF8134', 'EF8135',
                             'EF8136', 'EF8137', 'EF8139', 'EF8142', 'EF8142', 'EF8143',
                             'EF8144', 'EF8145', 'EF8146', 'EF8147', 'EF8148', 'EF8149',
                             'EF8149', 'EF8149', 'EF8150', 'EF8151', 'EF8152', 'EF8153',
                             'EF8154', 'EF8156', 'EF8159', 'EF8160', 'EF8162', 'EF8165',
                             'EF8167', 'EF8168', 'EF8170', 'EF8171', 'EF8172', 'EF8173',
                             'EF8174', 'EF8175', 'EF8176', 'EF8179', 'EF8182', 'EF8184',
                             'EF8186', 'EF8187', 'EF8188', 'EF8189', 'EF8191', 'EF8192',
                             'EF8194', 'EF8196', 'EF8197', 'EF8198', 'EF8199', 'EF8200',
                             'EF8201', 'EF8205', 'EF8207', 'EF8210', 'EF8211', 'EF8212',
                             'EF8213', 'EF8216', 'EF8217', 'EF8218', 'EF8219', 'EF8220',
                             'EF8158a', 'EF8158b', 'EF8158c', 'EF8224', 'EF8233',
                             'EF8234', 'EF8236', 'EF8237', 'SEF(warning_01)', 'SEF1018(world_map)',
                             'SEF1020(marker_open)', 'SEF1023(guardian_qattack_ready)',
                             'SEF1025(guardian_qattack_fire)', 'SEF1026(guardian_qattack_targeting)',
                             'SEF1028(actionmaker_click)', 'SEF1029(actionmaker_hover)',
                             'SEF1030(actionmaker_open)', 'SEF1031(actionmaker_release)',
                             'SEF1032(conversation_click)', 'SEF1033(conversation_hover)',
                             'SEF1034(f_key_to_continue_conversation)', 'SEF1035(f_key_to_start_conversation)',
                             'SEF1036(general_button_click)', 'SEF1037(general_button_release)',
                             'SEF1038(changing_tab)', 'SEF1043(list_open)', 'SEF1044(list_close)',
                             'SEF1045(Xbutton)', 'SEF1046(popup_window)', 'SEF1047(conversation_release)',
                             'SEF1048(f_key_hover)', 'SEF1049(general_button_hover)',
                             'SEF1050(tab_hover)', 'SEF1051(small_button_click)',
                             'SEF1052(small_button_hover)', 'SEF1053(small_button_release)',
                             'SEF1054(kirana_listup_hover)', 'SEF1055(kirana_listup_click)',
                             'SEF1057(kirana_listup_release)', 'SEF1058(kirana_listup_release_cancel)',
                             'SEF1063(f_key_click)', 'SEF1065(battle_book_use)',
                             'SEF1066(inventory_hover)', 'SEF1067(inventory_click)',
                             'SEF1068(inventory_block)', 'SEF1069(inventory_release)',
                             'SEF1070(tab_release)', 'SEF1071(battle_book_hover)',
                             'SEF1075(battle_book_appearance)', 'SEF1076(battle_book_disappearance)',
                             'UI_deliveryRangeSphere', 'balloon_marker', 'button_marker',
                             'caption_marker', 'close_marker', 'coordinates_marker',
                             'gauge_marker', 'label', 'list_marker', 'list_marker3',
                             'text_balloon_marker', 'text_edit_marker', 'tooltip_marker',
                             'window', 'window2')
    _App__PRELOAD_PROPPARTS = ('PP6954', 'PP6366(blue)', 'PP6366(green)', 'PP6366(purple)',
                               'PP6366(red)', 'PP6366(white)', 'PP6366(yellow)',
                               'PP6851', 'PP8200', 'PP8201', 'PP8202', 'PP8203',
                               'PP8204', 'PP8205', 'PP8206', 'PP8207', 'PP8208',
                               'PP8209', 'PP8210', 'PP8211', 'PP8212', 'PP8213',
                               'PP8214')
    _App__DUMMY_AVATAR_MESHSETS = ('AP7250', )


class AccountSession(natuum._entity.IEntitySubscriber, IGatewaySubscriber):

    def __init__(self):
        self._AccountSession__connected = False
        self._AccountSession__gatewaySubscription = None
        self._AccountSession__NGSClient = None
        self._AccountSession__JYPClient = None
        self._AccountSession__accountID = None
        self._AccountSession__kickoutMessage = None
        self._AccountSession__weakOnConntionQueueChangedCallback = None
        self._AccountSession__weakOnAccountAuthenticatedAfterWaitingQueueCallback = None
        self.OnDisconnected = natuum.signal.Signal(bool, str)
        self.OnBeginTransferMessage = natuum.signal.Signal(bool)
        self.OnPrepareTransferMessage = natuum.signal.Signal(str)
        self.OnFinishTransferMessage = natuum.signal.Signal(thing.system.uuid, str, bool)

    async def connect(self, callback=None, maxTryCount=-1):
        if self._AccountSession__connected:
            return
        await natuum.client.app.Instance.ClusterGuestNode.start()
        apartment = asyncio.get_event_loop().Apartment
        tryCount = 0
        entity = None
        while maxTryCount < 0 or maxTryCount > tryCount:
            try:
                entity = await apartment.EntityManager.findEntity(natuum.client.app.Instance.GatewayEntityID)
                break
            except natuum._entity.error.RemoteEntityServerNotFound:
                LOG_WARNING('RemoteEntityServer not found')
                return
            except natuum._entity.error.EntityNotExist:
                LOG_WARNING('GatewayEntity not exist')
                return
            except natuum._entity.error.EntityNotRestored:
                LOG_WARNING('GatewayEntity not restored. try after 5 seconds. try count : {}'.format(tryCount))
                tryCount += 1
                await asyncio.sleep(5)
            except natuum._entity.error.SubscriptionChannelClosed:
                tryCount += 1
                await asyncio.sleep(5)
            except asyncio.TimeoutError:
                return
            except asyncio.CancelledError:
                return

        if entity is None:
            return
        try:
            subscription = await natuum._entity.subscribeEntity(entity, 'hello', None, self)
        except natuum._entity.error.SubscribeFailed:
            return
        else:
            self._AccountSession__gatewaySubscription = subscription
            self._AccountSession__connected = True
            LOG_INFO('Connection to game server established')

    async def disconnect(self, forced=True):
        if not self._AccountSession__connected:
            return
        LOG_INFO('Connection to game server closed')
        self._AccountSession__connected = False
        self._AccountSession__gatewaySubscription = None
        self._AccountSession__NGSClient = None
        self._AccountSession__JYPClient = None
        await natuum.client.app.Instance.ClusterGuestNode.shutdown()
        self.OnDisconnected.emit(forced, self.KickOutMessage)
        self.KickOutMessage = None

    def setAuthenticationWaitingCallback(self, onConntionQueueChanged, onAccountAuthenticatedAfterWaitingQueue):
        self._AccountSession__weakOnConntionQueueChangedCallback = weakref.WeakMethod(onConntionQueueChanged) if onConntionQueueChanged is not None else None
        self._AccountSession__weakOnAccountAuthenticatedAfterWaitingQueueCallback = weakref.WeakMethod(onAccountAuthenticatedAfterWaitingQueue) if onAccountAuthenticatedAfterWaitingQueue is not None else None

     [USER=1333341624]Property[/USER]
    def Connected(self) -> 'bool':
        return self._AccountSession__connected

     [USER=1333341624]Property[/USER]
    def AccountID(self) -> 'str':
        return self._AccountSession__accountID

     [USER=162874]account[/USER]ID.setter
    def AccountID(self, accountID: 'str'):
        self._AccountSession__accountID = accountID

     [USER=1333341624]Property[/USER]
    def KickOutMessage(self) -> 'str':
        return self._AccountSession__kickoutMessage

     [USER=2000007488]KickOut[/USER]Message.setter
    def KickOutMessage(self, msg: 'str'):
        self._AccountSession__kickoutMessage = msg

     [USER=1333341624]Property[/USER]
    def GatewaySubscription(self):
        return self._AccountSession__gatewaySubscription

    def setupNGS(self):
        LOG_INFO('NGS:setupNGS')
        if self._AccountSession__NGSClient is not None:
            return
        natuum.client.app.Instance.nxlog.stageLog(510, 'setupNGS')

        def _sendMessage(weakSubscription, data):
            subscription = weakSubscription()
            if subscription is not None:
                _data = data.tobytes()
                subscription.MessageProxy.onNGSMessageReceived(_data, nowait=True)

        def _callSendMessage(loop, weakSelf, data):
            loop.call_soon_threadsafe(_sendMessage, weakSelf, data)

        callableFactory = thing.Common.CallableFactory()
        args = thing.Collection.Array()
        args.append(asyncio.get_event_loop())
        args.append(weakref.ref(self._AccountSession__gatewaySubscription))
        args.append(callableFactory.PlaceHolder[1])
        sendNGSMessage = callableFactory.bind(_callSendMessage, '__call__', args.iterate())
        self._AccountSession__NGSClient = thing.NGSClient.NGSClient(sendNGSMessage)
        if not self._AccountSession__NGSClient.isInitialized():
            natuum.client.app.Instance.showWindowsMessageBox('NGS 초기화에 실패하여 프로그램을 종료합니다.')
            natuum.client.app.Instance.GameLogic.turnOffGame()
        natuum.client.app.Instance.nxlog.stageLog(520, 'setupNGS')

    def setupJYP(self, nexonID):
        LOG_INFO('NGS:setupJYP')
        if self._AccountSession__JYPClient is not None:
            return
        self._AccountSession__JYPClient = thing.JYP.JYPClient(nexonID)
        self._AccountSession__JYPClient.collectThisThread()

    def onFrameRenderForJYP(self):
        if self._AccountSession__JYPClient is not None:
            self._AccountSession__JYPClient.onFrameRender()

    def onZoneEnterForJYP(self, zoneID):
        if self._AccountSession__JYPClient is not None:
            self._AccountSession__JYPClient.onSetZone(zoneID)

     [USER=215285]mess[/USER]ageRPC.impl(IGatewaySubscriber)
    def doAnything(self, *args, **kwds):
        pass

     [USER=215285]mess[/USER]ageRPC.impl(IGatewaySubscriber)
    def onBeginTransfer(self, targetZoneName: str):
        self.OnBeginTransferMessage.emit(targetZoneName)

     [USER=215285]mess[/USER]ageRPC.impl(IGatewaySubscriber)
    def onPrepareTransfer(self, zoneStyle: str):
        self.OnPrepareTransferMessage.emit(zoneStyle)

     [USER=215285]mess[/USER]ageRPC.impl(IGatewaySubscriber)
    async def onFinishTransfer(self, zoneEntityID: 'thing.system.uuid or None', reason: str, isTimeout: bool):
        self.OnFinishTransferMessage.emit(zoneEntityID, reason, isTimeout)

     [USER=215285]mess[/USER]ageRPC.impl(IGatewaySubscriber)
    def onNGSMessageReceivedFromServer(self, data):
        if self._AccountSession__NGSClient is not None:
            self._AccountSession__NGSClient.onReceive(data)

     [USER=215285]mess[/USER]ageRPC.impl(IGatewaySubscriber)
    async def notifyKickOut(self, type, errorCode):
        LOG_INFO('서버에 의해 게임을 강제 종료:{},{}.'.format(type, errorCode))
        text = '서버에 의해 프로그램이 종료됩니다.\n\n사유 : {}\n에러 코드 :{}'.format(type, errorCode)
        self._AccountSession__kickoutMessage = text

     [USER=215285]mess[/USER]ageRPC.impl(IGatewaySubscriber)
    async def notifyServerClock(self, now):
        Instance._setServerTime(now)

     [USER=186209]Thing[/USER].overrides(natuum._entity.IEntitySubscriber)
    def onMessage(self, serial: 'int', message: 'var'):
        pass

     [USER=215285]mess[/USER]ageRPC.impl(IGatewaySubscriber)
    async def checkAlive(self, channelID=None):
        pass

     [USER=186209]Thing[/USER].overrides(natuum._entity.IEntitySubscriber)
    def onChannelStatusChanged(self, status: 'int', detailedStatus: 'str'):
        asyncio.ensure_future_and_forget(self.disconnect())

     [USER=215285]mess[/USER]ageRPC.impl(IGatewaySubscriber)
    def onConnectionQueueChanged(self, left: 'int'):
        callback = self._AccountSession__weakOnConntionQueueChangedCallback()
        if callback is not None:
            callback(left)

     [USER=215285]mess[/USER]ageRPC.impl(IGatewaySubscriber)
    def onAccountAuthenticatedAfterWaitingQueue(self, userID: 'str', actors: 'collections.Sequence'):
        callback = self._AccountSession__weakOnAccountAuthenticatedAfterWaitingQueueCallback()
        if callback is not None:
            callback(userID, actors)

    _AccountSession__connected = None


class EngineConfig:

    def __init__(self, id: 'str', config: 'collisions.Mapping'):
        self._EngineConfig__id = id
        self._EngineConfig__config = config

     [USER=1333341624]Property[/USER]
    def ID(self):
        return self._EngineConfig__id

     [USER=1333341624]Property[/USER]
    def Config(self):
        return self._EngineConfig__config

    def apply(self):
        pass

    def onReload(self, newFactory):
        self._EngineConfig__config = newFactory._EngineConfig__config
        self.apply()

    _EngineConfig__id = None
    _EngineConfig__config = None
# okay decompiling app.pyc

This is decompiled with something else than uncompyle6 and says : okay decompiling

Edit n°654141621:

It is the same code as yours btw
 
Joined
Aug 14, 2009
Messages
2,304
Reaction score
1,189
Re: Peria chronicles

Is not the same as mine, I mean yes its app.pyc but it did compile successfully ;)

Code:
    def __collectMarkerResources(self):

        def collectResourceIDs(value, resourceIDs_):
            if isinstance(value, str) and natuum.builder.isResourceID(value):
                name, typeName, _ = natuum.builder.splitID(value)
                resourceIDs_[typeName].add(name)
            elif not isinstance(value, str) or isinstance(value, collections.Sequence):
                for value_ in value:
                    collectResourceIDs(value_, resourceIDs_)

            elif isinstance(value, collections.Mapping):
                for value_ in value.values():
                    collectResourceIDs(value_, resourceIDs_)

This was missing in mine

EDIT:

Thank you works <3 how did you do it?

Altho there is some kind of recursion error with the lines mine couldnt decompile lol:

RecursionError: maximum recursion depth exceeded

 
Back
Top