Skip to content

Commit 7444a1e

Browse files
authored
Merge pull request #729 from JiepengTan/pr_add_file_detect
Add file detect
2 parents a6641b6 + 0d44345 commit 7444a1e

10 files changed

Lines changed: 176 additions & 1 deletion

File tree

cmd/gox/go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ go 1.23.0
55
require github.com/goplus/spx/v2 v2.0.0-00010101000000-000000000000
66

77
require (
8+
github.com/h2non/filetype v1.1.3 // indirect
89
github.com/pkg/errors v0.9.1 // indirect
910
github.com/realdream-ai/mathf v0.0.0-20250513071532-e55e1277a8c5 // indirect
1011
golang.org/x/image v0.23.0 // indirect

cmd/gox/go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
2+
github.com/h2non/filetype v1.1.3 h1:FKkx9QbD7HR/zjK1Ia5XiBsq9zdLi5Kf3zGyFTAFkGg=
3+
github.com/h2non/filetype v1.1.3/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY=
24
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
35
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
46
github.com/realdream-ai/mathf v0.0.0-20250513071532-e55e1277a8c5 h1:KuC3mEHh8NPOaOR0acOW+6ISKL4S9iY3n102KE/tst0=

cmd/igox/go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ require (
1616
github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00 // indirect
1717
github.com/goplus/gogen v1.19.0 // indirect
1818
github.com/goplus/xgo v1.5.0 // indirect
19+
github.com/h2non/filetype v1.1.3 // indirect
1920
github.com/pkg/errors v0.9.1 // indirect
2021
github.com/qiniu/x v1.15.1 // indirect
2122
github.com/realdream-ai/mathf v0.0.0-20250513071532-e55e1277a8c5 // indirect

cmd/igox/go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ github.com/goplus/reflectx v1.4.2 h1:SlBhAPZz4899p4/JkQORDWqLwfmEZGNTQuD2w12A6Ws
1717
github.com/goplus/reflectx v1.4.2/go.mod h1:wHOS9ilbB4zrecI0W1dMmkW9JMcpXV7VjALVbNU9xfM=
1818
github.com/goplus/xgo v1.5.0 h1:cOSGtJOUfBkSFa6e9K8HCrJDtiNp0sRos5ZGoCOtEZQ=
1919
github.com/goplus/xgo v1.5.0/go.mod h1:v9VsPjlFeO3EWafE8Qz8iwMoqNqNwlX9uSZL5+ZGlVg=
20+
github.com/h2non/filetype v1.1.3 h1:FKkx9QbD7HR/zjK1Ia5XiBsq9zdLi5Kf3zGyFTAFkGg=
21+
github.com/h2non/filetype v1.1.3/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY=
2022
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
2123
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
2224
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=

game.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,7 @@ func Gopt_Game_Run(game Gamer, resource any, gameConf ...*Config) {
229229
panic(err)
230230
}
231231
resMgr.SetDefaultFont("res://engine/fonts/CnFont.ttf")
232+
engine.RegisterFileSystem(fs)
232233

233234
var conf Config
234235
var proj projConfig
@@ -1462,6 +1463,7 @@ func (p *Game) loadSound(name SoundName) (media Sound, err error) {
14621463
return
14631464
}
14641465
media.Path = prefix + "/" + media.Path
1466+
engine.CheckAssetFile(media.Path)
14651467
p.sounds.audios[name] = media
14661468
return
14671469
}
@@ -1481,7 +1483,6 @@ func (p *Game) Play__0(media Sound, action *PlayOptions) {
14811483
if debugInstr {
14821484
log.Println("Play", media.Path)
14831485
}
1484-
14851486
p.checkAudioId()
14861487
err := p.play(p.audioId, media, action)
14871488
if err != nil {

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ module github.com/goplus/spx/v2
33
go 1.23.0
44

55
require (
6+
github.com/h2non/filetype v1.1.3
67
github.com/pkg/errors v0.9.1
78
github.com/realdream-ai/mathf v0.0.0-20250513071532-e55e1277a8c5
89
golang.org/x/mobile v0.0.0-20220518205345-8578da9835fd

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
2+
github.com/h2non/filetype v1.1.3 h1:FKkx9QbD7HR/zjK1Ia5XiBsq9zdLi5Kf3zGyFTAFkGg=
3+
github.com/h2non/filetype v1.1.3/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY=
24
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
35
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
46
github.com/realdream-ai/mathf v0.0.0-20250513071532-e55e1277a8c5 h1:KuC3mEHh8NPOaOR0acOW+6ISKL4S9iY3n102KE/tst0=

internal/engine/util.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,77 @@
11
package engine
22

33
import (
4+
"fmt"
5+
"io"
46
"math"
57

8+
"log"
9+
10+
"github.com/goplus/spx/v2/fs"
11+
"github.com/goplus/spx/v2/internal/engine/platform"
12+
"github.com/goplus/spx/v2/pkg/filedetect"
613
gdx "github.com/goplus/spx/v2/pkg/gdspx/pkg/engine"
714
. "github.com/realdream-ai/mathf"
815
)
916

17+
var supportedFileTypes = map[string]bool{
18+
".png": true,
19+
".jpg": true,
20+
".jpeg": true,
21+
".svg": true,
22+
".webp": true,
23+
".mp3": true,
24+
".wav": true,
25+
}
26+
27+
// check invalid files
28+
var checkedAssetFiles = make(map[string]bool)
29+
30+
func RegisterFileSystem(fs fs.Dir) {
31+
if platform.IsWeb() {
32+
filedetect.RegisterIoReader(func(file string, length int) ([]byte, error) {
33+
rc, err := fs.Open(file)
34+
if err != nil {
35+
return nil, err
36+
}
37+
buf := make([]byte, length)
38+
defer rc.Close()
39+
40+
n, err := io.ReadFull(rc, buf)
41+
if err != nil {
42+
if err == io.ErrUnexpectedEOF {
43+
return buf[:n], nil
44+
}
45+
return buf[:n], err
46+
}
47+
return buf[:n], nil
48+
})
49+
}
50+
}
51+
52+
func CheckAssetFile(rawPath string) {
53+
if checkedAssetFiles[rawPath] {
54+
return
55+
}
56+
checkedAssetFiles[rawPath] = true
57+
path := ToAssetPath(rawPath)
58+
if platform.IsWeb() {
59+
path = path[7:] // remove "assets/"
60+
}
61+
info := filedetect.GetFileFormat(path)
62+
if !info.IsCorrect {
63+
supportStr := ""
64+
if !supportedFileTypes[info.Extension] {
65+
supportStr = ", \n and the current engine does not support this file type (" + info.Extension + "). "
66+
}
67+
msg := fmt.Sprintf("ERROR: The file (%s) has an incorrect extension, its actual format is %s"+supportStr, path, info.Extension)
68+
log.Println(msg)
69+
} else if !supportedFileTypes[info.Extension] {
70+
msg := fmt.Sprintf("ERROR: The file (%s) has an incorrect extension, current engine does not support this file type (%s). ", path, info.Extension)
71+
log.Println(msg)
72+
}
73+
}
74+
1075
// =============== factory ===================
1176

1277
func NewUiNode[T any]() *T {

pkg/filedetect/filedetect.go

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
package filedetect
2+
3+
import (
4+
"os"
5+
"strings"
6+
7+
"github.com/h2non/filetype"
8+
)
9+
10+
// FileFormatResult represents the file format detection result.
11+
type FileFormatResult struct {
12+
IsCorrect bool // Whether the format detection was successful and supported
13+
Extension string // The detected file extension
14+
}
15+
16+
var readFileByLen func(file string, len int) ([]byte, error)
17+
18+
func RegisterIoReader(onReadFile func(file string, len int) ([]byte, error)) {
19+
readFileByLen = onReadFile
20+
}
21+
22+
func getBuffer(filepath string, len int) ([]byte, error) {
23+
// Read file header for detection
24+
var buffer []byte
25+
var err error
26+
27+
if readFileByLen != nil {
28+
// Use registered reader function
29+
buffer, err = readFileByLen(filepath, len) // filetype needs max 262 bytes
30+
if err != nil {
31+
println(filepath, "header load failed1", err)
32+
33+
}
34+
return buffer, err
35+
} else {
36+
// Use standard file reading
37+
file, err := os.Open(filepath)
38+
if err != nil {
39+
println(filepath, "header load failed2", err)
40+
return buffer, err
41+
}
42+
defer file.Close()
43+
44+
buffer = make([]byte, len)
45+
n, err := file.Read(buffer)
46+
if err != nil && n == 0 {
47+
println(filepath, "header load failed3", err)
48+
return buffer, err
49+
}
50+
buffer = buffer[:n]
51+
}
52+
return buffer, err
53+
}
54+
55+
// GetFileFormat gets the file format and its correctness
56+
func GetFileFormat(filepath string) *FileFormatResult {
57+
// Extract file extension from filepath
58+
fileExt := ""
59+
if lastDot := strings.LastIndex(filepath, "."); lastDot != -1 {
60+
fileExt = strings.ToLower(filepath[lastDot:])
61+
}
62+
63+
// Check svg
64+
buffer, err := getBuffer(filepath, 64)
65+
if err != nil {
66+
println(filepath, "header load failed", err)
67+
return &FileFormatResult{IsCorrect: false, Extension: ""}
68+
}
69+
70+
if len(buffer) >= 5 {
71+
headerStr := strings.ToLower(string(buffer[:min(len(buffer), 64)]))
72+
if strings.Contains(headerStr, "<svg") || (strings.HasPrefix(headerStr, "<?xml") && strings.Contains(headerStr, "svg")) {
73+
isCorrect := strings.EqualFold(fileExt, ".svg")
74+
return &FileFormatResult{IsCorrect: isCorrect, Extension: ".svg"}
75+
}
76+
}
77+
78+
buffer, err = getBuffer(filepath, 262)
79+
if err != nil {
80+
println(filepath, "header load failed", err)
81+
return &FileFormatResult{IsCorrect: false, Extension: ""}
82+
}
83+
// Detect file type using filetype library
84+
kind, err := filetype.Match(buffer)
85+
if err != nil || kind == filetype.Unknown {
86+
return &FileFormatResult{IsCorrect: false, Extension: ""}
87+
}
88+
89+
// Get detected extension with dot prefix
90+
detectedExt := "." + kind.Extension
91+
92+
// IsCorrect means: file extension matches detected format
93+
isCorrect := strings.EqualFold(fileExt, detectedExt)
94+
95+
return &FileFormatResult{IsCorrect: isCorrect, Extension: detectedExt}
96+
}

spbase.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,8 +198,10 @@ func (p *baseObj) getProxy() *engine.Sprite {
198198

199199
func (p *baseObj) initWith(base string, sprite *spriteConfig) {
200200
if sprite.CostumeSet != nil {
201+
engine.CheckAssetFile(path.Join(base, sprite.CostumeSet.Path))
201202
initWithCS(p, base, sprite.CostumeSet)
202203
} else if sprite.CostumeMPSet != nil {
204+
engine.CheckAssetFile(path.Join(base, sprite.CostumeMPSet.Path))
203205
initWithCMPS(p, base, sprite.CostumeMPSet)
204206
} else {
205207
panic("sprite.init should have one of costumes, costumeSet and costumeMPSet")
@@ -268,6 +270,7 @@ func addCostumeWith(p *baseObj, name SpriteCostumeName, img *costumeSetImage, fa
268270
func (p *baseObj) initBackdrops(base string, costumes []*backdropConfig, costumeIndex int) {
269271
p.costumes = make([]*costume, len(costumes))
270272
for i, c := range costumes {
273+
engine.CheckAssetFile(path.Join(base, c.Path))
271274
p.costumes[i] = newCostume(base, &c.costumeConfig) // has error how to fixed it
272275
}
273276
if costumeIndex >= len(costumes) || costumeIndex < 0 {
@@ -279,6 +282,7 @@ func (p *baseObj) initBackdrops(base string, costumes []*backdropConfig, costume
279282
func (p *baseObj) init(base string, costumes []*costumeConfig, costumeIndex int) {
280283
p.costumes = make([]*costume, len(costumes))
281284
for i, c := range costumes {
285+
engine.CheckAssetFile(path.Join(base, c.Path))
282286
p.costumes[i] = newCostume(base, c)
283287
}
284288
if costumeIndex >= len(costumes) || costumeIndex < 0 {

0 commit comments

Comments
 (0)