ref: python3 conversion#2
Conversation
kangsterizer
left a comment
There was a problem hiding this comment.
Thanks for doing this!
Just a couple of nits - but it would also be helpful if you were able to split the commits in 2:
- the tab/space conversions
- the rest of the p3 conversion
this allows the diff to be more readable and its easier to catch any possible issue
otherwise, ill take another quick look when possible
|
thanks for the review, I totally didn't mean to PR this against upstream yet. I meant to PR it against my own fork as a way to track my progress. Good feedback though, i'll make sure to separate out the tab/space stuff vs. the rest |
This reverts commit 176c216.
ref: convert tabs to spaces, prt 2
|
Made some progress.. Now it's a matter of trying to connect and seeing what breaks. To test i've been running it in th container with:
With the latest changes, it'll start the server, but i haven't successfully managed to connect yet. Still working through the issues. To see the diff without the whitespace changes: 509df1b?diff=unified&w=1 |
| # this is an avaraline extension for nick coloring | ||
| if self.color >= 0: | ||
| data += pack( "!L" , self.color ) | ||
| # if self.color >= 0: |
|
come back! ;) |
|
@kangsterizer a lot remains to be tested. I have yet to login with the native client through Wine (i've only got apple silicon / linux machines to test with).. so far, simple actions work:
Full disclosure: I had claude help me get this moving again. So I'll be reviewing it closely as well. I did update the instructions for running the container with a bind mount and added port mappings for downloads. |
| raise HLException( "You are already logged in." , False) | ||
|
|
||
| login = HLEncode( packet.getString( DATA_LOGIN , HLEncode( "guest" ) ) ) | ||
| password = HLEncode( packet.getString( DATA_PASSWORD , "" ) ) | ||
| reason = server.checkForBan( user.ip ) | ||
|
|
||
| if reason != None: | ||
| raise HLException , ( "You are banned: %s" % reason , True ) | ||
|
|
||
| user.account = server.database.loadAccount( login ) | ||
| if user.account == None: | ||
| raise HLException , ( "Login is incorrect." , True ) | ||
| if user.account.password != md5( password ).hexdigest(): | ||
| user.nick = packet.getString( DATA_NICK , "unnamed" ) | ||
| server.logEvent( LOG_TYPE_LOGIN , "Login failure" , user ) | ||
| raise HLException , ( "Password is incorrect." , True ) | ||
| if user.account.fileRoot == "": | ||
| user.account.fileRoot = FILE_ROOT | ||
|
|
||
| self.handleUserChange( server , user , packet ) | ||
|
|
||
| info = HLPacket( HTLS_HDR_TASK , packet.seq ) | ||
| info.addString( DATA_SERVERNAME , SERVER_NAME ) | ||
| server.sendPacket( user.uid , info ) | ||
| server.logEvent( LOG_TYPE_LOGIN , "Login successful" , user ) | ||
| server.database.updateAccountStats( login , 0 , 0 , True ) | ||
| # Diagnostics: dump every object so we can see exactly what the | ||
| # client sent (type, size, hex). Helpful when porting against | ||
| # third-party clients whose framing might differ subtly. | ||
| try: | ||
| for obj in packet.objs: | ||
| hex_data = obj.data.hex() if isinstance( obj.data , (bytes , bytearray) ) else repr( obj.data ) | ||
| server.log.debug( | ||
| " login pkt obj type=0x%04x len=%d data=%s" , | ||
| obj.type , len( obj.data ) , hex_data , | ||
| ) | ||
| except Exception: | ||
| pass | ||
|
|
||
| # Set this after login, so the user does not get their own join packet. | ||
| # link user.valid = True | ||
| server.handleUserLogin( user ) #link | ||
|
|
||
| if user.isIRC: | ||
| ( c , u ) = server.clients[user.uid] | ||
| user.nick = user.nick.replace( " " , "_" ) | ||
|
|
||
| c.transport.write ( ":%s!~%s@localhost JOIN :#public\r\n" % (user.nick, user.nick) ) | ||
| userlist = server.getOrderedUserlist() | ||
| nicks = "" | ||
| for myuser in userlist: | ||
| if myuser.uid != user.uid: | ||
| nicks += " "+ircCheckUserNick( myuser ) | ||
| data = ":"+IRC_SERVER_NAME+" 353 "+user.nick+" = #public :"+ircCheckUserNick( user )+nicks+"\r\n" | ||
| data += ":"+IRC_SERVER_NAME+" 366 "+user.nick+" #public :End of /NAMES list.\r\n" | ||
| data += "NOTICE AUTH:*** You have been successfull logged in !\r\n" | ||
| data += "NOTICE *:*** You have been forced to join #public\r\n" | ||
| c.transport.write( data ) | ||
|
|
||
| # show welcome msg, needs script support in exec/login !!! | ||
| ret = "" | ||
| ret = shell_exec( user , 'login', '') | ||
| if ret != None: | ||
| chat = HLPacket( HTLS_HDR_CHAT ) | ||
| chat.addString( DATA_STRING , ret ) | ||
| server.sendPacket( user.uid , chat ) | ||
|
|
||
| def handleUserChange( self , server , user , packet ): | ||
| oldnick = user.nick | ||
| user.nick = packet.getString( DATA_NICK , user.nick ) | ||
| user.icon = packet.getNumber( DATA_ICON , user.icon ) | ||
| user.color = packet.getNumber( DATA_COLOR , user.color ) | ||
|
|
||
| # Limit nickname length. | ||
| user.nick = user.nick[:MAX_NICK_LEN] | ||
|
|
||
| # Set their admin status according to their kick priv. | ||
| #if user.hasPriv( PRIV_KICK_USERS ): | ||
| # user.status |= STATUS_ADMIN | ||
| #else: | ||
| # user.status &= ~STATUS_ADMIN | ||
|
|
||
| # Check to see if they can use any name; if not, set their nickname to their account name. | ||
| if not user.hasPriv( PRIV_USE_ANY_NAME ): | ||
| user.nick = user.account.name | ||
|
|
||
| change = HLPacket( HTLS_HDR_USER_CHANGE ) | ||
| change.addNumber( DATA_UID , user.uid ) | ||
| change.addString( DATA_NICK , user.nick ) | ||
| change.addNumber( DATA_ICON , user.icon ) | ||
| change.addNumber( DATA_STATUS , user.status ) | ||
| change.addString ( DATA_IRC_OLD_NICK , oldnick ) | ||
| if user.color >= 0L: | ||
| change.addInt32( DATA_COLOR , user.color ) | ||
|
|
||
| server.broadcastPacket( change ) | ||
|
|
||
| def handleUserList( self , server , user , packet ): | ||
| list = HLPacket( HTLS_HDR_TASK , packet.seq ) | ||
| for u in server.getOrderedUserlist(): | ||
| list.addBinary( DATA_USER , u.flatten() ) | ||
| server.sendPacket( user.uid , list ) | ||
| # ``DATA_LOGIN`` and ``DATA_PASSWORD`` carry XOR-encoded raw bytes, | ||
| # not text — use ``getBinary``. ``HLDecode`` auto-detects the | ||
| # client's XOR mask (0xFF for classic clients, 0x7F for the | ||
| # Mierau Swift client) and returns plaintext ``bytes``. The login | ||
| # goes to the account DB which stores ``str``, so decode it; the | ||
| # password stays bytes for md5. | ||
| raw_login_wire = packet.getBinary( DATA_LOGIN , HLEncode( "guest" ) ) | ||
| raw_pass_wire = packet.getBinary( DATA_PASSWORD , b"" ) | ||
| login_bytes = HLDecode( raw_login_wire ) | ||
| password = HLDecode( raw_pass_wire ) | ||
| # Some Hotline-1.x-era clients use the documented ``XOR 0xFF`` for | ||
| # logins/passwords; if a particular client doesn't, the decoded | ||
| # bytes won't match a real login. Log the hex of both the wire form | ||
| # and the post-HLEncode form so any encoding mismatch is obvious. | ||
| try: | ||
| server.log.debug( | ||
| "handleLogin wire bytes login=%s password=%s" , | ||
| raw_login_wire.hex() , raw_pass_wire.hex() , | ||
| ) | ||
| server.log.debug( | ||
| "handleLogin decoded login=%s password=<%d bytes>" , | ||
| login_bytes.hex() , len( password ) , | ||
| ) | ||
| except Exception: | ||
| pass | ||
| login = login_bytes.decode( 'mac-roman' , errors = 'replace' ) if isinstance( login_bytes , (bytes , bytearray) ) else login_bytes | ||
|
|
||
| def handleUserInfo( self , server , user , packet ): | ||
| uid = packet.getNumber( DATA_UID , 0 ) | ||
| u = server.getUser( uid ) | ||
|
|
||
| if not user.hasPriv( PRIV_USER_INFO ) and ( uid != user.uid ): | ||
| raise HLException , "You cannot view user information." | ||
| if u == None: | ||
| raise HLException , "Invalid user." | ||
|
|
||
| # Format the user's idle time. | ||
| secs = long( time.time() - u.lastPacketTime ) | ||
| days = secs / 86400 | ||
| secs -= ( days * 86400 ) | ||
| hours = secs / 3600 | ||
| secs -= ( hours * 3600 ) | ||
| mins = secs / 60 | ||
| secs -= ( mins * 60 ) | ||
| idle = "" | ||
| if days > 0: | ||
| idle = "%d:%02d:%02d:%02d" % ( days , hours , mins , secs ) | ||
| else: | ||
| idle = "%02d:%02d:%02d" % ( hours , mins , secs ) | ||
| if u.isIRC: | ||
| proto = "IRC" | ||
| else: | ||
| proto = "Hotline" | ||
| str = "nickname: %s\r uid: %s\r login: %s\rrealname: %s\r proto: %s\r address: %s\r idle: %s\r" % ( u.nick , u.uid , u.account.login , u.account.name , proto , u.ip , idle ) | ||
| str += "--------------------------------\r" | ||
| xfers = server.fileserver.findTransfersForUser( uid ) | ||
| for xfer in xfers: | ||
| type = ( "[DL]" , "[UL]" )[xfer.type] | ||
| speed = "%dk/sec" % ( xfer.getTotalBPS() / 1024 ) | ||
| str += "%s %-27.27s\r %d%% @ %s\r" % ( type , xfer.name , xfer.overallPercent() , speed ) | ||
| if len( xfers ) == 0: | ||
| str += "No file transfers.\r" | ||
| str += "--------------------------------\r" | ||
|
|
||
| info = HLPacket( HTLS_HDR_TASK , packet.seq ) | ||
| info.addNumber( DATA_UID , u.uid ) | ||
| info.addString( DATA_NICK , u.nick ) | ||
| info.addString( DATA_STRING , str ) | ||
| server.sendPacket( user.uid , info ) | ||
|
|
||
| def handleMessage( self , server , user , packet ): | ||
| uid = packet.getNumber( DATA_UID , 0 ) | ||
| str = packet.getString( DATA_STRING , "" ) | ||
|
|
||
| if not user.hasPriv( PRIV_SEND_MESSAGES ): | ||
| raise HLException , "You are not allowed to send messages." | ||
| if server.getUser( uid ) == None: | ||
| raise HLException , "Invalid user." | ||
|
|
||
| msg = HLPacket( HTLS_HDR_MSG ) | ||
| msg.addNumber( DATA_UID , user.uid ) | ||
| msg.addString( DATA_NICK , user.nick ) | ||
| msg.addString( DATA_STRING , str ) | ||
| server.sendPacket( uid , msg ) | ||
| server.sendPacket( user.uid , HLPacket( HTLS_HDR_TASK , packet.seq ) ) | ||
|
|
||
| def handleUserKick( self , server , user , packet ): | ||
| uid = packet.getNumber( DATA_UID , 0 ) | ||
| ban = packet.getNumber( DATA_BAN , 0 ) | ||
| who = server.getUser( uid ) | ||
|
|
||
| if not user.hasPriv( PRIV_KICK_USERS ): | ||
| raise HLException , "You are not allowed to disconnect users." | ||
| if who == None: | ||
| raise HLException , "Invalid user." | ||
| if who.account.login != user.account.login and who.hasPriv( PRIV_KICK_PROTECT ): | ||
| raise HLException , "%s cannot be disconnected." % who.nick | ||
|
|
||
| action = "Kicked" | ||
| if ban > 0: | ||
| action = "Banned" | ||
| server.addTempBan( who.ip , "Temporary ban." ) | ||
|
|
||
| server.disconnectUser( uid ) | ||
| server.sendPacket( user.uid , HLPacket( HTLS_HDR_TASK , packet.seq ) ) | ||
| server.logEvent( LOG_TYPE_USER , "%s %s [%s]" % ( action , who.nick , who.account.login ) , user ) | ||
|
|
||
| def handleBroadcast( self , server , user , packet ): | ||
| str = packet.getString( DATA_STRING , "" ) | ||
| if not user.hasPriv( PRIV_BROADCAST ): | ||
| raise HLException , "You cannot broadcast messages." | ||
| broadcast = HLPacket( HTLS_HDR_BROADCAST ) | ||
| broadcast.addString( DATA_STRING , str ) | ||
| server.broadcastPacket( broadcast ) | ||
| server.sendPacket( user.uid , HLPacket( HTLS_HDR_TASK , packet.seq ) ) | ||
|
|
||
| def handlePing( self , server , user , packet ): | ||
| server.sendPacket( user.uid , HLPacket( HTLS_HDR_PING , packet.seq ) ) | ||
| server.log.debug( "handleLogin: connID-side login=%r ip=%s" , login , user.ip ) |
There was a problem hiding this comment.
Most debugging information was added here.
|
this may be useful for testing some cases: https://github.com/kangsterizer/rusty-hx with AI usage, i find two patterns to be most helpful:
From a quick read though the diff look'd OK to me |
…ocker entrypoint helper script
| HTLS_HDR_NEWS_POST = 0x00000066 | ||
| HTLS_HDR_MSG = 0x00000068 | ||
| HTLS_HDR_CHAT = 0x0000006A | ||
| # Server→client push: the connection agreement text. Hotline clients |
There was a problem hiding this comment.
this is not working quite right at the moment, and blocking a bunch of features on login..
…itial unit and integration tests
|
@kangsterizer good tips. I had it start writing me a pytest suite with some initial coverage added. I'll be pretty opinionated about this as it's what matches most my professional background. It's looking good so far. In my testing most features are now workin with the QEMU Mac OS 9 client.. I am definitely interested in adding some e2e style tests using your TUI script , but i'll look to get more unit test coverage added first. Should be straight foward to test: ( the bind mount for /files is assuming you want to share files) Latest hurdle was getting agreements to work. |
|
I realize this diff is pretty useless.. At this point, it's not really worth making this a PR but rather making it's own |
Uh oh!
There was an error while loading. Please reload this page.