Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/master' into chore/readme
Browse files Browse the repository at this point in the history
  • Loading branch information
book000 committed Nov 15, 2024
2 parents 5cdbe08 + 12da7d9 commit 5307084
Show file tree
Hide file tree
Showing 11 changed files with 366 additions and 47 deletions.
6 changes: 5 additions & 1 deletion .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@
"extensions": ["ms-vscode.go"]
},
"settings": {
"go.useLanguageServer": true
"go.useLanguageServer": true,
"go.vetFlags": ["-unsafeptr=false"],
"gopls": {
"analyses": { "unsafeptr": false }
}
}
},
"postCreateCommand": "go mod download"
Expand Down
103 changes: 103 additions & 0 deletions cmd/splashscreen-changer/args.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package main

import (
"flag"
"fmt"
"os"
"path/filepath"
"reflect"
"strings"
)

// ヘルプメッセージを表示する関数
func printHelp() {
fmt.Println("Usage: splashscreen-changer [options]")
fmt.Println("Options:")
flag.PrintDefaults()
fmt.Println("Environment Variables:")
fmt.Printf(" %-20s %s\n", "CONFIG_PATH", "Path to the configuration file (default: data/config.yaml)")

// Config 構造体のフィールドから環境変数のキーを生成して表示
configType := reflect.TypeOf(Config{})
for i := 0; i < configType.NumField(); i++ {
section := configType.Field(i)
sectionType := section.Type

for j := 0; j < sectionType.NumField(); j++ {
field := sectionType.Field(j)
helpTag := field.Tag.Get("help")
envKey := strings.ToUpper(section.Name + "_" + field.Name)
defaultValue := field.Tag.Get("default")
if defaultValue != "" {
helpTag += fmt.Sprintf(" (default: %s)", defaultValue)
}
fmt.Printf(" %-20s %s\n", envKey, helpTag)
}
}

fmt.Println()
fmt.Println("GitHub: https://github.com/tomacheese/splashscreen-changer")
}

func getSourcePath(config *Config) (string, error) {
// 取得の優先度は以下。
// 1. 環境変数 SOURCE_PATH
// 2. 設定ファイル source.path
// 3. ユーザーフォルダの Pictures フォルダ内、VRChat フォルダ
// エラー。

// 1, 2 については、config.go にて実装済み。空値できた場合のみ、3 を行う
// 3 については、ユーザーフォルダの Pictures フォルダ内に VRChat フォルダが存在するか確認し、存在する場合はそのパスを返す
if config.Source.Path != "" {
return config.Source.Path, nil
}

errorRequired := fmt.Errorf("source.path is required")

// ユーザーフォルダの Pictures フォルダ内に VRChat フォルダが存在するか確認
picturesLegacyPath, errLegacy := getPicturesLegacyPath()
picturesNewPath, err := getPicturesPath()
if errLegacy != nil && err != nil {
return "", errorRequired
}

picturesPath := picturesLegacyPath
if errLegacy != nil {
picturesPath = picturesNewPath
}

vrchatPath := filepath.Join(picturesPath, "VRChat")
if _, err := os.Stat(vrchatPath); err == nil {
return vrchatPath, errorRequired
}

return "", errorRequired
}

