mirror of
https://github.com/Octops/quake-kube.git
synced 2026-04-10 20:00:32 +00:00
Initial commit
This commit is contained in:
156
internal/quake/content/download.go
Normal file
156
internal/quake/content/download.go
Normal file
@ -0,0 +1,156 @@
|
||||
package content
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
httputil "github.com/criticalstack/quake-kube/internal/util/net/http"
|
||||
)
|
||||
|
||||
func CopyAssets(u *url.URL, dir string) error {
|
||||
url := strings.TrimSuffix(u.String(), "/")
|
||||
files, err := getManifest(url)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, f := range files {
|
||||
path := filepath.Join(dir, f.Name)
|
||||
if _, err := os.Stat(path); !os.IsNotExist(err) {
|
||||
continue
|
||||
}
|
||||
data, err := httputil.GetBody(url + fmt.Sprintf("/assets/%d-%s", f.Checksum, f.Name))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ioutil.WriteFile(path, data, 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
if strings.HasPrefix(f.Name, "linuxq3ademo") {
|
||||
if err := extractDemoPack(path, dir); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if strings.HasPrefix(f.Name, "linuxq3apoint") {
|
||||
if err := extractPointPacks(path, dir); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getManifest(url string) ([]*File, error) {
|
||||
data, err := httputil.GetBody(url + "/assets/manifest.json")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
files := make([]*File, 0)
|
||||
if err := json.Unmarshal(data, &files); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return files, nil
|
||||
}
|
||||
|
||||
var gzipMagicHeader = []byte{'\x1f', '\x8b'}
|
||||
|
||||
func extractDemoPack(path, dir string) error {
|
||||
data, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
idx := bytes.Index(data, gzipMagicHeader)
|
||||
data = data[idx:]
|
||||
gr, err := gzip.NewReader(bytes.NewReader(data))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer gr.Close()
|
||||
|
||||
data, err = ioutil.ReadAll(gr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tr := tar.NewReader(bytes.NewReader(data))
|
||||
for {
|
||||
hdr, err := tr.Next()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if strings.HasSuffix(hdr.Name, ".pk3") {
|
||||
fmt.Printf("Downloaded %s\n", hdr.Name)
|
||||
data, err := ioutil.ReadAll(tr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
path := filepath.Join(dir, "baseq3", filepath.Base(hdr.Name))
|
||||
if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ioutil.WriteFile(path, data, 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func extractPointPacks(path, dir string) error {
|
||||
data, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
idx := bytes.Index(data, gzipMagicHeader)
|
||||
data = data[idx:]
|
||||
gr, err := gzip.NewReader(bytes.NewReader(data))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer gr.Close()
|
||||
|
||||
data, err = ioutil.ReadAll(gr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tr := tar.NewReader(bytes.NewReader(data))
|
||||
for {
|
||||
hdr, err := tr.Next()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if strings.HasSuffix(hdr.Name, ".pk3") {
|
||||
fmt.Printf("Downloaded %s\n", hdr.Name)
|
||||
data, err := ioutil.ReadAll(tr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
path := filepath.Join(dir, hdr.Name)
|
||||
if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ioutil.WriteFile(path, data, 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
56
internal/quake/content/files.go
Normal file
56
internal/quake/content/files.go
Normal file
@ -0,0 +1,56 @@
|
||||
package content
|
||||
|
||||
import (
|
||||
"hash/crc32"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type File struct {
|
||||
Name string `json:"name"`
|
||||
Compressed int64 `json:"compressed"`
|
||||
Checksum uint32 `json:"checksum"`
|
||||
}
|
||||
|
||||
func getAssets(dir string) (files []*File, err error) {
|
||||
err = walk(dir, func(path string, info os.FileInfo, err error) error {
|
||||
data, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
n := crc32.ChecksumIEEE(data)
|
||||
path = strings.TrimPrefix(path, dir+"/")
|
||||
files = append(files, &File{path, info.Size(), n})
|
||||
return nil
|
||||
}, ".pk3", ".sh", ".run")
|
||||
return
|
||||
}
|
||||
|
||||
func hasExts(path string, exts ...string) bool {
|
||||
for _, ext := range exts {
|
||||
if strings.HasSuffix(path, ext) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func walk(root string, walkFn filepath.WalkFunc, exts ...string) error {
|
||||
return filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
if os.IsPermission(err) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
if !hasExts(path, exts...) {
|
||||
return nil
|
||||
}
|
||||
if info.IsDir() {
|
||||
return nil
|
||||
}
|
||||
return walkFn(path, info, err)
|
||||
})
|
||||
}
|
||||
72
internal/quake/content/maps.go
Normal file
72
internal/quake/content/maps.go
Normal file
@ -0,0 +1,72 @@
|
||||
package content
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Map struct {
|
||||
File string `json:"file"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
func getMaps(dir string) (result []*Map, err error) {
|
||||
err = walk(dir, func(path string, info os.FileInfo, err error) error {
|
||||
mp, err := OpenMapPack(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer mp.Close()
|
||||
|
||||
maps, err := mp.Maps()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
result = append(result, maps...)
|
||||
return err
|
||||
}, ".pk3")
|
||||
return
|
||||
}
|
||||
|
||||
type MapPack struct {
|
||||
*os.File
|
||||
*zip.Reader
|
||||
|
||||
path string
|
||||
}
|
||||
|
||||
func OpenMapPack(path string) (*MapPack, error) {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
info, err := f.Stat()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r, err := zip.NewReader(f, info.Size())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mp := &MapPack{
|
||||
File: f,
|
||||
Reader: r,
|
||||
path: path,
|
||||
}
|
||||
return mp, nil
|
||||
}
|
||||
|
||||
func (m *MapPack) Maps() ([]*Map, error) {
|
||||
maps := make([]*Map, 0)
|
||||
for _, f := range m.Reader.File {
|
||||
if !hasExts(f.Name, ".bsp") {
|
||||
continue
|
||||
}
|
||||
path := filepath.Join(filepath.Base(filepath.Dir(m.path)), filepath.Base(m.path))
|
||||
mapName := strings.TrimSuffix(filepath.Base(f.Name), ".bsp")
|
||||
maps = append(maps, &Map{File: path, Name: mapName})
|
||||
}
|
||||
return maps, nil
|
||||
}
|
||||
133
internal/quake/content/router.go
Normal file
133
internal/quake/content/router.go
Normal file
@ -0,0 +1,133 @@
|
||||
package content
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/labstack/echo/v4/middleware"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
AssetsDir string
|
||||
}
|
||||
|
||||
func NewRouter(cfg *Config) (*echo.Echo, error) {
|
||||
e := echo.New()
|
||||
e.Use(middleware.Logger())
|
||||
e.Use(middleware.Recover())
|
||||
//e.Use(middleware.BodyLimit("100M"))
|
||||
e.Use(middleware.CORSWithConfig(middleware.CORSConfig{
|
||||
AllowOrigins: []string{"*"},
|
||||
AllowHeaders: []string{echo.HeaderOrigin, echo.HeaderContentType, echo.HeaderAccept},
|
||||
}))
|
||||
e.GET("/", func(c echo.Context) error {
|
||||
return c.HTML(http.StatusOK, `<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Map pack upload</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<a href="/maps">Show maps</a>
|
||||
|
||||
<h1>Upload map pack file</h1>
|
||||
|
||||
<form action="/maps" method="post" enctype="multipart/form-data">
|
||||
GameName: <input type="text" name="name" value="baseq3" /><br>
|
||||
Files: <input type="file" name="file"><br><br>
|
||||
<input type="submit" value="Submit">
|
||||
</form>
|
||||
</body>
|
||||
</html>`)
|
||||
})
|
||||
e.GET("/assets/manifest.json", func(c echo.Context) error {
|
||||
files, err := getAssets(cfg.AssetsDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return c.JSONPretty(http.StatusOK, files, " ")
|
||||
})
|
||||
e.GET("/assets/*", func(c echo.Context) error {
|
||||
path := filepath.Join(cfg.AssetsDir, c.Param("*"))
|
||||
d, f := filepath.Split(path)
|
||||
f = f[strings.Index(f, "-")+1:]
|
||||
path = filepath.Join(d, f)
|
||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||
return c.String(http.StatusNotFound, "file not found")
|
||||
}
|
||||
return c.File(path)
|
||||
})
|
||||
e.GET("/maps", func(c echo.Context) error {
|
||||
maps, err := getMaps(cfg.AssetsDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return c.JSONPretty(http.StatusOK, maps, " ")
|
||||
})
|
||||
e.POST("/maps", func(c echo.Context) error {
|
||||
name := c.FormValue("name")
|
||||
file, err := c.FormFile("file")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
src, err := file.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer src.Close()
|
||||
|
||||
if hasExts(file.Filename, ".zip") {
|
||||
r, err := zip.NewReader(src, file.Size)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
files := make([]string, 0)
|
||||
for _, f := range r.File {
|
||||
if !hasExts(f.Name, ".pk3") {
|
||||
continue
|
||||
}
|
||||
pak, err := f.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer pak.Close()
|
||||
|
||||
dst, err := os.Create(filepath.Join(cfg.AssetsDir, name, filepath.Base(f.Name)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer dst.Close()
|
||||
|
||||
if _, err = io.Copy(dst, pak); err != nil {
|
||||
return err
|
||||
}
|
||||
files = append(files, filepath.Base(f.Name))
|
||||
}
|
||||
if len(files) == 0 {
|
||||
return c.HTML(http.StatusOK, fmt.Sprintf("<p>File %s did not contain any map pack files.</p>", file.Filename))
|
||||
}
|
||||
for i, _ := range files {
|
||||
files[i] = "<li>" + files[i] + "</li>"
|
||||
}
|
||||
return c.HTML(http.StatusOK, fmt.Sprintf("<p>Loaded the following map packs from file %s:</p><ul>%s</ul>", file.Filename, strings.Join(files, "")))
|
||||
}
|
||||
dst, err := os.Create(filepath.Join(cfg.AssetsDir, name, file.Filename))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer dst.Close()
|
||||
|
||||
if _, err = io.Copy(dst, src); err != nil {
|
||||
return err
|
||||
}
|
||||
return c.HTML(http.StatusOK, fmt.Sprintf("<p>File %s uploaded successfully.</p>", filepath.Join(name, file.Filename)))
|
||||
})
|
||||
return e, nil
|
||||
}
|
||||
Reference in New Issue
Block a user