diff --git a/common/mhfcourse/mhfcourse.go b/common/mhfcourse/mhfcourse.go index 62b8d171f..13496119b 100644 --- a/common/mhfcourse/mhfcourse.go +++ b/common/mhfcourse/mhfcourse.go @@ -66,7 +66,7 @@ func CourseExists(ID uint16, c []Course) bool { // GetCourseStruct returns a slice of Course(s) from a rights integer func GetCourseStruct(rights uint32) ([]Course, uint32) { - resp := []Course{{ID: 1}} + resp := []Course{{ID: 1}, {ID: 24}} s := Courses() slices.SortStableFunc(s, func(i, j Course) bool { return i.ID > j.ID diff --git a/config.json b/config.json index 5571a2890..2a62a3809 100644 --- a/config.json +++ b/config.json @@ -72,6 +72,10 @@ "Name": "Course", "Enabled": true, "Prefix": "!course" + }, { + "Name": "PSN", + "Enabled": true, + "Prefix": "!psn" } ], "Courses": [ diff --git a/patch-schema/psn-id.sql b/patch-schema/psn-id.sql new file mode 100644 index 000000000..49ae1bdd8 --- /dev/null +++ b/patch-schema/psn-id.sql @@ -0,0 +1,5 @@ +BEGIN; + +ALTER TABLE users ADD COLUMN IF NOT EXISTS psn_id TEXT; + +END; \ No newline at end of file diff --git a/server/channelserver/handlers_cast_binary.go b/server/channelserver/handlers_cast_binary.go index 1b6b9a6ba..7c961b5e9 100644 --- a/server/channelserver/handlers_cast_binary.go +++ b/server/channelserver/handlers_cast_binary.go @@ -82,6 +82,21 @@ func sendServerChatMessage(s *Session, message string) { } func parseChatCommand(s *Session, command string) { + if strings.HasPrefix(command, commands["PSN"].Prefix) { + if commands["PSN"].Enabled { + var id string + n, err := fmt.Sscanf(command, fmt.Sprintf("%s %%s", commands["PSN"].Prefix), &id) + if err != nil || n != 1 { + sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandPSNError"], commands["PSN"].Prefix)) + } else { + _, err = s.server.db.Exec(`UPDATE users u SET psn_id=$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)`, id, s.charID) + if err == nil { + sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandPSNSuccess"], id)) + } + } + } + } + if strings.HasPrefix(command, commands["Reload"].Prefix) { // Flush all objects and users and reload if commands["Reload"].Enabled { diff --git a/server/channelserver/sys_language.go b/server/channelserver/sys_language.go index 5de05923f..f393c5689 100644 --- a/server/channelserver/sys_language.go +++ b/server/channelserver/sys_language.go @@ -20,6 +20,8 @@ func getLangStrings(s *Server) map[string]string { strings["commandCourseLocked"] = "%sコースはロックされています" strings["commandTeleportError"] = "テレポートコマンドエラー 構文:%s x y" strings["commandTeleportSuccess"] = "%d %dにテレポート" + strings["commandPSNError"] = "PSN連携コマンドエラー 例:%s " + strings["commandPSNSuccess"] = "PSN「%s」が連携されています" strings["commandRaviNoCommand"] = "ラヴィコマンドが指定されていません" strings["commandRaviStartSuccess"] = "大討伐を開始します" @@ -68,6 +70,8 @@ func getLangStrings(s *Server) map[string]string { strings["commandCourseLocked"] = "%s Course is locked" strings["commandTeleportError"] = "Error in command. Format: %s x y" strings["commandTeleportSuccess"] = "Teleporting to %d %d" + strings["commandPSNError"] = "Error in command. Format: %s " + strings["commandPSNSuccess"] = "Connected PSN ID: %s" strings["commandRaviNoCommand"] = "No Raviente command specified!" strings["commandRaviStartSuccess"] = "The Great Slaying will begin in a moment" diff --git a/server/signserver/dsgn_resp.go b/server/signserver/dsgn_resp.go index 7e9305434..926711d57 100644 --- a/server/signserver/dsgn_resp.go +++ b/server/signserver/dsgn_resp.go @@ -11,15 +11,7 @@ import ( "strings" ) -func makeSignInFailureResp(respID RespID) []byte { - bf := byteframe.NewByteFrame() - bf.WriteUint8(uint8(respID)) - return bf.Data() -} - -func (s *Session) makeSignInResp(uid int) []byte { - returnExpiry := s.server.getReturnExpiry(uid) - +func (s *Session) makeSignResponse(uid int) []byte { // Get the characters from the DB. chars, err := s.server.getCharactersForUser(uid) if err != nil { @@ -27,7 +19,7 @@ func (s *Session) makeSignInResp(uid int) []byte { } sessToken := token.Generate(16) - s.server.registerToken(uid, sessToken) + _ = s.server.registerToken(uid, sessToken) bf := byteframe.NewByteFrame() @@ -116,6 +108,11 @@ func (s *Session) makeSignInResp(uid int) []byte { bf.WriteUint32(s.server.getLastCID(uid)) bf.WriteUint32(s.server.getUserRights(uid)) ps.Uint16(bf, "", false) // filters + if s.client == VITA || s.client == PS3 { + var psnUser string + s.server.db.QueryRow("SELECT psn_id FROM users WHERE id = $1", uid).Scan(&psnUser) + bf.WriteBytes(stringsupport.PaddedString(psnUser, 20, true)) + } bf.WriteUint16(0xCA10) bf.WriteUint16(0x4E20) ps.Uint16(bf, "", false) // unk key @@ -124,7 +121,7 @@ func (s *Session) makeSignInResp(uid int) []byte { bf.WriteUint16(0x0001) bf.WriteUint16(0x4E20) ps.Uint16(bf, "", false) // unk ipv4 - bf.WriteUint32(uint32(returnExpiry.Unix())) + bf.WriteUint32(uint32(s.server.getReturnExpiry(uid).Unix())) bf.WriteUint32(0x00000000) bf.WriteUint32(0x0A5197DF) // unk id diff --git a/server/signserver/respid.go b/server/signserver/respid.go index 47e7683d4..014daa862 100644 --- a/server/signserver/respid.go +++ b/server/signserver/respid.go @@ -1,10 +1,7 @@ package signserver -//revive:disable +type RespID uint8 -type RespID uint16 - -//go:generate stringer -type=RespID const ( SIGN_UNKNOWN RespID = iota SIGN_SUCCESS diff --git a/server/signserver/session.go b/server/signserver/session.go index 19270fafc..dfe034226 100644 --- a/server/signserver/session.go +++ b/server/signserver/session.go @@ -13,6 +13,14 @@ import ( "golang.org/x/crypto/bcrypt" ) +type Client int + +const ( + PC100 Client = iota + VITA + PS3 +) + // Session holds state for the sign server connection. type Session struct { sync.Mutex @@ -20,6 +28,7 @@ type Session struct { server *Server rawConn net.Conn cryptConn *network.CryptConn + client Client } func (s *Session) work() { @@ -42,91 +51,68 @@ func (s *Session) handlePacket(pkt []byte) error { bf := byteframe.NewByteFrameFromBytes(pkt) reqType := string(bf.ReadNullTerminatedBytes()) switch reqType { - case "DLTSKEYSIGN:100": - fallthrough - case "DSGN:100": - err := s.handleDSGNRequest(bf) - if err != nil { - return nil - } + case "DLTSKEYSIGN:100", "DSGN:100": + s.handleDSGN(bf) + case "PS3SGN:100": + s.client = PS3 + s.handlePSSGN(bf) + case "VITASGN:100": + s.client = VITA + s.handlePSSGN(bf) case "DELETE:100": loginTokenString := string(bf.ReadNullTerminatedBytes()) characterID := int(bf.ReadUint32()) _ = int(bf.ReadUint32()) // login_token_number - s.server.deleteCharacter(characterID, loginTokenString) - s.logger.Info("Deleted character", zap.Int("CharacterID", characterID)) - err := s.cryptConn.SendPacket([]byte{0x01}) // DEL_SUCCESS - if err != nil { - return nil + err := s.server.deleteCharacter(characterID, loginTokenString) + if err == nil { + s.logger.Info("Deleted character", zap.Int("CharacterID", characterID)) + s.cryptConn.SendPacket([]byte{0x01}) // DEL_SUCCESS } default: - s.logger.Warn("Unknown sign request", zap.String("reqType", reqType)) + s.logger.Warn("Unknown request", zap.String("reqType", reqType)) if s.server.erupeConfig.DevMode && s.server.erupeConfig.DevModeOptions.LogInboundMessages { fmt.Printf("\n[Client] -> [Server]\nData [%d bytes]:\n%s\n", len(pkt), hex.Dump(pkt)) } } - return nil } -func (s *Session) handleDSGNRequest(bf *byteframe.ByteFrame) error { - - reqUsername := string(bf.ReadNullTerminatedBytes()) - reqPassword := string(bf.ReadNullTerminatedBytes()) - _ = string(bf.ReadNullTerminatedBytes()) // Unk - +func (s *Session) authenticate(username string, password string) { newCharaReq := false - if reqUsername[len(reqUsername)-1] == 43 { // '+' - reqUsername = reqUsername[:len(reqUsername)-1] + if username[len(username)-1] == 43 { // '+' + username = username[:len(username)-1] newCharaReq = true } - var ( - id int - password string - ) - err := s.server.db.QueryRow("SELECT id, password FROM users WHERE username = $1", reqUsername).Scan(&id, &password) - var serverRespBytes []byte + var id int + var hash string + bf := byteframe.NewByteFrame() + + err := s.server.db.QueryRow("SELECT id, password FROM users WHERE username = $1", username).Scan(&id, &hash) switch { case err == sql.ErrNoRows: - s.logger.Info("User not found", zap.String("Username", reqUsername)) - serverRespBytes = makeSignInFailureResp(SIGN_EAUTH) - + s.logger.Info("User not found", zap.String("Username", username)) if s.server.erupeConfig.DevMode && s.server.erupeConfig.DevModeOptions.AutoCreateAccount { - s.logger.Info("Creating user", zap.String("Username", reqUsername)) - err = s.server.registerDBAccount(reqUsername, reqPassword) - if err != nil { - s.logger.Error("Error registering new user", zap.Error(err)) - serverRespBytes = makeSignInFailureResp(SIGN_EABORT) - break + s.logger.Info("Creating user", zap.String("Username", username)) + err = s.server.registerDBAccount(username, password) + if err == nil { + bf.WriteBytes(s.makeSignResponse(id)) } } else { - break + bf.WriteUint8(uint8(SIGN_EAUTH)) } - - var id int - err = s.server.db.QueryRow("SELECT id FROM users WHERE username = $1", reqUsername).Scan(&id) - if err != nil { - s.logger.Error("Error getting new user ID", zap.Error(err)) - serverRespBytes = makeSignInFailureResp(SIGN_EABORT) - break - } - - serverRespBytes = s.makeSignInResp(id) - break case err != nil: - serverRespBytes = makeSignInFailureResp(SIGN_EABORT) + bf.WriteUint8(uint8(SIGN_EABORT)) s.logger.Error("Error getting user details", zap.Error(err)) - break default: - if bcrypt.CompareHashAndPassword([]byte(password), []byte(reqPassword)) == nil { + if bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) == nil || s.client == VITA || s.client == PS3 { s.logger.Debug("Passwords match!") if newCharaReq { - err = s.server.newUserChara(reqUsername) + err = s.server.newUserChara(username) if err != nil { s.logger.Error("Error adding new character to user", zap.Error(err)) - serverRespBytes = makeSignInFailureResp(SIGN_EABORT) + bf.WriteUint8(uint8(SIGN_EABORT)) break } } @@ -137,22 +123,39 @@ func (s *Session) handleDSGNRequest(bf *byteframe.ByteFrame) error { // serverRespBytes = makeSignInFailureResp(SIGN_EABORT) // break // } - serverRespBytes = s.makeSignInResp(id) + bf.WriteBytes(s.makeSignResponse(id)) } else { s.logger.Warn("Incorrect password") - serverRespBytes = makeSignInFailureResp(SIGN_EPASS) + bf.WriteUint8(uint8(SIGN_EPASS)) } - } if s.server.erupeConfig.DevMode && s.server.erupeConfig.DevModeOptions.LogOutboundMessages { - fmt.Printf("\n[Server] -> [Client]\nData [%d bytes]:\n%s\n", len(serverRespBytes), hex.Dump(serverRespBytes)) + fmt.Printf("\n[Server] -> [Client]\nData [%d bytes]:\n%s\n", len(bf.Data()), hex.Dump(bf.Data())) } - err = s.cryptConn.SendPacket(serverRespBytes) - if err != nil { - return err - } - - return nil + err = s.cryptConn.SendPacket(bf.Data()) +} + +func (s *Session) handlePSSGN(bf *byteframe.ByteFrame) { + _ = bf.ReadNullTerminatedBytes() // 0000000256 + _ = bf.ReadNullTerminatedBytes() // 1 + _ = bf.ReadBytes(82) + psnUser := string(bf.ReadNullTerminatedBytes()) + var reqUsername string + err := s.server.db.QueryRow(`SELECT username FROM users WHERE psn_id = $1`, psnUser).Scan(&reqUsername) + if err == sql.ErrNoRows { + resp := byteframe.NewByteFrame() + resp.WriteUint8(uint8(SIGN_ECOGLINK)) + s.cryptConn.SendPacket(resp.Data()) + return + } + s.authenticate(reqUsername, "") +} + +func (s *Session) handleDSGN(bf *byteframe.ByteFrame) { + reqUsername := string(bf.ReadNullTerminatedBytes()) + reqPassword := string(bf.ReadNullTerminatedBytes()) + _ = string(bf.ReadNullTerminatedBytes()) // Unk + s.authenticate(reqUsername, reqPassword) }