func getDestinationPath(config *Config) (string, error) {
// 取得の優先度は以下。
// 1. 環境変数 DESTINATION_PATH
// 2. 設定ファイル destination.path
// 3. Steam ライブラリフォルダから、VRChat のインストール先を取得
// エラー。

// 1, 2 については、config.go にて実装済み。空値できた場合のみ、3 を行う
// 3 については、Steam ライブラリフォルダから、VRChat のインストール先フォルダを返す(EasyAntiCheatフォルダがあることを確認する)。見つからない場合はエラーを返す
if config.Destination.Path != "" {
return config.Destination.Path, nil
}

errorRequired := fmt.Errorf("destination.path is required")

vrchatPath, err := findSteamGameDirectory("VRChat")
if err != nil {
return vrchatPath, nil
}

_, err = os.Stat(filepath.Join(vrchatPath, "EasyAntiCheat"))
if err != nil {
return "", fmt.Errorf("EasyAntiCheat folder not found in %s", vrchatPath)
}

return "", errorRequired
}
29 changes: 17 additions & 12 deletions cmd/splashscreen-changer/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ import (

type Config struct {
Source struct {
Path string `yaml:"path" help:"Path to the source directory" required:"true"`
Recursive bool `yaml:"recursive" help:"Whether to search for PNG files recursively. Default is false" default:"false"`
Path string `yaml:"path" help:"Path to the source directory. If not specified, the VRChat folder in the user's Pictures folder is searched and used if available. If not, an error is returned."`
Recursive bool `yaml:"recursive" help:"Whether to search for PNG files recursively" default:"true"`
} `yaml:"source" required:"true"`
Destination struct {
Path string `yaml:"path" help:"Path to the destination directory. The specified directory must have an EasyAntiCheat directory" required:"true"`
Path string `yaml:"path" help:"Path to the destination directory. The specified directory must have an EasyAntiCheat directory. If not specified, the VRChat folder is searched based on the Steam library folder and used if available. If not, an error is returned."`
Width int `yaml:"width" help:"Width of the destination image" default:"800"`
Height int `yaml:"height" help:"Height of the destination image" default:"450"`
} `yaml:"destination" required:"true"`
Expand Down Expand Up @@ -163,17 +163,22 @@ func checkConfig(config *Config) error {
}

// パスが存在するかチェック
if _, err := os.Stat(config.Source.Path); err != nil {
return fmt.Errorf("source path '%s' does not exist", config.Source.Path)
}
if _, err := os.Stat(config.Destination.Path); err != nil {
return fmt.Errorf("destination path '%s' does not exist", config.Destination.Path)
if config.Source.Path != "" {
if _, err := os.Stat(config.Source.Path); err != nil {
return fmt.Errorf("source path '%s' does not exist", config.Source.Path)
}
}

// destination.path には "EasyAntiCheat" ディレクトリが存在すること
eacPath := config.Destination.Path + "/EasyAntiCheat"
if _, err := os.Stat(eacPath); err != nil {
return fmt.Errorf("EasyAntiCheat directory not found in destination path '%s'", config.Destination.Path)
if config.Destination.Path != "" {
if _, err := os.Stat(config.Destination.Path); err != nil {
return fmt.Errorf("destination path '%s' does not exist", config.Destination.Path)
}

// destination.path には "EasyAntiCheat" ディレクトリが存在すること
eacPath := config.Destination.Path + "/EasyAntiCheat"
if _, err := os.Stat(eacPath); err != nil {
return fmt.Errorf("EasyAntiCheat directory not found in destination path '%s'", config.Destination.Path)
}
}

// destination.width が 0 より大きいこと
Expand Down
4 changes: 2 additions & 2 deletions cmd/splashscreen-changer/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,8 @@ destination:
if config.Source.Path != filepath.Join(tmpDir, "source") {
t.Errorf("Expected source path to be '%s', got '%s'", filepath.Join(tmpDir, "source"), config.Source.Path)
}
if config.Source.Recursive {
t.Errorf("Expected source recursive to be false, got true")
if !config.Source.Recursive {
t.Errorf("Expected source recursive to be true got false")
}
if config.Destination.Path != filepath.Join(tmpDir, "destination") {
t.Errorf("Expected destination path to be '%s', got '%s'", filepath.Join(tmpDir, "destination"), config.Destination.Path)
Expand Down
61 changes: 29 additions & 32 deletions cmd/splashscreen-changer/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
"image/png"
"os"
"path/filepath"
"reflect"
"strings"
"time"

Expand Down Expand Up @@ -132,32 +131,6 @@ func resizePNGFile(srcPath, destPath string, width, height int) error {
return nil
}

// ヘルプメッセージを表示する関数
func printHelp() {
fmt.Println("Usage: splashscreen-changer [options]")
fmt.Println("Options:")
flag.PrintDefaults()
fmt.Println("Environment Variables:")
fmt.Printf(" %-20s %s\n", "CONFIG_PATH", "Path to the configuration file (default: data/config.yaml)")

// Config 構造体のフィールドから環境変数のキーを生成して表示
configType := reflect.TypeOf(Config{})
for i := 0; i < configType.NumField(); i++ {
section := configType.Field(i)
sectionType := section.Type

for j := 0; j < sectionType.NumField(); j++ {
field := sectionType.Field(j)
helpTag := field.Tag.Get("help")
envKey := strings.ToUpper(section.Name + "_" + field.Name)
fmt.Printf(" %-20s %s\n", envKey, helpTag)
}
}

fmt.Println()
fmt.Println("GitHub: https://github.com/tomacheese/splashscreen-changer")
}

func main() {
// コマンドライン引数を解析する
helpFlag := flag.Bool("help", false, "Show help message")
Expand Down Expand Up @@ -186,19 +159,43 @@ func main() {
fmt.Println("Loading config file:", *configPath)
config, err := LoadConfig(*configPath)
if err != nil {
fmt.Println("Error:", err)
fmt.Println("Failed to load configuration file:", err)
return
}

sourcePath, err := getSourcePath(config)
if err != nil {
fmt.Println("Failed to obtain source path")
fmt.Println()
fmt.Println("The following steps are used to obtain the source paths. This error occurs because the following steps could not be taken to obtain the source path.")
fmt.Println("1. Environment variable SOURCE_PATH. If this is not set, the following steps are taken.")
fmt.Println("2. source.path in Configuration file. If this is not set, the following steps are taken.")
fmt.Println("3. Check if the VRChat folder exists in the Pictures folder in the user folder.")
fmt.Println("If the VRChat folder exists, the path to the VRChat folder is used as the source path.")
return
}

destinationPath, err := getDestinationPath(config)
if err != nil {
fmt.Println("Failed to obtain destination path")
fmt.Println()
fmt.Println("The following steps are used to obtain the destination paths. This error occurs because the following steps could not be taken to obtain the destination path.")
fmt.Println("1. Environment variable DESTINATION_PATH. If this is not set, the following steps are taken.")
fmt.Println("2. destination.path in Configuration file. If this is not set, the following steps are taken.")
fmt.Println("3. Get the installation destination folder of VRChat from the Steam library folder.")
fmt.Println("If the EasyAntiCheat folder exists in the VRChat folder, the path to the VRChat folder is used as the destination path.")
return
}

// 設定値を表示する
fmt.Printf("Source Path: %s\n", config.Source.Path)
fmt.Printf("Source Path: %s\n", sourcePath)
fmt.Printf("Source Recursive: %t\n", config.Source.Recursive)
fmt.Printf("Destination Path: %s\n", config.Destination.Path)
fmt.Printf("Destination Path: %s\n", destinationPath)
fmt.Printf("Destination Width: %d\n", config.Destination.Width)
fmt.Printf("Destination Height: %d\n", config.Destination.Height)

// ソースディレクトリ以下のPNGファイルをリストする
files, err := listPNGFiles(config.Source.Path, config.Source.Recursive)
files, err := listPNGFiles(sourcePath, config.Source.Recursive)
if err != nil {
fmt.Println("Error:", err)
return
Expand All @@ -219,7 +216,7 @@ func main() {
fmt.Println("Picked file:", pickedFile)

// ファイルをリサイズして EasyAntiCheat ディレクトリに保存する
destFile := filepath.Join(config.Destination.Path, "EasyAntiCheat", "SplashScreen.png")
destFile := filepath.Join(destinationPath, "EasyAntiCheat", "SplashScreen.png")
err = resizePNGFile(pickedFile, destFile, config.Destination.Width, config.Destination.Height)
if err != nil {
fmt.Println("Error:", err)
Expand Down
17 changes: 17 additions & 0 deletions cmd/splashscreen-changer/specialfolder_other.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
//go:build !windows
// +build !windows

package main

import (
"fmt"
"runtime"
)

func getPicturesLegacyPath() (string, error) {
return "", fmt.Errorf("unsupported OS: %s", runtime.GOOS)
}

func getPicturesPath() (string, error) {
return "", fmt.Errorf("unsupported OS: %s", runtime.GOOS)
}
47 changes: 47 additions & 0 deletions cmd/splashscreen-changer/specialfolder_windows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
//go:build windows
// +build windows

package main

import (
"fmt"
"syscall"
"unsafe"
)

// SHGetKnownFolderPathを使うための設定
var (
modShell32 = syscall.NewLazyDLL("shell32.dll")
procSHGetKnownFolderPath = modShell32.NewProc("SHGetKnownFolderPath")
modOle32 = syscall.NewLazyDLL("ole32.dll")
procCoTaskMemFree = modOle32.NewProc("CoTaskMemFree")
FOLDERID_PicturesLegacy = syscall.GUID{Data1: 0x0DDD015D, Data2: 0xB06C, Data3: 0x45D5, Data4: [8]byte{0x8C, 0x4C, 0xF5, 0x97, 0x13, 0x85, 0x46, 0x39}}
FOLDERID_Pictures = syscall.GUID{Data1: 0x33E28130, Data2: 0x4E1E, Data3: 0x4676, Data4: [8]byte{0x83, 0x5A, 0x98, 0x5A, 0x76, 0x87, 0x67, 0x4D}}
)

// getKnownFolderPath は、指定されたKnown Folderのパスを取得します。
func getKnownFolderPath(folderID *syscall.GUID) (string, error) {
var pathPtr uintptr
// SHGetKnownFolderPathを呼び出してフォルダパスを取得
ret, _, _ := procSHGetKnownFolderPath.Call(
uintptr(unsafe.Pointer(folderID)),
0,
0,
uintptr(unsafe.Pointer(&pathPtr)),
)
if ret != 0 {
return "", fmt.Errorf("failed to get folder path, error code: %d", ret)
}
defer procCoTaskMemFree.Call(pathPtr) // メモリ解放

// ポインタから文字列を取得
return syscall.UTF16ToString((*[1 << 16]uint16)(unsafe.Pointer(pathPtr))[:]), nil
}

func getPicturesLegacyPath() (string, error) {
return getKnownFolderPath(&FOLDERID_PicturesLegacy)
}

func getPicturesPath() (string, error) {
return getKnownFolderPath(&FOLDERID_Pictures)
}
21 changes: 21 additions & 0 deletions cmd/splashscreen-changer/steam_other.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//go:build !windows
// +build !windows

package main

import (
"fmt"
"runtime"
)

func GetSteamInstallFolder() (string, error) {
return "", fmt.Errorf("unsupported OS: %s", runtime.GOOS)
}

func getSteamLibraryFolders(_ string) ([]string, error) {
return nil, fmt.Errorf("unsupported OS: %s", runtime.GOOS)
}

func findSteamGameDirectory(_ string) (string, error) {
return "", fmt.Errorf("unsupported OS: %s", runtime.GOOS)
}
Loading

0 comments on commit 5307084

Please sign in to comment.