// PORT read-only feature protocol. Requests are serialized, bounded and tied to this game session. static const char c_szFeatureServiceScript[] = R"PY( import app import game import net import ui from mt_wiki_data import Number BITS={'mob':1,'chest':2,'stats':4,'sources':8,'dungeons':16,'calendar':32,'bosses':256,'travel':512} WHOLE=('stats','dungeons','bosses') # one reply with every row; no subject or page g_service=None g_tick=None _serial=0 def Signed(text): value=int(text) if len(text)>20 or not -(1<<63)<=value<(1<<63):raise ValueError('invalid stat value') return value class Service(object): def __init__(self): self.queue=[];self.pending=None;self.mask=None;self.nextSend=app.GetTime()+0.5 self.deadline=0;self.closed=False def Request(self,kind,subject,offset,callback): global _serial if kind not in BITS or not 0<=subject<=4294967295 or not 0<=offset<=4096 or len(self.queue)>=8: callback(None,'invalid');return 0 if (kind in WHOLE and (subject or offset)) or (kind=='calendar' and subject!=1): callback(None,'invalid');return 0 _serial=(_serial%2147483647)+1 self.queue.append(dict(id=_serial,kind=kind,subject=subject,offset=offset,callback=callback,retries=0)) return _serial def Tick(self): global _serial now=app.GetTime() if self.closed:return if self.pending and now>self.deadline: if self.pending['kind']=='cap': self.mask=0;self.pending=None else:self.Finish(None,'timeout') self.nextSend=now+0.3 if self.pending or not self.queue or now512:return True request=Number(w[2],2147483647) if not p or p['id']!=request:return True op=w[1] if op=='cap' and len(w)==6 and p['kind']=='cap': if Number(w[3])!=1:raise ValueError('unsupported protocol') self.mask=Number(w[4],4294967295);Number(w[5]);self.pending=None elif op=='error' and len(w)==4: if w[3]=='rate' and p['kind']!='cap' and p['retries']<2: p['retries']+=1;self.queue.insert(0,p);self.pending=None;self.nextSend=app.GetTime()+0.4 else:self.Finish(None,w[3]) elif op=='begin' and len(w)==9 and p['kind']!='cap': if 'result' in p or w[3]!=p['kind'] or Number(w[4])!=p['subject'] or Number(w[5])!=p['offset']: raise ValueError('mismatched response') total=Number(w[6],128 if p['kind'] in WHOLE else 256 if p['kind']=='calendar' else 4096) if total=limit or Number(w[3])!=p['offset']+len(rows):raise ValueError('invalid row order') if op=='row' and len(w)==12 and p['kind'] not in WHOLE+('calendar',): values=[Number(v) for v in w[4:]] if values[2] not in (0,1,2,3,4,5,10,11,20,21) or not values[0]:raise ValueError('invalid row') rows.append(values) elif op=='event' and len(w)==11 and p['kind']=='calendar': values=[Number(v) for v in w[4:]] if not values[0] or values[1] not in (1,2) or values[2]>=values[3] or values[4]>255 or values[5]>3 or values[6]>1:raise ValueError('invalid calendar event') if any(r[0]==values[0] for r in rows):raise ValueError('duplicate event') rows.append(values) elif op=='boss' and len(w)==13 and p['kind']=='bosses': # VNUM MAP X Y CHANNEL ALIVE MAX NEXT PERIOD; X/Y are map-local and may be negative. values=[Number(w[4]),Number(w[5]),Signed(w[6]),Signed(w[7])]+[Number(v) for v in w[8:]] if not values[0] or not values[1] or values[4]>255 or values[6]>255 or values[5]>values[6] or not values[8]:raise ValueError('invalid boss') rows.append(values) elif op=='stat' and len(w)==6 and p['kind'] in ('stats','dungeons'): if not w[4].replace('_','').isalpha() or len(w[4])>32 or any(r[0]==w[4] for r in rows):raise ValueError('invalid stat') rows.append((w[4],Signed(w[5]))) else:raise ValueError('invalid row format') elif op=='end' and len(w)==4 and 'result' in p: result=p['result'];expected=result['total'] if p['kind'] in WHOLE else min(8,result['total']-p['offset']) if Number(w[3])!=expected or len(result['rows'])!=expected:raise ValueError('incomplete reply') if p['kind']=='dungeons': values=dict(result['rows']) required=('lair_end','lair_join_end','lair_phase','lair_ticket','lair_tickets','lair_boss','lair_owned','lair_alive','lair_npc','lair_map') if any(k not in values or values[k]<0 for k in required):raise ValueError('incomplete dungeon snapshot') if values['lair_phase']>2 or not values['lair_ticket'] or values['lair_join_end']>values['lair_end']:raise ValueError('invalid dungeon state') weekly=('weekly_end','weekly_hunt_end','weekly_active','weekly_npc','weekly_map') if any(k in values for k in weekly) and any(k not in values or values[k]<0 for k in weekly):raise ValueError('incomplete weekly state') if values.get('weekly_active',0)>1:raise ValueError('invalid weekly state') self.Finish(result) else:raise ValueError('unknown response') except (ValueError,TypeError,IndexError,OverflowError): if p and self.pending is p: if p['kind']=='cap':self.mask=0;self.pending=None else:self.Finish(None,'bad_reply') return True def Close(self): self.closed=True;self.pending=None;self.queue=[] class TickWindow(ui.Window): def __init__(self): ui.Window.__init__(self);self.AddFlag('not_pick');self.SetSize(0,0);self.Show() def OnUpdate(self): if g_service:g_service.Tick() _open=game.GameWindow.Open _close=game.GameWindow.Close _command=game.GameWindow.BINARY_ServerCommand_Run def Open(self): global g_service,g_tick _open(self);g_service=Service();g_tick=TickWindow() def Close(self): global g_service,g_tick if g_service:g_service.Close();g_service=None if g_tick:g_tick.Hide();g_tick=None _close(self) def Command(self,line): if line.startswith('MTF1 '): if g_service:g_service.Receive(line) return 1 return _command(self,line) game.GameWindow.Open=Open game.GameWindow.Close=Close game.GameWindow.BINARY_ServerCommand_Run=Command )PY";