mirror of
https://github.com/CPunch/gopenfusion.git
synced 2024-12-04 22:46:32 +00:00
Compare commits
11 Commits
335fdb417c
...
2bd61dc571
Author | SHA1 | Date | |
---|---|---|---|
2bd61dc571 | |||
cba01a877d | |||
e1b9fa5d99 | |||
77751a2aa0 | |||
8e84f0c7b2 | |||
c902559eac | |||
1c40998cb6 | |||
988368c307 | |||
01ebf4499f | |||
3a14d807d2 | |||
141858d6c3 |
7
LICENSE.md
Normal file
7
LICENSE.md
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
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.
|
@ -1,5 +1,12 @@
|
|||||||
# 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
|
||||||
|
@ -2,7 +2,6 @@ package db
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"log"
|
|
||||||
|
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
|
||||||
@ -62,7 +61,6 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -280,11 +280,6 @@ 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))]
|
||||||
@ -302,6 +297,11 @@ 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,
|
||||||
|
@ -2,6 +2,7 @@ package login_test
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/binary"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
@ -24,6 +25,25 @@ 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)
|
||||||
@ -36,6 +56,18 @@ 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()
|
||||||
@ -95,6 +127,7 @@ 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())
|
||||||
@ -105,29 +138,23 @@ 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{
|
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: "testLoginSequence",
|
SzID: "testLoginSequence",
|
||||||
SzPassword: "test",
|
SzPassword: "test",
|
||||||
})
|
}, &resp)
|
||||||
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
|
|
||||||
err = protocol.NewPacket(evnt.Pkt).Decode(&resp)
|
|
||||||
is.NoErr(err) // packet.Decode() should not return an error
|
|
||||||
|
|
||||||
// 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())
|
||||||
@ -137,22 +164,80 @@ 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 create an account)
|
// send login request (this should not create an account)
|
||||||
err := peer.Send(protocol.P_CL2LS_REQ_LOGIN, protocol.SP_CL2LS_REQ_LOGIN{
|
var resp protocol.SP_LS2CL_REP_LOGIN_FAIL
|
||||||
|
sendAndRecv(peer, recv, is, protocol.P_CL2LS_REQ_LOGIN, protocol.P_LS2CL_REP_LOGIN_FAIL,
|
||||||
|
protocol.SP_CL2LS_REQ_LOGIN{
|
||||||
SzID: "",
|
SzID: "",
|
||||||
SzPassword: "",
|
SzPassword: "",
|
||||||
})
|
}, &resp)
|
||||||
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
|
|
||||||
err = protocol.NewPacket(evnt.Pkt).Decode(&resp)
|
|
||||||
is.NoErr(err) // packet.Decode() should not return an error
|
|
||||||
|
|
||||||
// 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
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user