Periachronicles

Page 2 of 3 FirstFirst 123 LastLast
Results 16 to 30 of 35
  1. #16
    Proficient Member Flipend0 is offline
    MemberRank
    Dec 2016 Join Date
    174Posts

    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.

  2. #17
    Valued Member ihaki is offline
    MemberRank
    Nov 2008 Join Date
    FranceLocation
    144Posts

    Re: Peria chronicles

    Quote Originally Posted by GigaToni View Post
    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

  3. #18
    Moderator GigaToni is offline
    ModeratorRank
    Aug 2009 Join Date
    GER / FRLocation
    2,329Posts

    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
    
        @Property
        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
    
        @Thing.overrides(natuum.app.App)
        def run(self) -> 'int, exit code':
            self._App__setup()
            ret = self._App__run()
            self._App__cleanup()
            return ret
    
        @Thing.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):
    
            @Thing.overrides(natuum.renderer.Renderer)
            def onRendererCloseButton(self, renderer: 'thing.system.IRenderer', private) -> None:
                Instance.quit(0)
    
            @Thing.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
    
        @classmethod
        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
    
        @Property
        def Connected(self) -> 'bool':
            return self._AccountSession__connected
    
        @Property
        def AccountID(self) -> 'str':
            return self._AccountSession__accountID
    
        @accountID.setter
        def AccountID(self, accountID: 'str'):
            self._AccountSession__accountID = accountID
    
        @Property
        def KickOutMessage(self) -> 'str':
            return self._AccountSession__kickoutMessage
    
        @KickOutMessage.setter
        def KickOutMessage(self, msg: 'str'):
            self._AccountSession__kickoutMessage = msg
    
        @Property
        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)
    
        @messageRPC.impl(IGatewaySubscriber)
        def doAnything(self, *args, **kwds):
            pass
    
        @messageRPC.impl(IGatewaySubscriber)
        def onBeginTransfer(self, targetZoneName: str):
            self.OnBeginTransferMessage.emit(targetZoneName)
    
        @messageRPC.impl(IGatewaySubscriber)
        def onPrepareTransfer(self, zoneStyle: str):
            self.OnPrepareTransferMessage.emit(zoneStyle)
    
        @messageRPC.impl(IGatewaySubscriber)
        async def onFinishTransfer(self, zoneEntityID: 'thing.system.uuid or None', reason: str, isTimeout: bool):
            self.OnFinishTransferMessage.emit(zoneEntityID, reason, isTimeout)
    
        @messageRPC.impl(IGatewaySubscriber)
        def onNGSMessageReceivedFromServer(self, data):
            if self._AccountSession__NGSClient is not None:
                self._AccountSession__NGSClient.onReceive(data)
    
        @messageRPC.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
    
        @messageRPC.impl(IGatewaySubscriber)
        async def notifyServerClock(self, now):
            Instance._setServerTime(now)
    
        @Thing.overrides(natuum._entity.IEntitySubscriber)
        def onMessage(self, serial: 'int', message: 'var'):
            pass
    
        @messageRPC.impl(IGatewaySubscriber)
        async def checkAlive(self, channelID=None):
            pass
    
        @Thing.overrides(natuum._entity.IEntitySubscriber)
        def onChannelStatusChanged(self, status: 'int', detailedStatus: 'str'):
            asyncio.ensure_future_and_forget(self.disconnect())
    
        @messageRPC.impl(IGatewaySubscriber)
        def onConnectionQueueChanged(self, left: 'int'):
            callback = self._AccountSession__weakOnConntionQueueChangedCallback()
            if callback is not None:
                callback(left)
    
        @messageRPC.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
    
        @Property
        def ID(self):
            return self._EngineConfig__id
    
        @Property
        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

  4. #19
    Valued Member ihaki is offline
    MemberRank
    Nov 2008 Join Date
    FranceLocation
    144Posts

    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
    
         @Property
        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
    
         @Thing.overrides(natuum.app.App)
        def run(self) -> 'int, exit code':
            self._App__setup()
            ret = self._App__run()
            self._App__cleanup()
            return ret
    
         @Thing.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):
    
             @Thing.overrides(natuum.renderer.Renderer)
            def onRendererCloseButton(self, renderer: 'thing.system.IRenderer', private) -> None:
                Instance.quit(0)
    
             @Thing.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
    
         @classmethod
        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
    
         @Property
        def Connected(self) -> 'bool':
            return self._AccountSession__connected
    
         @Property
        def AccountID(self) -> 'str':
            return self._AccountSession__accountID
    
         @accountID.setter
        def AccountID(self, accountID: 'str'):
            self._AccountSession__accountID = accountID
    
         @Property
        def KickOutMessage(self) -> 'str':
            return self._AccountSession__kickoutMessage
    
         @KickOutMessage.setter
        def KickOutMessage(self, msg: 'str'):
            self._AccountSession__kickoutMessage = msg
    
         @Property
        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)
    
         @messageRPC.impl(IGatewaySubscriber)
        def doAnything(self, *args, **kwds):
            pass
    
         @messageRPC.impl(IGatewaySubscriber)
        def onBeginTransfer(self, targetZoneName: str):
            self.OnBeginTransferMessage.emit(targetZoneName)
    
         @messageRPC.impl(IGatewaySubscriber)
        def onPrepareTransfer(self, zoneStyle: str):
            self.OnPrepareTransferMessage.emit(zoneStyle)
    
         @messageRPC.impl(IGatewaySubscriber)
        async def onFinishTransfer(self, zoneEntityID: 'thing.system.uuid or None', reason: str, isTimeout: bool):
            self.OnFinishTransferMessage.emit(zoneEntityID, reason, isTimeout)
    
         @messageRPC.impl(IGatewaySubscriber)
        def onNGSMessageReceivedFromServer(self, data):
            if self._AccountSession__NGSClient is not None:
                self._AccountSession__NGSClient.onReceive(data)
    
         @messageRPC.impl(IGatewaySubscriber)
        async def notifyKickOut(self, type, errorCode):
            LOG_INFO('서버에 의해 게임을 강제 종료:{},{}.'.format(type, errorCode))
            text = '서버에 의해 프로그램이 종료됩니다.\n\n사유 : {}\n에러 코드 :{}'.format(type, errorCode)
            self._AccountSession__kickoutMessage = text
    
         @messageRPC.impl(IGatewaySubscriber)
        async def notifyServerClock(self, now):
            Instance._setServerTime(now)
    
         @Thing.overrides(natuum._entity.IEntitySubscriber)
        def onMessage(self, serial: 'int', message: 'var'):
            pass
    
         @messageRPC.impl(IGatewaySubscriber)
        async def checkAlive(self, channelID=None):
            pass
    
         @Thing.overrides(natuum._entity.IEntitySubscriber)
        def onChannelStatusChanged(self, status: 'int', detailedStatus: 'str'):
            asyncio.ensure_future_and_forget(self.disconnect())
    
         @messageRPC.impl(IGatewaySubscriber)
        def onConnectionQueueChanged(self, left: 'int'):
            callback = self._AccountSession__weakOnConntionQueueChangedCallback()
            if callback is not None:
                callback(left)
    
         @messageRPC.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
    
         @Property
        def ID(self):
            return self._EngineConfig__id
    
         @Property
        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

  5. #20
    Moderator GigaToni is offline
    ModeratorRank
    Aug 2009 Join Date
    GER / FRLocation
    2,329Posts

    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


    https://pastebin.com/KSxpb1Vw

  6. #21
    Valued Member ihaki is offline
    MemberRank
    Nov 2008 Join Date
    FranceLocation
    144Posts

    Re: Peria chronicles

    I used decompile3, pycharm professionnal edition with a python 3.8 venv

    I am actually decompiling everything i can, gonna take a while, i'll post everything later

    Edit :
    Found something interesting in the engineUpdater.pyc, there is a link to the engine sources svn.
    I believe it is down at the moment we speak, but we might find some links working to the sources without having to decompile everything

  7. #22
    Moderator GigaToni is offline
    ModeratorRank
    Aug 2009 Join Date
    GER / FRLocation
    2,329Posts

    Re: Peria chronicles

    Unfortunately the SVN will never be online, the domain thingsoft.com is owned by a sedo parking service and not by the developer anymore

  8. #23
    Valued Member ihaki is offline
    MemberRank
    Nov 2008 Join Date
    FranceLocation
    144Posts

    Re: Peria chronicles

    If anyone wants it, there is the compiled app.py

    compiled

    Deleted this part of the code :

    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

  9. #24
    Moderator GigaToni is offline
    ModeratorRank
    Aug 2009 Join Date
    GER / FRLocation
    2,329Posts

    Re: Peria chronicles

    Okay deleting this check works however the client will not work on non-korean language systems XD

    18:18:19.278:natuum:WARNING:Build error [CB1_QSR1034@Quest] - ValueError: '완료 가능' is not a valid MarkerTypes
    18:18:19.278:natuum.builder:INFO: D:/Jenkins/workspace/NT_Live_CI/code/python/lib/enum.py:574 in _missing_() -
    18:18:19.278:natuum.builder:INFO: D:/Jenkins/workspace/NT_Live_CI/code/python/lib/enum.py:545 in __new__() -
    18:18:19.278:natuum.builder:INFO: D:/Jenkins/workspace/NT_Live_CI/code/python/lib/enum.py:561 in __new__() -
    18:18:19.382:natuum.client.resource:INFO:Preloading data [CB1_QSR1034@Quest] failed : '완료 가능' is not a valid MarkerTypes
    I am actually decompiling everything i can, gonna take a while, i'll post everything later
    You happened to have the quest.py?

  10. #25
    Valued Member ihaki is offline
    MemberRank
    Nov 2008 Join Date
    FranceLocation
    144Posts

    Re: Peria chronicles

    Hello,

    There is the quest.py

    Code:
    import abc, collections, enum, numbers, os, re, math, thing, natuum.util, natuum.cutscene, natuum.relationship, natuum.gameClass
    from natuum.gameClass.common import CarveResultEnum
    logQuest = os.environ.get('NT_QUEST_LOG', '0') != '0'
    if logQuest:
        if not __final__:
            LOG_DEBUG = natuum.getLogger('quest').debug
            LOG_INFO = natuum.getLogger('quest').info
    else:
    
        def LOG_DEBUG(*args):
            return True
    
    
    QUEST_STATE_KEY_STATE = 'state'
    QUEST_STATE_KEY_PENDING_NEXT_STATE = 'pendingNextState'
    QUEST_STATE_KEY_RESULT_STATE = 'resultState'
    QUEST_STATE_KEY_INSTANCE_ID = 'instanceID'
    QUEST_STATE_KEY_VARIABLES = 'variables'
    QUEST_STATE_KEY_TOKEN_ID = 'tokenID'
    QUEST_STATE_KEY_PATTERN_QUEST_KEY = 'patternQuestKey'
    QUEST_STATE_KEY_QUEST_GIVER_UID = 'questGiverUID'
    QUEST_STATE_KEY_FORWARD_REVERTIBLE = 'forwardRevertible'
    QUEST_STATE_KEY_BACKWARD_REVERTIBLE = 'backwardRevertible'
    QUEST_STATE_KEY_TRIGGERED_FORWARD_INDEX = 'triggeredForwardIndex'
    QUEST_STATE_KEY_PLAYED_COMPLETE_CUTSCENE = 'playedCompleteCutscene'
    QUEST_STATE_KEY_REWARD_APPLIED = 'rewardApplied'
    QUEST_STATE_CHANGE_TIMESTAMP = 'changedTime'
    QUEST_STATE_KEY_SELECTED = 'selected'
    QUEST_VAR_ELAPSED_TIME = 'elapsedTime'
    QUEST_VAR_FINISH_TIME = 'finishTime'
    QUEST_VAR_KEY_COUNT = 'count'
    QUEST_VAR_KEY_TARGET_CLASS_ID = 'targetClassID'
    QUEST_VAR_KEY_TARGET_COUNT = 'targetCount'
    SYSTEM_MESSAGE_ID_ITEM_TURNED_IN = 'Quest_msg_item_turned_in'
    _STRING_TRIGGER_TYPE_CONVERSATION = 'conversation'
    _STRING_TRIGGER_TYPE_PROXIMITY_ENTER = 'enter'
    _STRING_TRIGGER_TYPE_KILL_COUNT = 'killCount'
    _STRING_TRIGGER_TYPE_ITEM_COUNT = 'itemCount'
    _STRING_TRIGGER_TYPE_RANDOM_KILL_COUNT = 'randomKillCount'
    _STRING_TRIGGER_TYPE_RANDOM_ITEM_COUNT = 'randomItemCount'
    _STRING_TRIGGER_TYPE_TIMER = 'timer'
    _STRING_TRIGGER_TYPE_SELECTION = 'selection'
    _STRING_TRIGGER_TYPE_GUARDIAN_CHANGED = 'guardianChanged'
    _STRING_TRIGGER_TYPE_PROPERTY = 'property'
    _STRING_TRIGGER_TYPE_GUARDIAN_PROPERTY = 'guardianProperty'
    _STRING_TRIGGER_TYPE_CONTRACT = 'contract'
    _STRING_TRIGGER_TYPE_HAND = 'hand'
    _STRING_TRIGGER_TYPE_DECK = 'deck'
    _STRING_TRIGGER_TYPE_ACTION = 'action'
    _STRING_TRIGGER_TYPE_RELATIONSHIP = 'relationship'
    _STRING_TRIGGER_TYPE_DINE = 'dine'
    TutorialQuestsForStats = {v:i + 2000 for i, v in enumerate(('QDtutorial', 'QDtutorial2',
                                                                'QDtutorial3', 'QDtutorial3_1',
                                                                'QDtutorial4', 'QDtutorial4_1',
                                                                'QDtutorial4_2', 'QDtutorial5',
                                                                'QDtutorial5_1', 'QDtutorial6',
                                                                'QDtutorial7'))}
    MainQuestsForStats = {v:i + 3000 for i, v in enumerate(('CB1_QMR1000', 'CB1_QMR1001',
                                                            'CB1_QMR1002', 'CB1_QMR1004',
                                                            'CB1_QMR1003', 'CB1_QMR1005',
                                                            'CB1_QMR1006', 'CB1_QMR1007',
                                                            'CB1_QMR1008', 'CB1_QMR1009',
                                                            'CB1_QMR1042', 'CB1_QMR1010',
                                                            'CB1_QMR1011', 'CB1_QMR1066',
                                                            'CB1_QMR1067', 'CB1_QMR1068',
                                                            'CB1_QMR1012', 'CB1_QMR1013',
                                                            'CB1_QMR1014', 'CB1_QMR1015',
                                                            'CB1_QMR1017', 'CB1_QMR1016',
                                                            'CB1_QMR1018', 'CB1_QMR1019',
                                                            'CB1_QMR1020', 'CB1_QMR1021',
                                                            'CB1_QMR1022', 'CB1_QMR1023',
                                                            'CB1_QMR1043', 'CB1_QMR1024',
                                                            'CB1_QMR1025', 'CB1_QMR1060',
                                                            'CB1_QMR1061', 'CB1_QMR1062',
                                                            'CB1_QMR1026', 'CB1_QMR1027',
                                                            'CB1_QMR1028', 'CB1_QMR1029',
                                                            'CB1_QMR1031', 'CB1_QMR1030',
                                                            'CB1_QMR1032', 'CB1_QMR1033',
                                                            'CB1_QMR1034', 'CB1_QMR1035',
                                                            'CB1_QMR1036', 'CB1_QMR1037',
                                                            'CB1_QMR1044', 'CB1_QMR1038',
                                                            'CB1_QMR1039', 'CB1_QMR1063',
                                                            'CB1_QMR1064', 'CB1_QMR1065',
                                                            'CB1_QMR1040', 'CB1_QMR1041',
                                                            'CB1_QS1121', 'CB1_QS1122',
                                                            'CB1_QS1123', 'CB1_QS1204',
                                                            'CB1_QS1207', 'CB1_QS1208',
                                                            'CB1_QS1213', 'CB1_QS1137',
                                                            'CB1_QS1138', 'CB1_QS1139',
                                                            'CB1_QS1205', 'CB1_QS1209',
                                                            'CB1_QS1210', 'CB1_QS1215',
                                                            'CB1_QS1140', 'CB1_QS1141',
                                                            'CB1_QS1142', 'CB1_QS1206',
                                                            'CB1_QS1211', 'CB1_QS1212',
                                                            'CB1_QS1217'))}
    MainQuestsForStats.update(TutorialQuestsForStats)
    
    class ReportTargetType(enum.IntEnum):
        All = 0
        QuestGiver = 1
    
        @staticmethod
        def convert(value: 'str or None') -> 'ReportTargetType':
            if value is None or (value == natuum.quest.ReportTargetType.All.name):
                return natuum.quest.ReportTargetType.All
            return natuum.quest.ReportTargetType.QuestGiver
    
    
    class QuestType(enum.IntEnum):
        MainQuest = 0
        SubQuest = 1
        DailyQuest = 2
        GroupQuest = 3
    
        @staticmethod
        def convert(questType: 'str or None') -> 'QuestType':
            if questType is None or (questType == natuum.quest.QuestType.MainQuest.name):
                return natuum.quest.QuestType.MainQuest
            if questType == natuum.quest.QuestType.SubQuest.name:
                return natuum.quest.QuestType.SubQuest
            if questType == natuum.quest.QuestType.DailyQuest.name:
                return natuum.quest.QuestType.DailyQuest
            return natuum.quest.QuestType.GroupQuest
    
    
    class CountTypes(enum.Enum):
        LessThan = 'lessThan'
        GreaterThanOrEqualTo = 'greaterThanOrEqualTo'
    
        @staticmethod
        def convert(countType: 'None or str') -> 'CountTypes':
            if countType is None:
                return CountTypes.GreaterThanOrEqualTo
            return CountTypes(countType)
    
    
    class ComparisonTypes(enum.Enum):
        EQUAL = '=='
        NOT_EQUAL = '!='
        GREATER_THAN = '>'
        GREATER_THAN_OR_EQUAL_TO = '>='
        LESS_THAN = '<'
        LESS_THAN_OR_EQUAL_TO = '<='
        CONTAINS = 'contains'
        IN = 'in'
        RANGE = 'range'
    
              @classmethod
        def compare(cls, compType: 'ComparisonTypes', value: 'var', target: 'var'):
            if cls.CONTAINS == compType:
                return target in value
            if cls.IN == compType:
                return value in target
            if cls.RANGE == compType:
                return target[0] <= value and target[1] > value
            if cls.EQUAL == compType:
                return value == target
            if cls.NOT_EQUAL == compType:
                return value != target
            if cls.GREATER_THAN == compType:
                return value > target
            if cls.GREATER_THAN_OR_EQUAL_TO == compType:
                return value >= target
            if cls.LESS_THAN == compType:
                return value < target
            if cls.LESS_THAN_OR_EQUAL_TO == compType:
                return value <= target
            raise ValueError('invalid comparison type : {}'.format(compType))
    
    
    class QuantifierTypes(enum.Enum):
        FOR_ALL = 'forAll'
        THERE_EXSITS = 'thereExists'
    
    
    class ActionRoleTypes(enum.Enum):
        SUBJECT = 'subject'
        OBJECT = 'object'
        INDIRECT_OBJECT = 'indirectObject'
    
    
    class MarkerTypes(enum.Enum):
        EMPHASIS = '강조 환상'
        MOVE = '이동'
        STARTABLE = '시작 가능'
        UNSTARTABLE = '시작 불가능'
        ACTIVE = '진행 중'
        COMPLETE = '완료 가능'
        REQUEST_SELECTION = '선택 요청 환상'
    
        @staticmethod
        def isQuestStateMarker(markerType: 'MarkerTypes'):
            return MarkerTypes.STARTABLE == markerType or MarkerTypes.UNSTARTABLE == markerType or MarkerTypes.ACTIVE == markerType or MarkerTypes.COMPLETE == markerType
    
    
    class UIStates(enum.Enum):
        DEFAULT = 'Default'
        ACTIVE_COMPLETED = 'ActiveCompleted'
        ACTIVE_FAILED = 'ActiveFailed'
        INACTIVE_FINISHED = 'InactiveFinished'
        INACTIVE_NOT_STARTED = 'InactiveNotStarted'
    
              @classmethod
        def isActiveQuestState(cls, uiStates: 'UIStates') -> 'bool':
            return cls.DEFAULT == uiStates or cls.ACTIVE_COMPLETED == uiStates or cls.ACTIVE_FAILED == uiStates
    
              @classmethod
        def isDetailedActiveQuestState(cls, uiStates: 'UIStates') -> 'bool':
            return cls.ACTIVE_COMPLETED == uiStates or cls.ACTIVE_FAILED == uiStates
    
              @classmethod
        def iterateActiveStates(cls) -> 'generator of UIStates':
            for state in cls:
                if cls.isActiveQuestState(state):
                    yield state
    
              @classmethod
        def convertToUIStateMap(cls, stateMap: 'collections.Mapping', finished: 'collections.Sequence') -> 'collections.Mapping':
            uiStateMap = dict()
            for questID, subMap in stateMap.items():
                uiState = cls.getUIState(subMap)
                if uiState is None:
                    continue
                else:
                    uiStateMap[questID] = uiState
    
            for questID in finished:
                uiStateMap[questID] = cls.INACTIVE_FINISHED
    
            return uiStateMap
    
              @classmethod
        def toDescString(cls, value: 'UIStates') -> 'str':
            if cls.DEFAULT == value:
                return '기본'
            if cls.ACTIVE_COMPLETED == value:
                return '진행(조건 만족 상태)'
            if cls.ACTIVE_FAILED == value:
                return '진행(실패 상태)'
            if cls.INACTIVE_FINISHED == value:
                return '완료'
            if cls.INACTIVE_NOT_STARTED == value:
                return '시작 전'
            return ''
    
              @classmethod
        def getUIState(cls, subMap: 'collections.Mapping') -> 'None or UIStates':
            try:
                state = subMap[QUEST_STATE_KEY_STATE]
            except KeyError:
                return
            else:
                uiState = cls._translate(QuestStates(state))
                return uiState
    
              @classmethod
        def _translate(cls, state: 'QuestStates') -> 'None or UIStates':
            if QuestStates.COMPLETED == state or (QuestStates.FINISHED_CUTSCENE == state or QuestStates.FINISHED == state):
                return cls.ACTIVE_COMPLETED
            if QuestStates.ACCEPTED == state or (QuestStates.WATCH == state):
                return cls.DEFAULT
            if QuestStates.FAILED == state:
                return cls.ACTIVE_FAILED
    
    
    class MarkerStates(enum.Enum):
        ALL = 'All'
        ACCEPTED = 'Accepted'
        COMPLETED = 'Completed'
        FAILED = 'Failed'
    
              @classmethod
        def _translate(cls, state: 'MarkerStates') -> 'None or UIStates':
            if cls.ACCEPTED == state:
                return UIStates.DEFAULT
            if cls.COMPLETED == state:
                return UIStates.ACTIVE_COMPLETED
            if cls.FAILED == state:
                return UIStates.ACTIVE_FAILED
    
    
    class UIStateBoundInfo:
    
        def __init__(self, rawMap: 'collections.Mapping'):
            self._UIStateBoundInfo__infoMap = dict()
            for key, value in rawMap.items():
                self._UIStateBoundInfo__infoMap[UIStates(key)] = value
    
        def getValue(self, uiState: 'UIStates') -> 'None or var':
            if uiState in self._UIStateBoundInfo__infoMap:
                return self._UIStateBoundInfo__infoMap[uiState]
            return self._UIStateBoundInfo__infoMap.get(UIStates.DEFAULT)
    
        def ensureType(self, expectedType: 'var'):
            for value in self._UIStateBoundInfo__infoMap.values():
                if not isinstance(value, expectedType):
                    msg = 'invalid value : {}, expected type : {}'
                    raise ValueError(msg.format(value, expectedType))
    
        def ensureStates(self, checkActive: 'bool'=False, isNotDetailed: 'bool'=False):
            if checkActive:
                for state in self._UIStateBoundInfo__infoMap.keys():
                    if not UIStates.isActiveQuestState(state):
                        msg = 'invalid state : {}, expected active ui state'
                        raise ValueError(msg.format(state))
    
            if isNotDetailed:
                for state in self._UIStateBoundInfo__infoMap.keys():
                    if UIStates.isDetailedActiveQuestState(state):
                        msg = 'invalid state : {}, unexpected ui state'
                        raise ValueError(msg.format(state))
    
        _UIStateBoundInfo__infoMap = None
    
    
    class UIInfo:
        StateInfo = collections.namedtuple('StateInfo', ('Title', 'Description', 'SimpleDescription'))
        MarkerInfo = collections.namedtuple('MarkerInfo', ('Target', 'MarkerType', 'Tooltip',
                                                           'QuestType', 'Repeatable'))
        _UIInfo__FINISHED_TRIGGER_FORMAT = '<del>{}</del>'
    
        def __init__(self, questType: 'natuum.quest.QuestType', repeatable: 'bool', title: 'str', rawMarkerInfos: 'None or collections.Mapping', rawGuideTarget: 'None or collections.Mapping', rewardDesc: 'str', rawUIDesc: 'None or col
    lections.Mapping', rawUIDescPreq: 'None or collections.Mapping', rawUIDescTrigger: 'None or collections.Mapping'):
            self._UIInfo__title = title
            if rawMarkerInfos:
                stateMap = dict()
                markerMap = dict()
                for state, value in rawMarkerInfos.items():
                    markerState = MarkerStates(state)
                    stateMap[markerState] = self.StateInfo(title, '', value['questMarkerText'])
                    infoList = list()
                    for marker in value['markers']:
                        infoList.append(self.MarkerInfo(marker['markerTarget'], natuum.quest.MarkerTypes(marker['markerType']), marker.get('tooltip'), questType, repeatable))
    
                    markerMap[markerState] = infoList
    
                self._UIInfo__stringInfo = self._UIInfo__translateInfoMap(stateMap)
                self._UIInfo__markerInfo = self._UIInfo__translateInfoMap(markerMap)
            if rawGuideTarget:
                guideTarget = dict()
                for key, subMap in rawGuideTarget.items():
                    guideTarget[key] = AuxInfo.GuideTarget(subMap)
    
                self._UIInfo__guideTarget = UIStateBoundInfo(guideTarget)
                self._UIInfo__guideTarget.ensureType(AuxInfo.GuideTarget)
            self._UIInfo__rewardDesc = rewardDesc
            if rawUIDesc:
                self._UIInfo__uiDesc = UIStateBoundInfo(rawUIDesc)
                self._UIInfo__uiDesc.ensureType((str, type(None)))
                self._UIInfo__uiDesc.ensureStates(isNotDetailed=True)
            if rawUIDescPreq:
                self._UIInfo__uiDescPreq = UIStateBoundInfo(rawUIDescPreq)
                self._UIInfo__uiDescPreq.ensureType((str, type(None)))
                self._UIInfo__uiDescPreq.ensureStates(isNotDetailed=True)
            if rawUIDescTrigger:
                self._UIInfo__uiDescTrigger = UIStateBoundInfo(rawUIDescTrigger)
                self._UIInfo__uiDescTrigger.ensureType((str, type(None)))
    
              @Property
        def Title(self) -> 'str':
            return self._UIInfo__title
    
              @Property
        def RewardDesc(self) -> 'str':
            return self._UIInfo__rewardDesc
    
              @Property
        def HasGuideTarget(self) -> 'bool':
            return self._UIInfo__guideTarget is not None
    
        def getInfo(self, uiState: 'UIStates') -> 'None or .StateInfo':
            if self._UIInfo__stringInfo is not None:
                return self._UIInfo__stringInfo.getValue(uiState)
    
        def getMarkerInfo(self, uiState: 'UIStates') -> 'None or sequence of .MarkerInfo':
            if self._UIInfo__markerInfo is not None:
                return self._UIInfo__markerInfo.getValue(uiState)
    
        def getGuideTarget(self, uiState: 'UIStates') -> 'None or AuxInfo.GuideTarget':
            if self._UIInfo__guideTarget is not None:
                return self._UIInfo__guideTarget.getValue(uiState)
    
        def getDesc(self, uiState: 'UIStates') -> 'None or str':
            if self._UIInfo__uiDesc is not None:
                return self._UIInfo__uiDesc.getValue(uiState)
    
        def getPrerequisiteDesc(self, uiState: 'UIStates') -> 'None or str':
            if self._UIInfo__uiDescPreq is not None:
                return self._UIInfo__uiDescPreq.getValue(uiState)
    
        def getRawTriggerDesc(self, uiState: 'UIStates') -> 'None or str':
            if self._UIInfo__uiDescTrigger is not None:
                return self._UIInfo__uiDescTrigger.getValue(uiState)
    
        def getTriggerDesc(self, uiState: 'UIStates', descTriggerAux: 'None or str') -> 'None or str':
            raw = self.getRawTriggerDesc(uiState)
            if UIStates.DEFAULT == uiState:
                textList = list()
                if raw:
                    textList.append(raw)
                if descTriggerAux:
                    textList.append(descTriggerAux)
                text = '\n'.join(textList)
            elif UIStates.ACTIVE_COMPLETED == uiState:
                textList = list()
                defaultText = self.getRawTriggerDesc(UIStates.DEFAULT)
                if defaultText:
                    textList.append(self._UIInfo__FINISHED_TRIGGER_FORMAT.format(defaultText))
                if descTriggerAux:
                    textList.append(self._UIInfo__FINISHED_TRIGGER_FORMAT.format(descTriggerAux))
                if raw:
                    if defaultText != raw:
                        textList.append(raw)
                text = '\n'.join(textList)
            else:
                text = raw
            if text:
                return text
    
        @staticmethod
        def __translateInfoMap(infoMap: 'collections.Mapping') -> 'UIStateBoundInfo':
            translated = dict()
            for markerState, info in infoMap.items():
                key = MarkerStates._translate(markerState)
                if key is not None:
                    translated[key] = info
    
            defaultInfo = infoMap.get(MarkerStates.ALL)
            if defaultInfo is None:
                defaultInfo = infoMap.get(MarkerStates.ACCEPTED)
            if defaultInfo is not None:
                translated[UIStates.DEFAULT] = defaultInfo
            return UIStateBoundInfo(translated)
    
        _UIInfo__title = None
        _UIInfo__stringInfo = None
        _UIInfo__markerInfo = None
        _UIInfo__guideTarget = None
        _UIInfo__rewardDesc = None
        _UIInfo__uiDesc = None
        _UIInfo__uiDescPreq = None
        _UIInfo__uiDescTrigger = None
    
    
    class AuxInfo:
        _AuxInfo__AUX_KEY_GUIDED_QUEST_ID = 'guidedQuestID'
    
        class GuideTarget:
    
            class Types(enum.Enum):
                PERSON_NAME = 'PersonName'
                PERSON_UID = 'PersonUID'
                QUEST_ROLE = 'QuestRole'
                ITEM_NAME = 'ItemName'
                SIGNPOST = 'Signpost'
    
            Target = thing.accessor.ri(None, doc='str')
            TargetType = thing.accessor.ri(None, doc='.Types')
            TargetName = thing.accessor.ri(None, doc='str')
            ZoneName = thing.accessor.ri(None, doc='str')
    
            def __init__(self, subMap: 'collections.Mapping'):
                target = subMap['target']
                if not isinstance(target, str):
                    raise ValueError('invalid guide target : {}'.format(target))
                self.Target = target
                self.TargetType = self.Types(subMap['targetType'])
                self.TargetName = subMap.get('targetName')
                self.ZoneName = subMap.get('zoneName')
    
              @classmethod
        def getGuidedQuestID(cls, auxInfo: 'collections.Mapping') -> 'None or str':
            guidedQuestID = auxInfo.get(cls._AuxInfo__AUX_KEY_GUIDED_QUEST_ID)
            return guidedQuestID
    
              @classmethod
        def setGuidedQuestID(cls, auxInfo: 'collections.Mapping', questID: 'None or str') -> 'collections.Mapping':
            if questID is None:
                auxInfo.pop(cls._AuxInfo__AUX_KEY_GUIDED_QUEST_ID, None)
            else:
                auxInfo[cls._AuxInfo__AUX_KEY_GUIDED_QUEST_ID] = questID
            return auxInfo
    
    
    class QuestStates(enum.IntEnum):
        INIT = 0
        SELECT = 1
        ACCEPTED = 2
        REJECTED = 3
        WATCH = 4
        COMPLETED = 5
        FAILED = 6
        FINISHED_CUTSCENE = 7
        CANCELED_CUTSCENE = 8
        CANCELED = 9
        FINISHED = 10
    
        @staticmethod
        def isActive(state: 'QuestStates', isRejectable: 'bool') -> 'bool':
            if isRejectable:
                return state >= QuestStates.WATCH
            return True
    
              @classmethod
        def toString(cls, value: 'QuestStates') -> 'str':
            if cls.INIT == value:
                return '시작'
            if cls.SELECT == value:
                return '선택'
            if cls.ACCEPTED == value:
                return '수락'
            if cls.REJECTED == value:
                return '거절'
            if cls.WATCH == value:
                return '감시'
            if cls.COMPLETED == value:
                return '조건 만족'
            if cls.FAILED == value:
                return '실패'
            if cls.FINISHED_CUTSCENE == value:
                return '완료 컷신'
            if cls.CANCELED_CUTSCENE == value:
                return '취소 컷신'
            if cls.CANCELED == value:
                return '취소'
            if cls.FINISHED == value:
                return '완료'
            return '알 수 없는 상태'
    
              @classmethod
        def convertToStringMap(cls, stateMap: 'collections.Mapping', finished: 'collections.Sequence') -> 'collections.Mapping':
            textMap = dict()
            for questID, subMap in stateMap.items():
                state = subMap.get(QUEST_STATE_KEY_STATE)
                if state is not None:
                    textMap[questID] = cls.toString(state)
    
            for questID in finished:
                textMap[questID] = cls.toString(cls.FINISHED)
    
            return textMap
    
              @classmethod
        def isCompletable(cls, subMap: 'collections.Mapping') -> 'bool':
            return cls.COMPLETED == subMap.get(QUEST_STATE_KEY_STATE)
    
    
    class ResultStates(enum.IntEnum):
        SUCCEEDED = 0
        FAILED = 1
        FAILED_INVALID_ITEM_INFO = 2
        FAILED_ENTER_LIMIT = 3
        FAILED_CARVE_TARGET_NOT_FOUND = 4
        FAILED_CARVE_INVALID_KIRANA = 5
        FAILED_CARVE_INVALID_ITEM = 6
        FAILED_CARVE_ALREADY_CARVED = 7
        FAILED_CARVE_RESERVED_KIRANA = 8
    
              @classmethod
        def convertFromCarveResult(cls, value: 'int') -> 'ResultStates':
            if CarveResultEnum.SUCCEEDED == value:
                return cls.SUCCEEDED
            if CarveResultEnum.FAILED_ALREADY_CARVED == value:
                return cls.FAILED_CARVE_ALREADY_CARVED
            if CarveResultEnum.FAILED_INVALID_ITEM == value:
                return cls.FAILED_CARVE_INVALID_ITEM
            if CarveResultEnum.FAILED_INVALID_KIRANA == value:
                return cls.FAILED_CARVE_INVALID_KIRANA
            if CarveResultEnum.FAILED_RESERVED_KIRANA == value:
                return cls.FAILED_CARVE_RESERVED_KIRANA
            return cls.FAILED_ENTER_LIMIT
    
              @classmethod
        def convertToFailedMessageID(cls, value: 'int') -> 'None or str':
            if cls.FAILED == value:
                return 'Quest_msg_result_failed_default'
            if cls.FAILED_INVALID_ITEM_INFO == value:
                return 'Quest_msg_result_failed_invalid_item_info'
            if cls.FAILED_ENTER_LIMIT == value:
                return 'Quest_msg_result_failed_enter_limit'
            if cls.FAILED_CARVE_TARGET_NOT_FOUND == value:
                return 'Quest_msg_result_failed_carve_target_not_found'
            if cls.FAILED_CARVE_INVALID_KIRANA == value:
                return 'Quest_msg_result_failed_carve_invalid_kirana'
            if cls.FAILED_CARVE_INVALID_ITEM == value:
                return 'Quest_msg_result_failed_carve_invalid_item'
            if cls.FAILED_CARVE_ALREADY_CARVED == value:
                return 'Quest_msg_result_failed_carve_already_carved'
            if cls.FAILED_CARVE_RESERVED_KIRANA == value:
                return 'Quest_msg_result_failed_carve_reserved_kirana'
    
    
    class TriggerTypes(enum.IntEnum):
        CONVERSATION = 0
        PROXIMITY_ENTER = 1
        ITEM_COUNT = 2
        KILL_COUNT = 3
        RANDOM_ITEM_COUNT = 4
        RANDOM_KILL_COUNT = 5
        ACTION = 6
        SELECTION = 7
        TIMER = 8
        GUARDIAN_CHANGED = 9
        PROPERTY = 10
        GUARDIAN_PROPERTY = 11
        SELECT_STATE = 12
        CONTRACT = 13
        DECK = 14
        HAND = 15
        RELATIONSHIP = 16
        DINE = 17
    
        @staticmethod
        def isRevertible(triggerType: 'TriggerTypes') -> 'bool':
            return TriggerTypes.RANDOM_ITEM_COUNT == triggerType or TriggerTypes.PROPERTY == triggerType or TriggerTypes.GUARDIAN_PROPERTY == triggerType or TriggerTypes.ITEM_COUNT == triggerType or TriggerTypes.DECK == triggerType or
     TriggerTypes.HAND == triggerType
    
        @staticmethod
        def isInstant(triggerType: 'TriggerTypes') -> 'bool':
            return TriggerTypes.SELECTION == triggerType or TriggerTypes.SELECT_STATE == triggerType
    
        @staticmethod
        def _convert(value: 'str'):
            if value == _STRING_TRIGGER_TYPE_CONVERSATION:
                return TriggerTypes.CONVERSATION
            if value == _STRING_TRIGGER_TYPE_PROXIMITY_ENTER:
                return TriggerTypes.PROXIMITY_ENTER
            if value == _STRING_TRIGGER_TYPE_ITEM_COUNT:
                return TriggerTypes.ITEM_COUNT
            if value == _STRING_TRIGGER_TYPE_KILL_COUNT:
                return TriggerTypes.KILL_COUNT
            if value == _STRING_TRIGGER_TYPE_RANDOM_ITEM_COUNT:
                return TriggerTypes.RANDOM_ITEM_COUNT
            if value == _STRING_TRIGGER_TYPE_RANDOM_KILL_COUNT:
                return TriggerTypes.RANDOM_KILL_COUNT
            if value == _STRING_TRIGGER_TYPE_TIMER:
                return TriggerTypes.TIMER
            if value == _STRING_TRIGGER_TYPE_SELECTION:
                return TriggerTypes.SELECTION
            if value == _STRING_TRIGGER_TYPE_GUARDIAN_CHANGED:
                return TriggerTypes.GUARDIAN_CHANGED
            if value == _STRING_TRIGGER_TYPE_PROPERTY:
                return TriggerTypes.PROPERTY
            if value == _STRING_TRIGGER_TYPE_GUARDIAN_PROPERTY:
                return TriggerTypes.GUARDIAN_PROPERTY
            if value == _STRING_TRIGGER_TYPE_CONTRACT:
                return TriggerTypes.CONTRACT
            if value == _STRING_TRIGGER_TYPE_DECK:
                return TriggerTypes.DECK
            if value == _STRING_TRIGGER_TYPE_HAND:
                return TriggerTypes.HAND
            if value == _STRING_TRIGGER_TYPE_ACTION:
                return TriggerTypes.ACTION
            if value == _STRING_TRIGGER_TYPE_RELATIONSHIP:
                return TriggerTypes.RELATIONSHIP
            if value == _STRING_TRIGGER_TYPE_DINE:
                return TriggerTypes.DINE
            raise ValueError('invalid trigger type : {}'.format(value))
    
    
    class TriggerUsageTypes(enum.IntEnum):
        START = 0
        UPDATE = 1
    
    
    class DescDelims(enum.Enum):
        DungeonDesc = '<D>'
        EpicKiranaClassIDs = '<E>'
        TownDesc = '<T>'
    
    
    class DungeonDescDelims(enum.Enum):
        KiranaClassIDs = '<DA>'
        DungeonRecordKey = '<DB>'
    
    
    class ConversationTriggerInfo:
        QuestID = thing.accessor.ri(None, doc='str')
        UsageType = thing.accessor.ri(None, doc='TriggerUsageTypes')
        ListenerQuestRole = thing.accessor.ri(None, doc='str')
        SelectionText = thing.accessor.ri(None, doc='str')
        ConfirmText = thing.accessor.ri(None, doc='None or str')
    
        def __init__(self, listenerQuestRole: 'str', selectionText: 'str', confirmText: 'None or str', questID: 'str', usageType: 'TriggerUsageTypes'):
            self.QuestID = questID
            self.ListenerQuestRole = listenerQuestRole
            self.SelectionText = selectionText
            self.ConfirmText = confirmText
            self.UsageType = usageType
    
    
    class ContractInfo:
        TargetClassID = thing.accessor.ri(None, doc='thing.system.uuid')
    
        def __init__(self, targetClassID: 'str'):
            self.TargetClassID = natuum.util.convertToUUID(targetClassID)
    
    
    class DeckInfo:
        ClassIDCounts = thing.accessor.ri(None, doc='list')
    
        def __init__(self, classIDs: 'collections.Sequence'):
            countMap = dict()
            for classIDStr in classIDs:
                classID = natuum.util.convertToUUID(classIDStr)
                if classID not in countMap:
                    countMap[classID] = 0
                else:
                    countMap[classID] += 1
    
            self.ClassIDCounts = list()
            for classID, count in countMap.items():
                self.ClassIDCounts.append((classID, count))
    
    
    class HandInfo:
    
        class HandCondition:
            Quantifier = thing.accessor.ri(None, doc='None or QuantifierTypes')
            ClassIDs = thing.accessor.ri(None, doc='None or collections.Sequence')
            PropertyCondition = thing.accessor.ri(None, doc='None or PropertyCondition')
    
            def initialize(self, raw: 'None or collections.Mapping') -> 'bool':
                if raw is not None:
                    self.Quantifier = QuantifierTypes(raw['quantifier'])
                    classIDs = raw.get('classIDs')
                    if classIDs:
                        self.ClassIDs = list()
                        for classID in classIDs:
                            self.ClassIDs.append(natuum.util.convertToUUID(classID))
    
                    property = raw.get('property')
                    if property:
                        self.PropertyCondition = PropertyCondition(property)
                return self.Quantifier and (self.ClassIDs or self.PropertyCondition)
    
        AverageLevel = thing.accessor.ri(None, doc='float')
        Count = thing.accessor.ri(None, doc='int')
        Condition = thing.accessor.ri(None, doc='None or HandInfo.HandCondition')
    
        def __init__(self, raw: 'collections.Mapping'):
            self.AverageLevel = raw.get('minAverageLevel', 0.0)
            self.Count = int(raw['minCount'])
            condition = self.HandCondition()
            if condition.initialize(raw.get('condition')):
                self.Condition = condition
    
    
    class ActionInfo:
        ActionID = thing.accessor.ri(None, doc='str')
        HostRole = thing.accessor.ri(None, doc='str')
    
        def __init__(self, actionID: 'str', hostRole: 'str'):
            if not isinstance(actionID, str):
                raise ValueError('invalid actionID : {}'.format(actionID))
            self.ActionID = actionID
            self.HostRole = ActionRoleTypes(hostRole)
    
    
    class CountTriggerInfo:
        TargetClassID = thing.accessor.ri(None, doc='thing.system.uuid')
        TargetCount = thing.accessor.ri(None, doc='int')
        Type = thing.accessor.ri(None, doc='CountTypes')
        PropertyCondition = thing.accessor.ri(None, doc='None or PropertyCondition')
        TargetQuestRole = thing.accessor.ri(None, doc='None or str')
        UIName = thing.accessor.ri(None, doc='None or str')
    
        def __init__(self, targetClassID: 'str', targetCount: 'int', countType: 'str'=None, propertyCondition: 'PropertyCondition'=None, questRole: 'str'=None, uiName: 'str'=None):
            self.TargetClassID = natuum.util.convertToUUID(targetClassID)
            try:
                natuum.gameClass.getClassByID(self.TargetClassID)
            except Exception:
                raise ValueError('unknown classID : {}'.format(targetClassID))
    
            self.TargetCount = targetCount
            self.Type = CountTypes.convert(countType)
            self.PropertyCondition = propertyCondition
            self.TargetQuestRole = questRole
            self.UIName = uiName
    
    
    class RandomCountTriggerInfo:
        TargetClassIDCandidates = thing.accessor.ri(None, doc='list')
        TargetCountRangeMax = thing.accessor.ri(None, doc='int')
        TargetCountRangeMin = thing.accessor.ri(None, doc='int')
        Type = thing.accessor.ri(None, doc='CountTypes')
    
        def __init__(self, candidates: 'collections.Sequence', maxCount: 'int', minCount: 'int', countType: 'str'=None):
            if not candidates:
                raise ValueError('random trigger should have at least one candidate')
            if maxCount < minCount or (maxCount < 1 or minCount < 1):
                msg = 'invalid range : [ {}, {} ]'
                raise ValueError(msg.format(minCount, maxCount))
            self.TargetClassIDCandidates = list()
            for value in candidates:
                classID = natuum.util.convertToUUID(value)
                try:
                    natuum.gameClass.getClassByID(classID)
                except Exception:
                    raise ValueError('unknown classID : {}'.format(value))
    
                self.TargetClassIDCandidates.append(classID)
    
            self.TargetCountRangeMax = maxCount
            self.TargetCountRangeMin = minCount
            self.Type = CountTypes.convert(countType)
    
    
    class EnterTriggerInfo:
    
        class TargetTypes(enum.IntEnum):
            QuestRole = 0
            Name = 1
            Signpost = 2
    
            @staticmethod
            def convert(value: 'str') -> 'EnterTriggerInfo.TargetTypes':
                if value == EnterTriggerInfo.TargetTypes.QuestRole.name:
                    return EnterTriggerInfo.TargetTypes.QuestRole
                if value == EnterTriggerInfo.TargetTypes.Name.name:
                    return EnterTriggerInfo.TargetTypes.Name
                if value == EnterTriggerInfo.TargetTypes.Signpost.name:
                    return EnterTriggerInfo.TargetTypes.Signpost
                msg = 'invalid enter trigger target type : {}'
                raise ValueError(msg.format(value))
    
        Target = thing.accessor.ri(None, doc='str')
        Range = thing.accessor.ri(None, doc='float')
        Type = thing.accessor.ri(None, doc='EnterTriggerInfo.TargetTypes')
    
        def __init__(self, target: 'str', targetType: 'str', range: 'float'):
            self.Target = target
            self.Type = EnterTriggerInfo.TargetTypes.convert(targetType)
            self.Range = range
    
    
    class AcceptTriggerInfo:
    
        def __init__(self, acceptText: 'None or str', rejectText: 'None or str'):
            self._AcceptTriggerInfo__selectionMap = dict()
            self._AcceptTriggerInfo__keys = list()
            if acceptText is not None:
                self._AcceptTriggerInfo__selectionMap[acceptText] = QuestStates.ACCEPTED
                self._AcceptTriggerInfo__keys.append(acceptText)
            if rejectText is not None:
                self._AcceptTriggerInfo__selectionMap[rejectText] = QuestStates.REJECTED
                self._AcceptTriggerInfo__keys.append(rejectText)
                self._AcceptTriggerInfo__isRejectable = True
            else:
                self._AcceptTriggerInfo__isRejectable = False
    
              @Property
        def Keys(self) -> 'collections.Sequence':
            return self._AcceptTriggerInfo__keys
    
              @Property
        def IsRejectable(self) -> 'bool':
            return self._AcceptTriggerInfo__isRejectable
    
        def getValue(self, key: 'str') -> 'None or str':
            return self._AcceptTriggerInfo__selectionMap.get(key)
    
        _AcceptTriggerInfo__selectionMap = None
        _AcceptTriggerInfo__keys = None
        _AcceptTriggerInfo__isRejectable = None
    
    
    class SelectionTriggerInfo:
        Results = collections.namedtuple('Results', ('NextQuestIDs', ))
    
        def __init__(self, selectionMap: 'collecctions.Mapping'):
            self._SelectionTriggerInfo__selectionMap = collections.OrderedDict()
            self._SelectionTriggerInfo__keys = list()
            for selectionText, nextQuestIDs in selectionMap.items():
                if nextQuestIDs:
                    if isinstance(nextQuestIDs, collections.Sequence):
                        self._SelectionTriggerInfo__selectionMap[selectionText] = self.Results(nextQuestIDs)
                self._SelectionTriggerInfo__keys.append(selectionText)
    
              @Property
        def Keys(self) -> 'collections.Sequence':
            return self._SelectionTriggerInfo__keys
    
        def getResults(self, key: 'str') -> 'None or .Results':
            return self._SelectionTriggerInfo__selectionMap.get(key)
    
        _SelectionTriggerInfo__selectionMap = None
        _SelectionTriggerInfo__keys = None
    
    
    class GuardianChangedTriggerInfo:
        FromClassID = thing.accessor.ri(None, doc='None or thing.system.uuid')
        ToClassID = thing.accessor.ri(None, doc='None or thing.system.uuid')
        UsageType = thing.accessor.ri(None, doc='TriggerUsageTypes')
    
        def __init__(self, fromClassID: 'None or thing.system.uuid', classID: 'None or thing.system.uuid', usageType: 'TriggerUsageTypes'):
            if fromClassID is not None:
                self.FromClassID = natuum.util.convertToUUID(fromClassID)
            else:
                self.FromClassID = None
            if classID is not None:
                self.ToClassID = natuum.util.convertToUUID(classID)
            else:
                self.ToClassID = None
            self.UsageType = usageType
    
    
    class PropertyTriggerInfo:
        Condition = thing.accessor.ri(None, doc='PropertyCondition')
    
        def __init__(self, condition: 'collections.Mapping'):
            self.Condition = PropertyCondition(condition)
    
    
    class TriggerInfo:
    
        def __init__(self, triggerType: 'TriggerTypes', triggerName: 'str', auxArg: 'var'):
            self._TriggerInfo__triggerType = triggerType
            self._TriggerInfo__triggerName = triggerName
            self._TriggerInfo__auxArg = auxArg
    
              @Property
        def TriggerType(self) -> 'TriggerTypes':
            return self._TriggerInfo__triggerType
    
              @Property
        def TriggerName(self) -> 'str':
            return self._TriggerInfo__triggerName
    
              @Property
        def AuxArg(self) -> 'var':
            return self._TriggerInfo__auxArg
    
        _TriggerInfo__triggerType = None
        _TriggerInfo__triggerName = None
        _TriggerInfo__auxArg = None
    
    
    class TriggerInfoList:
    
        def __init__(self, infoList: 'collections.Sequence', isInstant: 'bool'):
            if not infoList:
                raise ValueError('invalid info list : {}'.format(infoList))
            for subList in infoList:
                if not subList:
                    raise ValueError('invalid sub info list : {}'.format(subList))
    
            self._TriggerInfoList__infoList = infoList
            self._TriggerInfoList__isInstant = isInstant
    
              @Property
        def IsInstant(self):
            return self._TriggerInfoList__isInstant
    
        def iterate(self) -> 'generator of list of TriggerInfo':
            for subList in self._TriggerInfoList__infoList:
                yield subList
    
        def getSubListByIndex(self, index: 'int') -> 'None or list of TriggerInfo':
            try:
                return self._TriggerInfoList__infoList[index]
            except IndexError:
                return
    
        def iterateWithIndex(self) -> 'generator of ( int, list of TriggerInfo )':
            for index, subList in enumerate(self._TriggerInfoList__infoList):
                yield (
                 index, subList)
    
        _TriggerInfoList__infoList = None
        _TriggerInfoList__isInstant = None
    
    
    class InverseTriggerInfoList(TriggerInfoList):
        ModifierTargets = thing.accessor.ri(None, doc='None or dict')
    
        def __init__(self, infoMap):
            super().__init__(list(infoMap.values()), False)
            self.ModifierTargets = self._InverseTriggerInfoList__getModifierTargets()
            self._InverseTriggerInfoList__infoMap = infoMap
    
        def getSubListByIndex(self, index: 'int') -> 'None or list of TriggerInfo':
            return self._InverseTriggerInfoList__infoMap.get(index)
    
        def iterateWithIndex(self) -> 'generator of ( int, list of TriggerInfo )':
            for index, subList in self._InverseTriggerInfoList__infoMap.items():
                yield (
                 index, subList)
    
        @staticmethod
        def create(triggerList: 'TriggerInfoList') -> 'None or InverseTriggerInfoList':
            revertible = dict()
            for index, subList in triggerList.iterateWithIndex():
                subRevertible = list()
                for info in subList:
                    if TriggerTypes.isRevertible(info.TriggerType):
                        subRevertible.append(info)
    
                if subRevertible:
                    revertible[index] = subRevertible
    
            if revertible:
                return InverseTriggerInfoList(revertible)
    
        def __getModifierTargets(self) -> 'None or collections.Mapping':
            targetMap = dict()
            for subList in self.iterate():
                for info in subList:
                    if not TriggerTypes.isRevertible(info.TriggerType):
                        continue
                    else:
                        if info.TriggerType not in targetMap:
                            targetMap[info.TriggerType] = set()
                        subSet = targetMap[info.TriggerType]
                    if TriggerTypes.ITEM_COUNT == info.TriggerType:
                        subSet.add((info.AuxArg.TargetClassID,
                         info.AuxArg.PropertyCondition))
                    else:
                        if TriggerTypes.RANDOM_ITEM_COUNT == info.TriggerType:
                            subSet.add(info)
                        else:
                            if TriggerTypes.PROPERTY == info.TriggerType:
                                subSet.add(info.AuxArg.Condition.PropertyName)
                            else:
                                if TriggerTypes.GUARDIAN_PROPERTY == info.TriggerType:
                                    subSet.add(info.AuxArg.Condition.PropertyName)
                                else:
                                    if TriggerTypes.DECK == info.TriggerType:
                                        continue
                    if TriggerTypes.HAND == info.TriggerType:
                        continue
    
            if targetMap:
                return targetMap
    
        _InverseTriggerInfoList__infoMap = None
    
    
    class CutsceneInfo:
        ScriptData = thing.accessor.ri(None, doc='natuum.cutscene.ScriptData')
        RoleNameMap = thing.accessor.ri(None, doc='dict')
        CaptionPositionMap = thing.accessor.ri(None, doc='dict')
        IsStaged = thing.accessor.ri(None, doc='bool')
        StageFlag = thing.accessor.ri(None, doc='None or int')
    
        def __init__(self, scriptData: 'natuum.cutscene.ScriptData', roleNameMap: 'collections.Mapping', captionPositionMap: 'collections.Mapping', isStaged: 'int or bool'):
            self.ScriptData = scriptData
            self.RoleNameMap = roleNameMap
            self.CaptionPositionMap = captionPositionMap
            self.IsStaged = bool(isStaged)
            self.StageFlag = natuum.cutscene.QuestStagedFlag if isStaged else None
    
    
    class RelationshipTarget(natuum.relationship.RelationshipTarget):
        STR_QUEST_GIVER = 'QUEST_GIVER'
    
              @Property
        def TargetQuestGiver(self):
            return self.Target == self.STR_QUEST_GIVER
    
    
    class RelationshipCondition:
        RelationshipTarget = thing.accessor.ri(None, doc='RelationshipTarget')
        Value = thing.accessor.ri(None, doc='Value')
        CountType = thing.accessor.ri(None, doc='CountTypes')
    
        def __init__(self, info: 'collections.Mapping'):
            self.RelationshipTarget = RelationshipTarget(info['target'])
            self.CountType = CountTypes.convert(info['countType'])
            value = info['value']
            if not isinstance(value, int):
                msg = 'invalid relationship value : {}'.format(value)
                raise ValueError(msg)
            self.Value = value
    
        def check(self, level: 'int') -> 'bool':
            if CountTypes.LessThan == self.CountType:
                return level < self.Value
            return level >= self.Value
    
    
    class RelationshipConditionList:
    
        class IGetter(metaclass=abc.ABCMeta):
    
                  @abc.abstractmethod
            def getRelationship(self, target: 'str', group: 'natuum.relationship.RelationshipGroup') -> 'int':
                pass
    
        def __init__(self, value: 'collections.Sequence'):
            self._RelationshipConditionList__giverRelationship = list()
            self._RelationshipConditionList__targetedRelationship = list()
            for info in value:
                cond = RelationshipCondition(info)
                if cond.RelationshipTarget.TargetQuestGiver:
                    self._RelationshipConditionList__giverRelationship.append(cond)
                else:
                    self._RelationshipConditionList__targetedRelationship.append(cond)
    
        def checkGiver(self, getter: '.IGetter', giverUID: 'str') -> 'bool':
            for cond in self._RelationshipConditionList__giverRelationship:
                relTarget = cond.RelationshipTarget
                level = getter.getRelationship(giverUID, relTarget.Group)
                if not cond.check(level):
                    return False
    
            return True
    
        def checkTargeted(self, getter: '.IGetter') -> 'bool':
            for cond in self._RelationshipConditionList__targetedRelationship:
                relTarget = cond.RelationshipTarget
                level = getter.getRelationship(relTarget.Target, relTarget.Group)
                if not cond.check(level):
                    return False
    
            return True
    
        _RelationshipConditionList__giverRelationship = None
        _RelationshipConditionList__targetedRelationship = None
    
    
    class DineEventTriggerInfo:
        FoodClassID = thing.accessor.ri(None, doc='thing.system.uuid')
    
        def __init__(self, info: 'collections.Mapping'):
            classID = natuum.util.convertToUUID(info['foodClassID'])
            try:
                natuum.gameClass.getClassByID(classID)
            except Exception:
                raise ValueError('invalid classID : {}'.format(classID))
    
            if not natuum.gameClass.isSameOrDescendant(classID, '{CL1096(Dish)}'):
                raise ValueError('{} is not a descendant of CL1096'.format(classID))
            self.FoodClassID = classID
    
    
    class Result:
    
        class Base:
            pass
    
        class Action(Base):
    
            def __init__(self, actionID: 'str', roleMap: 'None or collections.Mapping'):
                self._Action__actionID = actionID
                self._Action__roleMap = roleMap
    
                  @Property
            def ActionID(self) -> 'str':
                return self._Action__actionID
    
                  @Property
            def RoleMap(self) -> 'None or collections.Mapping':
                return self._Action__roleMap
    
            _Action__actionID = None
            _Action__roleMap = None
    
        class ItemBase(Base):
    
            def __init__(self, itemClassID: 'str', itemCount: 'int'):
                if not isinstance(itemClassID, str):
                    msg = 'invalid item classID : {}'.format(itemClassID)
                    raise ValueError(msg)
                self._ItemBase__itemClassID = itemClassID
                if not isinstance(itemCount, int):
                    msg = 'invalid item count : {}'.format(itemCount)
                    raise ValueError(msg)
                self._ItemBase__itemCount = itemCount
    
                  @Property
            def ItemClassID(self) -> 'str':
                return self._ItemBase__itemClassID
    
                  @Property
            def ItemCount(self) -> 'int':
                return self._ItemBase__itemCount
    
            _ItemBase__itemClassID = None
            _ItemBase__itemCount = None
    
        class CreateItem(ItemBase):
    
            def __init__(self, bind, *args):
                (super().__init__)(*args)
                self._CreateItem__bind = bind
    
                  @Property
            def Bind(self) -> 'bool':
                return self._CreateItem__bind
    
            _CreateItem__bind = None
    
        class DeleteItem(ItemBase):
            pass
    
        class Teleport(Base):
            NamedLocation = collections.namedtuple('NamedLocation', ('Name', 'RelativePosition'))
    
            def __init__(self, target: 'str', position: 'collections.Mapping or thing.system.vec'):
                self._Teleport__target = target
                if isinstance(position, thing.system.vec):
                    self._Teleport__position = position
                elif isinstance(position, collections.Mapping):
                    name = position.get('beaconName')
                    relativePos = position.get('relativePosition')
                    if isinstance(relativePos, (type(None), thing.system.vec)):
                        if isinstance(name, str):
                            self._Teleport__position = self.NamedLocation(name, relativePos)
                if self._Teleport__position is None:
                    raise ValueError('invalid position : {}'.format(position))
    
                  @Property
            def Target(self) -> 'str':
                return self._Teleport__target
    
                  @Property
            def Position(self) -> 'thing.system.vec or .NamedLocation':
                return self._Teleport__position
    
            _Teleport__target = None
            _Teleport__position = None
    
        class KiranaCarve(Base):
    
            class Types(enum.IntEnum):
                ClassID = 0
                QuestRole = 1
                ActorTable = 2
    
                      @classmethod
                def convert(cls, value: 'str') -> 'Result.KiranaCarve.Types':
                    if value == cls.ClassID.name:
                        return cls.ClassID
                    if value == cls.QuestRole.name:
                        return cls.QuestRole
                    if value == cls.ActorTable.name:
                        return cls.ActorTable
                    msg = 'invalid kirana carve target type : {}'
                    raise ValueError(msg.format(value))
    
            def __init__(self, targetType: 'str', targetPerson: 'str', targetItemClassID: 'str', questRolesToBeRemoved: 'None or collections.Sequence', targetItemBans: 'None or collections.Sequence', addToHand: 'bool'):
                if not isinstance(targetPerson, str):
                    msg = 'invalid kirana carve target : {}'
                    raise ValueError(msg.format(targetPerson))
                self._KiranaCarve__targetType = Result.KiranaCarve.Types.convert(targetType)
                if Result.KiranaCarve.Types.ClassID == self._KiranaCarve__targetType:
                    try:
                        natuum.gameClass.getClassByID(targetPerson)
                    except Exception:
                        raise ValueError('invalid classID : {}'.format(targetPerson))
    
                self._KiranaCarve__targetPerson = targetPerson
                self._KiranaCarve__targetItemClassID = targetItemClassID
                self._KiranaCarve__questRolesToBeRemoved = questRolesToBeRemoved
                self._KiranaCarve__targetItemBans = targetItemBans
                self._KiranaCarve__addToHand = addToHand
    
                  @Property
            def TargetType(self) -> 'Result.KiranaCarve.Types':
                return self._KiranaCarve__targetType
    
                  @Property
            def TargetPerson(self) -> 'str':
                return self._KiranaCarve__targetPerson
    
                  @Property
            def TargetItemClassID(self) -> 'str':
                return self._KiranaCarve__targetItemClassID
    
                  @Property
            def QuestRolesToBeRemoved(self) -> 'None or collections.Sequence':
                return self._KiranaCarve__questRolesToBeRemoved
    
                  @Property
            def TargetItemBans(self) -> 'None or collections.Sequence':
                return self._KiranaCarve__targetItemBans
    
                  @Property
            def AddToHand(self) -> 'bool':
                return self._KiranaCarve__addToHand
    
            _KiranaCarve__targetType = None
            _KiranaCarve__targetPerson = None
            _KiranaCarve__targetItemClassID = None
            _KiranaCarve__questRolesToBeRemoved = None
            _KiranaCarve__targetItemBans = None
            _KiranaCarve__addToHand = None
    
        class Experience(Base):
    
            def __init__(self, value: 'int'):
                self._Experience__value = value
    
                  @Property
            def Value(self) -> 'int':
                return self._Experience__value
    
            _Experience__value = None
    
        class Relationship(Base):
    
            def __init__(self, target: 'collections.Mapping', value: 'int'):
                self._Relationship__target = RelationshipTarget(target)
                self._Relationship__value = value
    
                  @Property
            def RelationshipTarget(self) -> 'RelationshipTarget':
                return self._Relationship__target
    
                  @Property
            def Value(self) -> 'int':
                return self._Relationship__value
    
            _Relationship__target = None
            _Relationship__value = None
    
        class Town(Base):
    
            def __init__(self, townID: 'str'):
                self._Town__townID = townID
    
                  @Property
            def TownID(self) -> 'str':
                return self._Town__townID
    
            _Town__townID = None
    
    
    class ResultList:
        PropertyNames = thing.accessor.ri(None, doc='list')
        Entry = collections.namedtuple('Entry', ('PropertyConditions', 'Results'))
    
        def __init__(self, raw: 'collections.Sequence'):
            self.PropertyNames = list()
            self._ResultList__resultList = list()
            propertyNameSet = set()
            for subMap in raw:
                propertyConditions = subMap.get('propertyConditions')
                if propertyConditions is not None:
                    propertyConditions = PropertyConditionList(propertyConditions)
                    propertyNameSet.update(propertyConditions.PropertyNames)
                else:
                    resultMap = dict()
                    for resultType, rawResults in subMap['results'].items():
                        if resultType == 'town':
                            resultMap[Result.Town] = {
                             Result.Town(rawResults)}
                        else:
                            if resultType == 'experience':
                                resultMap[Result.Experience] = {
                                 Result.Experience(rawResults)}
                            else:
                                for sub in rawResults:
                                    if resultType == 'action':
                                        resultClass = Result.Action
                                        result = resultClass(sub['actionID'], sub.get('roleMap'))
                                    elif resultType == 'createItem':
                                        resultClass = Result.CreateItem
                                        result = resultClass(sub.get('bind', False), sub['classID'], sub['count'])
                                    elif resultType == 'deleteItem':
                                        resultClass = Result.DeleteItem
                                        result = resultClass(sub['classID'], sub['count'])
                                    elif resultType == 'teleport':
                                        resultClass = Result.Teleport
                                        result = resultClass(sub['target'], sub['position'])
                                    elif resultType == 'relationship':
                                        resultClass = Result.Relationship
                                        result = resultClass(sub['target'], sub['value'])
                                    elif resultType == 'kiranaCarve':
                                        resultClass = Result.KiranaCarve
                                        result = resultClass(sub['targetType'], sub['targetPerson'], sub['targetItemClassID'], sub.get('questRolesToBeRemoved'), sub.get('targetItemBans'), sub.get('addToHand', False))
                                    else:
                                        raise ValueError('invalid result type : {}'.format(resultType))
                                    if resultClass not in resultMap:
                                        resultMap[resultClass] = set()
                                    else:
                                        resultMap[resultClass].add(result)
    
                    self._ResultList__resultList.append(self.Entry(propertyConditions, resultMap))
    
            self.PropertyNames.extend(propertyNameSet)
    
        def filterResults(self, propertyMap: 'None or collections.Mapping') -> 'collections.Mapping':
            filtered = dict()
            for entry in self._ResultList__resultList:
                conditions = entry.PropertyConditions
                if conditions is not None:
                    if propertyMap:
                        if not conditions.check(propertyMap):
                            continue
                        for resultClass, subList in entry.Results.items():
                            if resultClass not in filtered:
                                filtered[resultClass] = list()
                            else:
                                filtered[resultClass].extend(subList)
    
            return filtered
    
        _ResultList__resultList = None
    
    
    class PatternQuestStates(enum.IntEnum):
        ACTIVE = 0
        FINISHED = 2
        CANCELED = 1
    
        @staticmethod
        def serialize(questID: 'str', state: 'PatternQuestStates', timeStamp: 'int') -> '( str, int, str )':
            return (
             questID, int(state), str(timeStamp))
    
        @staticmethod
        def deserialize(value: 'collections.Sequence') -> '( str, PatternQuestStates, int )':
            questID, stateInt, timeStampStr = value
            return (
             questID, PatternQuestStates(stateInt), int(timeStampStr))
    
        @staticmethod
        def canStartPatternQuest(serialized: 'None or collections.Sequence', questID: 'None or str', current: 'int') -> 'bool or str':
            if not isinstance(serialized, collections.Sequence):
                return True
            prevQuestID, prevState, prevTimeStamp = PatternQuestStates.deserialize(serialized)
            if PatternQuestStates.ACTIVE == prevState:
                return False
            if PatternQuestStates.FINISHED == prevState:
                return prevTimeStamp < current
            if PatternQuestStates.CANCELED == prevState:
                if prevTimeStamp < current:
                    return True
                if questID is None:
                    return prevQuestID
                return questID == prevQuestID
            msg = 'invalid pattern quest state : {}'.format(prevState)
            raise NotImplementedError(msg)
    
        @staticmethod
        def hasPatternQuest(stateMap: 'collections.Mapping', questID: 'str', state: 'PatternQuestStates') -> 'bool':
            for serialized in stateMap.values():
                otherQuestID, otherState, *_ = PatternQuestStates.deserialize(serialized)
                if otherQuestID == questID:
                    if otherState == state:
                        return True
    
            return False
    
    
    class PatternQuestInfo:
        ResetDelay = thing.accessor.ri(None, doc='int')
    
        def __init__(self, resetDelay: 'int'):
            self.ResetDelay = resetDelay
    
    
    class PropertyCondition(natuum.cutscene.IPropertyCondition):
    
        def __init__(self, value: 'collections.Mapping'):
            self._PropertyCondition__propertyName = value['propertyName']
            self._PropertyCondition__comp = ComparisonTypes(value['comparisonMethod'])
            self._PropertyCondition__targetValueRaw = value['targetValue']
            try:
                self._PropertyCondition__targetValueUUID = natuum.util.convertToUUID(self._PropertyCondition__targetValueRaw)
            except ValueError:
                self._PropertyCondition__targetValueUUID = None
    
              @Property
              @Thing.overrides(natuum.cutscene.IPropertyCondition)
        def PropertyName(self) -> 'str':
            return self._PropertyCondition__propertyName
    
              @Thing.overrides(natuum.cutscene.IPropertyCondition)
        def check(self, value: 'var') -> 'bool':
            if self._PropertyCondition__targetValueUUID is not None:
                if self._PropertyCondition__check(value, self._PropertyCondition__targetValueUUID):
                    return True
            return self._PropertyCondition__check(value, self._PropertyCondition__targetValueRaw)
    
        def propertyConditionToString(self) -> 'str':
            return self.PropertyName + str(self._PropertyCondition__comp) + str(self._PropertyCondition__targetValueRaw)
    
        def __check(self, value: 'var', targetValue: 'var') -> 'bool':
            try:
                return ComparisonTypes.compare(self._PropertyCondition__comp, value, targetValue)
            except Exception:
                return False
    
        _PropertyCondition__propertyName = None
        _PropertyCondition__comp = None
        _PropertyCondition__targetValueRaw = None
        _PropertyCondition__targetValueUUID = None
    
    
    class PropertyConditionList:
        PropertyNames = thing.accessor.ri(None, doc='list')
    
        def __init__(self, value: 'collections.Sequence'):
            self._PropertyConditionList__conditionList = list()
            self.PropertyNames = list()
            for subMap in value:
                condition = PropertyCondition(subMap)
                self._PropertyConditionList__conditionList.append(condition)
                self.PropertyNames.append(condition.PropertyName)
    
        def check(self, propertyMap: 'collections.Mapping') -> 'bool':
            if not propertyMap:
                return False
            for key, value in propertyMap.items():
                if value == natuum.util.UUID_PROPERTY_NOT_FOUND:
                    return False
    
            for condition in self._PropertyConditionList__conditionList:
                if condition.PropertyName not in propertyMap:
                    return False
                if not condition.check(propertyMap[condition.PropertyName]):
                    return False
    
            return True
    
        def _checkProperty(self, name: 'str', value: 'var') -> 'bool':
            for condition in self._PropertyConditionList__conditionList:
                if condition.PropertyName == name:
                    if not condition.check(value):
                        return False
    
            return True
    
        _PropertyConditionList__conditionList = None
    
    
    class PrerequisiteInfo:
    
        def __init__(self, questID: 'str', raw: 'None or collections.Mapping'):
            self._PrerequisiteInfo__questID = questID
            if raw is None:
                self._PrerequisiteInfo__activeQuestIDs = None
                self._PrerequisiteInfo__inactiveQuestIDs = None
                self._PrerequisiteInfo__finishedQuestIDs = None
                self._PrerequisiteInfo__notInFinishedQuestIDs = None
                self._PrerequisiteInfo__notInDeck = None
                self._PrerequisiteInfo__gInfo = None
                self._PrerequisiteInfo__rInfo = None
                self._PrerequisiteInfo__guardianInfo = None
                self._PrerequisiteInfo__kiranaInfoMap = None
                self._PrerequisiteInfo__kiranaInfoNameMap = None
                self._PrerequisiteInfo__relationship = None
                self._PrerequisiteInfo__rPropNames = None
                self._PrerequisiteInfo__townID = None
                self._PrerequisiteInfo__unregTownIDs = None
            else:
                value = raw.get('activeQuestIDs')
                self._PrerequisiteInfo__activeQuestIDs = set(value) if value else None
                value = raw.get('inactiveQuestIDs')
                self._PrerequisiteInfo__inactiveQuestIDs = set(value) if value else None
                value = raw.get('finishedQuestIDs')
                self._PrerequisiteInfo__finishedQuestIDs = set(value) if value else None
                value = raw.get('notInFinishedQuestIDs')
                self._PrerequisiteInfo__notInFinishedQuestIDs = set(value) if value else None
                value = raw.get('notInDeck')
                if isinstance(value, collections.Sequence):
                    self._PrerequisiteInfo__notInDeck = set()
                    for classIDStr in value:
                        self._PrerequisiteInfo__notInDeck.add(natuum.util.convertToUUID(classIDStr))
    
                else:
                    self._PrerequisiteInfo__notInDeck = None
                value = raw.get('giverProperties')
                if value:
                    self._PrerequisiteInfo__gInfo = PropertyConditionList(value)
                rPropNames = set()
                value = raw.get('receiverProperties')
                if value:
                    self._PrerequisiteInfo__rInfo = PropertyConditionList(value)
                    rPropNames.update(self._PrerequisiteInfo__rInfo.PropertyNames)
                value = raw.get('guardianProperties')
                if value:
                    self._PrerequisiteInfo__guardianInfo = PropertyConditionList(value)
                value = raw.get('kiranaProperties')
                if value:
                    self._PrerequisiteInfo__kiranaInfoMap = dict()
                    self._PrerequisiteInfo__kiranaInfoNameMap = dict()
                    for rawClassID, condition in value.items():
                        classID = natuum.util.convertToUUID(rawClassID)
                        conditionList = PropertyConditionList(condition)
                        self._PrerequisiteInfo__kiranaInfoMap[classID] = conditionList
                        self._PrerequisiteInfo__kiranaInfoNameMap[classID] = conditionList.PropertyNames
    
                value = raw.get('relationship')
                if value:
                    self._PrerequisiteInfo__relationship = RelationshipConditionList(value)
                value = raw.get('registeredTown')
                if value:
                    self._PrerequisiteInfo__townID = value
                    rPropNames.add(natuum.gameClass.PROP_REGISTERED_TOWN)
                value = raw.get('unregisteredTowns')
                if value:
                    self._PrerequisiteInfo__unregTownIDs = value
                    rPropNames.add(natuum.gameClass.PROP_REGISTERED_TOWN)
                self._PrerequisiteInfo__rPropNames = list(rPropNames) if rPropNames else None
    
              @Property
        def GiverPropertyNames(self) -> 'None or collections.Sequence':
            if self._PrerequisiteInfo__gInfo is None:
                return
            return self._PrerequisiteInfo__gInfo.PropertyNames
    
              @Property
        def ReceiverPropertyNames(self) -> 'None or collections.Sequence':
            return self._PrerequisiteInfo__rPropNames
    
              @Property
        def GuardianPropertyNames(self) -> 'None or collections.Sequence':
            if self._PrerequisiteInfo__guardianInfo is None:
                return
            return self._PrerequisiteInfo__guardianInfo.PropertyNames
    
              @Property
        def KiranaPropertyNameMap(self) -> 'None or collections.Mapping':
            if self._PrerequisiteInfo__kiranaInfoNameMap is None:
                return
            return self._PrerequisiteInfo__kiranaInfoNameMap
    
              @Property
        def Relationship(self) -> 'None or RelationshipConditionList':
            return self._PrerequisiteInfo__relationship
    
        def canStart(self, questStates: 'collections.Mapping', finished: 'collections.Sequence', contracted: 'collections.Sequence') -> 'bool':
            if self._PrerequisiteInfo__questID in questStates or (self._PrerequisiteInfo__questID in finished):
                return False
            if self._PrerequisiteInfo__activeQuestIDs is not None:
                if not self._PrerequisiteInfo__activeQuestIDs.issubset(questStates):
                    return False
                if self._PrerequisiteInfo__inactiveQuestIDs is not None:
                    if self._PrerequisiteInfo__inactiveQuestIDs.intersection(questStates):
                        return False
                if self._PrerequisiteInfo__finishedQuestIDs is not None:
                    if not self._PrerequisiteInfo__finishedQuestIDs.issubset(finished):
                        return False
                    if self._PrerequisiteInfo__notInFinishedQuestIDs is not None:
                        if self._PrerequisiteInfo__notInFinishedQuestIDs.intersection(finished):
                            return False
                    if self._PrerequisiteInfo__notInDeck is not None:
                        if self._PrerequisiteInfo__notInDeck.intersection(contracted):
                            return False
                return True
    
        def checkGiverProperties(self, propMap: 'None or collections.Mapping') -> 'bool':
            if self._PrerequisiteInfo__gInfo is None:
                return True
            return propMap and self._PrerequisiteInfo__gInfo.check(propMap)
    
        def checkReceiverProperties(self, propMap: 'None or collections.Mapping') -> 'bool':
            if self._PrerequisiteInfo__townID or self._PrerequisiteInfo__unregTownIDs:
                townID = propMap.get(natuum.gameClass.PROP_REGISTERED_TOWN)
                if not self._PrerequisiteInfo__checkTownID(townID):
                    return False
                if self._PrerequisiteInfo__rInfo is None:
                    return True
                return propMap and self._PrerequisiteInfo__rInfo.check(propMap)
    
        def checkGuardianProperties(self, propMap: 'None or collections.Mapping') -> 'bool':
            if self._PrerequisiteInfo__guardianInfo is None:
                return True
            return propMap and self._PrerequisiteInfo__guardianInfo.check(propMap)
    
        def checkKiranaProperties(self, classID: 'thing.system.uuid', propMap: 'None or collections.Mapping') -> 'bool':
            info = self._PrerequisiteInfo__kiranaInfoMap.get(classID)
            if info is None:
                return True
            return propMap and info.check(propMap)
    
        def checkGiverRelationship(self, getter: '.IGetter', giverUID: 'str') -> 'bool':
            if not self._PrerequisiteInfo__relationship:
                return True
            return self._PrerequisiteInfo__relationship.checkGiver(getter, giverUID)
    
        def checkTargetedRelationship(self, getter: '.IGetter') -> 'bool':
            if not self._PrerequisiteInfo__relationship:
                return True
            return self._PrerequisiteInfo__relationship.checkTargeted(getter)
    
        def checkReceiverTownID(self, townID: 'str') -> 'bool':
            if not self._PrerequisiteInfo__checkTownID(townID):
                return False
            if not self._PrerequisiteInfo__rInfo:
                return True
            return self._PrerequisiteInfo__rInfo._checkProperty(natuum.gameClass.PROP_REGISTERED_TOWN, townID)
    
        def __checkTownID(self, townID: 'str') -> 'bool':
            if self._PrerequisiteInfo__townID:
                if self._PrerequisiteInfo__townID != townID:
                    return False
            if self._PrerequisiteInfo__unregTownIDs:
                if townID in self._PrerequisiteInfo__unregTownIDs:
                    return False
            return True
    
        _PrerequisiteInfo__questID = None
        _PrerequisiteInfo__activeQuestIDs = None
        _PrerequisiteInfo__inactiveQuestIDs = None
        _PrerequisiteInfo__finishedQuestIDs = None
        _PrerequisiteInfo__notInFinishedQuestIDs = None
        _PrerequisiteInfo__notInDeck = None
        _PrerequisiteInfo__gInfo = None
        _PrerequisiteInfo__rInfo = None
        _PrerequisiteInfo__guardianInfo = None
        _PrerequisiteInfo__kiranaInfoMap = None
        _PrerequisiteInfo__kiranaInfoNameMap = None
        _PrerequisiteInfo__relationship = None
        _PrerequisiteInfo__rPropNames = None
        _PrerequisiteInfo__townID = None
        _PrerequisiteInfo__unregTownIDs = None
    
    
    class QuestData:
        QuestID = thing.accessor.ri(None, doc='str')
        QuestName = thing.accessor.ri(None, doc='str')
        QuestType = thing.accessor.ri(None, doc='QuestType')
        UIInfo = thing.accessor.ri(None, doc='UIInfo')
        GuideKeyword = thing.accessor.ri(None, doc='None or str')
        Prerequisites = thing.accessor.ri(None, doc='PrerequisiteInfo')
        ReportTargetType = thing.accessor.ri(None, doc='ReportTargetType')
        ManuallyCancellable = thing.accessor.ri(None, doc='bool')
        Repeatable = thing.accessor.ri(None, doc='bool')
        StartableByTownUI = thing.accessor.ri(None, doc='bool')
        PatternQuestSettings = thing.accessor.ri(None, doc='PatternQuestInfo')
        Rewards = thing.accessor.ri(None, doc='None or ResultList')
        AcceptResults = thing.accessor.ri(None, doc='None or ResultList')
        ForwardResults = thing.accessor.ri(None, doc='None or ResultList')
        EndResults = thing.accessor.ri(None, doc='None or ResultList')
        CancelResults = thing.accessor.ri(None, doc='None or ResultList')
        StartTriggers = thing.accessor.ri(None, doc='set')
        TokenItem = thing.accessor.ri(None, doc='str')
        NextQuestIDs = thing.accessor.ri(None, doc='list')
        TurnInItems = thing.accessor.ri(None, doc='bool')
        ReceiverNotReadyCutscene = thing.accessor.ri(None, doc='CutsceneInfo')
        InitialCutscene = thing.accessor.ri(None, doc='CutsceneInfo')
        AcceptCutscene = thing.accessor.ri(None, doc='CutsceneInfo')
        RejectCutscene = thing.accessor.ri(None, doc='CutsceneInfo')
        CompleteCutscene = thing.accessor.ri(None, doc='CutsceneInfo')
        EndCutscene = thing.accessor.ri(None, doc='CutsceneInfo')
        ProgressCutscene = thing.accessor.ri(None, doc='CutsceneInfo')
        CancelCutscene = thing.accessor.ri(None, doc='CutsceneInfo')
        RewardCutscene = thing.accessor.ri(None, doc='CutsceneInfo')
        AcceptSelections = thing.accessor.ri(None, doc='TriggerInfo')
        ForwardTrigger = thing.accessor.ri(None, doc='TriggerInfoList')
        BackwardTrigger = thing.accessor.ri(None, doc='InverseTriggerInfoList')
        EndTrigger = thing.accessor.ri(None, doc='TriggerInfoList')
        ProgressTrigger = thing.accessor.ri(None, doc='TriggerInfoList')
        CancelTrigger = thing.accessor.ri(None, doc='TriggerInfoList')
        FailTrigger = thing.accessor.ri(None, doc='TriggerInfoList')
        RetryTrigger = thing.accessor.ri(None, doc='TriggerInfoList')
        HasStagedInitSequence = thing.accessor.ri(None, doc='bool')
        HasStagedEndCutscene = thing.accessor.ri(None, doc='bool')
        HasNonInstantMainTrigger = thing.accessor.ri(None, doc='bool')
        MIN_TRIGGER_INDEX = 0
        TRIGGER_NAME_PATTERN = re.compile('(.*?)([\\d]+)')
        TRIGGER_NAME_PREFIX_START = 'start/'
        TRIGGER_NAME_PREFIX_FORWARD = 'forward/'
        TRIGGER_NAME_PREFIX_END = 'end/'
        TRIGGER_NAME_PREFIX_PROGRESS = 'progress/'
        TRIGGER_NAME_PREFIX_CANCEL = 'cancel/'
        TRIGGER_NAME_PREFIX_FAIL = 'fail/'
        TRIGGER_NAME_PREFIX_RETRY = 'retry/'
        TRIGGER_NAME_PREFIX_SELECTION = 'selection/'
        TRIGGER_NAME_SORT_KEY_MAP = {TRIGGER_NAME_PREFIX_START: 1,
         TRIGGER_NAME_PREFIX_FORWARD: MIN_TRIGGER_INDEX,
         TRIGGER_NAME_PREFIX_END: MIN_TRIGGER_INDEX,
         TRIGGER_NAME_PREFIX_PROGRESS: math.inf,
         TRIGGER_NAME_PREFIX_CANCEL: math.inf,
         TRIGGER_NAME_PREFIX_FAIL: math.inf,
         TRIGGER_NAME_PREFIX_RETRY: math.inf,
         TRIGGER_NAME_PREFIX_SELECTION: math.inf}
    
        def __init__(self, questID: 'str', questName: 'str', manuallyCancellable: 'bool', repeatable: 'bool', startableByTownUI: 'bool', questType: 'QuestType', uiInfo: 'natuum.quest.UIInfo', guideKeyword: 'None or str', reportTargetT
    ype: 'ReportTargetType', patternQuestSettings: 'None or collections.Mapping', prerequisites: 'None or collections.Mapping', defaultRoleNameMap: 'collections.Mapping', defaultCaptionPositionMap: 'collections.Mapping', rewards: 'Non
    e or collections.Sequence', acceptResults: 'None or collections.Sequence', forwardResults: 'None or collections.Sequence', endResults: 'None or collections.Sequence', cancelResults: 'None or collections.Sequence', tokenItem: 'None
     or str', nextQuestIDs: 'None or collections.Sequence', acceptSelectionText: 'None or str', rejectSelectionText: 'None or str', startTriggers: 'None or collections.Sequence', forwardTrigger: 'None or collections.Mapping', endTrigg
    er: 'None or collections.Mapping', progressTrigger: 'None or collections.Mapping', cancelTrigger: 'None or collections.Mapping', failTrigger: 'None or collections.Mapping', retryTrigger: 'None or collections.Mapping', enableBackwa
    rdTrigger: 'bool', turnInItems: 'bool', hasStagedInitSequence: 'bool', receiverNotReadyCutscene: 'None or collections.Mapping', initialCutscene: 'None or collections.Mapping', acceptCutscene: 'None or collections.Mapping', rejectC
    utscene: 'None or collections.Mapping', completeCutscene: 'None or collections.Mapping', endCutscene: 'None or collections.Mapping', progressCutscene: 'None or collections.Mapping', cancelCutscene: 'None or collections.Mapping', r
    ewardCutscene: 'None or collections.Mapping'):
            if not isinstance(questID, str):
                raise ValueError('invalid quest ID : {}'.format(questID))
            if not isinstance(questName, str):
                raise ValueError('invalid quest name : {}'.format(questName))
            if not all((isinstance(key, str) and isinstance(value, str) for key, value in defaultRoleNameMap.items())):
                raise ValueError('invalid defaultRoleNameMap : {}'.format(defaultRoleNameMap))
            self.QuestID = questID
            self.QuestName = questName
            self.ManuallyCancellable = manuallyCancellable
            self.Repeatable = repeatable
            self.StartableByTownUI = startableByTownUI
            self.QuestType = questType
            self.UIInfo = uiInfo
            self.GuideKeyword = guideKeyword
            self.ReportTargetType = reportTargetType
            self.Prerequisites = PrerequisiteInfo(questID, prerequisites)
            self.TurnInItems = turnInItems
            if patternQuestSettings is not None:
                self.PatternQuestSettings = PatternQuestInfo(patternQuestSettings['resetDelay'])
            self._QuestData__defaultRoleNameMap = defaultRoleNameMap
            self._QuestData__defaultCaptionPositionMap = self._QuestData__resolveCaptionPositionMap(defaultCaptionPositionMap)
            self.Rewards = ResultList(rewards) if rewards else None
            self.AcceptResults = ResultList(acceptResults) if acceptResults else None
            self.ForwardResults = ResultList(forwardResults) if forwardResults else None
            self.EndResults = ResultList(endResults) if endResults else None
            self.CancelResults = ResultList(cancelResults) if cancelResults else None
            self.TokenItem = tokenItem
            self.NextQuestIDs = nextQuestIDs
            if acceptSelectionText is not None or (rejectSelectionText is not None):
                triggerName = QuestData.createTriggerName(self.TRIGGER_NAME_PREFIX_SELECTION, self.MIN_TRIGGER_INDEX)
                self.AcceptSelections = TriggerInfo(TriggerTypes.SELECT_STATE, triggerName, AcceptTriggerInfo(acceptSelectionText, rejectSelectionText))
            self.StartTriggers = set()
            if startTriggers is not None:
                index = self.MIN_TRIGGER_INDEX
                for rawTrigger in startTriggers:
                    triggerName = QuestData.createTriggerName(self.TRIGGER_NAME_PREFIX_START, index)
                    self.StartTriggers.add(self._QuestData__resolveTrigger(rawTrigger, triggerName, TriggerUsageTypes.START))
                    index += 1
    
            if forwardTrigger is not None:
                self.ForwardTrigger = self._QuestData__resolveTriggerInfoList(forwardTrigger, self.TRIGGER_NAME_PREFIX_FORWARD)
            if endTrigger is not None:
                self.EndTrigger = self._QuestData__resolveTriggerInfoList(endTrigger, self.TRIGGER_NAME_PREFIX_END)
            if progressTrigger is not None:
                self.ProgressTrigger = self._QuestData__resolveTriggerInfoList(progressTrigger, self.TRIGGER_NAME_PREFIX_PROGRESS)
            if cancelTrigger is not None:
                self.CancelTrigger = self._QuestData__resolveTriggerInfoList(cancelTrigger, self.TRIGGER_NAME_PREFIX_CANCEL)
            if failTrigger is not None:
                self.FailTrigger = self._QuestData__resolveTriggerInfoList(failTrigger, self.TRIGGER_NAME_PREFIX_FAIL)
            if retryTrigger is not None:
                self.RetryTrigger = self._QuestData__resolveTriggerInfoList(retryTrigger, self.TRIGGER_NAME_PREFIX_RETRY)
            if self.ForwardTrigger is not None and enableBackwardTrigger:
                self.BackwardTrigger = InverseTriggerInfoList.create(self.ForwardTrigger)
            else:
                self.BackwardTrigger = None
            if receiverNotReadyCutscene is not None:
                self.ReceiverNotReadyCutscene = (self._QuestData__resolveCutscene)(*receiverNotReadyCutscene)
            if initialCutscene is not None:
                self.InitialCutscene = (self._QuestData__resolveCutscene)(*initialCutscene[:-1], *(hasStagedInitSequence,))
            if acceptCutscene is not None:
                self.AcceptCutscene = (self._QuestData__resolveCutscene)(*acceptCutscene[:-1], *(hasStagedInitSequence,))
            if rejectCutscene is not None:
                self.RejectCutscene = (self._QuestData__resolveCutscene)(*rejectCutscene[:-1], *(hasStagedInitSequence,))
            if completeCutscene is not None:
                self.CompleteCutscene = (self._QuestData__resolveCutscene)(*completeCutscene)
            if endCutscene is not None:
                self.EndCutscene = (self._QuestData__resolveCutscene)(*endCutscene)
            if progressCutscene is not None:
                self.ProgressCutscene = (self._QuestData__resolveCutscene)(*progressCutscene)
            if cancelCutscene is not None:
                self.CancelCutscene = (self._QuestData__resolveCutscene)(*cancelCutscene)
            if rewardCutscene is not None:
                self.RewardCutscene = (self._QuestData__resolveCutscene)(*rewardCutscene)
            if self.AcceptSelections is not None or self.InitialCutscene is not None or self.AcceptCutscene is not None or self.RejectCutscene is not None:
                self.HasStagedInitSequence = hasStagedInitSequence
            else:
                self.HasStagedInitSequence = False
            if self.EndCutscene is not None:
                self.HasStagedEndCutscene = self.EndCutscene.IsStaged
            else:
                self.HasStagedEndCutscene = False
            hasNonInstantMainTrigger = False
            if self.ForwardTrigger is not None:
                hasNonInstantMainTrigger |= not self.ForwardTrigger.IsInstant
            if self.EndTrigger is not None:
                hasNonInstantMainTrigger |= not self.EndTrigger.IsInstant
            self.HasNonInstantMainTrigger = hasNonInstantMainTrigger
    
        @staticmethod
        def createTriggerName(prefix: 'str', index: 'int') -> 'str':
            return prefix + str(index)
    
        @staticmethod
        def getTriggerNameSortKey(triggerName: 'str') -> 'collections.Sequence':
            match = QuestData.TRIGGER_NAME_PATTERN.fullmatch(triggerName)
            if match is None:
                return (QuestData.MIN_TRIGGER_INDEX, QuestData.MIN_TRIGGER_INDEX)
            try:
                index = int(match.group(2))
            except Exception:
                index = QuestData.MIN_TRIGGER_INDEX
    
            return (
             QuestData.TRIGGER_NAME_SORT_KEY_MAP.get(match.group(1), QuestData.MIN_TRIGGER_INDEX), index)
    
        @staticmethod
        def isStartTrigger(triggerName: 'str') -> 'bool':
            return triggerName.startswith(QuestData.TRIGGER_NAME_PREFIX_START)
    
        def iterateTriggerInfos(self) -> 'generator of TriggerInfo':
    
            def iterate(trigger: 'None or natuum.quest.TriggerInfoList') -> 'generator of TriggerInfo':
                if isinstance(trigger, natuum.quest.TriggerInfoList):
                    for subList in trigger.iterate():
                        for triggerInfo in subList:
                            yield triggerInfo
    
            for triggerInfo in iterate(self.ForwardTrigger):
                yield triggerInfo
    
            for triggerInfo in iterate(self.EndTrigger):
                yield triggerInfo
    
            for triggerInfo in iterate(self.ProgressTrigger):
                yield triggerInfo
    
            for triggerInfo in iterate(self.CancelTrigger):
                yield triggerInfo
    
            for triggerInfo in iterate(self.FailTrigger):
                yield triggerInfo
    
            for triggerInfo in iterate(self.RetryTrigger):
                yield triggerInfo
    
            if self.StartTriggers:
                for triggerInfo in self.StartTriggers:
                    yield triggerInfo
    
            if self.AcceptSelections:
                yield self.AcceptSelections
    
        def __resolveTriggerInfoList(self, raw: 'collections.Sequence', prefix: 'str') -> 'None or TriggerInfoList':
            index = self.MIN_TRIGGER_INDEX
            orList = list()
            isInstant = False
            for subRaw in raw:
                if not subRaw:
                    continue
                else:
                    isInstantAndList = True
                    andList = list()
                    for sub in subRaw:
                        triggerName = QuestData.createTriggerName(prefix, index)
                        index += 1
                        trigger = self._QuestData__resolveTrigger(sub, triggerName, TriggerUsageTypes.UPDATE)
                        isInstantAndList &= TriggerTypes.isInstant(trigger.TriggerType)
                        andList.append(trigger)
    
                    isInstant |= isInstantAndList
                    orList.append(andList)
    
            if orList:
                return TriggerInfoList(orList, isInstant)
    
        def __resolveTrigger(self, raw: 'collections.Mapping', triggerName: 'str', usageType: 'TriggerUsageTypes') -> 'TriggerInfo':
            if not len(raw) == 1:
                raise ValueError('invalid trigger data : {}'.format(raw))
            for rawTriggerType, rawAuxArg in raw.items():
                triggerType = TriggerTypes._convert(rawTriggerType)
                if TriggerTypes.CONVERSATION == triggerType:
                    listenerQuestRole = rawAuxArg['listenerQuestRole']
                    selectionText = rawAuxArg['selectionText']
                    confirmText = rawAuxArg.get('confirmText')
                    auxArg = ConversationTriggerInfo(listenerQuestRole, selectionText, confirmText, self.QuestID, usageType)
                elif TriggerTypes.GUARDIAN_CHANGED == triggerType:
                    fromClassID = rawAuxArg['fromClassID']
                    toClassID = rawAuxArg['toClassID']
                    auxArg = GuardianChangedTriggerInfo(fromClassID, toClassID, usageType)
                elif TriggerTypes.PROPERTY == triggerType:
                    auxArg = PropertyTriggerInfo(rawAuxArg['condition'])
                elif TriggerTypes.GUARDIAN_PROPERTY == triggerType:
                    auxArg = PropertyTriggerInfo(rawAuxArg['condition'])
                elif TriggerTypes.RELATIONSHIP == triggerType:
                    auxArg = RelationshipCondition(rawAuxArg)
                elif TriggerTypes.DINE == triggerType:
                    auxArg = DineEventTriggerInfo(rawAuxArg)
                elif TriggerTypes.CONTRACT == triggerType:
                    auxArg = ContractInfo(rawAuxArg['classID'])
                elif TriggerTypes.DECK == triggerType:
                    auxArg = DeckInfo(rawAuxArg['classIDs'])
                elif TriggerTypes.HAND == triggerType:
                    auxArg = HandInfo(rawAuxArg)
                elif TriggerTypes.ACTION == triggerType:
                    auxArg = ActionInfo(rawAuxArg['actionID'], rawAuxArg['hostRole'])
                elif TriggerTypes.KILL_COUNT == triggerType:
                    targetClassID = rawAuxArg['classID']
                    targetCount = rawAuxArg['count']
                    questRole = rawAuxArg.get('questRole')
                    uiName = rawAuxArg.get('uiName')
                    auxArg = CountTriggerInfo(targetClassID, targetCount, questRole=questRole,
                      uiName=uiName)
                elif TriggerTypes.ITEM_COUNT == triggerType:
                    targetClassID = rawAuxArg['classID']
                    targetCount = rawAuxArg['count']
                    countType = rawAuxArg['countType']
                    propertyCondition = rawAuxArg.get('propertyCondition')
                    if propertyCondition is not None:
                        propertyCondition = PropertyCondition(propertyCondition)
                    uiName = rawAuxArg.get('uiName')
                    auxArg = CountTriggerInfo(targetClassID, targetCount, countType=countType,
                      propertyCondition=propertyCondition,
                      uiName=uiName)
                elif TriggerTypes.RANDOM_KILL_COUNT == triggerType:
                    candidates = rawAuxArg['classIDCandidates']
                    maxCount = rawAuxArg['countRange']['max']
                    minCount = rawAuxArg['countRange']['min']
                    auxArg = RandomCountTriggerInfo(candidates, maxCount, minCount)
                elif TriggerTypes.RANDOM_ITEM_COUNT == triggerType:
                    candidates = rawAuxArg['classIDCandidates']
                    maxCount = rawAuxArg['countRange']['max']
                    minCount = rawAuxArg['countRange']['min']
                    countType = rawAuxArg['countType']
                    auxArg = RandomCountTriggerInfo(candidates, maxCount, minCount, countType)
                elif TriggerTypes.PROXIMITY_ENTER == triggerType:
                    try:
                        auxArg = EnterTriggerInfo(rawAuxArg['target'], rawAuxArg['targetType'], rawAuxArg['range'])
                    except ValueError as e:
                        try:
                            raise ValueError('{} - {}'.format(self.QuestID, e))
                        finally:
                            e = None
                            del e
    
                elif TriggerTypes.TIMER == triggerType:
                    auxArg = rawAuxArg['delay']
                elif TriggerTypes.SELECTION == triggerType:
                    selectionMap = dict()
                    for selectionText, subMap in rawAuxArg.items():
                        selectionMap[selectionText] = subMap.get('nextQuestIDs')
    
                    auxArg = SelectionTriggerInfo(selectionMap)
                else:
                    raise ValueError('invalid trigger type : {}'.format(rawTriggerType))
                return TriggerInfo(triggerType, triggerName, auxArg)
    
        def __resolveCutscene(self, scriptData: 'natuum.cutscene.ScriptData', roleNameMap: 'None or collections.Mapping', captionPositionMap: 'None or collections.Mapping', isStaged: 'bool') -> 'CutsceneInfo':
            if roleNameMap is None:
                roleNameMap = self._QuestData__defaultRoleNameMap
            if captionPositionMap is None:
                captionPositionMap = self._QuestData__defaultCaptionPositionMap
            else:
                captionPositionMap = self._QuestData__resolveCaptionPositionMap(captionPositionMap)
            return CutsceneInfo(scriptData, roleNameMap, captionPositionMap, isStaged)
    
        def __resolveCaptionPositionMap(self, valueMap: 'None or collections.Mapping') -> 'collections.Mapping':
            converted = dict()
            if valueMap is not None:
                for key, value in valueMap.items():
                    converted[key] = natuum.cutscene._CaptionPositionTypes.fromString(value)
    
            return converted
    
        _QuestData__defaultRoleNameMap = None
        _QuestData__defaultCaptionPositionMap = None
    You can also try using Locale Emulator to simulate a korean system (might work this way)

    -- EDIT --

    Here is a link for every quest in the game

    Quest

    You can open it with your favorite text application

    By the way, what i did to get this is putting a file from the data folder, to the tpk folder (the one from unpack/pack).
    What you need to get this working, is to just have unpack.py and pack.py in a folder and a file.tpk / file.tpk.table in the root on this same folder.
    Open the unpack.py, change the name of the file @ line 256 and then run the script.
    I ran this script with pycharm, but any python ide will do.

    To pack it, it is the same thing as unpacking, place pack.py on the root of your pack/unpack folder, you should now have a folder named Quest with .cache folder inside and every quest.

    pack.py @ line 222, change the folder name for yours (ie : quest / race / soil / serverconfig... etc)
    Then run pack.py
    You have now quest.tpk and quest.tpk.table

    If this is not clear, i will provide screenshots

    As an exemple, this is the serverConfig.tpk

    Code:
    {
        "default": {
            "GatewayServers": [
                {
                    "port": 10010,
                    "host": "127.0.0.1"
                }
            ],
            "NXLogServer": "54.180.20.36:24200",
            "ChatManagerEntityID": null,
            "UnstableZoneManagerEntityID": null,
            "DocumentContentLibraryEntityID": null,
            "NXLogServiceID": "430007416",
            "terrainServers": {
                "99999": [
                    null,
                    "192.168.7.8:29000"
                ],
                "0": [
                    "192.168.7.8:29000",
                    "192.168.7.8:29000"
                ],
                "1": [
                    "192.168.7.8:29001",
                    "192.168.7.8:29001"
                ]
            },
            "ItemDesignLibraryEntityID": null,
            "GatewayEntityID": null
        },
        "peria-live": {
            "GatewayServers": [
                {
                    "port": 10010,
                    "host": "183.110.2.132"
                }
            ],
            "NXLogServer": "192.168.128.89:24200",
            "ChatManagerEntityID": null,
            "UnstableZoneManagerEntityID": null,
            "DocumentContentLibraryEntityID": null,
            "NXLogServiceID": "430011512",
            "terrainServers": {
                "99999": [
                    null,
                    "10.168.20.136:29000"
                ],
                "0": [
                    "183.110.2.136:29000",
                    "10.168.20.136:29000"
                ],
                "1": [
                    "183.110.2.136:29001",
                    "10.168.20.136:29001"
                ]
            },
            "ItemDesignLibraryEntityID": null,
            "GatewayEntityID": null
        },
        "peria-stage": {
            "GatewayServers": [
                {
                    "port": 10010,
                    "host": "183.110.1.70"
                }
            ],
            "NXLogServer": "192.168.128.88:24200",
            "ChatManagerEntityID": null,
            "UnstableZoneManagerEntityID": null,
            "DocumentContentLibraryEntityID": null,
            "NXLogServiceID": "430008440",
            "terrainServers": {
                "99999": [
                    null,
                    "10.168.10.72:29000"
                ],
                "0": [
                    "183.110.1.72:29000",
                    "10.168.10.72:29000"
                ],
                "1": [
                    "183.110.1.72:29001",
                    "10.168.10.72:29001"
                ]
            },
            "ItemDesignLibraryEntityID": null,
            "GatewayEntityID": null
        },
        "peria-dev": {
            "GatewayServers": [
                {
                    "port": 10010,
                    "host": "192.168.8.12"
                }
            ],
            "NXLogServer": "54.180.20.36:24200",
            "ChatManagerEntityID": null,
            "UnstableZoneManagerEntityID": null,
            "DocumentContentLibraryEntityID": null,
            "NXLogServiceID": null,
            "terrainServers": {
                "99999": [
                    null,
                    "192.168.7.8:29000"
                ],
                "0": [
                    "192.168.7.8:29000",
                    "192.168.7.8:29000"
                ],
                "1": [
                    "192.168.7.8:29001",
                    "192.168.7.8:29001"
                ]
            },
            "ItemDesignLibraryEntityID": null,
            "GatewayEntityID": null
        }
    }

  11. #26
    Infraction bannëd xlw00tlx is online now
    MemberRank
    Aug 2014 Join Date
    outer space!Location
    1,468Posts

    Re: Peria chronicles

    where is the client config file for graphics?it lagged so badly and i cant resize the window


  12. #27
    Valued Member ihaki is offline
    MemberRank
    Nov 2008 Join Date
    FranceLocation
    144Posts

    Re: Periachronicles

    Is anyone still working on this ?

    Didn't touch it for ages now...

    I might take a look at it pretty soon, after i'm done with some other project

  13. #28
    Apprentice OniZero is offline
    MemberRank
    Oct 2013 Join Date
    22Posts

    Re: Periachronicles

    Either the hype died down or they're trying to have something to show.

  14. #29
    Valued Member ihaki is offline
    MemberRank
    Nov 2008 Join Date
    FranceLocation
    144Posts

    Re: Periachronicles

    I don't know how to delete this
    Last edited by ihaki; 27-03-21 at 08:03 PM. Reason: Update

  15. #30
    Valued Member ihaki is offline
    MemberRank
    Nov 2008 Join Date
    FranceLocation
    144Posts

    Re: Periachronicles

    Here we go :









Page 2 of 3 FirstFirst 123 LastLast

Advertisement