Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
158 changes: 118 additions & 40 deletions core/core.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,13 @@
package core

import (
"context"
"errors"
"fmt"
"io"
"log"
"os"
"path"
"path/filepath"
"strings"

Expand All @@ -19,7 +23,7 @@ import (
"github.com/libretro/ludo/state"
"github.com/libretro/ludo/video"

"github.com/mholt/archiver/v3"
"github.com/mholt/archives"
)

var vid *video.Video
Expand Down Expand Up @@ -78,60 +82,134 @@ func unarchiveGame(filename string) (string, int64, error) {
path := ""
size := int64(0)
dst := os.TempDir()
ctx := context.Background()

var err error
// What a wonderful API, all this boilerplate to set an option
switch filepath.Ext(filename) {
case ".zip":
un := archiver.Zip{
OverwriteExisting: true,
}
err = un.Unarchive(filename, dst)
case ".tar":
un := archiver.Tar{
OverwriteExisting: true,
}
err = un.Unarchive(filename, dst)
case ".rar":
un := archiver.Rar{
OverwriteExisting: true,
}
err = un.Unarchive(filename, dst)
default:
// Unarchive with default option
err = archiver.Unarchive(filename, dst)
file, err := os.Open(filename)
if err != nil {
return path, size, err
}
defer file.Close()

format, stream, err := archives.Identify(ctx, filepath.Base(filename), file)
if err != nil {
return path, size, err
}

extPriority := 0 // current priority
// Give priority of some extensions (use upper case to be case insensitive)
extPrefered := make(map[string]int)
extPrefered[".cue"] = 1
extPrefered[".m3u"] = 2
extPrefered[".pbp"] = 3
extPriority := 0
extPrefered := map[string]int{
".cue": 1,
".m3u": 2,
".pbp": 3,
}

err = archiver.Walk(filename, func(f archiver.File) error {
fname := f.Name()
ext := filepath.Ext(fname)
selectGame := func(gamePath string, fsize int64) {
ext := filepath.Ext(gamePath)
if size == 0 {
// By default select the first file of the archive
path = filepath.Join(dst, fname)
size = f.Size()
path = gamePath
size = fsize
log.Println("first file in archive:", path, size)
}
// Check if a file (based on extension) has a higher priority
priority, ok := extPrefered[strings.ToLower(ext)]
if ok && priority > extPriority {
extPriority = priority
path = filepath.Join(dst, fname)
size = f.Size()
path = gamePath
size = fsize
log.Println("find a better file in archive:", path, size)
}
return nil
})
return path, size, err
}

if ex, ok := format.(archives.Extractor); ok {
err = ex.Extract(ctx, stream, func(ctx context.Context, f archives.FileInfo) error {
fname, err := archiveOutputPath(dst, f.NameInArchive)
if err != nil {
return err
}
if f.IsDir() {
return os.MkdirAll(fname, os.ModePerm)
}
if err := os.MkdirAll(filepath.Dir(fname), os.ModePerm); err != nil {
return err
}

in, err := f.Open()
if err != nil {
return err
}
defer in.Close()

mode := f.Mode().Perm()
if mode == 0 {
mode = 0644
}
out, err := os.OpenFile(fname, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, mode)
if err != nil {
return err
}

_, copyErr := io.Copy(out, in)
closeErr := out.Close()
if copyErr != nil {
return copyErr
}
if closeErr != nil {
return closeErr
}

selectGame(fname, f.Size())
return nil
})
return path, size, err
}

decomp, ok := format.(archives.Decompressor)
if !ok {
return path, size, fmt.Errorf("unsupported archive format: %T", format)
}

rc, err := decomp.OpenReader(stream)
if err != nil {
return path, size, err
}
defer rc.Close()

fname := strings.TrimSuffix(filepath.Base(filename), filepath.Ext(filename))
if fname == "" {
return path, size, errors.New("could not determine extracted filename")
}
path = filepath.Join(dst, fname)
out, err := os.Create(path)
if err != nil {
return "", 0, err
}
size, err = io.Copy(out, rc)
closeErr := out.Close()
if err != nil {
return "", 0, err
}
if closeErr != nil {
return "", 0, closeErr
}

return path, size, nil
}

func archiveOutputPath(dst, name string) (string, error) {
clean := path.Clean(strings.TrimPrefix(name, "/"))
if clean == "." || clean == "" {
return "", errors.New("invalid archive path")
}
if clean == ".." || strings.HasPrefix(clean, "../") {
return "", fmt.Errorf("unsafe archive path: %s", name)
}
fname := filepath.Join(dst, filepath.FromSlash(clean))
rel, err := filepath.Rel(dst, fname)
if err != nil {
return "", err
}
if rel == ".." || strings.HasPrefix(rel, ".."+string(filepath.Separator)) {
return "", fmt.Errorf("unsafe archive path: %s", name)
}
return fname, nil
}

// LoadGame loads a game. A core has to be loaded first.
Expand Down
20 changes: 14 additions & 6 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ require (
github.com/go-gl/glfw/v3.4/glfw v0.1.0-pre.1
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
github.com/lucasb-eyer/go-colorful v1.4.0
github.com/mholt/archiver/v3 v3.5.1
github.com/mholt/archives v0.1.5
github.com/pelletier/go-toml v1.9.5
github.com/tanema/gween v0.0.0-20250522035225-e874ee3ae01a
github.com/youpy/go-wav v0.3.2
Expand All @@ -18,19 +18,27 @@ require (
)

require (
github.com/STARRY-S/zip v0.2.3 // indirect
github.com/andybalholm/brotli v1.2.1 // indirect
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect
github.com/golang/snappy v1.0.0 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/bodgit/plumbing v1.3.0 // indirect
github.com/bodgit/sevenzip v1.6.1 // indirect
github.com/bodgit/windows v1.0.1 // indirect
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
github.com/klauspost/compress v1.18.5 // indirect
github.com/klauspost/pgzip v1.2.6 // indirect
github.com/nwaples/rardecode v1.1.3 // indirect
github.com/mikelolasagasti/xz v1.0.1 // indirect
github.com/minio/minlz v1.0.1 // indirect
github.com/nwaples/rardecode/v2 v2.2.0 // indirect
github.com/pierrec/lz4/v4 v4.1.26 // indirect
github.com/sorairolake/lzip-go v0.3.8 // indirect
github.com/spf13/afero v1.15.0 // indirect
github.com/ulikunitz/xz v0.5.15 // indirect
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
github.com/youpy/go-riff v0.1.0 // indirect
github.com/zaf/g711 v1.4.0 // indirect
go4.org v0.0.0-20230225012048-214862532bf5 // indirect
golang.org/x/sys v0.43.0 // indirect
golang.org/x/text v0.36.0 // indirect
)

go 1.25.0
Expand Down
Loading
Loading