mirror of
https://github.com/CPunch/gopenfusion.git
synced 2024-11-24 16:11:04 +00:00
major refactoring; started DB
- DB work has started in db. this will be a direct port of the OpenFusion DB format. - LoginServer is now less of a dummy. You can create and login to accounts, and create a character to go through the tutorial with. - config.go will host some commonly changed variables. - protocol: fixed a bug relating to arrays being ignored while encoding packets
This commit is contained in:
parent
1fff485f93
commit
8569225ec7
@ -4,4 +4,4 @@ A toy implementation of the [Fusionfall Packet Protocol](https://openpunk.com/pa
|
|||||||
|
|
||||||
## Generating structures
|
## Generating structures
|
||||||
|
|
||||||
Dump and decompile the `Assembly - CSharp.dll` assembly from the fusionfall main.unity3d, using a tool like [ilspycmd](https://www.nuget.org/packages/ilspycmd/). The structures can then be stripped from the source and passed to the `genstructs.py` script located in `tools/`. See the script for details on usage.
|
Dump and decompile the `Assembly - CSharp.dll` assembly from the fusionfall main.unity3d, using a tool like [ilspycmd](https://www.nuget.org/packages/ilspycmd/). The full output source can then be passed to `genstructs.py` script located in `tools/`, which will handle scraping constants and calculating structure padding. See the script for details on usage.
|
15
config/config.go
Normal file
15
config/config.go
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
var (
|
||||||
|
SPAWN_X = 632032
|
||||||
|
SPAWN_Y = 187177
|
||||||
|
SPAWN_Z = -5500
|
||||||
|
|
||||||
|
AEQUIP_COUNT = 9
|
||||||
|
AINVEN_COUNT = 50
|
||||||
|
ABANK_COUNT = 119
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetMaxHP(level int) int {
|
||||||
|
return (925 + 75*(level))
|
||||||
|
}
|
66
db/account.go
Normal file
66
db/account.go
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
package db
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
|
||||||
|
"github.com/CPunch/GopenFusion/protocol"
|
||||||
|
"github.com/blockloop/scan"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Account struct {
|
||||||
|
AccountID int
|
||||||
|
Login string
|
||||||
|
Password string
|
||||||
|
Selected int
|
||||||
|
AccountLevel int
|
||||||
|
Created int
|
||||||
|
LastLogin int
|
||||||
|
BannedUntil int
|
||||||
|
BannedSince int
|
||||||
|
BanReason string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAccount(db DBQuery, Login, Password string) (*Account, error) {
|
||||||
|
hash, err := bcrypt.GenerateFromPassword([]byte(Password), 12)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
row, err := db.Query("INSERT INTO Accounts (Login, Password, AccountLevel) VALUES(?, ?, ?) RETURNING *", Login, hash, protocol.CN_ACCOUNT_LEVEL__USER)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var account Account
|
||||||
|
if err := scan.Row(&account, row); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &account, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
LoginErrorInvalidID = fmt.Errorf("Invalid Login ID!")
|
||||||
|
LoginErrorInvalidPassword = fmt.Errorf("Invalid ID && Password combo!")
|
||||||
|
)
|
||||||
|
|
||||||
|
func TryLogin(db DBQuery, Login, Password string) (*Account, error) {
|
||||||
|
row, err := db.Query("SELECT * FROM Accounts WHERE Login=?", Login)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var account Account
|
||||||
|
if err := scan.Row(&account, row); err != nil {
|
||||||
|
return nil, LoginErrorInvalidID
|
||||||
|
}
|
||||||
|
|
||||||
|
if bcrypt.CompareHashAndPassword([]byte(account.Password), []byte(Password)) != nil {
|
||||||
|
return nil, LoginErrorInvalidPassword
|
||||||
|
}
|
||||||
|
|
||||||
|
// else, login was a success
|
||||||
|
return &account, nil
|
||||||
|
}
|
61
db/inventory.go
Normal file
61
db/inventory.go
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
package db
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/CPunch/GopenFusion/protocol"
|
||||||
|
"github.com/blockloop/scan"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Inventory struct {
|
||||||
|
PlayerID int
|
||||||
|
Slot int
|
||||||
|
ID int
|
||||||
|
Type int
|
||||||
|
Opt int
|
||||||
|
TimeLimit int
|
||||||
|
}
|
||||||
|
|
||||||
|
// start && end are both inclusive
|
||||||
|
func (db *DBHandler) GetPlayerInventorySlots(PlayerID int, start int, end int) ([]protocol.SItemBase, error) {
|
||||||
|
rows, err := db.Query("SELECT * FROM Inventory WHERE Slot BETWEEN ? AND ? AND PlayerID = ?", start, end, PlayerID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var inven []Inventory
|
||||||
|
if err := scan.Row(&inven, rows); err != nil {
|
||||||
|
return make([]protocol.SItemBase, end-start), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// populate an SItemBase array
|
||||||
|
items := make([]protocol.SItemBase, end-start)
|
||||||
|
for _, item := range inven {
|
||||||
|
items[item.Slot-start] = protocol.SItemBase{
|
||||||
|
IID: int16(item.ID),
|
||||||
|
IType: int16(item.Type),
|
||||||
|
IOpt: int32(item.Opt),
|
||||||
|
ITimeLimit: int32(item.TimeLimit),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// start is inclusive TODO: needs to be a transaction !!
|
||||||
|
func (db *DBHandler) SetPlayerInventorySlots(PlayerID int, start int, items []protocol.SItemBase) error {
|
||||||
|
// delete inventory slots
|
||||||
|
_, err := db.Query("DELETE FROM Inventory WHERE Slot BETWEEN ? AND ? AND PlayerID = ?", start, start+len(items)-1, PlayerID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// insert inventory
|
||||||
|
for i, item := range items {
|
||||||
|
if item.IID != 0 {
|
||||||
|
_, err := db.Query("INSERT INTO Inventory (PlayerID, Slot, ID, Type, Opt, TimeLimit) VALUES (?, ?, ?, ?, ?, ?)", PlayerID, start+i, item.IID, item.IType, item.IOpt, item.ITimeLimit)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
164
db/migrations/new.sql
Normal file
164
db/migrations/new.sql
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
-- this file has been lifted from https://github.com/OpenFusionProject/OpenFusion/blob/master/sql/tables.sql
|
||||||
|
-- all credit to original contributors!
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS Accounts (
|
||||||
|
AccountID INTEGER NOT NULL,
|
||||||
|
Login TEXT NOT NULL UNIQUE COLLATE NOCASE,
|
||||||
|
Password TEXT NOT NULL,
|
||||||
|
Selected INTEGER DEFAULT 1 NOT NULL,
|
||||||
|
AccountLevel INTEGER NOT NULL,
|
||||||
|
Created INTEGER DEFAULT (strftime('%s', 'now')) NOT NULL,
|
||||||
|
LastLogin INTEGER DEFAULT (strftime('%s', 'now')) NOT NULL,
|
||||||
|
BannedUntil INTEGER DEFAULT 0 NOT NULL,
|
||||||
|
BannedSince INTEGER DEFAULT 0 NOT NULL,
|
||||||
|
BanReason TEXT DEFAULT '' NOT NULL,
|
||||||
|
PRIMARY KEY(AccountID AUTOINCREMENT)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS Players (
|
||||||
|
PlayerID INTEGER NOT NULL,
|
||||||
|
AccountID INTEGER NOT NULL,
|
||||||
|
FirstName TEXT NOT NULL COLLATE NOCASE,
|
||||||
|
LastName TEXT NOT NULL COLLATE NOCASE,
|
||||||
|
NameCheck INTEGER NOT NULL,
|
||||||
|
Slot INTEGER NOT NULL,
|
||||||
|
Created INTEGER DEFAULT (strftime('%s', 'now')) NOT NULL,
|
||||||
|
LastLogin INTEGER DEFAULT (strftime('%s', 'now')) NOT NULL,
|
||||||
|
Level INTEGER DEFAULT 1 NOT NULL,
|
||||||
|
Nano1 INTEGER DEFAULT 0 NOT NULL,
|
||||||
|
Nano2 INTEGER DEFAULT 0 NOT NULL,
|
||||||
|
Nano3 INTEGER DEFAULT 0 NOT NULL,
|
||||||
|
AppearanceFlag INTEGER DEFAULT 0 NOT NULL,
|
||||||
|
TutorialFlag INTEGER DEFAULT 0 NOT NULL,
|
||||||
|
PayZoneFlag INTEGER DEFAULT 0 NOT NULL,
|
||||||
|
XCoordinate INTEGER NOT NULL,
|
||||||
|
YCoordinate INTEGER NOT NULL,
|
||||||
|
ZCoordinate INTEGER NOT NULL,
|
||||||
|
Angle INTEGER NOT NULL,
|
||||||
|
HP INTEGER NOT NULL,
|
||||||
|
FusionMatter INTEGER DEFAULT 0 NOT NULL,
|
||||||
|
Taros INTEGER DEFAULT 0 NOT NULL,
|
||||||
|
BatteryW INTEGER DEFAULT 0 NOT NULL,
|
||||||
|
BatteryN INTEGER DEFAULT 0 NOT NULL,
|
||||||
|
Mentor INTEGER DEFAULT 5 NOT NULL,
|
||||||
|
CurrentMissionID INTEGER DEFAULT 0 NOT NULL,
|
||||||
|
WarpLocationFlag INTEGER DEFAULT 0 NOT NULL,
|
||||||
|
SkywayLocationFlag BLOB NOT NULL,
|
||||||
|
FirstUseFlag BLOB NOT NULL,
|
||||||
|
Quests BLOB NOT NULL,
|
||||||
|
PRIMARY KEY(PlayerID AUTOINCREMENT),
|
||||||
|
FOREIGN KEY(AccountID) REFERENCES Accounts(AccountID) ON DELETE CASCADE,
|
||||||
|
UNIQUE (AccountID, Slot),
|
||||||
|
UNIQUE (FirstName, LastName)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS Appearances (
|
||||||
|
PlayerID INTEGER UNIQUE NOT NULL,
|
||||||
|
Body INTEGER DEFAULT 0 NOT NULL,
|
||||||
|
EyeColor INTEGER DEFAULT 1 NOT NULL,
|
||||||
|
FaceStyle INTEGER DEFAULT 1 NOT NULL,
|
||||||
|
Gender INTEGER DEFAULT 1 NOT NULL,
|
||||||
|
HairColor INTEGER DEFAULT 1 NOT NULL,
|
||||||
|
HairStyle INTEGER DEFAULT 1 NOT NULL,
|
||||||
|
Height INTEGER DEFAULT 0 NOT NULL,
|
||||||
|
SkinColor INTEGER DEFAULT 1 NOT NULL,
|
||||||
|
FOREIGN KEY(PlayerID) REFERENCES Players(PlayerID) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS Inventory (
|
||||||
|
PlayerID INTEGER NOT NULL,
|
||||||
|
Slot INTEGER NOT NULL,
|
||||||
|
ID INTEGER NOT NULL,
|
||||||
|
Type INTEGER NOT NULL,
|
||||||
|
Opt INTEGER NOT NULL,
|
||||||
|
TimeLimit INTEGER DEFAULT 0 NOT NULL,
|
||||||
|
FOREIGN KEY(PlayerID) REFERENCES Players(PlayerID) ON DELETE CASCADE,
|
||||||
|
UNIQUE (PlayerID, Slot)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS QuestItems (
|
||||||
|
PlayerID INTEGER NOT NULL,
|
||||||
|
Slot INTEGER NOT NULL,
|
||||||
|
ID INTEGER NOT NULL,
|
||||||
|
Opt INTEGER NOT NULL,
|
||||||
|
FOREIGN KEY(PlayerID) REFERENCES Players(PlayerID) ON DELETE CASCADE,
|
||||||
|
UNIQUE (PlayerID, Slot)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS Nanos (
|
||||||
|
PlayerID INTEGER NOT NULL,
|
||||||
|
ID INTEGER NOT NULL,
|
||||||
|
Skill INTEGER NOT NULL,
|
||||||
|
Stamina INTEGER DEFAULT 150 NOT NULL,
|
||||||
|
FOREIGN KEY(PlayerID) REFERENCES Players(PlayerID) ON DELETE CASCADE,
|
||||||
|
UNIQUE (PlayerID, ID)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS RunningQuests (
|
||||||
|
PlayerID INTEGER NOT NULL,
|
||||||
|
TaskID INTEGER NOT NULL,
|
||||||
|
RemainingNPCCount1 INTEGER NOT NULL,
|
||||||
|
RemainingNPCCount2 INTEGER NOT NULL,
|
||||||
|
RemainingNPCCount3 INTEGER NOT NULL,
|
||||||
|
FOREIGN KEY(PlayerID) REFERENCES Players(PlayerID) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS Buddyships (
|
||||||
|
PlayerAID INTEGER NOT NULL,
|
||||||
|
PlayerBID INTEGER NOT NULL,
|
||||||
|
FOREIGN KEY(PlayerAID) REFERENCES Players(PlayerID) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY(PlayerBID) REFERENCES Players(PlayerID) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS Blocks (
|
||||||
|
PlayerID INTEGER NOT NULL,
|
||||||
|
BlockedPlayerID INTEGER NOT NULL,
|
||||||
|
FOREIGN KEY(PlayerID) REFERENCES Players(PlayerID) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY(BlockedPlayerID) REFERENCES Players(PlayerID) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS EmailData (
|
||||||
|
PlayerID INTEGER NOT NULL,
|
||||||
|
MsgIndex INTEGER NOT NULL,
|
||||||
|
ReadFlag INTEGER NOT NULL,
|
||||||
|
ItemFlag INTEGER NOT NULL,
|
||||||
|
SenderID INTEGER NOT NULL,
|
||||||
|
SenderFirstName TEXT NOT NULL COLLATE NOCASE,
|
||||||
|
SenderLastName TEXT NOT NULL COLLATE NOCASE,
|
||||||
|
SubjectLine TEXT NOT NULL,
|
||||||
|
MsgBody TEXT NOT NULL,
|
||||||
|
Taros INTEGER NOT NULL,
|
||||||
|
SendTime INTEGER NOT NULL,
|
||||||
|
DeleteTime INTEGER NOT NULL,
|
||||||
|
FOREIGN KEY(PlayerID) REFERENCES Players(PlayerID) ON DELETE CASCADE,
|
||||||
|
UNIQUE(PlayerID, MsgIndex)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS EmailItems (
|
||||||
|
PlayerID INTEGER NOT NULL,
|
||||||
|
MsgIndex INTEGER NOT NULL,
|
||||||
|
Slot INTEGER NOT NULL,
|
||||||
|
ID INTEGER NOT NULL,
|
||||||
|
Type INTEGER NOT NULL,
|
||||||
|
Opt INTEGER NOT NULL,
|
||||||
|
TimeLimit INTEGER NOT NULL,
|
||||||
|
FOREIGN KEY(PlayerID) REFERENCES Players(PlayerID) ON DELETE CASCADE,
|
||||||
|
UNIQUE (PlayerID, MsgIndex, Slot)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS RaceResults(
|
||||||
|
EPID INTEGER NOT NULL,
|
||||||
|
PlayerID INTEGER NOT NULL,
|
||||||
|
Score INTEGER NOT NULL,
|
||||||
|
RingCount INTEGER NOT NULL,
|
||||||
|
Time INTEGER NOT NULL,
|
||||||
|
Timestamp INTEGER NOT NULL,
|
||||||
|
FOREIGN KEY(PlayerID) REFERENCES Players(PlayerID) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS RedeemedCodes(
|
||||||
|
PlayerID INTEGER NOT NULL,
|
||||||
|
Code TEXT NOT NULL,
|
||||||
|
FOREIGN KEY(PlayerID) REFERENCES Players(PlayerID) ON DELETE CASCADE,
|
||||||
|
UNIQUE (PlayerID, Code)
|
||||||
|
)
|
227
db/players.go
Normal file
227
db/players.go
Normal file
@ -0,0 +1,227 @@
|
|||||||
|
package db
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/CPunch/GopenFusion/config"
|
||||||
|
"github.com/CPunch/GopenFusion/protocol"
|
||||||
|
"github.com/blockloop/scan"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Player struct {
|
||||||
|
PlayerID int
|
||||||
|
AccountID int
|
||||||
|
FirstName string
|
||||||
|
LastName string
|
||||||
|
NameCheck int
|
||||||
|
Slot int
|
||||||
|
Created int
|
||||||
|
LastLogin int
|
||||||
|
Level int
|
||||||
|
Nano1 int
|
||||||
|
Nano2 int
|
||||||
|
Nano3 int
|
||||||
|
AppearanceFlag int
|
||||||
|
TutorialFlag int
|
||||||
|
PayZoneFlag int
|
||||||
|
XCoordinate int
|
||||||
|
YCoordinate int
|
||||||
|
ZCoordinate int
|
||||||
|
Angle int
|
||||||
|
HP int
|
||||||
|
FusionMatter int
|
||||||
|
Taros int
|
||||||
|
BatteryW int
|
||||||
|
BatteryN int
|
||||||
|
Mentor int
|
||||||
|
CurrentMissionID int
|
||||||
|
WarpLocationFlag int
|
||||||
|
SkywayLocationFlag []byte
|
||||||
|
FirstUseFlag []byte
|
||||||
|
Quests []byte
|
||||||
|
/* Appearance tbl */
|
||||||
|
Body int `db:"Body"`
|
||||||
|
EyeColor int `db:"EyeColor"`
|
||||||
|
FaceStyle int `db:"FaceStyle"`
|
||||||
|
Gender int `db:"Gender"`
|
||||||
|
HairColor int `db:"HairColor"`
|
||||||
|
HairStyle int `db:"HairStyle"`
|
||||||
|
Height int `db:"Height"`
|
||||||
|
SkinColor int `db:"SkinColor"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns PlayerID, error
|
||||||
|
func (db *DBHandler) NewPlayer(AccountID int, FirstName, LastName string, slot int) (int, error) {
|
||||||
|
nameCheck := 1 // for now, we approve all names
|
||||||
|
QuestFlag := make([]byte, 128)
|
||||||
|
SkywayLocationFlag := make([]byte, 16)
|
||||||
|
FirstUseFlag := make([]byte, 16)
|
||||||
|
|
||||||
|
var PlayerID int
|
||||||
|
if err := db.Transaction(func(tx *sql.Tx) error {
|
||||||
|
// create player
|
||||||
|
row, err := tx.Query(
|
||||||
|
"INSERT INTO Players (AccountID, Slot, FirstName, LastName, XCoordinate, YCoordinate, ZCoordinate, Angle, HP, NameCheck, Quests, SkywayLocationFlag, FirstUseFlag) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING PlayerID",
|
||||||
|
AccountID, slot, FirstName, LastName, config.SPAWN_X, config.SPAWN_Y, config.SPAWN_Z, 0, config.GetMaxHP(1), nameCheck, QuestFlag, SkywayLocationFlag, FirstUseFlag)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := scan.Row(&PlayerID, row); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// create appearance
|
||||||
|
if _, err := tx.Exec("INSERT INTO Appearances (PlayerID) VALUES (?)", PlayerID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
return -1, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return PlayerID, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: should this operate on the raw packet? should we do validation here or prior?
|
||||||
|
func (db *DBHandler) FinishPlayer(character *protocol.SP_CL2LS_REQ_CHAR_CREATE, AccountId int) error {
|
||||||
|
return db.Transaction(func(tx *sql.Tx) error {
|
||||||
|
// update AppearanceFlag
|
||||||
|
_, err := tx.Exec("UPDATE Players SET AppearanceFlag = 1 WHERE PlayerID = ? AND AccountID = ? AND AppearanceFlag = 0", character.PCStyle.IPC_UID, AccountId)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// update Appearance
|
||||||
|
_, err = tx.Exec("UPDATE Appearances SET Body = ?, EyeColor = ?, FaceStyle = ?, Gender = ?, HairColor = ?, HairStyle = ?, Height = ?, SkinColor = ? WHERE PlayerID = ?",
|
||||||
|
character.PCStyle.IBody,
|
||||||
|
character.PCStyle.IEyeColor,
|
||||||
|
character.PCStyle.IFaceStyle,
|
||||||
|
character.PCStyle.IGender,
|
||||||
|
character.PCStyle.IHairColor,
|
||||||
|
character.PCStyle.IHairStyle,
|
||||||
|
character.PCStyle.IHeight,
|
||||||
|
character.PCStyle.ISkinColor,
|
||||||
|
character.PCStyle.IPC_UID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// update Inventory
|
||||||
|
items := [3]int16{character.SOn_Item.IEquipUBID, character.SOn_Item.IEquipLBID, character.SOn_Item.IEquipFootID}
|
||||||
|
for i := 0; i < len(items); i++ {
|
||||||
|
_, err = tx.Exec("INSERT INTO Inventory (PlayerID, Slot, ID, Type, Opt) VALUES (?, ?, ?, ?, 1)", character.PCStyle.IPC_UID, i+1, items[i], i+1)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *DBHandler) FinishTutorial(PlayerID, AccountID int) error {
|
||||||
|
_, err := db.Exec("UPDATE Players SET TutorialFlag = 1 WHERE PlayerID = ? AND AccountID = ? AND TutorialFlag = 0", PlayerID, AccountID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: reference openfusion's finishTutorial for their academy specific patches
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns the deleted player row
|
||||||
|
func (db *DBHandler) DeletePlayer(PlayerID, AccountID int) (*Player, error) {
|
||||||
|
row, err := db.Query("DELETE FROM Players WHERE AccountID = ? AND PlayerID = ? RETURNING *")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var plr Player
|
||||||
|
if err := scan.Row(&plr, row); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &plr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *DBHandler) GetPlayer(PlayerID int) (*Player, error) {
|
||||||
|
rows, err := db.Query(`SELECT
|
||||||
|
p.PlayerID, p.AccountID, p.Slot, p.FirstName, p.LastName,
|
||||||
|
p.Level, p.Nano1, p.Nano2, p.Nano3,
|
||||||
|
p.AppearanceFlag, p.TutorialFlag, p.PayZoneFlag,
|
||||||
|
p.XCoordinate, p.YCoordinate, p.ZCoordinate, p.NameCheck,
|
||||||
|
p.Angle, p.HP, p.FusionMatter, p.Taros, p.Quests,
|
||||||
|
p.BatteryW, p.BatteryN, p.Mentor, p.WarpLocationFlag,
|
||||||
|
p.SkywayLocationFlag, p.CurrentMissionID, p.FirstUseFlag,
|
||||||
|
a.Body, a.EyeColor, a.FaceStyle, a.Gender, a.HairColor, a.HairStyle,
|
||||||
|
a.Height, a.SkinColor
|
||||||
|
FROM Players as p
|
||||||
|
INNER JOIN Appearances as a ON p.PlayerID = a.PlayerID
|
||||||
|
WHERE p.PlayerID = ?`, PlayerID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Print(PlayerID)
|
||||||
|
var plr Player
|
||||||
|
for rows.Next() {
|
||||||
|
if err := rows.Scan(
|
||||||
|
&plr.PlayerID, &plr.AccountID, &plr.Slot, &plr.FirstName, &plr.LastName,
|
||||||
|
&plr.Level, &plr.Nano1, &plr.Nano2, &plr.Nano3,
|
||||||
|
&plr.AppearanceFlag, &plr.TutorialFlag, &plr.PayZoneFlag,
|
||||||
|
&plr.XCoordinate, &plr.YCoordinate, &plr.ZCoordinate, &plr.NameCheck,
|
||||||
|
&plr.Angle, &plr.HP, &plr.FusionMatter, &plr.Taros, &plr.Quests,
|
||||||
|
&plr.BatteryW, &plr.BatteryN, &plr.Mentor, &plr.WarpLocationFlag,
|
||||||
|
&plr.SkywayLocationFlag, &plr.CurrentMissionID, &plr.FirstUseFlag,
|
||||||
|
&plr.Body, &plr.EyeColor, &plr.FaceStyle, &plr.Gender, &plr.HairColor, &plr.HairStyle,
|
||||||
|
&plr.Height, &plr.SkinColor); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &plr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *DBHandler) GetPlayers(AccountID int) ([]Player, error) {
|
||||||
|
rows, err := db.Query(`SELECT
|
||||||
|
p.PlayerID, p.AccountID, p.Slot, p.FirstName, p.LastName,
|
||||||
|
p.Level, p.Nano1, p.Nano2, p.Nano3,
|
||||||
|
p.AppearanceFlag, p.TutorialFlag, p.PayZoneFlag,
|
||||||
|
p.XCoordinate, p.YCoordinate, p.ZCoordinate, p.NameCheck,
|
||||||
|
p.Angle, p.HP, p.FusionMatter, p.Taros, p.Quests,
|
||||||
|
p.BatteryW, p.BatteryN, p.Mentor, p.WarpLocationFlag,
|
||||||
|
p.SkywayLocationFlag, p.CurrentMissionID, p.FirstUseFlag,
|
||||||
|
a.Body, a.EyeColor, a.FaceStyle, a.Gender, a.HairColor, a.HairStyle,
|
||||||
|
a.Height, a.SkinColor
|
||||||
|
FROM Players as p
|
||||||
|
INNER JOIN Appearances as a ON p.PlayerID = a.PlayerID
|
||||||
|
WHERE p.AccountID = ?`, AccountID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var plrs []Player
|
||||||
|
for rows.Next() {
|
||||||
|
plr := Player{}
|
||||||
|
|
||||||
|
if err := rows.Scan(
|
||||||
|
&plr.PlayerID, &plr.AccountID, &plr.Slot, &plr.FirstName, &plr.LastName,
|
||||||
|
&plr.Level, &plr.Nano1, &plr.Nano2, &plr.Nano3,
|
||||||
|
&plr.AppearanceFlag, &plr.TutorialFlag, &plr.PayZoneFlag,
|
||||||
|
&plr.XCoordinate, &plr.YCoordinate, &plr.ZCoordinate, &plr.NameCheck,
|
||||||
|
&plr.Angle, &plr.HP, &plr.FusionMatter, &plr.Taros, &plr.Quests,
|
||||||
|
&plr.BatteryW, &plr.BatteryN, &plr.Mentor, &plr.WarpLocationFlag,
|
||||||
|
&plr.SkywayLocationFlag, &plr.CurrentMissionID, &plr.FirstUseFlag,
|
||||||
|
&plr.Body, &plr.EyeColor, &plr.FaceStyle, &plr.Gender, &plr.HairColor, &plr.HairStyle,
|
||||||
|
&plr.Height, &plr.SkinColor); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
plrs = append(plrs, plr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return plrs, nil
|
||||||
|
}
|
81
db/schema.go
Normal file
81
db/schema.go
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
package db
|
||||||
|
|
||||||
|
/*
|
||||||
|
This database has been based off of openfusion's. Databases should be completely interchangable between
|
||||||
|
openfusion and gopenfusion.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
_ "embed"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
_ "github.com/glebarez/go-sqlite"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DBHandler struct {
|
||||||
|
db *sql.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
type DBQuery interface {
|
||||||
|
Query(query string, args ...any) (*sql.Rows, error)
|
||||||
|
Exec(query string, args ...any) (sql.Result, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:embed migrations/new.sql
|
||||||
|
var createDBQuery string
|
||||||
|
|
||||||
|
var (
|
||||||
|
DefaultDB *DBHandler
|
||||||
|
)
|
||||||
|
|
||||||
|
func OpenLiteDB(dbPath string) (*DBHandler, error) {
|
||||||
|
sqliteFmt := fmt.Sprintf("%s", dbPath)
|
||||||
|
|
||||||
|
db, err := sql.Open("sqlite", sqliteFmt)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &DBHandler{db}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *DBHandler) Query(query string, args ...any) (*sql.Rows, error) {
|
||||||
|
return db.db.Query(query, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *DBHandler) Exec(query string, args ...any) (sql.Result, error) {
|
||||||
|
return db.db.Exec(query, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *DBHandler) Close() error {
|
||||||
|
return db.db.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *DBHandler) Setup() error {
|
||||||
|
// create db tables
|
||||||
|
_, err := db.db.Exec(createDBQuery)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// calls transaction, if transaction returns a non-nil error the transaction is rolled back. otherwise the transaction is committed
|
||||||
|
func (db *DBHandler) Transaction(transaction func(*sql.Tx) error) (err error) {
|
||||||
|
tx, err := db.db.Begin()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if p := recover(); p != nil {
|
||||||
|
// we panic'd ??? rollback and rethrow
|
||||||
|
tx.Rollback()
|
||||||
|
panic(p)
|
||||||
|
} else if err != nil {
|
||||||
|
tx.Rollback()
|
||||||
|
} else {
|
||||||
|
err = tx.Commit()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
err = transaction(tx)
|
||||||
|
return
|
||||||
|
}
|
18
go.mod
18
go.mod
@ -1,3 +1,21 @@
|
|||||||
module github.com/CPunch/GopenFusion
|
module github.com/CPunch/GopenFusion
|
||||||
|
|
||||||
go 1.19
|
go 1.19
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/blockloop/scan v1.3.0
|
||||||
|
github.com/glebarez/go-sqlite v1.21.0
|
||||||
|
golang.org/x/crypto v0.7.0
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||||
|
github.com/google/uuid v1.3.0 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.17 // indirect
|
||||||
|
github.com/remyoudompheng/bigfft v0.0.0-20230126093431-47fa9a501578 // indirect
|
||||||
|
golang.org/x/sys v0.6.0 // indirect
|
||||||
|
modernc.org/libc v1.22.2 // indirect
|
||||||
|
modernc.org/mathutil v1.5.0 // indirect
|
||||||
|
modernc.org/memory v1.5.0 // indirect
|
||||||
|
modernc.org/sqlite v1.20.4 // indirect
|
||||||
|
)
|
||||||
|
36
go.sum
Normal file
36
go.sum
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
github.com/blockloop/scan v1.3.0 h1:p8xnajpGA3d/V6o23IBFdQ764+JnNJ+PQj+OwT+rkdg=
|
||||||
|
github.com/blockloop/scan v1.3.0/go.mod h1:qd+3w68+o7m5Xhj9X5SlJH2rbFyK8w0WT47Rkuer010=
|
||||||
|
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||||
|
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||||
|
github.com/glebarez/go-sqlite v1.21.0 h1:b8MHPtBagkSD2gntImZPsG3o3QEXgMDxguW/GLUonHQ=
|
||||||
|
github.com/glebarez/go-sqlite v1.21.0/go.mod h1:GodsA6yGSa3eKbvpr7dS+JaqazzVfMcjIXvx6KHhW/c=
|
||||||
|
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
|
||||||
|
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||||
|
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
|
||||||
|
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||||
|
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||||
|
github.com/remyoudompheng/bigfft v0.0.0-20230126093431-47fa9a501578 h1:VstopitMQi3hZP0fzvnsLmzXZdQGc4bEcgu24cp+d4M=
|
||||||
|
github.com/remyoudompheng/bigfft v0.0.0-20230126093431-47fa9a501578/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||||
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
|
||||||
|
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
||||||
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
|
||||||
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
modernc.org/libc v1.22.2 h1:4U7v51GyhlWqQmwCHj28Rdq2Yzwk55ovjFrdPjs8Hb0=
|
||||||
|
modernc.org/libc v1.22.2/go.mod h1:uvQavJ1pZ0hIoC/jfqNoMLURIMhKzINIWypNM17puug=
|
||||||
|
modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=
|
||||||
|
modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
||||||
|
modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds=
|
||||||
|
modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
|
||||||
|
modernc.org/sqlite v1.20.4 h1:J8+m2trkN+KKoE7jglyHYYYiaq5xmz2HoHJIiBlRzbE=
|
||||||
|
modernc.org/sqlite v1.20.4/go.mod h1:zKcGyrICaxNTMEHSr1HQ2GUraP0j+845GYw37+EyT6A=
|
8
main.go
8
main.go
@ -1,8 +1,14 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import "github.com/CPunch/GopenFusion/server"
|
import (
|
||||||
|
"github.com/CPunch/GopenFusion/db"
|
||||||
|
"github.com/CPunch/GopenFusion/server"
|
||||||
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
db.DefaultDB, _ = db.OpenLiteDB("test.db")
|
||||||
|
db.DefaultDB.Setup()
|
||||||
|
|
||||||
server := server.NewLoginServer()
|
server := server.NewLoginServer()
|
||||||
server.Start()
|
server.Start()
|
||||||
}
|
}
|
||||||
|
@ -125,12 +125,12 @@ func (pkt *Packet) Encode(data interface{}) {
|
|||||||
for i := 0; i < sz; i++ {
|
for i := 0; i < sz; i++ {
|
||||||
pkt.encodeStructField(rv.Type().Field(i), rv.Field(i))
|
pkt.encodeStructField(rv.Type().Field(i), rv.Field(i))
|
||||||
}
|
}
|
||||||
case reflect.Slice: // (untested)
|
case reflect.Array:
|
||||||
sz := rv.Len()
|
sz := rv.Len()
|
||||||
|
|
||||||
// encode data
|
// encode data
|
||||||
for i := 0; i < sz; i++ {
|
for i := 0; i < sz; i++ {
|
||||||
elem := rv.Index(i).Addr()
|
elem := rv.Index(i)
|
||||||
pkt.Encode(elem.Addr().Interface())
|
pkt.Encode(elem.Addr().Interface())
|
||||||
}
|
}
|
||||||
case reflect.Uint8:
|
case reflect.Uint8:
|
||||||
@ -215,7 +215,7 @@ func (pkt *Packet) Decode(data interface{}) {
|
|||||||
for i := 0; i < sz; i++ {
|
for i := 0; i < sz; i++ {
|
||||||
pkt.decodeStructField(rv.Type().Field(i), rv.Field(i))
|
pkt.decodeStructField(rv.Type().Field(i), rv.Field(i))
|
||||||
}
|
}
|
||||||
case reflect.Array: // (untested)
|
case reflect.Array:
|
||||||
sz := rv.Len()
|
sz := rv.Len()
|
||||||
|
|
||||||
// decode data
|
// decode data
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
package protocol
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"time"
|
|
||||||
|
"github.com/CPunch/GopenFusion/db"
|
||||||
|
"github.com/CPunch/GopenFusion/protocol"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -14,30 +16,40 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type ClientHandler interface {
|
type ClientHandler interface {
|
||||||
HandlePacket(client *Client, typeID uint32, pkt *Packet)
|
HandlePacket(client *Client, typeID uint32, pkt *protocol.Packet)
|
||||||
|
Connect(client *Client)
|
||||||
Disconnect(client *Client)
|
Disconnect(client *Client)
|
||||||
}
|
}
|
||||||
|
|
||||||
type Client struct {
|
type Client struct {
|
||||||
handler ClientHandler
|
E_key []byte
|
||||||
conn net.Conn
|
FE_key []byte
|
||||||
e_key []byte
|
SzID string
|
||||||
fe_key []byte
|
AccountID int
|
||||||
whichKey int
|
Player *db.Player
|
||||||
|
handler ClientHandler
|
||||||
|
conn net.Conn
|
||||||
|
alive bool
|
||||||
|
whichKey int
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewClient(handler ClientHandler, conn net.Conn) *Client {
|
func NewClient(handler ClientHandler, conn net.Conn) *Client {
|
||||||
return &Client{
|
return &Client{
|
||||||
handler: handler,
|
E_key: []byte(protocol.DEFAULT_KEY),
|
||||||
conn: conn,
|
FE_key: nil,
|
||||||
e_key: []byte(DEFAULT_KEY),
|
SzID: "",
|
||||||
whichKey: USE_E,
|
AccountID: -1,
|
||||||
|
Player: nil,
|
||||||
|
handler: handler,
|
||||||
|
conn: conn,
|
||||||
|
alive: true,
|
||||||
|
whichKey: USE_E,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (client *Client) Send(data interface{}, typeID uint32) {
|
func (client *Client) Send(data interface{}, typeID uint32) {
|
||||||
// encode
|
// encode
|
||||||
pkt := NewPacket(make([]byte, 0))
|
pkt := protocol.NewPacket(make([]byte, 0))
|
||||||
pkt.Encode(data)
|
pkt.Encode(data)
|
||||||
log.Printf("Sending %#v, sizeof: %d", data, len(pkt.Buf))
|
log.Printf("Sending %#v, sizeof: %d", data, len(pkt.Buf))
|
||||||
|
|
||||||
@ -55,9 +67,9 @@ func (client *Client) Send(data interface{}, typeID uint32) {
|
|||||||
// encrypt typeID & body
|
// encrypt typeID & body
|
||||||
switch client.whichKey {
|
switch client.whichKey {
|
||||||
case USE_E:
|
case USE_E:
|
||||||
EncryptData(tmp, client.e_key)
|
protocol.EncryptData(tmp, client.E_key)
|
||||||
case USE_FE:
|
case USE_FE:
|
||||||
EncryptData(tmp, client.fe_key)
|
protocol.EncryptData(tmp, client.FE_key)
|
||||||
}
|
}
|
||||||
|
|
||||||
// write packet body
|
// write packet body
|
||||||
@ -66,32 +78,14 @@ func (client *Client) Send(data interface{}, typeID uint32) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (client *Client) AcceptLogin(SZID string, IClientVerC int32, ISlotNum int8, data []SP_LS2CL_REP_CHAR_INFO) {
|
func (client *Client) Kill() {
|
||||||
resp := &SP_LS2CL_REP_LOGIN_SUCC{
|
if !client.alive {
|
||||||
SzID: SZID,
|
return
|
||||||
ICharCount: int8(len(data)),
|
|
||||||
ISlotNum: ISlotNum,
|
|
||||||
IPaymentFlag: 1,
|
|
||||||
IOpenBetaFlag: 0,
|
|
||||||
UiSvrTime: uint64(time.Now().Unix()),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
client.Send(resp, P_LS2CL_REP_LOGIN_SUCC)
|
client.alive = false
|
||||||
client.e_key = CreateNewKey(
|
client.conn.Close()
|
||||||
resp.UiSvrTime,
|
client.handler.Disconnect(client)
|
||||||
uint64(resp.ICharCount+1),
|
|
||||||
uint64(resp.ISlotNum+1),
|
|
||||||
)
|
|
||||||
client.fe_key = CreateNewKey(
|
|
||||||
binary.LittleEndian.Uint64([]byte(DEFAULT_KEY)),
|
|
||||||
uint64(IClientVerC),
|
|
||||||
1,
|
|
||||||
)
|
|
||||||
|
|
||||||
// send characters (if any)
|
|
||||||
for i := 0; i < len(data); i++ {
|
|
||||||
client.Send(data[i], P_LS2CL_REP_CHAR_INFO)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (client *Client) ClientHandler() {
|
func (client *Client) ClientHandler() {
|
||||||
@ -99,11 +93,10 @@ func (client *Client) ClientHandler() {
|
|||||||
if err := recover(); err != nil {
|
if err := recover(); err != nil {
|
||||||
log.Printf("Client %p panic'd! %v", client, err)
|
log.Printf("Client %p panic'd! %v", client, err)
|
||||||
}
|
}
|
||||||
client.conn.Close()
|
client.Kill()
|
||||||
client.handler.Disconnect(client)
|
|
||||||
}()
|
}()
|
||||||
|
|
||||||
tmp := make([]byte, 4, CN_PACKET_BUFFER_SIZE)
|
tmp := make([]byte, 4, protocol.CN_PACKET_BUFFER_SIZE)
|
||||||
for {
|
for {
|
||||||
// read packet size
|
// read packet size
|
||||||
if _, err := client.conn.Read(tmp); err != nil {
|
if _, err := client.conn.Read(tmp); err != nil {
|
||||||
@ -112,7 +105,7 @@ func (client *Client) ClientHandler() {
|
|||||||
sz := int(binary.LittleEndian.Uint32(tmp))
|
sz := int(binary.LittleEndian.Uint32(tmp))
|
||||||
|
|
||||||
// client should never send a packet size outside of this range
|
// client should never send a packet size outside of this range
|
||||||
if sz > CN_PACKET_BUFFER_SIZE || sz < 4 {
|
if sz > protocol.CN_PACKET_BUFFER_SIZE || sz < 4 {
|
||||||
panic(fmt.Errorf("[FATAL] malicious packet size received! %d", sz))
|
panic(fmt.Errorf("[FATAL] malicious packet size received! %d", sz))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -122,12 +115,12 @@ func (client *Client) ClientHandler() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// decrypt && grab typeID
|
// decrypt && grab typeID
|
||||||
DecryptData(tmp[:sz], client.e_key)
|
protocol.DecryptData(tmp[:sz], client.E_key)
|
||||||
typeID := uint32(binary.LittleEndian.Uint32(tmp[:4]))
|
typeID := uint32(binary.LittleEndian.Uint32(tmp[:4]))
|
||||||
|
|
||||||
// dispatch packet
|
// dispatch packet
|
||||||
log.Printf("Got packet ID: %x, with a sizeof: %d\n", typeID, sz)
|
log.Printf("Got packet ID: %x, with a sizeof: %d\n", typeID, sz)
|
||||||
pkt := NewPacket(tmp[4:sz])
|
pkt := protocol.NewPacket(tmp[4:sz])
|
||||||
client.handler.HandlePacket(client, typeID, pkt)
|
client.handler.HandlePacket(client, typeID, pkt)
|
||||||
|
|
||||||
// reset tmp
|
// reset tmp
|
171
server/login.go
Normal file
171
server/login.go
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/CPunch/GopenFusion/config"
|
||||||
|
"github.com/CPunch/GopenFusion/db"
|
||||||
|
"github.com/CPunch/GopenFusion/protocol"
|
||||||
|
"github.com/CPunch/GopenFusion/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
LOGIN_DATABASE_ERROR = 0
|
||||||
|
LOGIN_ID_DOESNT_EXIST = 1
|
||||||
|
LOGIN_ID_AND_PASSWORD_DO_NOT_MATCH = 2
|
||||||
|
LOGIN_ID_ALREADY_IN_USE = 3
|
||||||
|
LOGIN_ERROR = 4
|
||||||
|
LOGIN_CLIENT_VERSION_OUTDATED = 6
|
||||||
|
LOGIN_YOU_ARE_NOT_AN_AUTHORIZED_BETA_TESTER = 7
|
||||||
|
LOGIN_AUTHENTICATION_CONNECTION_ERROR = 8
|
||||||
|
LOGIN_UPDATED_EUALA_REQUIRED = 9
|
||||||
|
)
|
||||||
|
|
||||||
|
func (server *LoginServer) AcceptLogin(client *Client, SzID string, IClientVerC int32, ISlotNum int8, data []protocol.SP_LS2CL_REP_CHAR_INFO) {
|
||||||
|
client.SzID = SzID
|
||||||
|
|
||||||
|
resp := &protocol.SP_LS2CL_REP_LOGIN_SUCC{
|
||||||
|
SzID: SzID,
|
||||||
|
ICharCount: int8(len(data)),
|
||||||
|
ISlotNum: ISlotNum,
|
||||||
|
IPaymentFlag: 1,
|
||||||
|
IOpenBetaFlag: 0,
|
||||||
|
UiSvrTime: uint64(time.Now().Unix()),
|
||||||
|
}
|
||||||
|
|
||||||
|
client.Send(resp, protocol.P_LS2CL_REP_LOGIN_SUCC)
|
||||||
|
client.E_key = protocol.CreateNewKey(
|
||||||
|
resp.UiSvrTime,
|
||||||
|
uint64(resp.ICharCount+1),
|
||||||
|
uint64(resp.ISlotNum+1),
|
||||||
|
)
|
||||||
|
client.FE_key = protocol.CreateNewKey(
|
||||||
|
binary.LittleEndian.Uint64([]byte(protocol.DEFAULT_KEY)),
|
||||||
|
uint64(IClientVerC),
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
|
||||||
|
// send characters (if any)
|
||||||
|
for i := 0; i < len(data); i++ {
|
||||||
|
client.Send(&data[i], protocol.P_LS2CL_REP_CHAR_INFO)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (server *LoginServer) Login(client *Client, pkt *protocol.Packet) {
|
||||||
|
var loginPkt protocol.SP_CL2LS_REQ_LOGIN
|
||||||
|
pkt.Decode(&loginPkt)
|
||||||
|
|
||||||
|
SendError := func(e int32) {
|
||||||
|
client.Send(&protocol.SP_LS2CL_REP_LOGIN_FAIL{
|
||||||
|
IErrorCode: e,
|
||||||
|
SzID: loginPkt.SzID,
|
||||||
|
}, protocol.P_LS2CL_REP_LOGIN_FAIL)
|
||||||
|
}
|
||||||
|
|
||||||
|
// client is resending a login packet??
|
||||||
|
if client.AccountID != -1 {
|
||||||
|
SendError(LOGIN_ERROR)
|
||||||
|
panic(fmt.Errorf("Out of order P_CL2LS_REQ_LOGIN!"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// attempt login
|
||||||
|
account, err := db.TryLogin(db.DefaultDB, loginPkt.SzID, loginPkt.SzPassword)
|
||||||
|
if err == db.LoginErrorInvalidID {
|
||||||
|
// this is the default behavior, auto create the account if the ID isn't in use
|
||||||
|
account, err = db.NewAccount(db.DefaultDB, loginPkt.SzID, loginPkt.SzPassword)
|
||||||
|
if err != nil {
|
||||||
|
// respond with a dummy login error packet so the client doesn't get softlocked
|
||||||
|
SendError(LOGIN_DATABASE_ERROR)
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
} else if err == db.LoginErrorInvalidPassword {
|
||||||
|
// respond with invalid password
|
||||||
|
SendError(LOGIN_ID_AND_PASSWORD_DO_NOT_MATCH)
|
||||||
|
return
|
||||||
|
} else if err != nil { // wtf?
|
||||||
|
SendError(LOGIN_DATABASE_ERROR)
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// grab player data
|
||||||
|
client.AccountID = account.AccountID
|
||||||
|
plrs, err := db.DefaultDB.GetPlayers(account.AccountID)
|
||||||
|
if err != nil {
|
||||||
|
SendError(LOGIN_DATABASE_ERROR)
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// build character list
|
||||||
|
charInfo := make([]protocol.SP_LS2CL_REP_CHAR_INFO, 0)
|
||||||
|
for _, plr := range plrs {
|
||||||
|
PCStyle, PCStyle2 := util.Player2PCStyle(&plr)
|
||||||
|
info := protocol.SP_LS2CL_REP_CHAR_INFO{
|
||||||
|
ISlot: int8(plr.Slot),
|
||||||
|
ILevel: int16(plr.Level),
|
||||||
|
SPC_Style: PCStyle,
|
||||||
|
SPC_Style2: PCStyle2,
|
||||||
|
IX: int32(plr.XCoordinate),
|
||||||
|
IY: int32(plr.YCoordinate),
|
||||||
|
IZ: int32(plr.ZCoordinate),
|
||||||
|
}
|
||||||
|
|
||||||
|
AEquip, err := db.DefaultDB.GetPlayerInventorySlots(plr.PlayerID, 0, config.AEQUIP_COUNT-1)
|
||||||
|
if err != nil {
|
||||||
|
SendError(LOGIN_DATABASE_ERROR)
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
copy(info.AEquip[:], AEquip)
|
||||||
|
charInfo = append(charInfo, info)
|
||||||
|
}
|
||||||
|
|
||||||
|
server.AcceptLogin(client, loginPkt.SzID, loginPkt.IClientVerC, 1, charInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (server *LoginServer) SaveCharacterName(client *Client, pkt *protocol.Packet) {
|
||||||
|
var charPkt protocol.SP_CL2LS_REQ_SAVE_CHAR_NAME
|
||||||
|
pkt.Decode(&charPkt)
|
||||||
|
|
||||||
|
if client.AccountID == -1 {
|
||||||
|
client.Send(&protocol.SP_LS2CL_REP_SAVE_CHAR_NAME_FAIL{}, protocol.P_LS2CL_REP_SAVE_CHAR_NAME_FAIL)
|
||||||
|
panic(fmt.Errorf("Out of order P_LS2CL_REP_SAVE_CHAR_NAME_FAIL!"))
|
||||||
|
}
|
||||||
|
|
||||||
|
PlayerID, err := db.DefaultDB.NewPlayer(client.AccountID, charPkt.SzFirstName, charPkt.SzLastName, int(charPkt.ISlotNum))
|
||||||
|
if err != nil {
|
||||||
|
client.Send(&protocol.SP_LS2CL_REP_SAVE_CHAR_NAME_FAIL{}, protocol.P_LS2CL_REP_SAVE_CHAR_NAME_FAIL)
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
client.Send(&protocol.SP_LS2CL_REP_SAVE_CHAR_NAME_SUCC{
|
||||||
|
IPC_UID: int64(PlayerID),
|
||||||
|
ISlotNum: charPkt.ISlotNum,
|
||||||
|
IGender: charPkt.IGender,
|
||||||
|
SzFirstName: charPkt.SzFirstName,
|
||||||
|
SzLastName: charPkt.SzLastName,
|
||||||
|
}, protocol.P_LS2CL_REP_SAVE_CHAR_NAME_SUCC)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (server *LoginServer) CharacterCreate(client *Client, pkt *protocol.Packet) {
|
||||||
|
var charPkt protocol.SP_CL2LS_REQ_CHAR_CREATE
|
||||||
|
pkt.Decode(&charPkt)
|
||||||
|
|
||||||
|
if err := db.DefaultDB.FinishPlayer(&charPkt, client.AccountID); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
plr, err := db.DefaultDB.GetPlayer(int(charPkt.PCStyle.IPC_UID))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
PCStyle, PCStyle2 := util.Player2PCStyle(plr)
|
||||||
|
client.Send(&protocol.SP_LS2CL_REP_CHAR_CREATE_SUCC{
|
||||||
|
ILevel: int16(plr.Level),
|
||||||
|
SPC_Style: PCStyle,
|
||||||
|
SPC_Style2: PCStyle2,
|
||||||
|
SOn_Item: protocol.SOnItem{ /*TODO*/ },
|
||||||
|
}, protocol.P_LS2CL_REP_CHAR_CREATE_SUCC)
|
||||||
|
}
|
@ -4,14 +4,15 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/CPunch/GopenFusion/protocol"
|
"github.com/CPunch/GopenFusion/protocol"
|
||||||
)
|
)
|
||||||
|
|
||||||
type LoginServer struct {
|
type LoginServer struct {
|
||||||
listener net.Listener
|
listener net.Listener
|
||||||
clients map[*protocol.Client]bool
|
clients map[*Client]bool
|
||||||
unregister chan *protocol.Client
|
lock sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewLoginServer() *LoginServer {
|
func NewLoginServer() *LoginServer {
|
||||||
@ -21,9 +22,8 @@ func NewLoginServer() *LoginServer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return &LoginServer{
|
return &LoginServer{
|
||||||
listener: listener,
|
listener: listener,
|
||||||
clients: make(map[*protocol.Client]bool),
|
clients: make(map[*Client]bool),
|
||||||
unregister: make(chan *protocol.Client),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -31,33 +31,22 @@ func (server *LoginServer) Start() {
|
|||||||
log.Print("Server hosted on 127.0.0.1:23000")
|
log.Print("Server hosted on 127.0.0.1:23000")
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
conn, err := server.listener.Accept()
|
||||||
case client := <-server.unregister:
|
if err != nil {
|
||||||
delete(server.clients, client)
|
log.Println("Connection error: ", err)
|
||||||
fmt.Printf("Client %p disconnected\n", client)
|
return
|
||||||
default:
|
|
||||||
conn, err := server.listener.Accept()
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Connection error: ", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
client := protocol.NewClient(server, conn)
|
|
||||||
server.clients[client] = true
|
|
||||||
fmt.Printf("Client %p connected\n", client)
|
|
||||||
go client.ClientHandler()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
client := NewClient(server, conn)
|
||||||
|
server.Connect(client)
|
||||||
|
go client.ClientHandler()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (server *LoginServer) HandlePacket(client *protocol.Client, typeID uint32, pkt *protocol.Packet) {
|
func (server *LoginServer) HandlePacket(client *Client, typeID uint32, pkt *protocol.Packet) {
|
||||||
switch typeID {
|
switch typeID {
|
||||||
case protocol.P_CL2LS_REQ_LOGIN:
|
case protocol.P_CL2LS_REQ_LOGIN:
|
||||||
var loginPkt protocol.SP_CL2LS_REQ_LOGIN
|
server.Login(client, pkt)
|
||||||
pkt.Decode(&loginPkt)
|
|
||||||
|
|
||||||
// TODO: for now, we're a dummy server
|
|
||||||
client.AcceptLogin(loginPkt.SzID, loginPkt.IClientVerC, 1, []protocol.SP_LS2CL_REP_CHAR_INFO{})
|
|
||||||
case protocol.P_CL2LS_REQ_CHECK_CHAR_NAME:
|
case protocol.P_CL2LS_REQ_CHECK_CHAR_NAME:
|
||||||
var charPkt protocol.SP_CL2LS_REQ_CHECK_CHAR_NAME
|
var charPkt protocol.SP_CL2LS_REQ_CHECK_CHAR_NAME
|
||||||
pkt.Decode(&charPkt)
|
pkt.Decode(&charPkt)
|
||||||
@ -67,21 +56,24 @@ func (server *LoginServer) HandlePacket(client *protocol.Client, typeID uint32,
|
|||||||
SzLastName: charPkt.SzLastName,
|
SzLastName: charPkt.SzLastName,
|
||||||
}, protocol.P_LS2CL_REP_CHECK_CHAR_NAME_SUCC)
|
}, protocol.P_LS2CL_REP_CHECK_CHAR_NAME_SUCC)
|
||||||
case protocol.P_CL2LS_REQ_SAVE_CHAR_NAME:
|
case protocol.P_CL2LS_REQ_SAVE_CHAR_NAME:
|
||||||
var charPkt protocol.SP_CL2LS_REQ_SAVE_CHAR_NAME
|
server.SaveCharacterName(client, pkt)
|
||||||
pkt.Decode(&charPkt)
|
case protocol.P_CL2LS_REQ_CHAR_CREATE:
|
||||||
|
server.CharacterCreate(client, pkt)
|
||||||
client.Send(&protocol.SP_LS2CL_REP_SAVE_CHAR_NAME_SUCC{
|
|
||||||
IPC_UID: 1,
|
|
||||||
ISlotNum: charPkt.ISlotNum,
|
|
||||||
IGender: charPkt.IGender,
|
|
||||||
SzFirstName: charPkt.SzFirstName,
|
|
||||||
SzLastName: charPkt.SzLastName,
|
|
||||||
}, protocol.P_LS2CL_REP_SAVE_CHAR_NAME_SUCC)
|
|
||||||
default:
|
default:
|
||||||
log.Printf("[WARN] unsupported packet ID: %x\n", typeID)
|
log.Printf("[WARN] unsupported packet ID: %x\n", typeID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (server *LoginServer) Disconnect(client *protocol.Client) {
|
func (server *LoginServer) Disconnect(client *Client) {
|
||||||
server.unregister <- client
|
server.lock.Lock()
|
||||||
|
delete(server.clients, client)
|
||||||
|
fmt.Printf("Client %p disconnected\n", client)
|
||||||
|
server.lock.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (server *LoginServer) Connect(client *Client) {
|
||||||
|
server.lock.Lock()
|
||||||
|
server.clients[client] = true
|
||||||
|
fmt.Printf("Client %p connected\n", client)
|
||||||
|
server.lock.Unlock()
|
||||||
}
|
}
|
||||||
|
28
util/player.go
Normal file
28
util/player.go
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/CPunch/GopenFusion/db"
|
||||||
|
"github.com/CPunch/GopenFusion/protocol"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Player2PCStyle(plr *db.Player) (protocol.SPCStyle, protocol.SPCStyle2) {
|
||||||
|
return protocol.SPCStyle{
|
||||||
|
IPC_UID: int64(plr.PlayerID),
|
||||||
|
INameCheck: int8(plr.NameCheck),
|
||||||
|
SzFirstName: plr.FirstName,
|
||||||
|
SzLastName: plr.LastName,
|
||||||
|
IGender: int8(plr.Gender),
|
||||||
|
IFaceStyle: int8(plr.FaceStyle),
|
||||||
|
IHairStyle: int8(plr.HairStyle),
|
||||||
|
IHairColor: int8(plr.HairColor),
|
||||||
|
ISkinColor: int8(plr.SkinColor),
|
||||||
|
IEyeColor: int8(plr.EyeColor),
|
||||||
|
IHeight: int8(plr.Height),
|
||||||
|
IBody: int8(plr.Body),
|
||||||
|
},
|
||||||
|
protocol.SPCStyle2{
|
||||||
|
IAppearanceFlag: int8(plr.AppearanceFlag),
|
||||||
|
ITutorialFlag: int8(plr.TutorialFlag),
|
||||||
|
IPayzoneFlag: int8(plr.PayZoneFlag),
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user