Compare commits

..

No commits in common. "2bd61dc57193734d970aeb158fab1991b8b75acd" and "335fdb417ca36bc81863f19bfe3b3432c2d830b7" have entirely different histories.

5 changed files with 33 additions and 130 deletions

View File

@ -1,7 +0,0 @@
Copyright © 2022 Gopenfusion Contributors
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -1,12 +1,5 @@
# gopenfusion # gopenfusion
<p align="center">
<a href="https://github.com/CPunch/gopenfusion/actions/workflows/tests.yaml"><img src="https://github.com/CPunch/gopenfusion/actions/workflows/tests.yaml/badge.svg?branch=main" alt="Workflow"></a>
<a href="https://github.com/CPunch/gopenfusion/blob/main/LICENSE.md"><img src="https://img.shields.io/github/license/CPunch/gopenfusion" alt="License"></a>
<br>
<a href="https://asciinema.org/a/625524" target="_blank"><img src="https://asciinema.org/a/625524.svg" /></a>
</p>
A toy implementation of the [Fusionfall Packet Protocol](https://openpunk.com/pages/fusionfall-openfusion/) (see: `cnet/`) and accompanying services, written in Go. A toy implementation of the [Fusionfall Packet Protocol](https://openpunk.com/pages/fusionfall-openfusion/) (see: `cnet/`) and accompanying services, written in Go.
## Landwalker demo ## Landwalker demo

View File

@ -2,6 +2,7 @@ package db
import ( import (
"errors" "errors"
"log"
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
@ -61,6 +62,7 @@ func (db *DBHandler) TryLogin(Login, Password string) (*Account, error) {
var account Account var account Account
row.Next() row.Next()
if err := sqlscan.ScanRow(&account, row); err != nil { if err := sqlscan.ScanRow(&account, row); err != nil {
log.Printf("Error scanning row: %v", err)
return nil, ErrLoginInvalidID return nil, ErrLoginInvalidID
} }

View File

@ -280,6 +280,11 @@ func (server *LoginServer) ShardSelect(peer *cnet.Peer, pkt protocol.Packet) err
return fmt.Errorf("loginServer has found no linked shards") return fmt.Errorf("loginServer has found no linked shards")
} }
key, err := protocol.GenSerialKey()
if err != nil {
return err
}
// TODO: better shard selection logic pls // TODO: better shard selection logic pls
// for now, pick random shard // for now, pick random shard
shard := shards[rand.Intn(len(shards))] shard := shards[rand.Intn(len(shards))]
@ -297,11 +302,6 @@ func (server *LoginServer) ShardSelect(peer *cnet.Peer, pkt protocol.Packet) err
return SendFail(peer) return SendFail(peer)
} }
key, err := protocol.GenSerialKey()
if err != nil {
return err
}
// share the login attempt // share the login attempt
server.redisHndlr.QueueLogin(key, redis.LoginMetadata{ server.redisHndlr.QueueLogin(key, redis.LoginMetadata{
FEKey: peer.FE_key, FEKey: peer.FE_key,

View File

@ -2,7 +2,6 @@ package login_test
import ( import (
"context" "context"
"encoding/binary"
"fmt" "fmt"
"net" "net"
"os" "os"
@ -25,25 +24,6 @@ var (
rh *redis.RedisHandler rh *redis.RedisHandler
) )
var (
testCharCreate = protocol.SP_CL2LS_REQ_CHAR_CREATE{
PCStyle: protocol.SPCStyle{
INameCheck: 1, SzFirstName: "Hector",
SzLastName: "Bannonvenom", IGender: 1, IFaceStyle: 1,
IHairStyle: 17, IHairColor: 11, ISkinColor: 10, IEyeColor: 2,
IHeight: 1, IBody: 0, IClass: 0,
},
SOn_Item: protocol.SOnItem{
IEquipHandID: 0, IEquipUBID: 53, IEquipLBID: 17, IEquipFootID: 58,
IEquipHeadID: 0, IEquipFaceID: 0, IEquipBackID: 0,
},
SOn_Item_Index: protocol.SOnItem_Index{
IEquipUBID_index: 15, IEquipLBID_index: 12, IEquipFootID_index: 17,
IFaceStyle: 2, IHairStyle: 18,
},
}
)
func makeDummyPeer(ctx context.Context, is *is.I, recv chan<- *cnet.PacketEvent) *cnet.Peer { func makeDummyPeer(ctx context.Context, is *is.I, recv chan<- *cnet.PacketEvent) *cnet.Peer {
conn, err := net.Dial("tcp", fmt.Sprintf("127.0.0.1:%d", loginPort)) conn, err := net.Dial("tcp", fmt.Sprintf("127.0.0.1:%d", loginPort))
is.NoErr(err) is.NoErr(err)
@ -56,18 +36,6 @@ func makeDummyPeer(ctx context.Context, is *is.I, recv chan<- *cnet.PacketEvent)
return peer return peer
} }
func sendAndRecv(peer *cnet.Peer, recv chan *cnet.PacketEvent, is *is.I, sID, rID uint32, out, in interface{}) {
// send out packet
err := peer.Send(sID, out)
is.NoErr(err) // peer.Send() should not return an error
// receive response
evnt := <-recv
is.Equal(evnt.PktID, rID) // should receive expected type
is.NoErr(protocol.NewPacket(evnt.Pkt).Decode(in)) // packet.Decode() should not return an error
}
func TestMain(m *testing.M) { func TestMain(m *testing.M) {
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
defer cancel() defer cancel()
@ -127,7 +95,6 @@ func TestMain(m *testing.M) {
os.Exit(ret) os.Exit(ret)
} }
// This test tries a typical login sequence.
func TestLoginSuccSequence(t *testing.T) { func TestLoginSuccSequence(t *testing.T) {
is := is.New(t) is := is.New(t)
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
@ -138,23 +105,29 @@ func TestLoginSuccSequence(t *testing.T) {
defer peer.Kill() defer peer.Kill()
// send login request (this should create an account) // send login request (this should create an account)
err := peer.Send(protocol.P_CL2LS_REQ_LOGIN, protocol.SP_CL2LS_REQ_LOGIN{
SzID: "testLoginSequence",
SzPassword: "test",
})
is.NoErr(err) // peer.Send() should not return an error
// receive login response
evnt := <-recv
is.Equal(int(evnt.PktID), protocol.P_LS2CL_REP_LOGIN_SUCC) // should receive P_LS2CL_REP_LOGIN_SUCC
var resp protocol.SP_LS2CL_REP_LOGIN_SUCC var resp protocol.SP_LS2CL_REP_LOGIN_SUCC
sendAndRecv(peer, recv, is, protocol.P_CL2LS_REQ_LOGIN, protocol.P_LS2CL_REP_LOGIN_SUCC, err = protocol.NewPacket(evnt.Pkt).Decode(&resp)
protocol.SP_CL2LS_REQ_LOGIN{ is.NoErr(err) // packet.Decode() should not return an error
SzID: "testLoginSequence",
SzPassword: "test",
}, &resp)
// verify response // verify response
is.Equal(resp.SzID, "testLoginSequence") // should have the same ID is.Equal(resp.SzID, "testLoginSequence") // should have the same ID
is.Equal(resp.ICharCount, int8(0)) // should have 0 characters is.Equal(resp.ICharCount, int8(0)) // should have 0 characters
// verify account was created // verify account was created
_, err := testDB.TryLogin("testLoginSequence", "test") _, err = testDB.TryLogin("testLoginSequence", "test")
is.NoErr(err) // TryLogin() should not return an error is.NoErr(err) // TryLogin() should not return an error
} }
// This test tries a typical login sequence, but with an invalid password.
func TestLoginFailSequence(t *testing.T) { func TestLoginFailSequence(t *testing.T) {
is := is.New(t) is := is.New(t)
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
@ -164,80 +137,22 @@ func TestLoginFailSequence(t *testing.T) {
peer := makeDummyPeer(ctx, is, recv) peer := makeDummyPeer(ctx, is, recv)
defer peer.Kill() defer peer.Kill()
// send login request (this should not create an account) // send login request (this should create an account)
err := peer.Send(protocol.P_CL2LS_REQ_LOGIN, protocol.SP_CL2LS_REQ_LOGIN{
SzID: "",
SzPassword: "",
})
is.NoErr(err) // peer.Send() should not return an error
// receive login response
evnt := <-recv
is.Equal(int(evnt.PktID), protocol.P_LS2CL_REP_LOGIN_FAIL) // should receive P_LS2CL_REP_LOGIN_FAIL
var resp protocol.SP_LS2CL_REP_LOGIN_FAIL var resp protocol.SP_LS2CL_REP_LOGIN_FAIL
sendAndRecv(peer, recv, is, protocol.P_CL2LS_REQ_LOGIN, protocol.P_LS2CL_REP_LOGIN_FAIL, err = protocol.NewPacket(evnt.Pkt).Decode(&resp)
protocol.SP_CL2LS_REQ_LOGIN{ is.NoErr(err) // packet.Decode() should not return an error
SzID: "",
SzPassword: "",
}, &resp)
// verify response // verify response
is.Equal(resp.SzID, "") // should have the same ID is.Equal(resp.SzID, "") // should have the same ID
is.Equal(resp.IErrorCode, int32(login.LOGIN_ID_AND_PASSWORD_DO_NOT_MATCH)) // should respond with LOGIN_ID_AND_PASSWORD_DO_NOT_MATCH is.Equal(resp.IErrorCode, int32(login.LOGIN_ID_AND_PASSWORD_DO_NOT_MATCH)) // should respond with LOGIN_ID_AND_PASSWORD_DO_NOT_MATCH
} }
// This test tries a typical login sequence, and creates a character
func TestCharacterSequence(t *testing.T) {
is := is.New(t)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
recv := make(chan *cnet.PacketEvent)
peer := makeDummyPeer(ctx, is, recv)
defer peer.Kill()
// send login request (this should create an account)
var resp protocol.SP_LS2CL_REP_LOGIN_SUCC
sendAndRecv(peer, recv, is, protocol.P_CL2LS_REQ_LOGIN, protocol.P_LS2CL_REP_LOGIN_SUCC,
protocol.SP_CL2LS_REQ_LOGIN{
SzID: "testCharacterSequence",
SzPassword: "test",
}, &resp)
// verify response
is.Equal(resp.SzID, "testCharacterSequence") // should have the same ID
is.Equal(resp.ICharCount, int8(0)) // should have 0 characters
// perform key swap
peer.E_key = protocol.CreateNewKey(
resp.UiSvrTime,
uint64(resp.ICharCount+1),
uint64(resp.ISlotNum+1),
)
peer.FE_key = protocol.CreateNewKey(
binary.LittleEndian.Uint64([]byte(protocol.DEFAULT_KEY)),
0,
1,
)
// send character name check request
var charResp protocol.SP_LS2CL_REP_SAVE_CHAR_NAME_SUCC
sendAndRecv(peer, recv, is, protocol.P_CL2LS_REQ_SAVE_CHAR_NAME, protocol.P_LS2CL_REP_SAVE_CHAR_NAME_SUCC,
protocol.SP_CL2LS_REQ_SAVE_CHAR_NAME{
ISlotNum: 1,
IGender: 1,
IFNCode: 260,
ILNCode: 551,
IMNCode: 33,
SzFirstName: testCharCreate.PCStyle.SzFirstName,
SzLastName: testCharCreate.PCStyle.SzLastName,
}, &charResp)
// verify response
is.Equal(charResp.ISlotNum, int8(1)) // should have the same slot number
is.Equal(charResp.IGender, int8(1)) // should have the same gender
is.Equal(charResp.SzFirstName, testCharCreate.PCStyle.SzFirstName) // should have the same first name
is.Equal(charResp.SzLastName, testCharCreate.PCStyle.SzLastName) // should have the same last name
// send character create request
charCreate := testCharCreate
charCreate.PCStyle.IPC_UID = charResp.IPC_UID
var charCreateResp protocol.SP_LS2CL_REP_CHAR_CREATE_SUCC
sendAndRecv(peer, recv, is, protocol.P_CL2LS_REQ_CHAR_CREATE, protocol.P_LS2CL_REP_CHAR_CREATE_SUCC,
charCreate, &charCreateResp)
// verify response
is.Equal(charCreate.PCStyle, charCreateResp.SPC_Style) // should have the same PCStyle
is.Equal(charCreate.SOn_Item, charCreateResp.SOn_Item) // should have the same SOn_Item
}