rendered paste body#!/usr/bin/pythonMAXTRANS = 5000DMF = 16DVELF = 1 # enums ganked from sauerbraten/src/fpsgame/game.h for the most partganked_enums = """ SV_INITS2C, SV_INITC2S, SV_POS, SV_TEXT, SV_SOUND, SV_CDIS, SV_DIED, SV_DAMAGE, SV_SHOT, SV_FRAGS, SV_GUNSELECT, SV_MAPCHANGE, SV_MAPVOTE, SV_ITEMSPAWN, SV_ITEMPICKUP, SV_DENIED, SV_PING, SV_PONG, SV_CLIENTPING, SV_TIMEUP, SV_MAPRELOAD, SV_ITEMACC, SV_SERVMSG, SV_ITEMLIST, SV_RESUME, SV_EDITENT, SV_EDITF, SV_EDITT, SV_EDITM, SV_FLIP, SV_COPY, SV_PASTE, SV_ROTATE, SV_REPLACE, SV_DELCUBE, SV_NEWMAP, SV_GETMAP, SV_MASTERMODE, SV_KICK, SV_CURRENTMASTER, SV_SPECTATOR, SV_SETMASTER, SV_SETTEAM, SV_BASES, SV_BASEINFO, SV_TEAMSCORE, SV_REPAMMO, SV_FORCEINTERMISSION, SV_ANNOUNCE, SV_CLIENT"""sauerConst = {}i = 0for var in ganked_enums.split(","): sauerConst[var.strip()] = i i += 1import sauerServerimport enetimport stuffimport timefrom threading import Thread# needed for sauerBufferfrom array import array# needed for exception loggingimport sys, tracebacksauer_players = {}def appendPacket(a,b): a.data += b.dataclass sauerBuffer: def __init__(self, ent=None): if ent is not None: intro = posPacket(ent).data self.data = array('B', intro) else: self.data = array('B') def putint(self, value): if (-127 < value) and (value < 128): self.data.insert(0, value & 0xFF) elif (-0x8000 <= value) and (value < 0x8000): self.data.insert(0, 0x80) self.data.insert(0, value & 0xFF) self.data.insert(0, (value >> 8) & 0xFF) else: self.data.insert(0, 0x81) self.data.insert(0, value & 0xFF) self.data.insert(0, (value >> 8) & 0xFF) self.data.insert(0, (value >> 16) & 0xFF) self.data.insert(0, (value >> 24) & 0xFF) def getint(self): a = self.data.pop() if (0x80 == a): a = self.data.pop() a |= self.data.pop() << 8 if (0x8000 & a): # fix negative values a = - (0xFFFF ^ (a-1)) elif (0x81 == a): a = self.data.pop() a |= self.data.pop() << 8 a |= self.data.pop() << 16 a |= self.data.pop() << 24 if (0x80000000 & a): # fix negative values a = - (0xFFFFFFFF ^ (a-1)) elif (0x80 & a): # fix negative values a = - (0xFF ^ (a-1)) return a def putuint(self, value): """much smaller encoding for unsigned integers up to 28 bits, but can handle sign smaller encoding for unsigned integers up to 28 bits, but can handle signed""" if ((1 << 28) <= value) or (value < -(1 << 27)): raise ValueError("more than 28 bits used") if (value < 0) or ((1<<21) <= value): self.data.insert(0, 0x80 | (value & 0x7F)) self.data.insert(0, 0x80 | ((value >> 7) & 0x7F)) self.data.insert(0, 0x80 | ((value >> 14) & 0x7F)) self.data.insert(0, 0xFF & (value >> 21)) elif (value < (1 << 7)): self.data.insert(0, 0xFF & value) elif (value < (1 << 14)): self.data.insert(0, 0x80 | (value & 0x7F)) self.data.insert(0, 0xFF & (value >> 7)) else: self.data.insert(0, 0x80 | (value & 0x7F)) self.data.insert(0, 0x80 | ((value >> 7) & 0x7F)) self.data.insert(0, 0xFF & (value >> 14)) def getuint(self): a = self.data.pop() if (0x80 & a): a += (self.data.pop() << 7) - 0x80 if (a & (1<<14)): a += (self.data.pop() << 14) - (1<<14) if (a & (1<<21)): a += (self.data.pop() << 21) - (1<<21) if(a & (1<<28)): # fix negative values a |= 0xF0000000 a = - (0xFFFFFFFF ^ (a-1)) return a def hasData(self): if len(self.data) > 0: return True else: return False def putstr(self, msg): for char in msg: self.putint(ord(char)) self.putint(0) def getstr(self): buffer = "" x = self.getint() while x != 0: buffer += chr(x) x = self.getint() return buffer def test_int(self, exhaustive = 0): """test putint and getint""" print "test_int: border cases ... " for value in [0, 1, -1, 127, -126, 128, -127, 0x7FFF, -0x7FFF, -0x8000, 0x8000, -0x8001, 0x7ABCDEF9, -0x7ABCDEF9, 0x7FFFFFFF, -0x7FFFFFFE, 1 << 27, (1 << 27) - 1, -(1 << 27), 1-(1 << 27), 1 << 28, (1 << 28) - 1, -(1 << 28), 1-(1 << 28)]: p = sauerBuffer() try: p.putint(value) except: print "could not putint ", value continue if (p.getint() != value) or (0 != len(p.data)): print "FAILED ", value if(exhaustive): print "test_int: exhaustive ... (this is going to take quite a bit)" b = 2 ** 31 lower_bound = -b upper_bound = b i = lower_bound while i < upper_bound: p = sauerBuffer() try: p.putint(i) except: print "could not putint ", i continue if(p.getint() != i): print "FAILED ", i i+= 1 print "DONE" def test_uint(self, exhaustive = 0): """test putuint and getuint""" print "test_uint: border cases ... " for value in [0, 1, -1, 127, -126, 128, -127, 0x7FFF, -0x7FFF, -0x8000, 0x8000, -0x8001, 1 << 27, (1 << 27) - 1, -(1 << 27), 1-(1 << 27), (1 << 28) - 1]: p = sauerBuffer() try: p.putuint(value) except: print "could not putuint ", value continue if (p.getuint() != value) or (0 != len(p.data)): print "FAILED ", value, "(", result, ")" if(exhaustive): print "test_uint: exhaustive ... (this is going to take quite a bit)" b = (1 << 28) - 1 lower_bound = -0xFFFFF upper_bound = b i = lower_bound while i < upper_bound: p = sauerBuffer() try: p.putuint(i) except: print "could not putuint ", i continue if (p.getuint() != i) or (0 != len(p.data)): print "FAILED ", i i+= 1 print "DONE"class sauerAdvertiserThread(Thread): __stop = False def stop(self): self.__stop = True def run(self): # register print "advertising server presence to sauerbraten master server" while not self.__stop: # TODO cant sleep this long or takes forever to shutdown when we wait for this thread to quit #time.sleep(60 * 15) passclass sauerServerThread(Thread): __stop = False buffer = sauerBuffer() def stop(self): self.__stop = True def run(self): serverAddress = enet.Address(enet.HOST_ANY, 28785) # SAUERBRATEN_SERVER_PORT server = enet.Host(serverAddress, 32, 0, 0) print "now listening for sauerbraten connections on port ", serverAddress.port while not self.__stop: event = server.service(1000) #print event.type print "event type ", event.type, " got ", id(event.peer) if event.type == 1: new_player = sauerPlayer(event.peer) elif event.type == 2: leaving_player = sauer_players[id(event.peer)] leaving_player.onDisconnect() elif event.type == 3: # why is this showing a different peer id self.handleInput(sauer_players[id(event.peer)], event.packet) for player in sauer_players: sauer_players[player].sendQueuedUpdates() #time.sleep(.03) def handleInput(self, player, packet): pw = self.buffer pw.data += packet.data if pw.debug is True: print packet.data while pw.hasData(): type = pw.getint() if pw.debug is True: print "got ", type if type == 2: #print "POS packet pw.getint() # client num #print "xyz yaw" player.o = (pw.getuint() / DMF, pw.getuint() / DMF, pw.getuint() / DMF) # xyz player.yaw = pw.getuint() # yaw player.pitch = pw.getint() # pitch player.roll = pw.getint() # roll player.v = (pw.getint()/DVELF, pw.getint()/DVELF, pw.getint()/DVELF) # velocity xyz #print "State Info" tmp = pw.getint() if tmp & 0x20: # physstate pack if pw.debug is True: print " ------- found x,y gravity vectors" player.g = (pw.getint()/DVELF, pw.getint()/DVELF, player.g[2]) # gravity x y plus current elif tmp & 0x10: # physstate pack if pw.debug is True: print " ------- found z gravity vector" player.g = (player.g[0], player.g[1], pw.getint()/DVELF) #gravity z pw.getint() # state pack player.setPosition(player.o, player.yaw, player.pitch, player.roll, player.v) if player.waitingForMapChange == True: player.onMapChange() player.waitingForMapChange = False elif type == 1: # player init info u_name = pw.getStr() # ignore name changes from registered players if player.dbID == -1: player.name = u_name team = pw.getStr() if team == "VILLE": player.enhanced = True player.lifesequence = pw.getint() #pw.getint() #pw.getint() print "got name ", u_name, " team ", team elif type == 3: # text from player, command or chat text = pw.getStr().strip() try: player.cmd(text) except: t, v, tb = sys.exc_info() print "EXCEPTION:\n".join(traceback.format_exception(t,v,tb)) elif type == 7: victim_num = pw.getint() dmg = pw.getint() # damage pw.getint() # lifesequence of victim pw.getint() #x pw.getint() #y pw.getint() #z victim = None if victim_num == -1: victim = player elif stuff.ents.has_key(victim_num): victim = stuff.ents[victim_num] if victim != None: if dmg == 50: player.attack(victim) else: player.select(victim) elif type == 10: # map name pw.getStr() name = pw.getint() elif type == 16: # ping pw.getint() elif type == 21: # server message, needs to be here to avoid crashing on forged packet becasue its a fixed length message pw.getStr() elif type == 23: #item list while pw.getint() != -1: continue elif type == 24: # entity type print "i ", pw.getint() print "x ", pw.getint() print "y ", pw.getint() print "z ", pw.getint() print "type ", pw.getint() print "p1 ", pw.getint() print "p2 ", pw.getint() print "p3 ", pw.getint() print "p4 ", pw.getint() elif type == 29 or type == 31: # copy from edit mode x1 = pw.getint() # x1 y1 = pw.getint() # y1 z1 = pw.getint() # z1 x2 = pw.getint() # x2 y2 = pw.getint() # y2 z2 = pw.getint() # z2 gridsize = pw.getint() #grid pw.getint() #grid orientation pw.getint() #cx pw.getint() #cxs pw.getint() #cy pw.getint() #cys pw.getint() #corner if(type == 31): pw.getint() # tex pw.getint() # newtex pw.getint() # orient # shove selection coordinates into a structure so we can organize them properly tselect = [x1,y1,z1,x1 + (x2 * gridsize),y1 + (y2 * gridsize),z1 + (z2 * gridsize)] psA = [0,0,0] psB = [0,0,0] for i in (0,1,2): if tselect[i] < tselect[i+3]: psA[i] = tselect[i] psB[i] = tselect[i+3] else: psB[i] = tselect[i+3] psA[i] = tselect[i] player.selectionA = (psA[0], psA[1], psA[2]) player.selectionB = (psB[0], psB[1], psB[2]) player.msg("Area selected.") elif type == 38: # ctf bases while pw.getint() != -1: getint(p) getint(p) elif type == 39: # ctf base info pw.getint() pw.getStr() pw.getStr() pw.getint() elif type == 40: # team score pw.getStr() pw.getint() else: print "Unknown packet type %d." % type print "We should never get a packet type %d!" % type print "Disconnecting ", player.name player.onDisconnect() # empty the buffer so we don't proccess any other packets self.buffer.data = "" #raise SystemExit #if(x > 0 & pw.hasData()): # if(type < 1): # for a in range(x): pw.getint() # print "Recovered, skipped %d integer" % x #else: # print "Non-fixed packet size, thus non-recoverable." #raise SystemExitdef newGuiPacket(name, contents): b = sauerBuffer() b.putint(sauerConst['SV_NEWGUI']) b.putstr(name) b.putstr(contents) return bdef showGuiPacket(name): b = sauerBuffer() b.putint(sauerConst['SV_SHOWGUI']) b.putstr(name) return bdef hideGuiPacket(name): b = sauerBuffer() b.putint(sauerConst['SV_HIDEGUI']) b.putstr(name) return bdef statsPacket(ent): b = sauerBuffer() b.putint(sauerConst['SV_STATS']) b.putint(ent.lifesequence) b.putint(ent.hp) return bdef mapChangePacket(map, mode): b = sauerBuffer() b.putint(sauerConst['SV_MAPCHANGE']) b.putstr("maps/" + map) b.putint(mode) return bdef welcomePacket(ent): b = sauerBuffer() b.putint(sauerConst['SV_INITS2C']) b.putint(ent.entnum) b.putint(252) # Protocol Version # send game mode 4 to start so players cant do dmg to self with grenade launcher b.putint(4) return bdef msgPacket(msg): b = sauerBuffer() b.putint(sauerConst['SV_SERVMSG']) b.putstr(msg) return bdef itemListPacket(): b = sauerBuffer() b.putint(sauerConst['SV_ITEMLIST']) b.putint(-1) return bdef posPacket(ent): b = sauerBuffer() b.putint(sauerConst['SV_POS']) b.putint(ent.entnum) b.putuint(ent.o[0] * DMF) b.putuint(ent.o[1] * DMF) b.putuint(ent.o[2] * DMF) b.putuint(ent.yaw) b.putint(ent.pitch) b.putint(ent.roll) b.putint(ent.v[0] * DVELF) b.putint(ent.v[1] * DVELF) b.putint(ent.v[2] * DVELF) # TODO: should be gravity x or gravity Y if False or False: b.putint(self.g[0] * DVELF) b.putint(self.g[1] * DVELF) # TODO: should be gravity 0 if False: b.putint(self.g[0] * DVELF) b.putint(ent.move_pack) return bdef infoPacket(ent): # even though we don't need immortal objects to have hp in sauerville, sauerbraten does if hasattr(ent, "maxhp"): maxhp = ent.maxhp else: maxhp = 1 b = sauerBuffer() b.putint(sauerConst['SV_INITC2S']) b.putstr(ent.name) b.putstr(ent.model) # used by sauer for frags b.putstr(ent.lifesequence) b.putstr(maxhp) b.putstr(0) # frags return bdef damagePacket(ent, damage): b = sauerBuffer(ent) b.putint(sauerConst['SV_DAMAGE']) b.putint(self.entnum) putint(damage) putint(ent.lifesequence) # frags putint(0) putint(0) putint(0) return bdef editEntPacket(experiencer, ent, itemnum, o, type, a, b, c, d): b = sauerBuffer(experiencer) b.data += ent.posPacket().data b.putint(sauerConst['SV_EDITENT']) b.putint(itemnum) b.putint(o[0] * DMF) b.putint(p[1] * DMF) b.putint(o[2] * DMF) b.putint(type) # these 4 have different meanings depending on enttype b.putint(a) b.putint(b) b.putint(c) b.putint(d) return bdef removeEntPacket(experiencer, ent): b = sauerBuffer() b.putint(CV_DIS) b.putint(ent.entnum) b.putint()class sauerPlayer(stuff.player): locked = False debug = True def __init__(self, peer): self.chanPos = [] self.chanServ = [] self.chanClient = [] self.peer = peer sauer_players[id(self.peer)] = self super(sauerPlayer, self).__init__() #if self.debug is not True: self.queue(welcomePacket(self), desc="welcome") self.msg("register <password> to create a character or use login if you already have an account.") self.waitingForMapChange = True def queue(self, packet, ent=None, isPosUpdate=False, desc=None): if self.debug is True: if desc is not None: print "sending ", desc g = sauerBuffer() g.data = packet.data while g.hasData(): print g.getint() else: print "sending unknown packet" while self.locked: pass if ent is None: if isPosUpdate: self.chanPos.append(packet) else: self.chanServ.append(packet) else: self.chanClient.append((ent, packet)) def menu(self, name): return sauerMenu(self, name) def sendQueuedUpdates(self): while self.locked: pass self.locked = True for channel in [(self.chanPos, 1), (self.chanServ, 2)]: if channel[0] != []: data = "" for packet in channel[0]: data += packet.data p = enet.Packet(data, flags=enet.PACKET_FLAG_RELIABLE) self.peer.send(channel[1], p) if self.chanClient != []: data = "" for ent,unintroduced_packet in self.chanClient: data += introductionPacket(ent, unintroduced_packet).data data += unintroduced_packet.data p = enet.Packet(data, flags=enet.PACKET_FLAG_RELIABLE) self.peer.send(2, p) self.chanPos = [] self.chanServ = [] self.chanClient = [] self.locked = False def hearEquipment(self): self.msg("EQUIPMENT") menu = self.menu("Equipment") for slot in self.slots: if self.slots[slot] is not None: name = self.slots[slot].name else: name = "<nothing>" submenu = self.menu("Equiped Item") submenu.button("drop", "say drop") submenu.button("sell", "say sell") menu.button(slot + ": " + name, "newgui item [ " + submenu.txt + "]; showgui item;") menu.fire() def hearInventory(self): menu = self.menu("Inventory") for item in self.inventory: submenu = self.menu("item") submenu.button("drop", "say drop " + item.name) submenu.button("sell", "say sell " + item.name) menu.button(item.name, "newgui item [ " + submenu.txt + "]; showgui item;") if len(self.inventory) == 0: menu.txt = "<nothing>" menu.button("status", "select " + self.name + "; say examine;") menu.fire() def hearExamine(self, ent): menu = self.menu("Examine") sale_set = set() if hasattr(ent, "selling"): sale_set = ent.selling if hasattr(ent, "making"): sale_set = sale_set.union(ent.making) if hasattr(ent, "selling") and sale_set != []: menu.tab("selling") for x in sale_set: menu.button(x.name, "say buy " + x.name) menu.tab("info") menu.text(ent.name) if hasattr(ent, "maxhp"): menu.text("hp: " + str(ent.hp) + " / " + str(ent.maxhp)) if hasattr(ent, "xp"): menu.text("xp: " + str(ent.xp)) if hasattr(ent, "level"): menu.text("level: " + str(ent.level)) if hasattr(ent, "gold"): menu.text("gold: " + str(ent.gold)) if hasattr(ent, "lootable"): if ent.lootable == "auto": menu.button("say take " + ent.name, "get") else: if len(ent.contents) == 0: menu.text(ent.name + " is empty.") else: for x in ent.contents: menu.button("say take " + x.name, "take " + x.name) if self.admin: menu.tab("admin") if(hasattr(self.selection, "face")): menu.button("face", "say face") if(hasattr(self.selection, "setPosition")): menu.button("summon", "say summon") if(hasattr(self.selection, "name")): menu.button("rename", "saycommand [/say foo ]") if(hasattr(self.selection, "save")): menu.button("save", "say save") menu.fire() def hearStats(self): if self.debug is True: return self.queue(statsPacket(self), desc="hearStats") def introductionPacket(ent, unintroduced_packet): b = sauerBuffer() l = len(unintroduced_packet.data) b.data += struct.pack("qqq", ent.entnum, length & 0xFF, length >> 8) return b def hearPoof(self, ent): if self.debug is True: return self.queue(removeEntPacket(ent), desc="hearPoof") def hearMapChange(self, map, mode): #if self.debug is True: return self.queue(mapChangePacket(map, mode), desc="hearMapChange") def hearLocation(self, ent): #if self.debug is True: return if ent is not self: #self.msg("I heard a " + ent.name + "move to " + str(ent.o[0]) + ", " + str(ent.o[1])) self.queue(posPacket(ent), isPosUpdate=True, desc="hearLocation") def hearInfo(self, ent): if self.debug is True: return if ent is not self: self.queue(infoPacket(ent), ent=ent, desc="hearInfo") def msg(self, text): if self.debug is True: return self.queue(messagePacket(text), desc="msg") def select(self, ent): super(sauerPlayer, self).select(ent) self.cmd("examine") def onDisconnect(self): super(sauerPlayer, self).onDisconnect() del sauer_players[self.peer] self.peer.disconnect() def teleport(self, nexto): #kinda hacky to have the editEntPacket not be a self method #but in the long term we shouldn't need it because we should #be able to remove the pos from the start of editEntPacket if self.debug is True: return super(sauerPlayer, self).onDisconnect() placeTeleDestPacket = editEntPacket(self, 100, nexto, 16, 27, 1, 0, 0) # place teleporter on top of player placeTeleDoorPacket = editEntPacket(self, 101, nexto, 15, 1, 0, 0, 0) self.queue(placeTeleDestPacket, ent=self, desc="teleport place destination") self.queue(placeTeleDoorPacket, ent=self, desc="teleport place entrance") super(sauerPlayer, self).teleport(nexto) # we have never seen our entnum connect so we believe its another player # make us believe we have disconnected removeGhostPacket = removeEntPacket(self) self.queue(removeGhostPacket(), desc="teleport remove door") def onMapChange(self): if(self.dbID != -1 and self.nextMapLoc != False and self.nextMapLoc != (0,0,0)): # try to avoid infinite teleporting loops :) #print "teleporting ", self.name #if self.distance(self.nextMapLoc) > 20: self.teleport(self.nextMapLoc) self.nextMapLoc = False super(sauerPlayer, self).onMapChange() def setMap(self, new_map, nextloc = False): self.waitingForMapChange = True old_ents = self.world.ents # remove all the old ents for i in old_ents: self.hearPoof(old_ents[i]) # add all the new ents world = worlds.getWorldByMapName(new_map) new_ents = self.world.ents for i in new_ents: self.hearInfo(new_ents[i]) # let admins have edit privs, tell client to change the map if self.admin: mode = 1 else: mode = 4 self.hearMapChange(new_map, mode) # this should eventually go away entirely self.queue(itemListPacket(), desc="itemListPacket from setMap") # send hp, etc self.hearStats() # actually do the real map change in the stuff super(sauerPlayer, self).setMap(new_map, nextloc)class sauerMenu: txt = "" def __init__(self, player, name): self.name = name self.player = player def title(self, text): self.txt += "guititle [" + self.sauerfy(text) + "];" def tab(self, text): self.txt += "guitab [" + self.sauerfy(text) + "];" def text(self, text): self.txt += "guitext [" + self.sauerfy(text) + "];" def button(self, text, cmd): self.txt += "guibutton [" + self.sauerfy(text) + "] [" + self.sauerfy(cmd) + "];" def fire(self): pass self.player.queue(newGuiPacket("A", "B"), desc="new gui from fire") self.player.queue(showGuiPacket("B"), desc="show gui from fire") def sauerfy(self, text): return textif __name__ == '__main__': n = sauerBuffer() #n.test_int() #n.test_uint() n.putint(0) #print repr(n.data) #print n.getint() print "to see the real deal run server.py NOT this file"