super-mario-maker/database/init_postgres.go
2025-02-19 22:53:57 -05:00

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!")
}