mirror of
https://github.com/PretendoNetwork/super-mario-maker.git
synced 2026-03-22 01:44:07 -05:00
282 lines
7.8 KiB
Go
282 lines
7.8 KiB
Go
package database
|
|
|
|
import (
|
|
"os"
|
|
"time"
|
|
|
|
"github.com/PretendoNetwork/super-mario-maker/globals"
|
|
"github.com/lib/pq"
|
|
)
|
|
|
|
func initPostgres() {
|
|
_, err := Postgres.Exec(`CREATE SCHEMA IF NOT EXISTS datastore`)
|
|
if err != nil {
|
|
globals.Logger.Critical(err.Error())
|
|
os.Exit(0)
|
|
}
|
|
|
|
globals.Logger.Success("datastore Postgres schema created")
|
|
|
|
// * Super Mario Maker has non-standard DataID requirements.
|
|
// * DataID 900000 is reserved for the event course metadata
|
|
// * file, and official event courses begin at DataID 930010
|
|
// * and end at DataID 930050. To prevent a collision
|
|
// * eventually, we need to start course IDs AFTER 930050
|
|
// *
|
|
// * DataIDs are stored and processed as uint64, however
|
|
// * Super Mario Maker can not use the full uint64 range.
|
|
// * This is because course share codes are generated from the
|
|
// * courses DataID. A course share code is an 8 byte hex
|
|
// * string, where the upper 2 bytes are the checksum of the
|
|
// * lower 6 bytes. The lower 6 bytes are the courses DataID
|
|
// *
|
|
// * Super Mario Maker is only capable of displaying codes up
|
|
// * to 0xFFFFFFFFFFFF, essentially truncating DataIDs down to
|
|
// * 48 bit integers instead of 64 bit. I doubt we will ever
|
|
// * hit even the 32 bit limit, let alone 48, but this is here
|
|
// * just in case
|
|
_, err = Postgres.Exec(`CREATE SEQUENCE IF NOT EXISTS datastore.object_data_id_seq
|
|
INCREMENT 1
|
|
MINVALUE 1
|
|
MAXVALUE 281474976710656
|
|
START 940000
|
|
CACHE 1`, // * Honestly I don't know what CACHE does but I saw it recommended so here it is
|
|
)
|
|
if err != nil {
|
|
globals.Logger.Critical(err.Error())
|
|
os.Exit(0)
|
|
}
|
|
|
|
// * "deletion_reason" and "under_review" are specific to SMM.
|
|
// * Everything else is stock
|
|
_, err = Postgres.Exec(`CREATE TABLE IF NOT EXISTS datastore.objects (
|
|
data_id bigint NOT NULL DEFAULT nextval('datastore.object_data_id_seq') PRIMARY KEY,
|
|
upload_completed boolean NOT NULL DEFAULT FALSE,
|
|
deleted boolean NOT NULL DEFAULT FALSE,
|
|
deletion_reason int NOT NULL DEFAULT 0,
|
|
under_review boolean NOT NULL DEFAULT FALSE,
|
|
owner int,
|
|
size int,
|
|
name text,
|
|
data_type int,
|
|
meta_binary bytea,
|
|
permission int,
|
|
permission_recipients int[],
|
|
delete_permission int,
|
|
delete_permission_recipients int[],
|
|
flag int,
|
|
period int,
|
|
refer_data_id bigint,
|
|
tags text[],
|
|
persistence_slot_id int,
|
|
extra_data text[],
|
|
access_password bigint NOT NULL DEFAULT 0,
|
|
update_password bigint NOT NULL DEFAULT 0,
|
|
creation_date timestamp,
|
|
update_date timestamp
|
|
)`)
|
|
if err != nil {
|
|
globals.Logger.Critical(err.Error())
|
|
os.Exit(0)
|
|
}
|
|
|
|
// * Unsure what like half of this is but the client sends it so we saves it
|
|
_, err = Postgres.Exec(`CREATE TABLE IF NOT EXISTS datastore.object_ratings (
|
|
data_id bigint,
|
|
slot smallint,
|
|
flag smallint,
|
|
internal_flag smallint,
|
|
lock_type smallint,
|
|
initial_value bigint,
|
|
range_min int,
|
|
range_max int,
|
|
period_hour smallint,
|
|
period_duration int,
|
|
total_value bigint,
|
|
count int NOT NULL DEFAULT 0,
|
|
PRIMARY KEY(data_id, slot)
|
|
)`)
|
|
if err != nil {
|
|
globals.Logger.Critical(err.Error())
|
|
os.Exit(0)
|
|
}
|
|
|
|
// * Custom rankings are specific to SMM
|
|
// TODO - Store the period? What even is the period of custom rankings?
|
|
_, err = Postgres.Exec(`CREATE TABLE IF NOT EXISTS datastore.object_custom_rankings (
|
|
data_id bigint,
|
|
application_id bigint,
|
|
value bigint,
|
|
PRIMARY KEY(data_id, application_id)
|
|
)`)
|
|
if err != nil {
|
|
globals.Logger.Critical(err.Error())
|
|
os.Exit(0)
|
|
}
|
|
|
|
// * BufferQueues are specific to SMM
|
|
// * Real server does not allow duplicate buffers in a given slot for an object,
|
|
// * even if uploaded by different users. We could change this, but I don't see
|
|
// * much point
|
|
_, err = Postgres.Exec(`CREATE TABLE IF NOT EXISTS datastore.buffer_queues (
|
|
data_id bigint,
|
|
slot int,
|
|
creation_date timestamp,
|
|
buffer bytea,
|
|
PRIMARY KEY(data_id, slot, buffer)
|
|
)`)
|
|
if err != nil {
|
|
globals.Logger.Critical(err.Error())
|
|
os.Exit(0)
|
|
}
|
|
|
|
// * Course records are specific to SMM
|
|
_, err = Postgres.Exec(`CREATE TABLE IF NOT EXISTS datastore.course_records (
|
|
data_id bigint,
|
|
slot int,
|
|
first_pid int,
|
|
best_pid int,
|
|
best_score int,
|
|
creation_date timestamp,
|
|
update_date timestamp,
|
|
PRIMARY KEY(data_id, slot)
|
|
)`)
|
|
if err != nil {
|
|
globals.Logger.Critical(err.Error())
|
|
os.Exit(0)
|
|
}
|
|
|
|
globals.Logger.Success("Postgres tables created")
|
|
|
|
ensureEventCourseMetaDataFileExists()
|
|
}
|
|
|
|
func ensureEventCourseMetaDataFileExists() {
|
|
// * The event course metadata file is REQUIRED for
|
|
// * Super Mario Maker to open Course World. This
|
|
// * ensures that it exists both in S3 and in the
|
|
// * database before booting. It has the reserved
|
|
// * DataID 900000
|
|
bucket := os.Getenv("PN_SMM_CONFIG_S3_BUCKET")
|
|
key := "900000.bin"
|
|
|
|
objectSizeS3, err := globals.S3ObjectSize(bucket, key)
|
|
if err != nil {
|
|
globals.Logger.Errorf("Failed to stat event course metadata file. Ensure your S3 credentials are correct and the 900000.bin file is uploaded to your bucket. S3 error: %s", err.Error())
|
|
os.Exit(0)
|
|
}
|
|
|
|
globals.Logger.Success("Event course metadata file found. Verifying database")
|
|
|
|
var exists bool
|
|
err = Postgres.QueryRow(`SELECT EXISTS(SELECT 1 FROM datastore.objects WHERE data_id=900000) AS "exists"`).Scan(&exists)
|
|
if err != nil {
|
|
globals.Logger.Errorf("Error querying for event course metadata object in Postgres: %s", err.Error())
|
|
os.Exit(0)
|
|
}
|
|
|
|
now := time.Now()
|
|
|
|
if !exists {
|
|
var dataID uint64
|
|
|
|
globals.Logger.Info("Event course metadata object not found in Postgres. Creating")
|
|
|
|
err := Postgres.QueryRow(`INSERT INTO datastore.objects (
|
|
data_id,
|
|
upload_completed,
|
|
owner,
|
|
size,
|
|
name,
|
|
data_type,
|
|
meta_binary,
|
|
permission,
|
|
permission_recipients,
|
|
delete_permission,
|
|
delete_permission_recipients,
|
|
flag,
|
|
period,
|
|
refer_data_id,
|
|
tags,
|
|
persistence_slot_id,
|
|
extra_data,
|
|
creation_date,
|
|
update_date
|
|
) VALUES (
|
|
$1,
|
|
$2,
|
|
$3,
|
|
$4,
|
|
$5,
|
|
$6,
|
|
$7,
|
|
$8,
|
|
$9,
|
|
$10,
|
|
$11,
|
|
$12,
|
|
$13,
|
|
$14,
|
|
$15,
|
|
$16,
|
|
$17,
|
|
$18,
|
|
$19
|
|
) RETURNING data_id`,
|
|
900000,
|
|
true,
|
|
2, // * "Quazal Rendez-Vous" special account
|
|
objectSizeS3,
|
|
"", // * Has no name
|
|
50, // * Metadata file has DataType 50. Event courses have DataType 51
|
|
[]byte{}, // * No MetaBinary
|
|
0, // * Accessible by everyone
|
|
pq.Array([]uint32{}),
|
|
3, // * THE REAL SERVER HAS THIS SET TO 0, FOR EVERYONE. THAT'S SUPER INSECURE.
|
|
pq.Array([]uint32{}),
|
|
0,
|
|
64306,
|
|
0,
|
|
pq.Array([]string{}),
|
|
0, // * Unsure what the slot ID actually is
|
|
pq.Array([]string{}),
|
|
now,
|
|
now,
|
|
).Scan(&dataID)
|
|
if err != nil {
|
|
globals.Logger.Errorf("Error creating event course metadata object: %s", err.Error())
|
|
os.Exit(0)
|
|
}
|
|
} else {
|
|
var objectSizeDB uint32
|
|
|
|
err := Postgres.QueryRow(`SELECT size FROM datastore.objects WHERE data_id=900000`).Scan(&objectSizeDB)
|
|
if err != nil {
|
|
globals.Logger.Errorf("Error querying event course metadata object size: %s", err.Error())
|
|
os.Exit(0)
|
|
}
|
|
|
|
if objectSizeS3 != uint64(objectSizeDB) {
|
|
globals.Logger.Success("Event course metadata object found in Postgres! Updating size")
|
|
|
|
_, err := Postgres.Exec(`UPDATE datastore.objects SET size=$1, update_date=$2 WHERE data_id=900000`, objectSizeS3, now)
|
|
if err != nil {
|
|
globals.Logger.Errorf("Error updating event course metadata object size: %s", err.Error())
|
|
os.Exit(0)
|
|
}
|
|
} else {
|
|
globals.Logger.Success("Event course metadata object found in Postgres!")
|
|
}
|
|
|
|
globals.Logger.Info("Ensuring event course metadata object has correct delete permission")
|
|
|
|
_, err = Postgres.Exec(`UPDATE datastore.objects SET delete_permission=3 WHERE data_id=900000`)
|
|
if err != nil {
|
|
globals.Logger.Errorf("Error updating event course metadata object delete permission: %s", err.Error())
|
|
os.Exit(0)
|
|
}
|
|
}
|
|
|
|
globals.Logger.Success("Event course metadata object found and is up to date!")
|
|
}
|