├── .gitignore ├── LICENSE ├── README.md ├── README_EN.md ├── app.go ├── build ├── README.md ├── appicon.png ├── darwin │ ├── Info.dev.plist │ └── Info.plist ├── sh │ ├── dev.bat │ ├── macos │ │ └── prod.sh │ ├── prod_x64.bat │ ├── prod_x86.bat │ └── test.bat └── windows │ ├── icon.ico │ ├── info.json │ ├── installer │ ├── project.nsi │ └── wails_tools.nsh │ └── wails.exe.manifest ├── components ├── backup.go ├── bat.go ├── cache.go ├── font.go ├── hfsdb.go ├── linkFile.go ├── lua.go ├── output.go ├── platform.go ├── rom.go ├── rungame.go ├── thumb.go ├── upgrade.go └── zip.go ├── config ├── config.go ├── romBase.go ├── romSetting.go ├── share.go └── subgame.go ├── constant ├── args.go ├── const.go └── path.go ├── controller ├── audio.go ├── cache.go ├── config.go ├── controller.go ├── dialog.go ├── image.go ├── input.go ├── menu.go ├── others.go ├── platform.go ├── rom.go ├── romManage.go ├── rombase.go ├── shortcut.go ├── simulator.go └── upgrade.go ├── data.dll ├── db ├── config.go ├── configVO.go ├── db.go ├── filter.go ├── menu.go ├── menuVO.go ├── platform.go ├── platformUi.go ├── platformVO.go ├── rom.go ├── romVO.go ├── rombaseAlias.go ├── rombaseEnum.go ├── shortcut.go ├── simulator.go ├── thumbs.go └── upgrade.go ├── frontend ├── README.md ├── index.html ├── package-lock.json ├── package.json ├── postcss.config.cjs ├── public │ └── images │ │ ├── context │ │ ├── alias.png │ │ ├── app.png │ │ ├── audio.png │ │ ├── baseinfo.png │ │ ├── copy.png │ │ ├── delete.png │ │ ├── doc.png │ │ ├── down.png │ │ ├── fav_0.png │ │ ├── fav_1.png │ │ ├── files.png │ │ ├── folder.png │ │ ├── hide_0.png │ │ ├── hide_1.png │ │ ├── move.png │ │ ├── output.png │ │ ├── rename.png │ │ ├── strategy.png │ │ ├── sub.png │ │ ├── thumbs.png │ │ ├── unlink.png │ │ └── up.png │ │ ├── ctl │ │ ├── +.png │ │ ├── 1.png │ │ ├── 2.png │ │ ├── 3.png │ │ ├── 4.png │ │ ├── 5.png │ │ ├── 6.png │ │ ├── 7.png │ │ ├── 8.png │ │ ├── 9.png │ │ ├── A.png │ │ ├── B.png │ │ ├── C.png │ │ ├── D.png │ │ ├── E.png │ │ ├── F.png │ │ ├── K.png │ │ ├── N.png │ │ ├── P.png │ │ └── S.png │ │ ├── ico.png │ │ ├── list-style │ │ ├── 1.png │ │ ├── 2.png │ │ ├── 3.png │ │ └── 4.png │ │ ├── rom_animate.png │ │ └── svg │ │ ├── complete0.svg │ │ ├── complete1.svg │ │ ├── complete2.svg │ │ ├── number.svg │ │ └── time.svg ├── quasar.config.js ├── src │ ├── App.vue │ ├── boot │ │ ├── .gitkeep │ │ ├── axios.ts │ │ ├── constant.ts │ │ ├── i18n.ts │ │ └── imgObserver.ts │ ├── components │ │ ├── AboutComponent.vue │ │ ├── CopyrightComponent.vue │ │ ├── SDialogComponent.vue │ │ ├── UpgradeComponent.vue │ │ ├── dialog.ts │ │ ├── models.ts │ │ └── utils.ts │ ├── css │ │ ├── app.css │ │ ├── classic │ │ │ ├── common.css │ │ │ ├── contentBar.css │ │ │ ├── contentContext.css │ │ │ ├── headerBarFilter.css │ │ │ ├── layout.css │ │ │ ├── menuBar.css │ │ │ ├── platformBar.css │ │ │ └── rightBar.css │ │ ├── grid.css │ │ ├── manage.css │ │ ├── modules.css │ │ ├── page │ │ │ └── platform.css │ │ ├── playnite │ │ │ ├── contentBar.css │ │ │ ├── layout.css │ │ │ ├── platform.css │ │ │ └── romlistBar.css │ │ ├── romManage.css │ │ ├── tiny │ │ │ ├── layout.css │ │ │ ├── platform.css │ │ │ └── romlistBar.css │ │ └── transitions.css │ ├── env.d.ts │ ├── i18n │ │ ├── en-US │ │ │ └── index.ts │ │ └── index.ts │ ├── pages │ │ ├── ErrorNotFound.vue │ │ ├── classic │ │ │ ├── ContentBar.vue │ │ │ ├── HeaderBarFilter.vue │ │ │ ├── HeaderBarLogo.vue │ │ │ ├── HeaderBarTool.vue │ │ │ ├── Layout.vue │ │ │ ├── LeftBarMenu.vue │ │ │ ├── LeftBarPlatform.vue │ │ │ ├── RightBar.vue │ │ │ ├── configUI │ │ │ │ ├── Layout.vue │ │ │ │ └── UiConst.vue │ │ │ ├── context │ │ │ │ ├── BaseInfo.vue │ │ │ │ ├── Context.vue │ │ │ │ ├── CopyModule.vue │ │ │ │ ├── MoveModule.vue │ │ │ │ ├── ShareRom.vue │ │ │ │ ├── SubGame.vue │ │ │ │ └── Thumbs.vue │ │ │ └── modules │ │ │ │ ├── EventGamepad.vue │ │ │ │ ├── EventKeyboard.vue │ │ │ │ ├── FabInputRom.vue │ │ │ │ ├── FabMenu.vue │ │ │ │ ├── FabOutputRom.vue │ │ │ │ └── RomSim.vue │ │ ├── config │ │ │ ├── Layout.vue │ │ │ ├── RombaseEnum.vue │ │ │ └── Shortcut.vue │ │ ├── configPlatform │ │ │ ├── Layout.vue │ │ │ ├── RombaseAlias.vue │ │ │ └── Simulators.vue │ │ ├── home │ │ │ └── Layout.vue │ │ ├── playnite │ │ │ ├── ContentBar.vue │ │ │ ├── HeaderBarTool.vue │ │ │ ├── Layout.vue │ │ │ ├── Platform.vue │ │ │ ├── RomListBar.vue │ │ │ ├── configUI │ │ │ │ ├── Layout.vue │ │ │ │ └── UiConst.vue │ │ │ └── modules │ │ │ │ ├── EventGamepad.vue │ │ │ │ └── EventKeyboard.vue │ │ ├── romManage │ │ │ ├── Layout.vue │ │ │ ├── Repeat.vue │ │ │ ├── Rombase.vue │ │ │ ├── Romfile.vue │ │ │ ├── Simulator.vue │ │ │ └── Unowned.vue │ │ ├── test │ │ │ └── Layout.vue │ │ └── tiny │ │ │ ├── HeaderBarTool.vue │ │ │ ├── Layout.vue │ │ │ ├── Platform.vue │ │ │ ├── RomListBar.vue │ │ │ ├── configUI │ │ │ ├── Layout.vue │ │ │ └── UiConst.vue │ │ │ └── modules │ │ │ ├── EventGamepad.vue │ │ │ └── EventKeyboard.vue │ ├── quasar.d.ts │ ├── router │ │ ├── index.ts │ │ └── routes.ts │ ├── shims-vue.d.ts │ └── stores │ │ ├── example-store.ts │ │ ├── globalData.ts │ │ ├── index.ts │ │ └── store-flag.d.ts ├── tsconfig.json ├── vite.config.js └── vue.config.js ├── go.mod ├── go.sum ├── main.go ├── main_test.go ├── modules ├── audio.go ├── backup.go ├── cache.go ├── config.go ├── dialog.go ├── image.go ├── inputPlatform.go ├── menu.go ├── others.go ├── platform.go ├── rom.go ├── romConfig.go ├── romManage.go ├── romRename.go ├── romSimSetting.go ├── rombaseAlias.go ├── rombaseEnum.go ├── runGame.go ├── shortcut.go ├── simulator.go ├── strategyFiles.go ├── thumb.go └── upgrade.go ├── readme ├── 1.jpg ├── 2.jpg ├── 3.jpg └── logo.png ├── request ├── input.go ├── platform.go └── rom.go ├── server └── http.go ├── utils ├── alert.go ├── args.go ├── calljs.go ├── conv.go ├── convert.go ├── csv.go ├── encode.go ├── file.go ├── filepath.go ├── hfsdb.go ├── html.go ├── http.go ├── image.go ├── log.go ├── pinyin │ ├── .travis.yml │ ├── LICENSE │ ├── Makefile │ ├── README.md │ ├── codecov.yml │ ├── error.go │ ├── pinyin.go │ ├── pinyin.txt │ ├── pinyin_test.go │ └── resource.go ├── rom.go ├── slice.go ├── string.go ├── struct.go ├── system.go ├── translate.go ├── uuid.go ├── version.go └── wails.go └── wails.json /.gitignore: -------------------------------------------------------------------------------- 1 | build/bin 2 | build/sh/prod_build.bat 3 | node_modules 4 | frontend/dist 5 | frontend/.quasar 6 | frontend/wailsjs 7 | tmp 8 | frontend/package.json.md5 9 | frontend/public/images/custom 10 | .idea 11 | frontend/public/temp 12 | .env 13 | cache 14 | .github 15 | .gitee -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 frontlon 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ![](readme/logo.png) 4 | 5 | 6 | 7 | [[EN]](README_EN.md) 8 | 9 | **SimUI Pulsar 是由热爱街机文化的玩家开发的免费专业游戏ROM管理软件。是SimUI的重构版本。** 10 | 11 | 12 | 13 | ## 软件介绍 14 | 15 | 我们希望打造极致的产品,融入工匠精神,不断探索每一种更好的可能。SIMUI以资料收集和分享为圆心,为玩家打造更具品质的模拟游戏图书馆软件。 16 | 17 | 软件支持自由添加游戏平台、支持多游戏模拟器、多游戏目录; 18 | 19 | 支持ROM别名,ROM子游戏、支持游戏资料定义; 20 | 21 | 支持多种展示图显示、支持gif动画、支持游戏音乐、支持视频; 22 | 23 | 支持游戏简介、游戏攻略,支持富文本显示; 24 | 25 | 支持自定义皮肤、支持多语言; 26 | 27 | 支持手柄、支持摇杆; 28 | 29 | 软件懒加载机制、软件改 30 | 31 | 名、下载缩略图、自定义实用工具等诸多功能。 32 | 33 | ![](readme/1.jpg) 34 | 35 | ![](readme/2.jpg) 36 | 37 | ![](readme/3.jpg) 38 | 39 |   40 | ## 软件地址 41 | 42 | [www.simui.net/](http://www.simui.net/) 43 | 44 | 45 | 46 | ## 技术栈 47 | 48 | GOLANG + wails + vue3 + quasar 49 | 50 | ## 目录说明 51 | 52 | ``` 53 | build 编译目录 54 | components 业务组件 55 | config 配置相关 56 | constant 常量定义 57 | controller (前端调用入口) 58 | db 数据库dao 59 | frontend 前端代码 60 | modules 业务模块 61 | request 结构体定义 62 | utils 公共库 63 | ``` 64 | 65 | 66 | 67 | ## 相关技术文档 68 | 69 | wails:https://wails.io/docs/introduction/ 70 | 71 | vue3:https://vuejs.org/guide/introduction.html 72 | 73 | quasar:https://quasar.dev/docs 74 | -------------------------------------------------------------------------------- /README_EN.md: -------------------------------------------------------------------------------- 1 | ![](readme/logo.png) 2 | 3 | [[简体中文]](README.md) 4 | 5 | **SimUI Pulsar is a free, professional game ROM management software developed by players who love arcade culture. It is a restructured version of SimUI.** 6 | 7 | ## Software Introduction 8 | 9 | We aim to create the ultimate product, incorporating a craftsman spirit, and continually exploring every better possibility. SIMUI, centered around data collection and sharing, is designed to provide players with a high-quality emulated game library software. 10 | 11 | The software supports the addition of game platforms freely, supports multiple game emulators, and multiple game directories; 12 | 13 | Supports ROM aliases, ROM sub-games, and game metadata definitions; 14 | 15 | Supports various display images, GIF animations, game music, and videos; 16 | 17 | Supports game descriptions and guides, with rich text display; 18 | 19 | Supports custom skins and multiple languages; 20 | 21 | Supports gamepads and joysticks; 22 | 23 | The software features lazy loading, renaming, thumbnail downloads, custom utilities, and many other functions. 24 | 25 | ![](readme/1.jpg) 26 | 27 | ![](readme/2.jpg) 28 | 29 | ![](readme/3.jpg) 30 | 31 |   32 | 33 | ## Software Website 34 | 35 | [www.simui.net/](http://www.simui.net/) 36 | 37 | ## Technology Stack 38 | 39 | GOLANG + wails + vue3 + quasar 40 | 41 | ## Directory Explanation 42 | 43 | ``` 44 | build Compilation directory 45 | components Business components 46 | config Configuration 47 | constant Constant definitions 48 | controller (Frontend call entry) 49 | db Database DAO 50 | frontend Frontend code 51 | modules Business modules 52 | request Structure definitions 53 | utils Common libraries 54 | ``` 55 | 56 | ## Related Technical Documentation 57 | 58 | wails: https://wails.io/docs/introduction/ 59 | 60 | vue3: https://vuejs.org/guide/introduction.html 61 | 62 | quasar: https://quasar.dev/docs 63 | -------------------------------------------------------------------------------- /app.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "github.com/wailsapp/wails/v2/pkg/runtime" 6 | "simUI/config" 7 | ) 8 | 9 | // App struct 10 | type App struct { 11 | ctx context.Context 12 | } 13 | 14 | // NewApp creates a new App application struct 15 | func NewApp() *App { 16 | return &App{} 17 | } 18 | 19 | // startup is called when the app starts. The context is saved 20 | // so we can call the runtime methods 21 | func (a *App) startup(ctx context.Context) { 22 | a.ctx = ctx 23 | config.Ctx = ctx 24 | 25 | //做一些配置 26 | runtime.WindowSetDarkTheme(ctx) //设为黑色主题 27 | } 28 | -------------------------------------------------------------------------------- /build/README.md: -------------------------------------------------------------------------------- 1 | # Build Directory 2 | 3 | The build directory is used to house all the build files and assets for your application. 4 | 5 | The structure is: 6 | 7 | * bin - Output directory 8 | * darwin - macOS specific files 9 | * windows - Windows specific files 10 | 11 | ## Mac 12 | 13 | The `darwin` directory holds files specific to Mac builds. 14 | These may be customised and used as part of the build. To return these files to the default state, simply delete them 15 | and 16 | build with `wails build`. 17 | 18 | The directory contains the following files: 19 | 20 | - `Info.plist` - the main plist file used for Mac builds. It is used when building using `wails build`. 21 | - `Info.dev.plist` - same as the main plist file but used when building using `wails dev`. 22 | 23 | ## Windows 24 | 25 | The `windows` directory contains the manifest and rc files used when building with `wails build`. 26 | These may be customised for your application. To return these files to the default state, simply delete them and 27 | build with `wails build`. 28 | 29 | - `icon.ico` - The icon used for the application. This is used when building using `wails build`. If you wish to 30 | use a different icon, simply replace this file with your own. If it is missing, a new `icon.ico` file 31 | will be created using the `appicon.png` file in the build directory. 32 | - `installer/*` - The files used to create the Windows installer. These are used when building using `wails build`. 33 | - `info.json` - Application details used for Windows builds. The data here will be used by the Windows installer, 34 | as well as the application itself (right click the exe -> properties -> details) 35 | - `wails.exe.manifest` - The main application manifest file. -------------------------------------------------------------------------------- /build/appicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frontlon/simUI-pulsar/2822bcf2b0f630925a29b4d929d6b846af63db41/build/appicon.png -------------------------------------------------------------------------------- /build/darwin/Info.dev.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CFBundlePackageType 5 | APPL 6 | CFBundleName 7 | {{.Info.ProductName}} 8 | CFBundleExecutable 9 | {{.Name}} 10 | CFBundleIdentifier 11 | com.wails.{{.Name}} 12 | CFBundleVersion 13 | {{.Info.ProductVersion}} 14 | CFBundleGetInfoString 15 | {{.Info.Comments}} 16 | CFBundleShortVersionString 17 | {{.Info.ProductVersion}} 18 | CFBundleIconFile 19 | iconfile 20 | LSMinimumSystemVersion 21 | 10.13.0 22 | NSHighResolutionCapable 23 | true 24 | NSHumanReadableCopyright 25 | {{.Info.Copyright}} 26 | NSAppTransportSecurity 27 | 28 | NSAllowsLocalNetworking 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /build/darwin/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CFBundlePackageType 5 | APPL 6 | CFBundleName 7 | {{.Info.ProductName}} 8 | CFBundleExecutable 9 | {{.Name}} 10 | CFBundleIdentifier 11 | com.wails.{{.Name}} 12 | CFBundleVersion 13 | {{.Info.ProductVersion}} 14 | CFBundleGetInfoString 15 | {{.Info.Comments}} 16 | CFBundleShortVersionString 17 | {{.Info.ProductVersion}} 18 | CFBundleIconFile 19 | iconfile 20 | LSMinimumSystemVersion 21 | 10.13.0 22 | NSHighResolutionCapable 23 | true 24 | NSHumanReadableCopyright 25 | {{.Info.Copyright}} 26 | 27 | -------------------------------------------------------------------------------- /build/sh/dev.bat: -------------------------------------------------------------------------------- 1 | cd %~dp0 2 | cd ../../ 3 | wails dev -loglevel "Info" -frontenddevserverurl "http://localhost:9000" -------------------------------------------------------------------------------- /build/sh/macos/prod.sh: -------------------------------------------------------------------------------- 1 | cd %~dp0 2 | cd ../../../ 3 | SET CGO_ENABLED=0 4 | SET GOOS=darwin 5 | SET GOARCH=amd64 6 | go build main.go 7 | pause -------------------------------------------------------------------------------- /build/sh/prod_x64.bat: -------------------------------------------------------------------------------- 1 | cd %~dp0 2 | cd ../../ 3 | wails build -ldflags="-H windowsgui -w -s -X main.buildTime=" -o ../bin/simui-pulsar.exe -------------------------------------------------------------------------------- /build/sh/prod_x86.bat: -------------------------------------------------------------------------------- 1 | cd %~dp0 2 | cd ../../ 3 | set CGO_ENABLED=1 4 | set GOOS=windows 5 | set GOARCH=386 6 | wails build -ldflags="-H windowsgui -w -s -X main.buildTime=" -o ../bin/simui-pulsar-x86.exe -------------------------------------------------------------------------------- /build/sh/test.bat: -------------------------------------------------------------------------------- 1 | cd %~dp0 2 | cd ../../ 3 | wails build -debug -devtools -upx -o ../bin/simui-pulsar-test.exe -------------------------------------------------------------------------------- /build/windows/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frontlon/simUI-pulsar/2822bcf2b0f630925a29b4d929d6b846af63db41/build/windows/icon.ico -------------------------------------------------------------------------------- /build/windows/info.json: -------------------------------------------------------------------------------- 1 | { 2 | "fixed": { 3 | "file_version": "{{.Info.ProductVersion}}" 4 | }, 5 | "info": { 6 | "0000": { 7 | "ProductVersion": "{{.Info.ProductVersion}}", 8 | "CompanyName": "{{.Info.CompanyName}}", 9 | "FileDescription": "{{.Info.ProductName}}", 10 | "LegalCopyright": "{{.Info.Copyright}}", 11 | "ProductName": "{{.Info.ProductName}}", 12 | "Comments": "{{.Info.Comments}}" 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /build/windows/wails.exe.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | true/pm 12 | permonitorv2,permonitor 13 | 14 | 15 | -------------------------------------------------------------------------------- /components/backup.go: -------------------------------------------------------------------------------- 1 | package components 2 | 3 | import ( 4 | "simUI/constant" 5 | "simUI/utils" 6 | "strings" 7 | ) 8 | 9 | // 复制文件夹时,生成 src 和 dst 两个路径 10 | func GetSrcAndDstPath(root, p, resType string) (string, string) { 11 | if root == "" || p == "" { 12 | return "", "" 13 | } 14 | 15 | if resType == "simulator" { 16 | p = utils.GetFilePath(p) 17 | } 18 | 19 | //绝对路径不拷贝 20 | src := strings.Replace(p, root, "", 1) 21 | if utils.IsAbsPath(src) { 22 | return p, "" 23 | } 24 | src = root + src 25 | dst := constant.ROOT_PATH + p 26 | 27 | //目录不存在,则新建 28 | fileType, _ := utils.CheckFileOrDir(dst) 29 | if fileType == 2 { 30 | utils.CreateDir(dst) 31 | 32 | //已存在,不复制 33 | if !utils.IsDirEmpty(dst) { 34 | return "", "" 35 | } 36 | } else { 37 | //已存在,不复制 38 | if utils.FileExists(dst) { 39 | return "", "" 40 | } 41 | } 42 | return src, dst 43 | } 44 | -------------------------------------------------------------------------------- /components/bat.go: -------------------------------------------------------------------------------- 1 | package components 2 | 3 | import ( 4 | "simUI/utils" 5 | ) 6 | 7 | func CreateGameBat(batPath, gamePath string) error { 8 | relPath := utils.GetRelPathByTowPath(batPath, gamePath) 9 | f := utils.GetFileNameAndExt(gamePath) 10 | 11 | bat := "" 12 | bat += "cd %~dp0\r\n" 13 | bat += `cd "` + relPath + `"` + "\r\n" 14 | bat += `start "" "` + f + `"` + "\r\n" 15 | bat = utils.Utf8ToGbk(bat) 16 | return utils.CreateFile(batPath, bat) 17 | 18 | } 19 | -------------------------------------------------------------------------------- /components/font.go: -------------------------------------------------------------------------------- 1 | package components 2 | 3 | import ( 4 | "github.com/adrg/sysfont" 5 | "simUI/constant" 6 | "simUI/db" 7 | "simUI/utils" 8 | "strings" 9 | ) 10 | 11 | // 读取用户字体 12 | func GetUserFontList() []db.PlatformUIFont { 13 | 14 | files, err := utils.ScanCurrentDir(constant.FONT_PATH) 15 | 16 | list := []db.PlatformUIFont{} 17 | 18 | if err != nil || len(files) == 0 { 19 | return list 20 | } 21 | 22 | for _, f := range files { 23 | 24 | if f.IsDir() { 25 | continue 26 | } 27 | 28 | ext := strings.ToLower(utils.GetFileExt(f.Name())) 29 | if _, ok := constant.FONT_EXTS[ext]; !ok { 30 | continue 31 | } 32 | 33 | fonts := db.PlatformUIFont{ 34 | Type: 2, 35 | Family: utils.GetFileName(f.Name()), 36 | Format: constant.FONT_EXTS[ext], 37 | Src: utils.ToRelPath(constant.FONT_PATH+f.Name(), ""), 38 | } 39 | list = append(list, fonts) 40 | } 41 | 42 | return list 43 | } 44 | 45 | /* 46 | 读取系统字体列表 47 | */ 48 | func GetSystemFontList() []db.PlatformUIFont { 49 | 50 | // 创建一个字体查找器 51 | finder := sysfont.NewFinder(nil) 52 | 53 | // 获取系统所有字体 54 | fonts := finder.List() 55 | 56 | if fonts == nil || len(fonts) == 0 { 57 | return []db.PlatformUIFont{} 58 | } 59 | 60 | // 打印字体名称 61 | list := []string{} 62 | for _, font := range fonts { 63 | if font.Family == "" { 64 | continue 65 | } 66 | list = append(list, font.Family) 67 | } 68 | 69 | list = utils.SliceRemoveDuplicate(list) 70 | 71 | resp := []db.PlatformUIFont{} 72 | for _, family := range list { 73 | f := db.PlatformUIFont{ 74 | Type: 1, 75 | Family: family, 76 | } 77 | resp = append(resp, f) 78 | 79 | } 80 | return resp 81 | } 82 | -------------------------------------------------------------------------------- /components/linkFile.go: -------------------------------------------------------------------------------- 1 | package components 2 | 3 | import ( 4 | "errors" 5 | "os" 6 | "path/filepath" 7 | "simUI/utils" 8 | "strings" 9 | ) 10 | 11 | // rom链接文件类型 12 | var RomLinkExt = ".slnk" 13 | var RomLinkSplit = "||" 14 | 15 | type Slnk struct { 16 | RelRomPath string //rom相对路径 17 | AbsRomPath string //rom绝对路径 18 | Params []string //启动参数 19 | RomName string //rom名称 20 | } 21 | 22 | // 读取所有rom链接 23 | func ReadAllRomLinks(LinkPath string) (map[string]string, error) { 24 | result := map[string]string{} 25 | if err := filepath.Walk(LinkPath, 26 | func(p string, f os.FileInfo, err error) error { 27 | 28 | if f == nil { 29 | return nil 30 | } 31 | 32 | if f.IsDir() { 33 | return nil 34 | } 35 | if utils.GetFileExt(f.Name()) != RomLinkExt { 36 | return nil 37 | } 38 | romPath, _ := utils.ReadFile(p, false) 39 | result[utils.GetFileName(romPath)] = p 40 | return nil 41 | }); err != nil { 42 | return nil, err 43 | 44 | } 45 | return result, nil 46 | } 47 | 48 | // 读取一个rom的所有链接文件 49 | func ReadRomLinksByRom(LinkPath, romName string) ([]string, error) { 50 | result := []string{} 51 | if err := filepath.Walk(LinkPath, 52 | func(p string, f os.FileInfo, err error) error { 53 | 54 | if f == nil { 55 | return nil 56 | } 57 | 58 | if f.IsDir() { 59 | return nil 60 | } 61 | if utils.GetFileExt(f.Name()) != RomLinkExt { 62 | return nil 63 | } 64 | romPath, _ := utils.ReadFile(p, false) 65 | 66 | fileRomName := utils.GetFileName(romPath) 67 | 68 | if fileRomName == romName { 69 | result = append(result, p) 70 | } 71 | 72 | return nil 73 | }); err != nil { 74 | return nil, err 75 | 76 | } 77 | return result, nil 78 | } 79 | 80 | // 读取rom链接文件 81 | func GetLinkFileData(p string) *Slnk { 82 | 83 | if p == "" { 84 | return &Slnk{} 85 | } 86 | 87 | content, err := utils.ReadFile(p, false) 88 | 89 | arr := strings.Split(content, RomLinkSplit) 90 | romPath := arr[0] 91 | param := []string{} 92 | if len(arr) > 1 { 93 | param = strings.Split(arr[1], " ") 94 | } 95 | if err != nil { 96 | return &Slnk{} 97 | } 98 | 99 | data := &Slnk{ 100 | RelRomPath: romPath, 101 | RomName: utils.GetFileName(romPath), 102 | AbsRomPath: utils.ToAbsPath(romPath, ""), 103 | Params: param, 104 | } 105 | return data 106 | } 107 | 108 | // 创建rom链接文件 109 | func CreateLinkFile(p string, f, params string) error { 110 | if utils.FileExists(p) { 111 | return nil 112 | } 113 | content := f 114 | if params != "" { 115 | content = content + RomLinkSplit + params 116 | } 117 | 118 | err := utils.CreateFile(p, content) 119 | if err != nil { 120 | return nil 121 | } 122 | return nil 123 | } 124 | 125 | // 设置rom链接文件 126 | func UpdateLinkFileData(p, romPath, param string) error { 127 | 128 | if p == "" { 129 | return nil 130 | } 131 | 132 | if !utils.FileExists(p) { 133 | return errors.New("链接文件不存在") 134 | } 135 | 136 | content := romPath + RomLinkSplit + param 137 | return utils.OverlayWriteFile(p, content) 138 | } 139 | -------------------------------------------------------------------------------- /components/lua.go: -------------------------------------------------------------------------------- 1 | package components 2 | 3 | import ( 4 | "github.com/Shopify/go-lua" 5 | "simUI/constant" 6 | "simUI/utils" 7 | ) 8 | 9 | // 调用Lua代码 10 | func CallLua(luaFile string, simPath string, romPath string) { 11 | go func() { 12 | var luaState *lua.State 13 | luaState = lua.NewState() 14 | lua.OpenLibraries(luaState) 15 | 16 | if !utils.IsAbsPath(luaFile) { 17 | luaFile = constant.ROOT_PATH + luaFile 18 | } 19 | 20 | if err := lua.DoFile(luaState, luaFile); err != nil { 21 | utils.WriteLog("Lua Run Error:" + err.Error()) 22 | return 23 | } 24 | 25 | // 调用lua函数 26 | luaState.Global("main") 27 | 28 | // 传递参数给lua函数 29 | luaState.PushString(constant.ROOT_PATH) //simui根目录 30 | luaState.PushString(simPath) //模拟器文件 31 | luaState.PushString(romPath) //rom文件 32 | if err := luaState.ProtectedCall(3, 0, 0); err != nil { 33 | utils.WriteLog("Lua Error:" + err.Error()) 34 | return 35 | } 36 | }() 37 | } 38 | -------------------------------------------------------------------------------- /components/output.go: -------------------------------------------------------------------------------- 1 | package components 2 | 3 | import ( 4 | "archive/zip" 5 | "io" 6 | "os" 7 | "simUI/constant" 8 | "simUI/utils" 9 | ) 10 | 11 | var zipMethod = 1 //0仅存储;1压缩 12 | var TmpOutputIniPath = "./cache/config.ini" //ini文件路径 13 | 14 | /** 15 | * 将资源文件添加到压缩文件中 16 | **/ 17 | func CompressZip(file *os.File, prefix string, zw *zip.Writer) error { 18 | info, err := file.Stat() 19 | if err != nil { 20 | return err 21 | } 22 | if info.IsDir() { 23 | if prefix != "" { 24 | prefix = prefix + "/" + info.Name() 25 | } else { 26 | prefix = info.Name() 27 | } 28 | fileInfos, err := file.Readdir(-1) 29 | if err != nil { 30 | return err 31 | } 32 | for _, fi := range fileInfos { 33 | f, err := os.Open(file.Name() + "/" + fi.Name()) 34 | defer f.Close() 35 | if err != nil { 36 | return err 37 | } 38 | err = CompressZip(f, prefix, zw) 39 | if err != nil { 40 | return err 41 | } 42 | } 43 | } else { 44 | 45 | header, err := zip.FileInfoHeader(info) 46 | if err != nil { 47 | return err 48 | } 49 | if prefix != "" { 50 | header.Name = prefix + "/" + header.Name 51 | } 52 | if zipMethod == 1 { 53 | header.Method = zip.Deflate //压缩 54 | } else { 55 | header.Method = zip.Store //仅存储 56 | } 57 | 58 | writer, err := zw.CreateHeader(header) 59 | if err != nil { 60 | return err 61 | } 62 | 63 | _, err = io.Copy(writer, file) 64 | if err != nil { 65 | return err 66 | } 67 | } 68 | return nil 69 | } 70 | 71 | // 检查分享文件 72 | func CheckZip(zipFile string) (bool, error) { 73 | exists := false 74 | reader, err := zip.OpenReader(zipFile) 75 | if err != nil { 76 | return false, err 77 | } 78 | defer reader.Close() 79 | for _, file := range reader.File { 80 | rc, err := file.Open() 81 | if err != nil { 82 | continue 83 | } 84 | defer rc.Close() 85 | 86 | f := utils.GetFileNameAndExt(file.Name) 87 | if f == constant.SHARE_FILE_NAME { 88 | exists = true 89 | break 90 | } 91 | } 92 | return exists, nil 93 | } 94 | 95 | // 分享文件解压 96 | func DecompressZip(zipFile, rootPath, romPath string) error { 97 | reader, err := zip.OpenReader(zipFile) 98 | if err != nil { 99 | return err 100 | } 101 | defer reader.Close() 102 | for _, file := range reader.File { 103 | rc, err := file.Open() 104 | if err != nil { 105 | return err 106 | } 107 | defer rc.Close() 108 | 109 | m := utils.GetFilePath(file.Name) 110 | f := rootPath + "/" + file.Name 111 | r := rootPath 112 | if m == constant.RES_DIR["rom"] { 113 | f = romPath + "/" + file.Name 114 | r = romPath 115 | } 116 | err = os.MkdirAll(r, 0755) 117 | if err != nil { 118 | return err 119 | } 120 | w, err := os.Create(f) 121 | if err != nil { 122 | return err 123 | } 124 | defer w.Close() 125 | _, err = io.Copy(w, rc) 126 | if err != nil { 127 | return err 128 | } 129 | w.Close() 130 | rc.Close() 131 | } 132 | return nil 133 | } 134 | -------------------------------------------------------------------------------- /components/platform.go: -------------------------------------------------------------------------------- 1 | package components 2 | 3 | import ( 4 | "simUI/constant" 5 | "simUI/db" 6 | ) 7 | 8 | // 读取全部资源目录 9 | // typ:0全部 1图片 2图片+视频 10 | func GetResPath(platformId uint32, typ int) map[string]string { 11 | 12 | thumb, snap, poster, packing, title, cassette := "", "", "", "", "", "" 13 | icon, gif, background, video := "", "", "", "" 14 | doc, strategy, files, upload := "", "", "", "" 15 | 16 | info := (&db.Platform{}).GetVOById(platformId, false) 17 | 18 | if info != nil { 19 | thumb = info.ThumbPath 20 | snap = info.SnapPath 21 | poster = info.PosterPath 22 | packing = info.PackingPath 23 | title = info.TitlePath 24 | cassette = info.CassettePath 25 | icon = info.IconPath 26 | gif = info.GifPath 27 | background = info.BackgroundPath 28 | video = info.VideoPath 29 | doc = info.DocPath 30 | strategy = info.StrategyPath 31 | //audio = info.AudioPath 32 | files = info.FilesPath 33 | upload = info.UploadPath 34 | } 35 | 36 | res := map[string]string{ 37 | "thumb": thumb, 38 | "snap": snap, 39 | "poster": poster, 40 | "packing": packing, 41 | "title": title, 42 | "cassette": cassette, 43 | "icon": icon, 44 | "gif": gif, 45 | "background": background, 46 | } 47 | if typ == 0 || typ == 2 { 48 | res["video"] = video 49 | } 50 | if typ == 0 { 51 | res["doc"] = doc 52 | res["strategy"] = strategy 53 | //res["audio"] = audio 54 | res["file"] = files 55 | res["upload"] = upload 56 | } 57 | return res 58 | } 59 | 60 | // 读取资源类型名 61 | func GetResExts() map[string][]string { 62 | res := map[string][]string{} 63 | 64 | picExt := constant.MEDIA_EXTS 65 | docExt := constant.DOC_EXTS 66 | fileExt := constant.FILE_EXTS 67 | //audioExt := constant.AUDIO_EXTS 68 | 69 | res["thumb"] = picExt 70 | res["snap"] = picExt 71 | res["poster"] = picExt 72 | res["packing"] = picExt 73 | res["title"] = picExt 74 | res["cassette"] = picExt 75 | res["icon"] = picExt 76 | res["gif"] = picExt 77 | res["background"] = picExt 78 | res["video"] = picExt 79 | res["doc"] = docExt 80 | res["strategy"] = docExt 81 | res["files"] = fileExt 82 | //res["audio"] = audioExt 83 | return res 84 | } 85 | 86 | // 根据图片类型 读取 图片路径 87 | func GetResPathByType(typ string, platformId uint32) string { 88 | resPath := GetResPath(platformId, 0) 89 | return resPath[typ] 90 | } 91 | -------------------------------------------------------------------------------- /components/rom.go: -------------------------------------------------------------------------------- 1 | package components 2 | 3 | import ( 4 | "os" 5 | "path/filepath" 6 | "simUI/constant" 7 | "simUI/utils" 8 | "strings" 9 | ) 10 | 11 | // 读取游戏主资源 12 | func GetMasterRes(typ string, platform uint32, romName string) string { 13 | 14 | fileName := "" 15 | resName := "" 16 | pth := GetResPathByType(typ, platform) 17 | types := GetResExts() 18 | if pth != "" { 19 | for _, v := range types[typ] { 20 | fileName = pth + "/" + romName + v 21 | if utils.FileExists(fileName) { 22 | resName = fileName 23 | break 24 | } 25 | } 26 | } 27 | 28 | return resName 29 | } 30 | 31 | /** 32 | * 读取游戏子资源 33 | **/ 34 | func GetSlaveRes(dir string, masterRomName string) ([]string, error) { 35 | 36 | dir = utils.ToAbsPath(masterRomName, dir) 37 | 38 | files := []string{} 39 | extMap := utils.SliceToMap(constant.MEDIA_EXTS) 40 | 41 | resList, err := utils.ScanCurrentDir(dir) 42 | if err != nil { 43 | return nil, nil 44 | } 45 | 46 | for _, f := range resList { 47 | if f.IsDir() { 48 | // 忽略目录 49 | continue 50 | } 51 | 52 | ext := utils.GetFileExt(f.Name()) 53 | if _, ok := extMap[ext]; !ok { 54 | //不是图片,忽略 55 | continue 56 | } 57 | 58 | abs := utils.ToAbsPath(f.Name(), dir) 59 | files = append(files, abs) 60 | 61 | } 62 | 63 | return files, err 64 | } 65 | 66 | // rom封装主展示图 67 | func GetMasterRomThumbs(thumbType string, platform uint32, romNameMap map[string]string) map[string]string { 68 | dir := GetResPathByType(thumbType, platform) 69 | if dir == "" { 70 | return map[string]string{} 71 | } 72 | 73 | thumbsMap := map[string]string{} 74 | picMap := utils.SliceToMap(constant.MEDIA_EXTS) 75 | 76 | filepath.Walk(dir, func(filename string, f os.FileInfo, err error) error { //遍历目录 77 | if err != nil { //忽略错误 78 | return err 79 | } 80 | 81 | if f.IsDir() { // 忽略目录 82 | return nil 83 | } 84 | 85 | fExt := strings.ToLower(utils.GetFileExt(f.Name())) 86 | if _, ok := picMap[fExt]; !ok { 87 | return nil 88 | } 89 | 90 | fName := utils.GetFileName(f.Name()) 91 | 92 | if _, ok := romNameMap[fName]; !ok { 93 | return nil 94 | } 95 | 96 | if _, ok := thumbsMap[fName]; ok { 97 | return nil 98 | } 99 | 100 | thumbsMap[fName] = filename 101 | 102 | return nil 103 | }) 104 | 105 | return thumbsMap 106 | } 107 | 108 | /** 109 | * 读取游戏文件资源 110 | **/ 111 | func GetFileRes(dir string, romName string) ([]string, error) { 112 | 113 | dir = utils.ToAbsPath(romName, dir) 114 | 115 | files := []string{} 116 | 117 | resList, err := utils.ScanCurrentDir(dir) 118 | if err != nil { 119 | return nil, nil 120 | } 121 | 122 | for _, f := range resList { 123 | if f.IsDir() { 124 | // 忽略目录 125 | continue 126 | } 127 | 128 | abs := utils.ToAbsPath(f.Name(), dir) 129 | files = append(files, abs) 130 | } 131 | 132 | return files, err 133 | } 134 | -------------------------------------------------------------------------------- /components/upgrade.go: -------------------------------------------------------------------------------- 1 | package components 2 | 3 | import ( 4 | "simUI/constant" 5 | "simUI/utils" 6 | ) 7 | 8 | // 创建升级配置文件 9 | func UpgradeCreateBat() (string, error) { 10 | content := `@echo off 11 | cd %~dp0 12 | IF "%~1"=="" ( 13 | echo param error. 14 | exit 15 | ) 16 | set "currDir=%~dp0" 17 | set "zipDir=%~1" 18 | IF NOT EXIST "%zipDir%" ( 19 | echo unzip dir is not exists. 20 | exit 21 | ) 22 | 23 | echo start upgrade... 24 | ping 127.0.0.1 -n 5 > nul 25 | XCOPY /S /y "%zipDir%*" "%currDir%" 26 | start "" "%currDir%simui-pulsar.exe" 27 | RMDIR /s /q "%zipDir%" 28 | DEL "%~f0" 29 | ` 30 | p := constant.ROOT_PATH + "upgrade.bat" 31 | if err := utils.CreateFile(p, content); err != nil { 32 | return "", err 33 | } 34 | return p, nil 35 | } 36 | -------------------------------------------------------------------------------- /components/zip.go: -------------------------------------------------------------------------------- 1 | package components 2 | 3 | import ( 4 | "archive/zip" 5 | "github.com/axgle/mahonia" 6 | "io" 7 | "os" 8 | "path/filepath" 9 | "simUI/constant" 10 | "simUI/utils" 11 | "strings" 12 | ) 13 | 14 | /* 15 | zip解压 16 | */ 17 | 18 | func UnzipRom(zipFile string) (string, error) { 19 | 20 | if strings.ToLower(filepath.Ext(zipFile)) != ".zip" { 21 | return "", nil 22 | } 23 | 24 | zipReader, err := zip.OpenReader(zipFile) 25 | if err != nil { 26 | return "", err 27 | } 28 | defer zipReader.Close() 29 | 30 | //拼接解压路径 31 | zipfileName := utils.GetFileName(zipFile) 32 | fpath := constant.CACHE_UNZIP_PATH + zipfileName + "/" 33 | 34 | if !utils.IsDirEmpty(fpath) { 35 | return fpath, nil 36 | } 37 | 38 | if !utils.DirExists(fpath) { 39 | if err = utils.CreateDir(fpath); err != nil { 40 | } 41 | } 42 | 43 | for _, f := range zipReader.File { 44 | //解决中文文件名乱码问题 45 | enc := mahonia.NewDecoder("gbk") 46 | f.Name = enc.ConvertString(f.Name) 47 | 48 | //开始解压 49 | if f.FileInfo().IsDir() { 50 | if err = utils.CreateDir(fpath + f.Name); err != nil { 51 | return "", err 52 | } 53 | } else { 54 | srcFile, err := f.Open() 55 | if err != nil { 56 | return "", err 57 | } 58 | defer srcFile.Close() 59 | 60 | dstPath := fpath + f.Name 61 | dstFile, err := os.OpenFile(dstPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode()) 62 | if err != nil { 63 | return "", err 64 | } 65 | defer dstFile.Close() 66 | 67 | _, err = io.Copy(dstFile, srcFile) 68 | if err != nil { 69 | return "", err 70 | } 71 | 72 | } 73 | } 74 | return fpath, nil 75 | } 76 | 77 | /* 78 | 清理解压缓存 79 | */ 80 | func ClearZipRom() error { 81 | err := os.RemoveAll(constant.CACHE_UNZIP_PATH) 82 | if err != nil { 83 | return err 84 | } 85 | return nil 86 | } 87 | -------------------------------------------------------------------------------- /config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "github.com/go-ini/ini" 7 | "os" 8 | "simUI/constant" 9 | "simUI/db" 10 | "simUI/utils" 11 | ) 12 | 13 | // 配置文件 14 | var ( 15 | Cfg *ConfStruct //公共配置 16 | Ctx context.Context 17 | ) 18 | 19 | // 配置文件 20 | type ConfStruct struct { 21 | LangList []string //语言列表 22 | Lang map[string]string //语言项 23 | } 24 | 25 | /* 26 | 初始化读取配置 27 | @author frontLon 28 | */ 29 | func InitConf() error { 30 | 31 | err := errors.New("") 32 | 33 | //config配置 34 | configData := (&db.Config{}).GetConfig(true) 35 | 36 | //语言列表 37 | Cfg.LangList, err = getLangList() 38 | if err != nil { 39 | return err 40 | } 41 | //语言配置定义 42 | Cfg.Lang, err = getLang(configData.Lang) 43 | if err != nil { 44 | return err 45 | } 46 | return nil 47 | } 48 | 49 | // 读取语言参数配置 50 | func getLang(lang string) (map[string]string, error) { 51 | langpath := constant.LANG_PATH 52 | fpath := langpath + lang + ".ini" 53 | section := make(map[string]string) 54 | 55 | //如果默认语言不存在,则读取列表中的其他语言 56 | if !utils.FileExists(fpath) { 57 | if len(Cfg.LangList) > 0 { 58 | for langName, langFile := range Cfg.LangList { 59 | fpath = langpath + langFile 60 | //如果找到其他语言,则将第一项更新到数据库配置中 61 | if err := (&db.Config{}).UpdateOne("lang", langName); err != nil { 62 | return section, err 63 | } 64 | break 65 | } 66 | } 67 | } 68 | 69 | file, err := ini.LoadSources(ini.LoadOptions{IgnoreInlineComment: true}, fpath) 70 | 71 | if err != nil { 72 | return section, err 73 | } 74 | 75 | section = file.Section("").KeysHash() 76 | return section, nil 77 | } 78 | 79 | // 读取语言文件列表 80 | func getLangList() ([]string, error) { 81 | list := []string{} 82 | lists, _ := os.ReadDir(constant.LANG_PATH) 83 | for _, fi := range lists { 84 | if !fi.IsDir() { // 忽略目录 85 | list = append(list, utils.GetFileName(fi.Name())) 86 | } 87 | } 88 | return list, nil 89 | } 90 | -------------------------------------------------------------------------------- /config/share.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "encoding/json" 5 | "simUI/utils" 6 | ) 7 | 8 | type Share struct { 9 | RomFile string //master.zip 10 | SubGame []string //sub.zip 11 | Rombase *RomBase 12 | } 13 | 14 | // 读取分享配置 15 | func GetShareData(filePath string) (map[string]*Share, error) { 16 | 17 | share := map[string]*Share{} 18 | 19 | if !utils.FileExists(filePath) { 20 | return share, nil 21 | } 22 | content, _ := utils.ReadFile(filePath, false) 23 | if content != "" { 24 | json.Unmarshal([]byte(content), &share) 25 | } 26 | return share, nil 27 | } 28 | 29 | // 覆写分享配置 30 | func WriteDataToShareFile(filePath string, data map[string]*Share) error { 31 | content, _ := json.Marshal(data) 32 | utils.OverlayWriteFile(filePath, string(content)) 33 | return nil 34 | } 35 | -------------------------------------------------------------------------------- /config/subgame.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "encoding/json" 5 | "simUI/db" 6 | "simUI/utils" 7 | ) 8 | 9 | // 子游戏文件名 => 主游戏文件名 10 | var SubGameList = map[uint32]map[string]string{} 11 | 12 | // 读取子游戏配置 13 | func GetSubGame(platform uint32, build bool) (map[string]string, error) { 14 | 15 | if len(SubGameList) == 0 { 16 | SubGameList = map[uint32]map[string]string{} 17 | } 18 | 19 | //如果已经读取,则直接返回 20 | if len(SubGameList[platform]) != 0 && build == false { 21 | return SubGameList[platform], nil 22 | } 23 | 24 | platformInfo := (&db.Platform{}).GetVOById(platform, false) 25 | section := map[string]string{} 26 | 27 | if !utils.FileExists(platformInfo.SubGameFile) { 28 | return section, nil 29 | } 30 | content, _ := utils.ReadFile(platformInfo.SubGameFile, false) 31 | if content != "" { 32 | json.Unmarshal([]byte(content), §ion) 33 | SubGameList[platform] = section 34 | } 35 | return section, nil 36 | } 37 | 38 | // 根据主游戏,读取所属的子游戏 39 | func GetSubGameByParent(platform uint32, romName string) ([]string, error) { 40 | 41 | //如果已经读取,则直接返回 42 | datas, _ := GetSubGame(platform, false) 43 | 44 | result := []string{} 45 | for k, v := range datas { 46 | if v == romName { 47 | result = append(result, k) 48 | } 49 | } 50 | return result, nil 51 | } 52 | 53 | // 设置子游戏 54 | func SetSubGame(platform uint32, slave, master string) error { 55 | if len(SubGameList) == 0 { 56 | SubGameList = map[uint32]map[string]string{} 57 | } 58 | if len(SubGameList[platform]) == 0 { 59 | SubGameList[platform] = map[string]string{} 60 | } 61 | SubGameList[platform][slave] = master 62 | 63 | platformInfo := (&db.Platform{}).GetVOById(platform, false) 64 | 65 | return WriteDataToSubGameFile(platformInfo.SubGameFile, SubGameList[platform]) 66 | } 67 | 68 | // 删除子游戏 69 | func DelSubGame(platform uint32, slave string) error { 70 | if len(SubGameList) == 0 { 71 | SubGameList = map[uint32]map[string]string{} 72 | } 73 | if len(SubGameList[platform]) == 0 { 74 | SubGameList[platform] = map[string]string{} 75 | } 76 | 77 | if _, ok := SubGameList[platform][slave]; !ok { 78 | return nil 79 | } 80 | 81 | delete(SubGameList[platform], slave) 82 | 83 | platformInfo := (&db.Platform{}).GetVOById(platform, false) 84 | 85 | return WriteDataToSubGameFile(platformInfo.SubGameFile, SubGameList[platform]) 86 | } 87 | 88 | // 覆写子游戏配置 89 | func WriteDataToSubGameFile(filePath string, data map[string]string) error { 90 | content, _ := json.Marshal(data) 91 | utils.OverlayWriteFile(filePath, string(content)) 92 | return nil 93 | } 94 | -------------------------------------------------------------------------------- /constant/args.go: -------------------------------------------------------------------------------- 1 | package constant 2 | 3 | /*type CmdArgs struct { 4 | Db string 5 | Dev bool 6 | }*/ 7 | 8 | //var ARGS CmdArgs 9 | -------------------------------------------------------------------------------- /constant/const.go: -------------------------------------------------------------------------------- 1 | package constant 2 | 3 | // 是否为开发环境 4 | var DEV = false 5 | 6 | var DB_ADD_MAX_NUM = 999 //数据库每次查询/写入的最大数量 7 | 8 | var VERSION_NO = "" //当前软件版本号 9 | var BUILD_TIME = "" //软件编译时间 10 | 11 | // doc文档支持的扩展名 12 | var DOC_EXTS = []string{".txt", ".html", ".htm", ".md"} 13 | 14 | // 支持的图片类型 15 | var MEDIA_EXTS = []string{".png", ".jpg", ".gif", ".webp", ".jpeg", ".ico", ".mp4", ".webm", "avif"} 16 | 17 | // 支持的音频类型 18 | var AUDIO_EXTS = []string{".mp3", ".dmi", ".wav", ".wma"} 19 | 20 | // 可直接运行的doc文档支持的扩展名 21 | var FILE_EXTS = []string{ 22 | ".html", ".htm", ".mht", ".mhtml", ".url", 23 | ".chm", ".pdf", ".doc", ".docx", ".ppt", ".pptx", ".xls", ".xlsx", ".rtf", 24 | ".exe", ".com", ".cmd", ".bat", ".lnk", 25 | } 26 | 27 | // 可直接运行的扩展名 28 | var RUN_EXTS = []string{".exe", ".cmd", ".bat", ".lnk"} 29 | 30 | // 可直接通过explorer运行的扩展名 31 | var EXPLORER_EXTS = []string{".lnk"} 32 | 33 | // 支持的字体类型 34 | var FONT_EXTS = map[string]string{ 35 | ".woff": "woff", 36 | ".woff2": "woff2", 37 | ".ttf": "truetype", 38 | ".otf": "opentype", 39 | ".eot": "embedded-opentype", 40 | ".svg": "svg", 41 | ".svgz": "svg", 42 | } 43 | 44 | /*EXPLORER_EXTS = []string{ 45 | ".lnk", ".html", ".htm", ".mht", ".mhtml", ".url", 46 | ".chm", ".doc", ".docx", ".ppt", ".pptx", ".xls", ".xlsx", ".rtf", 47 | } //通过explorer运行的扩展名*/ 48 | 49 | // 主题标识配置 50 | var UI_DEFAULT = "Default" 51 | var UI_PLAYNITE = "Playnite" 52 | var UI_TINY = "Tiny" 53 | 54 | // 平台资源文件夹定义 55 | var RES_DIR = map[string]string{ 56 | "rom": "roms", 57 | "thumb": "thumbs", 58 | "snap": "snaps", 59 | "poster": "poster", 60 | "packing": "packing", 61 | "title": "title", 62 | "cassette": "cassette", 63 | "icon": "icon", 64 | "gif": "gif", 65 | "background": "background", 66 | "video": "video", 67 | "doc": "docs", 68 | "strategy": "strategies", 69 | "audio": "audio", 70 | "file": "files", 71 | "upload": "uploads", 72 | "link": "links", 73 | } 74 | 75 | // 默认展示图排序 76 | var DefaultThumbOrders = []string{ 77 | "thumb", "snap", "poster", "packing", "title", "cassette", "icon", "gif", "background", "video", 78 | } 79 | 80 | // 默认过滤器 81 | var DefaultListColumns = []string{ 82 | "BaseNameEn", "BaseNameJp", "BaseType", "BaseYear", "BasePublisher", "BaseProducer", 83 | "BaseCountry", "BaseTranslate", "BaseVersion", "Score", "Complete", "Menu", 84 | } 85 | -------------------------------------------------------------------------------- /constant/path.go: -------------------------------------------------------------------------------- 1 | package constant 2 | 3 | // 项目目录 4 | var ROOT_PATH = "" //软件根目录 5 | var CACHE_PATH = "" //缓存目录 6 | var CACHE_UNZIP_PATH = "" //资源解压目录 7 | var CACHE_THUMB_PATH = "" //展示图备份目录 8 | var CACHE_UNOWNED_PATH = "" //无效资源备份目录 9 | var CACHE_REPEAT_PATH = "" //重复ROM备份目录 10 | var LANG_PATH = "" //语言目录 11 | var RESOURCE_PATH = "" //软件资源目录 12 | var FONT_PATH = "" //字体目录 13 | 14 | var ROMBASE_FILE_NAME = "rombase" //平台资料文件名 15 | var SETTING_FILE_NAME = "romsetting.csv" //平台rom配置文件名 16 | var SUBGAME_FILE_NAME = "subgame.json" //子游戏配置文件 17 | var SHARE_FILE_NAME = "share.json" //分享配置 18 | var SHARE_ZIP_EXT = ".shr" //分享文件扩展名 19 | -------------------------------------------------------------------------------- /controller/audio.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | // 读取游戏音频 4 | //func (a *Controller) GetGameAudios(id uint64) string { 5 | // return Resp(modules.GetGameAudios(id)) 6 | //} 7 | 8 | // 编辑游戏音频 9 | //func (a *Controller) UpdateGameAudio(lists []string) string { 10 | // return Resp(modules.UpdateGameAudio(lists)) 11 | //} 12 | 13 | /*// 添加游戏音频 14 | func (a *Controller) AddGameAudio(id uint64, lists []string) string { 15 | return Resp(modules.AddGameAudio(id, lists)) 16 | }*/ 17 | 18 | /*// 改名游戏音频 19 | func (a *Controller) RenameGameAudio(pth string, newName string) string { 20 | return Resp(modules.RenameGameAudio(pth, newName)) 21 | } 22 | 23 | // 删除游戏音频 24 | func (a *Controller) DelGameAudio(pth string) string { 25 | return Resp("", modules.DelGameAudio(pth)) 26 | }*/ 27 | 28 | /* 29 | func AudioController() { 30 | 31 | //读取音频文件列表 32 | utils.Window.DefineFunction("GetAudio", func(args ...*sciter.Value) *sciter.Value { 33 | id := uint64(utils.ToInt(args[0].String())) 34 | volist, err := modules.GetAudioList(id) 35 | if err != nil { 36 | utils.WriteLog(err.Error()) 37 | return utils.ErrorMsg(err.Error()) 38 | } 39 | jsonInfo, _ := json.Marshal(volist) 40 | return sciter.NewValue(string(jsonInfo)) 41 | }) 42 | 43 | //上传文件 44 | utils.Window.DefineFunction("UploadAudioFile", func(args ...*sciter.Value) *sciter.Value { 45 | id := uint64(utils.ToInt(args[0].String())) 46 | name := args[1].String() 47 | p := args[2].String() 48 | 49 | relPath, err := modules.UploadAudioFile(id, name, p) 50 | if err != nil { 51 | utils.WriteLog(err.Error()) 52 | return utils.ErrorMsg(err.Error()) 53 | } 54 | 55 | return sciter.NewValue(relPath) 56 | }) 57 | 58 | //更新配置 59 | utils.Window.DefineFunction("UpdateAudio", func(args ...*sciter.Value) *sciter.Value { 60 | id := uint64(utils.ToInt(args[0].String())) 61 | data := args[1].String() 62 | if err := modules.UpdateAudio(id, data); err != nil { 63 | utils.WriteLog(err.Error()) 64 | return utils.ErrorMsg(err.Error()) 65 | } 66 | return sciter.NullValue() 67 | }) 68 | 69 | //播放音频文件 70 | utils.Window.DefineFunction("PlayAudio", func(args ...*sciter.Value) *sciter.Value { 71 | urls := []string{} 72 | json.Unmarshal([]byte(args[0].String()), &urls) 73 | 74 | for k, v := range urls { 75 | urls[k] = utils.ToAbsPath(v) 76 | } 77 | 78 | if err := components.PlayAudio(urls); err != nil { 79 | utils.WriteLog(err.Error()) 80 | return utils.ErrorMsg(err.Error()) 81 | } 82 | 83 | return sciter.NullValue() 84 | }) 85 | 86 | }*/ 87 | -------------------------------------------------------------------------------- /controller/cache.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import "simUI/modules" 4 | 5 | // 更新缓存 6 | func (a *Controller) CreateRomCache(platform uint32) string { 7 | return Resp(modules.CreateRomCache(platform)) 8 | } 9 | 10 | // 清理游戏统计信息 11 | func (a *Controller) ClearGameStat() string { 12 | return Resp("", modules.ClearGameStat()) 13 | } 14 | 15 | // 清理游戏资料 16 | func (a *Controller) ClearNotExistGameConfig() string { 17 | return Resp("", modules.ClearNotExistGameConfig()) 18 | } 19 | -------------------------------------------------------------------------------- /controller/config.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "encoding/json" 5 | "simUI/db" 6 | "simUI/modules" 7 | ) 8 | 9 | // 读取配置 10 | func (a *Controller) GetConfig() string { 11 | return Resp(modules.GetConfig()) 12 | } 13 | 14 | // 读取全部平台 - 平台原信息 15 | func (a *Controller) GetBaseConfig() string { 16 | return Resp(modules.GetBaseConfig()) 17 | } 18 | 19 | // 更新一条配置 20 | func (a *Controller) UpdateOneConfig(key, data string) string { 21 | return Resp("", modules.UpdateOneConfig(key, data)) 22 | } 23 | 24 | // 更新基本配置 25 | func (a *Controller) UpdateBaseConfig(data string) string { 26 | d := db.ConfigVO{} 27 | json.Unmarshal([]byte(data), &d) 28 | return Resp("", modules.UpdateBaseConfig(d)) 29 | } 30 | 31 | // 更新颜色配置 32 | func (a *Controller) UpdateColorsConfig(data string) string { 33 | return Resp("", modules.UpdateOneConfig("Colors", data)) 34 | } 35 | 36 | // 读取当前主题 37 | func (a *Controller) GetTheme() string { 38 | return Resp(modules.GetTheme()) 39 | } 40 | 41 | // 设置当前主题 42 | func (a *Controller) SetTheme(theme string) string { 43 | return Resp("", modules.SetTheme(theme)) 44 | } 45 | 46 | // 更新展示图排序 47 | func (a *Controller) UpdateThumbsOrders(orders []string) string { 48 | return Resp("", modules.UpdateThumbsOrders(orders)) 49 | } 50 | 51 | // 读取字体列表 52 | func (a *Controller) GetFontList() string { 53 | return Resp(modules.GetFontList()) 54 | } 55 | -------------------------------------------------------------------------------- /controller/controller.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "encoding/json" 5 | ) 6 | 7 | type Controller struct { 8 | } 9 | 10 | // NewApp creates a new App application struct 11 | func NewController() *Controller { 12 | return &Controller{} 13 | } 14 | 15 | type resp struct { 16 | Data any `json:"data"` 17 | Err string `json:"err"` 18 | } 19 | 20 | func Resp(data any, err error) string { 21 | errMsg := "" 22 | if err != nil { 23 | errMsg = err.Error() 24 | } 25 | result := resp{ 26 | Data: data, 27 | Err: errMsg, 28 | } 29 | //fmt.Println("api resp:", data, err) 30 | js, _ := json.Marshal(result) 31 | return string(js) 32 | } 33 | -------------------------------------------------------------------------------- /controller/dialog.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "simUI/modules" 5 | ) 6 | 7 | // 弹出文件选择窗口 8 | func (a *Controller) OpenFileDialog(typ string) string { 9 | return Resp(modules.OpenFileDialog(typ)) 10 | } 11 | 12 | // 弹出多文件选择窗口 13 | func (a *Controller) OpenMultiFileDialog(typ string) string { 14 | return Resp(modules.OpenMultiFileDialog(typ)) 15 | } 16 | 17 | // 弹出目录选择窗口 18 | func (a *Controller) OpenDirectoryDialog() string { 19 | return Resp(modules.OpenDirectoryDialog()) 20 | } 21 | 22 | // 保存文件对话框 23 | func (a *Controller) SaveFileDialog(filename string) string { 24 | return Resp(modules.SaveFileDialog(filename)) 25 | } 26 | 27 | // 编辑器图片选择框 28 | func (a *Controller) OpenFileDialogForEditor() string { 29 | return Resp(modules.OpenFileDialogForEditor()) 30 | } 31 | -------------------------------------------------------------------------------- /controller/image.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "simUI/modules" 5 | ) 6 | 7 | // 读取游戏图集 8 | func (a *Controller) GetGameThumbs(id uint64, typ string) string { 9 | return Resp(modules.GetGameThumbs(id, typ)) 10 | } 11 | 12 | // 添加展示图 13 | func (a *Controller) AddGameThumb(romId uint64, typ string, imageList []string) string { 14 | return Resp(modules.AddThumb(romId, typ, imageList)) 15 | } 16 | 17 | // 删除展示图 18 | func (a *Controller) DelGameThumb(romId uint64, typ string, master uint8, imgPath string) string { 19 | return Resp("", modules.DeleteThumb(romId, typ, master, imgPath)) 20 | } 21 | 22 | // 图集排序 23 | func (a *Controller) SortGameThumb(romId uint64, typ string, imgList []string) string { 24 | return Resp(modules.SortGameThumb(romId, typ, imgList)) 25 | } 26 | 27 | // 读取网络图片 28 | func (a *Controller) LoadWebThumbs(engine string, keyword string, page int) string { 29 | return Resp(modules.LoadWebThumbs(engine, keyword, page)) 30 | } 31 | 32 | // 下载网络图片 33 | func (a *Controller) DownloadThumb(romId uint64, typ string, master uint8, httpUrl string, ext string) string { 34 | return Resp(modules.DownloadThumb(romId, typ, master, httpUrl, ext)) 35 | } 36 | 37 | // base64转图片 38 | func (a *Controller) CreateRomResByBase64(id uint64, resType string, slaveRes uint8, fileType, base64Str string) string { 39 | return Resp(modules.CreateRomResByBase64(id, resType, slaveRes, fileType, base64Str)) 40 | } 41 | -------------------------------------------------------------------------------- /controller/input.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "errors" 5 | "simUI/modules" 6 | ) 7 | 8 | // 导出一个rom 9 | func (a *Controller) OutputOneRom(id uint64, dir string, outputSubRom, outputRes, outputConf bool) string { 10 | return Resp("", modules.OutputOneRom(id, dir, outputSubRom, outputRes, outputConf)) 11 | } 12 | 13 | // 导出一个平台rom 14 | func (a *Controller) OutputRomByPlatform(id uint32, dir string, outputSim bool) string { 15 | return Resp("", modules.OutputRomByPlatform(id, dir, outputSim)) 16 | } 17 | 18 | // 添加游戏 19 | func (a *Controller) AddGame(opt string, platform uint32, menu string, romPath string, files []string, bootParam string, isBatFile uint8) string { 20 | 21 | if len(files) == 0 { 22 | return Resp("", errors.New("files not be empty")) 23 | } 24 | 25 | var err error 26 | 27 | switch opt { 28 | case "rom": 29 | err = modules.AddFileGame(platform, menu, romPath, files) 30 | case "pc": 31 | err = modules.AddPcOrFolderGame(platform, menu, romPath, files[0], bootParam, isBatFile) 32 | case "ps3": 33 | err = modules.AddPcOrFolderGame(platform, menu, romPath, files[0], "", 0) 34 | case "share": 35 | err = modules.InputOneShare(platform, menu, romPath, files[0]) 36 | } 37 | 38 | return Resp("", err) 39 | } 40 | 41 | // 导出一张图片 42 | func (a *Controller) OutputOneImage(src, dst string) string { 43 | return Resp("", modules.OutputOneImage(src, dst)) 44 | 45 | } 46 | -------------------------------------------------------------------------------- /controller/menu.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "simUI/modules" 5 | ) 6 | 7 | // 读取平台下的所有目录 8 | func (a *Controller) GetMenuList(platform uint32) string { 9 | return Resp(modules.GetMenuList(platform)) 10 | } 11 | 12 | // 添加目录 13 | func (a *Controller) AddMenu(platform uint32, path, name string) string { 14 | return Resp("", modules.AddMenu(platform, path, name)) 15 | } 16 | 17 | // 编辑目录 18 | func (a *Controller) RenameMenu(platform uint32, path, newName string) string { 19 | return Resp("", modules.RenameMenu(platform, path, newName)) 20 | } 21 | 22 | // 删除目录 23 | func (a *Controller) DeleteMenu(platform uint32, path string) string { 24 | return Resp("", modules.DeleteMenu(platform, path)) 25 | } 26 | 27 | // 排序目录 28 | func (a *Controller) SortMenu(platform uint32, paths []string) string { 29 | return Resp("", modules.SortMenu(platform, paths)) 30 | } 31 | -------------------------------------------------------------------------------- /controller/others.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import "simUI/modules" 4 | 5 | // 启动上传服务 6 | func (a *Controller) StartUploadServer() string { 7 | return Resp(modules.StartUploadServer(), nil) 8 | } 9 | -------------------------------------------------------------------------------- /controller/platform.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "encoding/json" 5 | "simUI/modules" 6 | "simUI/request" 7 | ) 8 | 9 | // 读取全部平台 - VO数据 10 | func (a *Controller) GetPlatform() string { 11 | return Resp(modules.GetAllPlatform(), nil) 12 | } 13 | 14 | // 读取全部平台 - 平台原信息 15 | func (a *Controller) GetPlatformOriginal() string { 16 | return Resp(modules.GetPlatformOriginal()) 17 | } 18 | 19 | // 读取一个平台信息 20 | func (a *Controller) GetPlatformById(id uint32) string { 21 | return Resp(modules.GetPlatformById(id)) 22 | } 23 | 24 | // 读取平台标签 25 | func (a *Controller) GetAllPlatformTag() string { 26 | return Resp(modules.GetAllPlatformTag()) 27 | } 28 | 29 | // 读取平台UI配置 30 | func (a *Controller) GetPlatformUi(id uint32, theme string) string { 31 | return Resp(modules.GetPlatformUi(id, theme)) 32 | } 33 | 34 | // 添加一个平台 35 | func (a *Controller) AddPlatform(name string) string { 36 | return Resp(modules.AddPlatform(name)) 37 | } 38 | 39 | // 编辑平台信息 40 | func (a *Controller) UpdatePlatform(data string) string { 41 | d := request.UpdatePlatform{} 42 | json.Unmarshal([]byte(data), &d) 43 | return Resp(modules.UpdatePlatform(d)) 44 | } 45 | 46 | // 编辑平台简介 47 | func (a *Controller) UpdatePlatformDesc(id uint32, desc string) string { 48 | return Resp("", modules.UpdatePlatformDesc(id, desc)) 49 | } 50 | 51 | // 更新平台排序 52 | func (a *Controller) UpdatePlatformSort(data string) string { 53 | d := []uint32{} 54 | json.Unmarshal([]byte(data), &d) 55 | return Resp("", modules.UpdatePlatformSort(d)) 56 | } 57 | 58 | // 删除一个平台 59 | func (a *Controller) DelPlatform(id uint32) string { 60 | return Resp("", modules.DelPlatform(id)) 61 | } 62 | 63 | // 更新平台UI设置 64 | func (a *Controller) UpdatePlatformUi(id uint32, theme string, data string) string { 65 | return Resp("", modules.UpdatePlatformUi(id, theme, data)) 66 | } 67 | 68 | // 在资源管理器中打开目录 69 | func (a *Controller) OpenFolder(opt string, id uint64) string { 70 | return Resp("", modules.OpenFolder(opt, id)) 71 | } 72 | 73 | // 在资源管理器中打开目录 - 直接访问文件 74 | func (a *Controller) OpenFolderByPath(pth string) string { 75 | return Resp("", modules.OpenFolderByPath(pth)) 76 | } 77 | -------------------------------------------------------------------------------- /controller/romManage.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "simUI/modules" 5 | ) 6 | 7 | // rom文件重命名 8 | func (a *Controller) RenameRomFile(id uint64, name string) string { 9 | return Resp("", modules.RenameRomFile(id, name)) 10 | } 11 | 12 | // 删除rom和资源 13 | func (a *Controller) DeleteRom(id uint64, delSubRom uint8, delRes uint8) string { 14 | return Resp("", modules.DeleteRomAndRes(id, delSubRom, delRes)) 15 | } 16 | 17 | // 修改游戏别名 18 | func (a *Controller) RenameRomLink(id uint64, name string) string { 19 | return Resp("", modules.RenameRomLink(id, name)) 20 | } 21 | 22 | // 复制链接文件 23 | func (a *Controller) CopyRomLink(id uint64, menu string) string { 24 | return Resp(modules.CopyRomLink(id, menu)) 25 | } 26 | 27 | // 移动链接文件 28 | func (a *Controller) MoveRomLink(id uint64, menu string) string { 29 | return Resp("", modules.MoveRomLink(id, menu)) 30 | } 31 | 32 | // 删除rom链接 33 | func (a *Controller) DeleteRomLink(id uint64) string { 34 | return Resp("", modules.DeleteRomLink(id)) 35 | } 36 | 37 | // 绑定子游戏 38 | func (a *Controller) BindSubGame(pid, sid uint64) string { 39 | return Resp("", modules.BindSubGame(pid, sid)) 40 | } 41 | 42 | // 解绑子游戏 43 | func (a *Controller) UnBindSubGame(id uint64) string { 44 | return Resp("", modules.UnBindSubGame(id)) 45 | } 46 | 47 | // 批量复制链接文件 48 | func (a *Controller) BatchCopyRomLink(ids []uint64, menu string) string { 49 | return Resp("", modules.BatchCopyRomLink(ids, menu)) 50 | } 51 | 52 | // 批量移动链接文件 53 | func (a *Controller) BatchMoveRomLink(ids []uint64, menu string) string { 54 | return Resp("", modules.BatchMoveRomLink(ids, menu)) 55 | } 56 | 57 | // 批量删除rom链接 58 | func (a *Controller) BatchDeleteRomLink(ids []uint64) string { 59 | return Resp("", modules.BatchDeleteRomLink(ids)) 60 | } 61 | 62 | // 批量删除rom和资源 63 | func (a *Controller) BatchDeleteRom(ids []uint64, delSubRom, delRes uint8) string { 64 | return Resp("", modules.BatchDeleteRomAndRes(ids, delSubRom, delRes)) 65 | } 66 | 67 | // 查找无效资源 68 | func (a *Controller) CheckUnownedRes(platform uint32) string { 69 | return Resp(modules.CheckUnownedRes(platform)) 70 | } 71 | 72 | // 删除无效资源文件 73 | func (a *Controller) DeleteUnownedFile(platform uint32, ids []string) string { 74 | return Resp("", modules.DeleteUnownedFile(platform, ids)) 75 | } 76 | 77 | // 打开备份文件夹 78 | func (a *Controller) OpenCacheFolder(typ string, create int) string { 79 | return Resp("", modules.OpenCacheFolder(typ, create)) 80 | } 81 | 82 | // 检查重复rom 83 | func (a *Controller) CheckRomRepeat(platform uint32) string { 84 | return Resp(modules.CheckRomRepeat(platform)) 85 | } 86 | 87 | // 删除重复ROM文件 88 | func (a *Controller) DeleteRepeatFile(ids []string) string { 89 | return Resp("", modules.DeleteRepeatFile(ids)) 90 | } 91 | -------------------------------------------------------------------------------- /controller/rombase.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "encoding/json" 5 | "simUI/db" 6 | "simUI/modules" 7 | ) 8 | 9 | // 读取全部枚举 10 | func (a *Controller) GetRomBaseEnum() string { 11 | return Resp(modules.GetRomBaseEnum()) 12 | } 13 | 14 | // 根据类型读取资料枚举 15 | func (a *Controller) GetRomBaseEnumByType(typ string) string { 16 | return Resp(modules.GetRomBaseEnumByType(typ)) 17 | } 18 | 19 | // 更新资料枚举 20 | func (a *Controller) UpdateRomBaseEnumByType(typ string, data string) string { 21 | d := []string{} 22 | _ = json.Unmarshal([]byte(data), &d) 23 | return Resp("", modules.UpdateRomBaseEnum(typ, d)) 24 | } 25 | 26 | // 读取资料项别名 27 | func (a *Controller) GetRomBaseAlias(platform uint32) string { 28 | return Resp(modules.GetRomBaseAliasByPlatform(platform)) 29 | } 30 | 31 | // 更新资料项别名 32 | func (a *Controller) UpdateRomBaseAlias(platform uint32, data string) string { 33 | d := map[string]string{} 34 | _ = json.Unmarshal([]byte(data), &d) 35 | return Resp("", modules.UpdateRomBaseAlias(platform, d)) 36 | } 37 | 38 | // 读取一条rombase信息 39 | func (a *Controller) GetRomBase(id uint64) string { 40 | return Resp(modules.GetRomBase(id)) 41 | } 42 | 43 | // 编辑rombase信息 44 | func (a *Controller) SetRomBase(id uint64, data, name string) string { 45 | d := map[string]string{} 46 | _ = json.Unmarshal([]byte(data), &d) 47 | return Resp(modules.SetRomBase(id, d, name)) 48 | } 49 | 50 | // 批量rombase信息 51 | func (a *Controller) BatchSetRomBase(platform uint32, data string) string { 52 | d := map[string]*db.RomSimpleVO{} 53 | _ = json.Unmarshal([]byte(data), &d) 54 | return Resp("", modules.BatchSetRomBase(platform, d)) 55 | } 56 | -------------------------------------------------------------------------------- /controller/shortcut.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "encoding/json" 5 | "simUI/db" 6 | "simUI/modules" 7 | ) 8 | 9 | // 读取快捷工具 10 | func (a *Controller) GetShortcuts(isAbs bool) string { 11 | return Resp(modules.GetShortcuts(isAbs)) 12 | } 13 | 14 | // 更新快捷工具 15 | func (a *Controller) UpdateShortcut(data string) string { 16 | d := []*db.Shortcut{} 17 | json.Unmarshal([]byte(data), &d) 18 | return Resp(modules.UpdateShortcut(d)) 19 | } 20 | 21 | // 更新快捷方式排序 22 | func (a *Controller) UpdateShortcutSort(data string) string { 23 | d := []uint32{} 24 | json.Unmarshal([]byte(data), &d) 25 | return Resp("", modules.UpdateShortcutSort(d)) 26 | } 27 | -------------------------------------------------------------------------------- /controller/simulator.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "encoding/json" 5 | "simUI/db" 6 | "simUI/modules" 7 | ) 8 | 9 | // 读取所有模拟器 10 | func (a *Controller) GetAllSimulator() string { 11 | return Resp(modules.GetAllSimulator()) 12 | } 13 | 14 | // 读取一个平台下的所有模拟器 15 | func (a *Controller) GetSimulatorByPlatform(id uint32) string { 16 | return Resp(modules.GetSimulatorByPlatform(id)) 17 | } 18 | 19 | // 读取一个模拟器 20 | func (a *Controller) GetSimulatorById(id uint32) string { 21 | return Resp(modules.GetSimulatorById(id)) 22 | } 23 | 24 | // 添加一个模拟器 25 | func (a *Controller) AddSimulator(platformId uint32, name string) string { 26 | return Resp(modules.AddSimulator(platformId, name)) 27 | } 28 | 29 | // 更新模拟器 30 | func (a *Controller) UpdateSimulator(data string) string { 31 | d := db.Simulator{} 32 | json.Unmarshal([]byte(data), &d) 33 | return Resp(modules.UpdateSimulator(d)) 34 | } 35 | 36 | // 更新模拟器排序 37 | func (a *Controller) UpdateSimulatorSort(data string) string { 38 | d := []uint32{} 39 | json.Unmarshal([]byte(data), &d) 40 | return Resp("", modules.UpdateSimulatorSort(d)) 41 | } 42 | 43 | // 删除模拟器 44 | func (a *Controller) DelSimulator(id uint32) string { 45 | return Resp("", modules.DelSimulator(id)) 46 | } 47 | 48 | // 设置rom模拟器id 49 | func (a *Controller) SetRomSimId(romId uint64, simId uint32) string { 50 | return Resp("", modules.SetRomSimId(romId, simId)) 51 | } 52 | 53 | // 批量设置rom模拟器id 54 | func (a *Controller) BatchSetRomSimId(romIds []uint64, simId uint32) string { 55 | return Resp("", modules.BatchSetRomSimId(romIds, simId)) 56 | } 57 | -------------------------------------------------------------------------------- /controller/upgrade.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "simUI/modules" 5 | ) 6 | 7 | // 检测升级 8 | func (a *Controller) CheckUpgrade(newVersion string) string { 9 | return Resp(modules.CheckUpgrade(newVersion)) 10 | } 11 | 12 | // 跳过更新 13 | func (a *Controller) JumpUpgrade(version string) string { 14 | return Resp("", modules.JumpUpgrade(version)) 15 | } 16 | 17 | // 下载新版本 18 | func (a *Controller) DownloadNewVersion(url string) string { 19 | return Resp(modules.DownloadNewVersion(url)) 20 | } 21 | 22 | // 安装新版本 23 | func (a *Controller) InstallUpgrade(version string, unzipPath string) string { 24 | return Resp("", modules.InstallUpgrade(version, unzipPath)) 25 | } 26 | -------------------------------------------------------------------------------- /data.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frontlon/simUI-pulsar/2822bcf2b0f630925a29b4d929d6b846af63db41/data.dll -------------------------------------------------------------------------------- /db/config.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "fmt" 5 | _ "github.com/mattn/go-sqlite3" 6 | "simUI/utils" 7 | ) 8 | 9 | type Config struct { 10 | Name string 11 | Data string 12 | Desc string 13 | } 14 | 15 | func (*Config) TableName() string { 16 | return "config" 17 | } 18 | 19 | // 根据id查询一条数据 20 | func (m *Config) Get() (map[string]string, error) { 21 | 22 | volist := []*Config{} 23 | result := getDb().Table(m.TableName()).Select("name,data").Find(&volist) 24 | 25 | if result.Error != nil { 26 | fmt.Println(result.Error.Error()) 27 | } 28 | 29 | data := map[string]string{} 30 | for _, v := range volist { 31 | data[v.Name] = v.Data 32 | } 33 | 34 | return data, result.Error 35 | } 36 | 37 | // 根据id查询一条数据 38 | func (m *Config) GetByName(name string) (string, error) { 39 | 40 | vo := &Config{} 41 | result := getDb().Table(m.TableName()).Select("data").Where("name = ?", name).First(&vo) 42 | 43 | if result.Error != nil { 44 | fmt.Println(result.Error.Error()) 45 | return "", result.Error 46 | } 47 | return vo.Data, nil 48 | } 49 | 50 | // 更新一个字段 51 | func (m *Config) UpdateOne(name string, data interface{}) error { 52 | result := getDb().Table(m.TableName()).Where("name = ?", name).Update("data", data) 53 | 54 | if result.Error != nil { 55 | fmt.Println(result.Error.Error()) 56 | } 57 | return result.Error 58 | } 59 | 60 | // 更新一个字段,如果不存在则新增 61 | func (m *Config) UpsertOne(name string, data interface{}) error { 62 | count := 0 63 | result := getDb().Table(m.TableName()).Where("name = ?", name).Count(&count) 64 | if result.Error != nil { 65 | return result.Error 66 | } 67 | 68 | if count > 0 { 69 | result = getDb().Table(m.TableName()).Where("name = ?", name).Update("data", data) 70 | } else { 71 | c := &Config{ 72 | Name: name, 73 | Data: utils.ToString(data), 74 | } 75 | result = getDb().Create(&c) 76 | } 77 | 78 | return result.Error 79 | } 80 | 81 | func (m *Config) Add() { 82 | getDb().Create(&m) 83 | } 84 | -------------------------------------------------------------------------------- /db/filter.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "fmt" 5 | _ "github.com/mattn/go-sqlite3" 6 | "math" 7 | ) 8 | 9 | type Filter struct { 10 | Platform uint32 11 | Type string 12 | Name string 13 | } 14 | 15 | type FilterModel Filter 16 | 17 | func (*Filter) TableName() string { 18 | return "filter" 19 | } 20 | 21 | // 写入数据 22 | func (m *Filter) BatchAdd(data []*Filter) { 23 | 24 | if len(data) == 0 { 25 | return 26 | } 27 | 28 | tx := getDb().Begin() 29 | for _, v := range data { 30 | tx.Create(&v) 31 | } 32 | tx.Commit() 33 | } 34 | 35 | func (*Filter) GetAll() ([]*Filter, error) { 36 | volist := []*Filter{} 37 | 38 | result := getDb().Select("name,type").Order("name ASC").Group("type,name").Find(&volist) 39 | if result.Error != nil { 40 | fmt.Println(result.Error) 41 | } 42 | 43 | return volist, nil 44 | } 45 | 46 | func (*Filter) GetByPlatform(platform uint32) ([]*Filter, error) { 47 | volist := []*Filter{} 48 | 49 | result := getDb().Select("name,type").Where("platform = ?", platform).Order("name ASC").Find(&volist) 50 | if result.Error != nil { 51 | fmt.Println(result.Error) 52 | } 53 | 54 | return volist, nil 55 | } 56 | 57 | // 删除记录 58 | func (m *Filter) DeleteByFileNames(platform uint32, t string, nameList []string) error { 59 | 60 | if len(nameList) == 0 { 61 | return nil 62 | } 63 | 64 | listLen := len(nameList) 65 | 66 | ceil := int(math.Ceil(float64(listLen) / float64(maxVar))) 67 | 68 | for i := 0; i < ceil; i++ { 69 | start := i * maxVar 70 | end := (i + 1) * maxVar 71 | if end > listLen { 72 | end = listLen 73 | } 74 | list := nameList[start:end] 75 | getDb().Where("platform = ? AND type = ? AND name in (?)", platform, t, list).Delete(&m) 76 | } 77 | 78 | return nil 79 | } 80 | 81 | // 删除不存在的平台下的所有menu 82 | func (m *Filter) DeleteByNotPlatform(platforms []string) error { 83 | 84 | if len(platforms) == 0 { 85 | m.Truncate() 86 | return nil 87 | } 88 | 89 | model := &Filter{} 90 | result := getDb().Not("platform", platforms).Delete(&model) 91 | 92 | if result.Error != nil { 93 | fmt.Println(result.Error) 94 | } 95 | 96 | return result.Error 97 | } 98 | 99 | // 清空表数据 100 | func (m *Filter) Truncate() error { 101 | result := getDb().Delete(&m) 102 | if result.Error != nil { 103 | fmt.Println(result.Error) 104 | } 105 | return result.Error 106 | } 107 | -------------------------------------------------------------------------------- /db/menuVO.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "fmt" 5 | _ "github.com/mattn/go-sqlite3" 6 | "strings" 7 | ) 8 | 9 | type MenuVO struct { 10 | Name string 11 | Path string 12 | Platform uint32 13 | SubMenu []*MenuVO //子目录 14 | } 15 | 16 | // 根据条件,查询多条数据 17 | func (m *Menu) GetVoByPlatform(platform uint32) ([]*MenuVO, error) { 18 | where := map[string]interface{}{} 19 | 20 | if platform > 0 { 21 | where["platform"] = platform 22 | } 23 | 24 | volist := []*MenuVO{} 25 | sublist := map[string][]*MenuVO{} 26 | 27 | menus, err := m.GetByPlatform(platform) 28 | if err != nil { 29 | fmt.Println(err) 30 | return volist, err 31 | } 32 | 33 | for _, v := range menus { 34 | item := &MenuVO{ 35 | Name: v.Name, 36 | Path: v.Path, 37 | Platform: v.Platform, 38 | SubMenu: []*MenuVO{}, 39 | } 40 | patharr := strings.Split(v.Path, "/") 41 | if len(patharr) <= 3 { 42 | volist = append(volist, item) 43 | } else { 44 | if _, ok := sublist[patharr[1]]; ok { 45 | sublist[patharr[1]] = append(sublist[patharr[1]], item) 46 | } else { 47 | sublist[patharr[1]] = []*MenuVO{item} 48 | } 49 | } 50 | } 51 | 52 | //填充子目录 53 | for k, v := range volist { 54 | patharr := strings.Split(v.Path, "/") 55 | if _, ok := sublist[patharr[1]]; ok { 56 | volist[k].SubMenu = sublist[patharr[1]] 57 | } 58 | } 59 | 60 | return volist, nil 61 | } 62 | -------------------------------------------------------------------------------- /db/rombaseAlias.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "fmt" 5 | _ "github.com/mattn/go-sqlite3" 6 | ) 7 | 8 | type RombaseAlias struct { 9 | Platform uint32 // 平台id 10 | Type string // 类型 11 | Alias string // 别名 12 | } 13 | 14 | func (*RombaseAlias) TableName() string { 15 | return "rombase_alias" 16 | } 17 | 18 | // 写入cate数据 19 | func (m *RombaseAlias) Add() error { 20 | result := getDb().Create(&m) 21 | 22 | if result.Error != nil { 23 | fmt.Println(result.Error) 24 | } 25 | 26 | return nil 27 | } 28 | 29 | // 更新喜爱状态 30 | func (m *RombaseAlias) UpdateByType() error { 31 | //更新数据 32 | result := getDb().Table(m.TableName()).Where("platform=? AND type = ?", m.Platform, m.Type).Update("alias", m.Alias) 33 | if result.Error != nil { 34 | fmt.Println(result.Error) 35 | return result.Error 36 | } 37 | 38 | return nil 39 | } 40 | 41 | // 根据类型读取数据 42 | func (*RombaseAlias) GetByPlatform(platform uint32) (map[string]string, error) { 43 | volist := []*RombaseAlias{} 44 | result := getDb().Where("platform = ?", platform).Find(&volist) 45 | if result.Error != nil { 46 | fmt.Println(result.Error.Error()) 47 | } 48 | 49 | data := map[string]string{} 50 | if len(volist) > 0 { 51 | for _, v := range volist { 52 | data[v.Type] = v.Alias 53 | } 54 | } 55 | return data, result.Error 56 | } 57 | 58 | // 删除记录 59 | func (m *RombaseAlias) DeleteByType() error { 60 | result := getDb().Where("platform = ? AND type=? ", m.Platform, m.Type).Delete(&m) 61 | return result.Error 62 | } 63 | -------------------------------------------------------------------------------- /db/rombaseEnum.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "fmt" 5 | _ "github.com/mattn/go-sqlite3" 6 | ) 7 | 8 | type RombaseEnum struct { 9 | Type string // 类型 10 | Name string // 名称 11 | Sort uint32 // 排序 12 | } 13 | 14 | func (*RombaseEnum) TableName() string { 15 | return "rombase_enum" 16 | } 17 | 18 | // 批量写入 19 | func (m *RombaseEnum) BatchAdd(romlist []*RombaseEnum) error { 20 | 21 | if len(romlist) == 0 { 22 | return nil 23 | } 24 | 25 | tx := getDb().Begin() 26 | for _, v := range romlist { 27 | tx.Create(&v) 28 | } 29 | tx.Commit() 30 | return nil 31 | } 32 | 33 | // 读取全部数据 34 | func (*RombaseEnum) GetAll() ([]*RombaseEnum, error) { 35 | volist := []*RombaseEnum{} 36 | result := getDb().Order("sort Asc").Find(&volist) 37 | if result.Error != nil { 38 | fmt.Println(result.Error.Error()) 39 | } 40 | return volist, result.Error 41 | } 42 | 43 | // 根据类型读取数据 44 | func (*RombaseEnum) GetByType(t string) ([]*RombaseEnum, error) { 45 | volist := []*RombaseEnum{} 46 | result := getDb().Where("type=?", t).Order("sort Asc").Find(&volist) 47 | if result.Error != nil { 48 | fmt.Println(result.Error.Error()) 49 | } 50 | return volist, result.Error 51 | } 52 | 53 | // 删除一个类型 54 | func (m *RombaseEnum) DeleteByType() error { 55 | result := getDb().Where("type=? ", m.Type).Delete(&m) 56 | return result.Error 57 | } 58 | -------------------------------------------------------------------------------- /db/shortcut.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "fmt" 5 | _ "github.com/mattn/go-sqlite3" 6 | ) 7 | 8 | type Shortcut struct { 9 | Id uint32 10 | Name string 11 | Path string 12 | Sort uint32 13 | } 14 | 15 | func (*Shortcut) TableName() string { 16 | return "shortcut" 17 | } 18 | 19 | // 写入数据 20 | func (m *Shortcut) Add() (int64, error) { 21 | 22 | result := getDb().Create(&m) 23 | 24 | if result.Error != nil { 25 | fmt.Println(result.Error) 26 | } 27 | 28 | return int64(m.Id), result.Error 29 | } 30 | 31 | // 读取所有数据 32 | func (sim *Shortcut) GetAll() ([]*Shortcut, error) { 33 | volist := []*Shortcut{} 34 | result := getDb().Order("sort,id ASC").Find(&volist) 35 | if result.Error != nil { 36 | fmt.Println(result.Error) 37 | } 38 | return volist, nil 39 | } 40 | 41 | // 更新一条记录 42 | func (m *Shortcut) UpdateById() error { 43 | create := map[string]interface{}{ 44 | "name": m.Name, 45 | "path": m.Path, 46 | "sort": m.Sort, 47 | } 48 | result := getDb().Table(m.TableName()).Where("id=?", m.Id).Updates(create) 49 | 50 | if result.Error != nil { 51 | fmt.Println(result.Error) 52 | } 53 | return result.Error 54 | } 55 | 56 | // 更新排序 57 | func (m *Shortcut) UpdateSortById() error { 58 | result := getDb().Table(m.TableName()).Where("id=?", m.Id).Update("sort", m.Sort) 59 | if result.Error != nil { 60 | fmt.Println(result.Error) 61 | } 62 | return result.Error 63 | } 64 | 65 | // 删除一条记录 66 | func (m *Shortcut) DeleteById() error { 67 | result := getDb().Where("id=?", m.Id).Delete(&m) 68 | if result.Error != nil { 69 | fmt.Println(result.Error) 70 | } 71 | return result.Error 72 | } 73 | -------------------------------------------------------------------------------- /db/thumbs.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "fmt" 5 | _ "github.com/mattn/go-sqlite3" 6 | ) 7 | 8 | type Thumbs struct { 9 | Platform uint32 10 | Type string 11 | ImageId string 12 | } 13 | 14 | var ThumbMap map[uint32]map[string]uint8 15 | 16 | func (*Thumbs) TableName() string { 17 | return "thumbs" 18 | } 19 | 20 | // 写入数据 21 | func (m *Thumbs) Add() error { 22 | 23 | result := getDb().Create(&m) 24 | if result.Error != nil { 25 | fmt.Println(result.Error) 26 | } 27 | 28 | return result.Error 29 | } 30 | 31 | func (m *Thumbs) GetByPlatform(platform uint32) (map[string]uint8, error) { 32 | 33 | if _, ok := ThumbMap[platform]; ok { 34 | return ThumbMap[platform], nil 35 | } 36 | 37 | volist := []*Thumbs{} 38 | 39 | result := getDb().Select("image_id").Where("platform = ?", platform).Find(&volist) 40 | if result.Error != nil { 41 | fmt.Println(result.Error) 42 | } 43 | 44 | platformMap := make(map[string]uint8) 45 | if len(volist) > 0 { 46 | for _, v := range volist { 47 | platformMap[v.ImageId] = 1 48 | } 49 | } else { 50 | //随便塞一个数据,防止穿透 51 | platformMap["a"] = 1 52 | } 53 | 54 | ThumbMap[platform] = platformMap 55 | return platformMap, nil 56 | } 57 | 58 | func (m *Thumbs) checkImage(platform uint32, imageId string) bool { 59 | 60 | if _, ok := ThumbMap[platform]; !ok { 61 | m.GetByPlatform(platform) 62 | } 63 | 64 | if _, ok := ThumbMap[platform][imageId]; ok { 65 | return true 66 | } 67 | 68 | return false 69 | } 70 | -------------------------------------------------------------------------------- /db/upgrade.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | _ "github.com/mattn/go-sqlite3" 5 | ) 6 | 7 | // 数据库更新列表,列表中数据不能删除,如果有重复update,则将老记录留空,最后添加新记录 8 | // 如果有添加,就往最后添加,不要往前面或中间添加 9 | func DbUpdateSqlList(version string) []string { 10 | return []string{ 11 | //`update config SET version = ` + version + ` where id = 1`, 12 | `ALTER TABLE platform ADD COLUMN "hide_name" INTEGER NOT NULL DEFAULT 0`, 13 | `ALTER TABLE rom ADD COLUMN "sim_setting" TEXT NOT NULL DEFAULT ''`, 14 | ``, 15 | `CREATE UNIQUE INDEX "idx_name" ON "config" ("name");`, 16 | `INSERT INTO config ("name", "data", "desc") VALUES ('GameMultiOpen', '0', '是否允许模拟器游戏多开')`, 17 | `ALTER TABLE rom ADD COLUMN "favorite" INTEGER NOT NULL DEFAULT 0`, 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /frontend/README.md: -------------------------------------------------------------------------------- 1 | //wails dev -appargs "-dev" 2 | //wails dev -s -skipbindings -appargs "-dev" 3 | //wails build -upx -ldflags="-H windowsgui -w -s" -------------------------------------------------------------------------------- /frontend/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <%= productName %> 5 | 6 | 7 | 8 | 9 | 10 | 11 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "quasar-project", 3 | "version": "0.0.1", 4 | "description": "A Quasar Project", 5 | "productName": "Quasar App", 6 | "author": "frontlon ", 7 | "private": true, 8 | "scripts": { 9 | "test": "echo \"No test specified\" && exit 0", 10 | "dev": "quasar dev", 11 | "build": "quasar build" 12 | }, 13 | "dependencies": { 14 | "@imengyu/vue3-context-menu": "^1.3.3", 15 | "@quasar/extras": "^1.16.6", 16 | "animate.css": "^4.1.1", 17 | "axios": "^1.2.1", 18 | "pinia": "^2.0.11", 19 | "quasar": "^2.12.7", 20 | "sortablejs": "^1.15.0", 21 | "swiper": "^11.1.3", 22 | "v-viewer": "^3.0.11", 23 | "viewerjs": "^1.11.5", 24 | "vue": "^3.0.0", 25 | "vue-i18n": "^9.2.2", 26 | "vue-router": "^4.0.0", 27 | "vue-star-rating": "^2.1.0", 28 | "vuedraggable": "^4.1.0" 29 | }, 30 | "devDependencies": { 31 | "@intlify/vite-plugin-vue-i18n": "^3.3.1", 32 | "@quasar/app-vite": "^1.6.2", 33 | "@types/node": "^12.20.21", 34 | "autoprefixer": "^10.4.2", 35 | "typescript": "^4.5.4" 36 | }, 37 | "engines": { 38 | "node": "^18 || ^16 || ^14.19", 39 | "npm": ">= 6.13.4", 40 | "yarn": ">= 1.21.1" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /frontend/postcss.config.cjs: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | // https://github.com/michael-ciniawsky/postcss-load-config 3 | 4 | module.exports = { 5 | plugins: [ 6 | // https://github.com/postcss/autoprefixer 7 | require('autoprefixer')({ 8 | overrideBrowserslist: [ 9 | 'last 4 Chrome versions', 10 | 'last 4 Firefox versions', 11 | 'last 4 Edge versions', 12 | 'last 4 Safari versions', 13 | 'last 4 Android versions', 14 | 'last 4 ChromeAndroid versions', 15 | 'last 4 FirefoxAndroid versions', 16 | 'last 4 iOS versions' 17 | ] 18 | }) 19 | 20 | // https://github.com/elchininet/postcss-rtlcss 21 | // If you want to support RTL css, then 22 | // 1. yarn/npm install postcss-rtlcss 23 | // 2. optionally set quasar.config.js > framework > lang to an RTL language 24 | // 3. uncomment the following line: 25 | // require('postcss-rtlcss') 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /frontend/public/images/context/alias.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frontlon/simUI-pulsar/2822bcf2b0f630925a29b4d929d6b846af63db41/frontend/public/images/context/alias.png -------------------------------------------------------------------------------- /frontend/public/images/context/app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frontlon/simUI-pulsar/2822bcf2b0f630925a29b4d929d6b846af63db41/frontend/public/images/context/app.png -------------------------------------------------------------------------------- /frontend/public/images/context/audio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frontlon/simUI-pulsar/2822bcf2b0f630925a29b4d929d6b846af63db41/frontend/public/images/context/audio.png -------------------------------------------------------------------------------- /frontend/public/images/context/baseinfo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frontlon/simUI-pulsar/2822bcf2b0f630925a29b4d929d6b846af63db41/frontend/public/images/context/baseinfo.png -------------------------------------------------------------------------------- /frontend/public/images/context/copy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frontlon/simUI-pulsar/2822bcf2b0f630925a29b4d929d6b846af63db41/frontend/public/images/context/copy.png -------------------------------------------------------------------------------- /frontend/public/images/context/delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frontlon/simUI-pulsar/2822bcf2b0f630925a29b4d929d6b846af63db41/frontend/public/images/context/delete.png -------------------------------------------------------------------------------- /frontend/public/images/context/doc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frontlon/simUI-pulsar/2822bcf2b0f630925a29b4d929d6b846af63db41/frontend/public/images/context/doc.png -------------------------------------------------------------------------------- /frontend/public/images/context/down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frontlon/simUI-pulsar/2822bcf2b0f630925a29b4d929d6b846af63db41/frontend/public/images/context/down.png -------------------------------------------------------------------------------- /frontend/public/images/context/fav_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frontlon/simUI-pulsar/2822bcf2b0f630925a29b4d929d6b846af63db41/frontend/public/images/context/fav_0.png -------------------------------------------------------------------------------- /frontend/public/images/context/fav_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frontlon/simUI-pulsar/2822bcf2b0f630925a29b4d929d6b846af63db41/frontend/public/images/context/fav_1.png -------------------------------------------------------------------------------- /frontend/public/images/context/files.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frontlon/simUI-pulsar/2822bcf2b0f630925a29b4d929d6b846af63db41/frontend/public/images/context/files.png -------------------------------------------------------------------------------- /frontend/public/images/context/folder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frontlon/simUI-pulsar/2822bcf2b0f630925a29b4d929d6b846af63db41/frontend/public/images/context/folder.png -------------------------------------------------------------------------------- /frontend/public/images/context/hide_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frontlon/simUI-pulsar/2822bcf2b0f630925a29b4d929d6b846af63db41/frontend/public/images/context/hide_0.png -------------------------------------------------------------------------------- /frontend/public/images/context/hide_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frontlon/simUI-pulsar/2822bcf2b0f630925a29b4d929d6b846af63db41/frontend/public/images/context/hide_1.png -------------------------------------------------------------------------------- /frontend/public/images/context/move.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frontlon/simUI-pulsar/2822bcf2b0f630925a29b4d929d6b846af63db41/frontend/public/images/context/move.png -------------------------------------------------------------------------------- /frontend/public/images/context/output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frontlon/simUI-pulsar/2822bcf2b0f630925a29b4d929d6b846af63db41/frontend/public/images/context/output.png -------------------------------------------------------------------------------- /frontend/public/images/context/rename.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frontlon/simUI-pulsar/2822bcf2b0f630925a29b4d929d6b846af63db41/frontend/public/images/context/rename.png -------------------------------------------------------------------------------- /frontend/public/images/context/strategy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frontlon/simUI-pulsar/2822bcf2b0f630925a29b4d929d6b846af63db41/frontend/public/images/context/strategy.png -------------------------------------------------------------------------------- /frontend/public/images/context/sub.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frontlon/simUI-pulsar/2822bcf2b0f630925a29b4d929d6b846af63db41/frontend/public/images/context/sub.png -------------------------------------------------------------------------------- /frontend/public/images/context/thumbs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frontlon/simUI-pulsar/2822bcf2b0f630925a29b4d929d6b846af63db41/frontend/public/images/context/thumbs.png -------------------------------------------------------------------------------- /frontend/public/images/context/unlink.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frontlon/simUI-pulsar/2822bcf2b0f630925a29b4d929d6b846af63db41/frontend/public/images/context/unlink.png -------------------------------------------------------------------------------- /frontend/public/images/context/up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frontlon/simUI-pulsar/2822bcf2b0f630925a29b4d929d6b846af63db41/frontend/public/images/context/up.png -------------------------------------------------------------------------------- /frontend/public/images/ctl/+.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frontlon/simUI-pulsar/2822bcf2b0f630925a29b4d929d6b846af63db41/frontend/public/images/ctl/+.png -------------------------------------------------------------------------------- /frontend/public/images/ctl/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frontlon/simUI-pulsar/2822bcf2b0f630925a29b4d929d6b846af63db41/frontend/public/images/ctl/1.png -------------------------------------------------------------------------------- /frontend/public/images/ctl/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frontlon/simUI-pulsar/2822bcf2b0f630925a29b4d929d6b846af63db41/frontend/public/images/ctl/2.png -------------------------------------------------------------------------------- /frontend/public/images/ctl/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frontlon/simUI-pulsar/2822bcf2b0f630925a29b4d929d6b846af63db41/frontend/public/images/ctl/3.png -------------------------------------------------------------------------------- /frontend/public/images/ctl/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frontlon/simUI-pulsar/2822bcf2b0f630925a29b4d929d6b846af63db41/frontend/public/images/ctl/4.png -------------------------------------------------------------------------------- /frontend/public/images/ctl/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frontlon/simUI-pulsar/2822bcf2b0f630925a29b4d929d6b846af63db41/frontend/public/images/ctl/5.png -------------------------------------------------------------------------------- /frontend/public/images/ctl/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frontlon/simUI-pulsar/2822bcf2b0f630925a29b4d929d6b846af63db41/frontend/public/images/ctl/6.png -------------------------------------------------------------------------------- /frontend/public/images/ctl/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frontlon/simUI-pulsar/2822bcf2b0f630925a29b4d929d6b846af63db41/frontend/public/images/ctl/7.png -------------------------------------------------------------------------------- /frontend/public/images/ctl/8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frontlon/simUI-pulsar/2822bcf2b0f630925a29b4d929d6b846af63db41/frontend/public/images/ctl/8.png -------------------------------------------------------------------------------- /frontend/public/images/ctl/9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frontlon/simUI-pulsar/2822bcf2b0f630925a29b4d929d6b846af63db41/frontend/public/images/ctl/9.png -------------------------------------------------------------------------------- /frontend/public/images/ctl/A.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frontlon/simUI-pulsar/2822bcf2b0f630925a29b4d929d6b846af63db41/frontend/public/images/ctl/A.png -------------------------------------------------------------------------------- /frontend/public/images/ctl/B.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frontlon/simUI-pulsar/2822bcf2b0f630925a29b4d929d6b846af63db41/frontend/public/images/ctl/B.png -------------------------------------------------------------------------------- /frontend/public/images/ctl/C.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frontlon/simUI-pulsar/2822bcf2b0f630925a29b4d929d6b846af63db41/frontend/public/images/ctl/C.png -------------------------------------------------------------------------------- /frontend/public/images/ctl/D.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frontlon/simUI-pulsar/2822bcf2b0f630925a29b4d929d6b846af63db41/frontend/public/images/ctl/D.png -------------------------------------------------------------------------------- /frontend/public/images/ctl/E.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frontlon/simUI-pulsar/2822bcf2b0f630925a29b4d929d6b846af63db41/frontend/public/images/ctl/E.png -------------------------------------------------------------------------------- /frontend/public/images/ctl/F.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frontlon/simUI-pulsar/2822bcf2b0f630925a29b4d929d6b846af63db41/frontend/public/images/ctl/F.png -------------------------------------------------------------------------------- /frontend/public/images/ctl/K.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frontlon/simUI-pulsar/2822bcf2b0f630925a29b4d929d6b846af63db41/frontend/public/images/ctl/K.png -------------------------------------------------------------------------------- /frontend/public/images/ctl/N.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frontlon/simUI-pulsar/2822bcf2b0f630925a29b4d929d6b846af63db41/frontend/public/images/ctl/N.png -------------------------------------------------------------------------------- /frontend/public/images/ctl/P.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frontlon/simUI-pulsar/2822bcf2b0f630925a29b4d929d6b846af63db41/frontend/public/images/ctl/P.png -------------------------------------------------------------------------------- /frontend/public/images/ctl/S.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frontlon/simUI-pulsar/2822bcf2b0f630925a29b4d929d6b846af63db41/frontend/public/images/ctl/S.png -------------------------------------------------------------------------------- /frontend/public/images/ico.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frontlon/simUI-pulsar/2822bcf2b0f630925a29b4d929d6b846af63db41/frontend/public/images/ico.png -------------------------------------------------------------------------------- /frontend/public/images/list-style/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frontlon/simUI-pulsar/2822bcf2b0f630925a29b4d929d6b846af63db41/frontend/public/images/list-style/1.png -------------------------------------------------------------------------------- /frontend/public/images/list-style/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frontlon/simUI-pulsar/2822bcf2b0f630925a29b4d929d6b846af63db41/frontend/public/images/list-style/2.png -------------------------------------------------------------------------------- /frontend/public/images/list-style/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frontlon/simUI-pulsar/2822bcf2b0f630925a29b4d929d6b846af63db41/frontend/public/images/list-style/3.png -------------------------------------------------------------------------------- /frontend/public/images/list-style/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frontlon/simUI-pulsar/2822bcf2b0f630925a29b4d929d6b846af63db41/frontend/public/images/list-style/4.png -------------------------------------------------------------------------------- /frontend/public/images/rom_animate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frontlon/simUI-pulsar/2822bcf2b0f630925a29b4d929d6b846af63db41/frontend/public/images/rom_animate.png -------------------------------------------------------------------------------- /frontend/public/images/svg/complete2.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/public/images/svg/number.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/public/images/svg/time.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/App.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 13 | 20 | -------------------------------------------------------------------------------- /frontend/src/boot/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frontlon/simUI-pulsar/2822bcf2b0f630925a29b4d929d6b846af63db41/frontend/src/boot/.gitkeep -------------------------------------------------------------------------------- /frontend/src/boot/axios.ts: -------------------------------------------------------------------------------- 1 | import { boot } from 'quasar/wrappers'; 2 | import axios, { AxiosInstance } from 'axios'; 3 | 4 | declare module '@vue/runtime-core' { 5 | interface ComponentCustomProperties { 6 | $axios: AxiosInstance; 7 | $api: AxiosInstance; 8 | } 9 | } 10 | 11 | // Be careful when using SSR for cross-request state pollution 12 | // due to creating a Singleton instance here; 13 | // If any client changes this (global) instance, it might be a 14 | // good idea to move this instance creation inside of the 15 | // "export index () => {}" function below (which runs individually 16 | // for each client) 17 | const api = axios.create({ baseURL: 'https://api.example.com' }); 18 | 19 | export default boot(({ app }) => { 20 | // for use inside Vue files (Options API) through this.$axios and this.$api 21 | 22 | app.config.globalProperties.$axios = axios; 23 | // ^ ^ ^ this will allow you to use this.$axios (for Vue Options API form) 24 | // so you won't necessarily have to import axios in each vue file 25 | 26 | app.config.globalProperties.$api = api; 27 | // ^ ^ ^ this will allow you to use this.$api (for Vue Options API form) 28 | // so you can easily perform requests against your app's API 29 | }); 30 | 31 | export { api }; 32 | -------------------------------------------------------------------------------- /frontend/src/boot/constant.ts: -------------------------------------------------------------------------------- 1 | //是否为开发模式 2 | export const IS_DEV = false; 3 | 4 | //模拟器常用启动参数 5 | export const SIMULATOR_BOOT_PARAM_OPTIONS = [ 6 | {label: 'Default', value: '{RomFullPath}',}, 7 | {label: 'MAME', value: '{RomName}',}, 8 | {label: 'Winkawaks', value: '{RomName}',}, 9 | {label: 'FBA Shuffle', value: '-g {RomName} -w',}, 10 | {label: 'Demul', value: '-run=dc -image={RomFullPath}',}, 11 | {label: 'ePsXe', value: '-loadbin {RomFullPath} -nogui',}, 12 | {label: 'Dolphin', value: '-e {RomFullPath}',}, 13 | {label: 'OpenBOR', value: 'load {RomFullPath}',}, 14 | {label: 'RetroArch', value: '-L cores\\xxx.dll {RomFullPath}',}, 15 | {label: 'DosBox', value: '-conf {RomFullPath} -exit',}, 16 | {label: 'DuckStation', value: '{RomFullPath} -batch',}, 17 | {label: 'Nebula2', value: '{RomName}',}, 18 | { 19 | label: 'NullDC', 20 | value: '-config nullDC:Emulator.Autostart=1 -config ImageReader:LoadDefaultImage=1 -config ImageReader:DefaultImage={RomFullPath}', 21 | }, 22 | {label: 'Yabuse', value: '–iso={RomFullPath}',}, 23 | {label: 'PCem', value: '–config {RomFullPath} -f',}, 24 | {label: 'Cemu', value: '-g {RomFullPath} -f',}, 25 | ]; 26 | 27 | //编辑器工具栏 28 | export const EDITOR_TOOLBAR = [ 29 | [ 30 | 'viewsource', 31 | { 32 | label: "", 33 | icon: "format_align_left", 34 | list: 'only-icons', 35 | options: ['left', 'center', 'right', 'justify'] 36 | }, 37 | { 38 | label: "", 39 | icon: "title", 40 | list: 'no-icons', 41 | options: ['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'] 42 | }, 43 | 'bold', 'italic', 'strike', 'underline', 'subscript', 'superscript', 44 | 'code', 'quote', 'unordered', 'ordered', 'outdent', 'indent', 45 | 'image', 'link', 'hr', 46 | 'removeFormat', 'fullscreen' 47 | ], 48 | ] 49 | 50 | //菜单图标大小 51 | export const CONTEXT_ICON_SIZE = { 52 | width: '20px', 53 | height: '20px', 54 | } -------------------------------------------------------------------------------- /frontend/src/boot/i18n.ts: -------------------------------------------------------------------------------- 1 | import { boot } from 'quasar/wrappers'; 2 | import { createI18n } from 'vue-i18n'; 3 | 4 | import messages from 'src/i18n'; 5 | 6 | export type MessageLanguages = keyof typeof messages; 7 | // Type-define 'en-US' as the master schema for the resource 8 | export type MessageSchema = typeof messages['en-US']; 9 | 10 | // See https://vue-i18n.intlify.dev/guide/advanced/typescript.html#global-resource-schema-type-definition 11 | /* eslint-disable @typescript-eslint/no-empty-interface */ 12 | declare module 'vue-i18n' { 13 | // define the locale messages schema 14 | export interface DefineLocaleMessage extends MessageSchema {} 15 | 16 | // define the datetime format schema 17 | export interface DefineDateTimeFormat {} 18 | 19 | // define the number format schema 20 | export interface DefineNumberFormat {} 21 | } 22 | /* eslint-enable @typescript-eslint/no-empty-interface */ 23 | 24 | export default boot(({ app }) => { 25 | const i18n = createI18n({ 26 | locale: 'en-US', 27 | legacy: false, 28 | messages, 29 | }); 30 | 31 | // Set i18n instance on app 32 | app.use(i18n); 33 | }); 34 | -------------------------------------------------------------------------------- /frontend/src/boot/imgObserver.ts: -------------------------------------------------------------------------------- 1 | // 定义一个全局的 Intersection Observer 实例 2 | const observer = new IntersectionObserver(entries => { 3 | entries.forEach(entry => { 4 | 5 | const lazyImage = entry.target.querySelector('img') as HTMLImageElement; 6 | console.log(lazyImage); 7 | 8 | if (lazyImage) { 9 | const src = lazyImage.src; 10 | 11 | lazyImage.dataset.src = lazyImage.dataset.src || src; 12 | 13 | if (entry.isIntersecting) { 14 | lazyImage.src = lazyImage.dataset.src || ''; 15 | // observer.unobserve(lazyImage); 16 | } else { 17 | lazyImage.src = ''; 18 | //lazyImage.removeAttribute("src") 19 | 20 | } 21 | } 22 | 23 | }); 24 | }); 25 | 26 | // vue3 自定义指令 27 | export default { 28 | mounted(el: HTMLElement) { 29 | observer.observe(el); 30 | }, 31 | unmounted(el: HTMLElement) { 32 | observer.unobserve(el); 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /frontend/src/components/AboutComponent.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 34 | 35 | -------------------------------------------------------------------------------- /frontend/src/components/CopyrightComponent.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 24 | 25 | -------------------------------------------------------------------------------- /frontend/src/components/SDialogComponent.vue: -------------------------------------------------------------------------------- 1 | 35 | 36 | 85 | 86 | 100 | -------------------------------------------------------------------------------- /frontend/src/components/dialog.ts: -------------------------------------------------------------------------------- 1 | import {QDialogOptions} from 'quasar' 2 | import SDialogComponent from "components/SDialogComponent.vue"; 3 | import {ref} from "vue"; 4 | 5 | //弹出dialog 6 | export function getPromptOpts(title: string, msg: string, okLabel: string, persistent: boolean, val: any = null, subTitle = ""): QDialogOptions { 7 | let data = { 8 | component: SDialogComponent, 9 | componentProps: { 10 | title: title, 11 | subTitle: subTitle, 12 | message: msg, 13 | persistent: persistent, 14 | okLabel: okLabel, 15 | } 16 | } 17 | if (val !== null) { 18 | data.componentProps.input = ref(val) 19 | } 20 | return data 21 | } 22 | -------------------------------------------------------------------------------- /frontend/src/components/models.ts: -------------------------------------------------------------------------------- 1 | export interface Todo { 2 | id: number; 3 | content: string; 4 | } 5 | 6 | export interface Meta { 7 | totalCount: number; 8 | } 9 | -------------------------------------------------------------------------------- /frontend/src/css/app.css: -------------------------------------------------------------------------------- 1 | /* app global css */ 2 | :root { 3 | --color-0: #000; 4 | --color-1: #111; 5 | --color-2: #222; 6 | --color-3: #333; 7 | --color-4: #444; 8 | --color-5: #555; 9 | --color-6: #666; 10 | --color-7: #777; 11 | --color-8: #888; 12 | --color-9: #999; 13 | --color-10: #AAA; 14 | --color-11: #BBB; 15 | --color-12: #CCC; 16 | --color-13: #DDD; 17 | --color-14: #EEE; 18 | --color-15: #FFF; 19 | --color-text: #FFF; 20 | --color-text-brand: #FFF; 21 | } 22 | 23 | * { 24 | color: var(--color-text); 25 | } 26 | 27 | body.body--dark{ 28 | background: var(--color-1); 29 | user-select:none; 30 | } 31 | 32 | .border-in{ 33 | border:1px solid var(--color-1); 34 | } 35 | 36 | .border-out{ 37 | border:1px solid var(--color-3); 38 | } 39 | 40 | .wrap { 41 | white-space:normal; 42 | word-wrap:break-word; 43 | word-break:break-all; 44 | } 45 | 46 | a { 47 | color: inherit; 48 | text-decoration: none; 49 | } 50 | 51 | .scrollable { 52 | overflow-y: hidden; 53 | scrollbar-gutter: stable; 54 | } 55 | 56 | .scrollable:hover { 57 | overflow-y: scroll; 58 | } 59 | 60 | .select-no-option { 61 | font-size: 12px; 62 | color: var(--color-9); 63 | } 64 | 65 | .btn-active { 66 | background: var(--q-primary); 67 | } 68 | 69 | .btn-active * { 70 | color: var(--color-text-brand); 71 | } 72 | 73 | .btn-primary { 74 | background: var(--q-primary); 75 | height: auto!important; 76 | } 77 | 78 | .btn-primary * { 79 | color: var(--color-text-brand); 80 | } 81 | 82 | .q-loading .q-loading__box{ 83 | background: var(--color-1)!important; 84 | color: var(--color-text)!important; 85 | } 86 | 87 | .selectable{ 88 | user-select: text; 89 | } 90 | 91 | .update-btn{ 92 | padding: 0 30px!important; 93 | } 94 | -------------------------------------------------------------------------------- /frontend/src/css/classic/common.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --content-backgorund-image: ""; 3 | --content-backgorund-size: ""; 4 | --content-backgorund-repeat: ""; 5 | --content-backgorund-mask: ""; 6 | --base-font-size: ""; 7 | } -------------------------------------------------------------------------------- /frontend/src/css/classic/contentContext.css: -------------------------------------------------------------------------------- 1 | 2 | .q-item { 3 | font-size: 12px !important; 4 | border: 0; 5 | padding: 0 10px; 6 | height: 32px; 7 | min-height: 32px; 8 | line-height: 32px; 9 | } 10 | 11 | .context-list .q-item { 12 | padding: 0 0 0 10px; 13 | } 14 | 15 | .context-list .q-icon { 16 | font-size: 12px; 17 | } 18 | 19 | .q-item:nth-child(odd) { 20 | background: var(--color-3); 21 | } 22 | 23 | .q-item:nth-child(even) { 24 | background: var(--color-4); 25 | } 26 | 27 | .context-list .q-separator { 28 | border: 0; 29 | height: 1px; 30 | line-height: 1px; 31 | border: none; 32 | border-top: 1px solid var(--color-0); 33 | border-bottom: 1px solid var(--color-4); 34 | } 35 | 36 | .avatar { 37 | max-width: 24px; 38 | min-width: 24px; 39 | } 40 | 41 | .avatar img { 42 | width: 16px; 43 | } 44 | 45 | /*运行游戏*/ 46 | .rungame { 47 | padding-left: 0 !important; 48 | } 49 | 50 | .rungame-left { 51 | padding-right: 10px !important; 52 | } 53 | 54 | .rungame-right { 55 | padding-left: 0px; 56 | } 57 | 58 | .rungame-right-ico { 59 | height: 32px; 60 | padding-left: 10px; 61 | } 62 | 63 | .move-list .q-item:nth-child(odd) { 64 | background: none; 65 | } 66 | 67 | .move-list .q-item:nth-child(even) { 68 | background: none; 69 | } 70 | 71 | .sub-tree{ 72 | padding:10px 10px 10px 30px 73 | } 74 | 75 | .move-list .submenu-active{ 76 | color: var(--color-15); 77 | background: var(--q-primary) !important; 78 | } -------------------------------------------------------------------------------- /frontend/src/css/classic/headerBarFilter.css: -------------------------------------------------------------------------------- 1 | .filter-wrapper { 2 | padding-bottom: 5px; 3 | margin: 0; 4 | background: var(--color-3); 5 | } 6 | 7 | .filter-wrapper .search-btn { 8 | height: 25px; 9 | width: 50px; 10 | min-height: 25px; 11 | margin: 4px 0 0 4px; 12 | padding: 0; 13 | font-size: 10px; 14 | } 15 | 16 | .filter-input { 17 | font-size: 12px; 18 | background: var(--color-2); 19 | width: 80px; 20 | } 21 | 22 | :deep(.q-select) { 23 | min-height: 25px !important; 24 | max-width: 100px; 25 | height: 25px !important; 26 | font-size: 12px; 27 | line-height: 12px; 28 | } 29 | 30 | 31 | :deep(.q-field__input) { 32 | min-height: 25px !important; 33 | height: 25px !important; 34 | 35 | } 36 | 37 | :deep(.q-field__native) { 38 | min-height: 25px !important; 39 | height: 25px !important; 40 | padding: 0; 41 | white-space: nowrap; 42 | overflow: hidden; 43 | } 44 | 45 | :deep(.q-field__control) { 46 | min-height: 25px !important; 47 | height: 25px !important; 48 | padding: 0 1px 0 5px; 49 | } 50 | 51 | :deep(.q-field__marginal) { 52 | min-height: 25px !important; 53 | height: 25px !important; 54 | } 55 | 56 | .filter-badge{ 57 | padding: 5px; 58 | background: var(--color-3); 59 | } -------------------------------------------------------------------------------- /frontend/src/css/classic/layout.css: -------------------------------------------------------------------------------- 1 | body{ 2 | font-size: 36px; 3 | width:100px!important; 4 | 5 | } 6 | 7 | .border-in{ 8 | font-size: var(--base-font-size); 9 | } 10 | -------------------------------------------------------------------------------- /frontend/src/css/classic/menuBar.css: -------------------------------------------------------------------------------- 1 | 2 | /* 目录栏 */ 3 | .menu-wrapper { 4 | height: calc(100vh - 64px) !important; 5 | background: var(--color-3); 6 | } 7 | 8 | .menu-title { 9 | padding: 0 10px; 10 | background: var(--color-2); 11 | color: var(--color-7); 12 | font-size: calc(var(--base-font-size) - 2px); 13 | } 14 | 15 | .menu-title .q-badge { 16 | padding: 5px; 17 | background: var(--color-3); 18 | } 19 | 20 | .menu-item { 21 | text-align: left; 22 | padding: 0 16px; 23 | font-size: calc(var(--base-font-size) - 2px); 24 | 25 | } 26 | 27 | .menu-sub-item-wrapper { 28 | background: var(--color-2); 29 | box-shadow: inset 0 0 5px var(--color-1); 30 | font-size: calc(var(--base-font-size) - 2px); 31 | } 32 | 33 | .item-font, .q-item { 34 | font-size: calc(var(--base-font-size) - 2px); 35 | } 36 | 37 | .menu-sub-item .q-item-label { 38 | text-indent: 20px; 39 | padding: 20px; 40 | } 41 | 42 | .menu-sub-item .q-item-section { 43 | text-indent: 20px; 44 | padding: 20px; 45 | } 46 | 47 | .menu-item .q-item-label { 48 | text-indent: 20px; 49 | padding: 20px; 50 | } 51 | 52 | .menu-item .q-item-section { 53 | text-indent: 20px; 54 | padding: 20px; 55 | } 56 | -------------------------------------------------------------------------------- /frontend/src/css/classic/platformBar.css: -------------------------------------------------------------------------------- 1 | 2 | /* 平台栏 */ 3 | .platform-wrapper { 4 | box-shadow: inset -5px 0 10px var(--color-1); 5 | height: calc(100vh - 64px) !important; 6 | background: var(--color-2); 7 | } 8 | 9 | .platform-item { 10 | border-bottom: 1px solid var(--color-0); 11 | border-top: 1px solid var(--color-4); 12 | text-align: center; 13 | min-height: 60px; 14 | } 15 | 16 | .platform-item:first-child { 17 | border-top: 0; 18 | } 19 | 20 | .platform-item:last-child { 21 | border-bottom: 0; 22 | } 23 | 24 | .platform-item .q-img { 25 | width: 32px; 26 | height: 32px; 27 | margin: 0 auto 5px auto 28 | } 29 | 30 | .platform-active { 31 | box-shadow: inset 0 0 10px var(--color-1); 32 | } 33 | 34 | -------------------------------------------------------------------------------- /frontend/src/css/grid.css: -------------------------------------------------------------------------------- 1 | .col-1{ 2 | width: 4.166666667%!important; 3 | } 4 | .col-2{ 5 | width: 8.333333333%!important; 6 | } 7 | .col-3{ 8 | width: 12.5%!important; 9 | } 10 | .col-4{ 11 | width: 16.66666667%!important; 12 | } 13 | .col-5{ 14 | width: 20.833333333%!important; 15 | } 16 | .col-6{ 17 | width: 25%!important; 18 | } 19 | .col-7{ 20 | width: 29.166666667%!important; 21 | } 22 | .col-8{ 23 | width: 33.33333333%!important; 24 | } 25 | .col-9{ 26 | width: 37.5%!important; 27 | } 28 | .col-10{ 29 | width: 41.66666667%!important; 30 | } 31 | .col-11{ 32 | width: 45.833333333%!important; 33 | } 34 | .col-12{ 35 | width: 50%!important; 36 | } 37 | .col-13{ 38 | width: 54.166666667%!important; 39 | } 40 | .col-14{ 41 | width: 58.33333333%!important; 42 | } 43 | .col-15{ 44 | width: 62.5%!important; 45 | } 46 | .col-16{ 47 | width: 66.66666667%!important; 48 | } 49 | .col-17{ 50 | width: 70.833333333%!important; 51 | } 52 | .col-18{ 53 | width: 75%!important; 54 | } 55 | .col-19{ 56 | width: 79.166666667%!important; 57 | } 58 | .col-20{ 59 | width: 83.33333333%!important; 60 | } 61 | .col-21{ 62 | width: 87.5%!important; 63 | } 64 | .col-22{ 65 | width: 91.66666667%!important; 66 | } 67 | .col-23{ 68 | width: 95.833333333%!important; 69 | } 70 | .col-24{ 71 | width: 100%!important; 72 | } -------------------------------------------------------------------------------- /frontend/src/css/manage.css: -------------------------------------------------------------------------------- 1 | html,body{ 2 | height: auto; 3 | overflow: visible; 4 | border: 1px solid #f00; 5 | } 6 | .manage-wrapper { 7 | max-width: 60%; 8 | margin: 0 auto; 9 | } 10 | 11 | .manage-wrapper .q-tab-panels { 12 | background: none; 13 | padding: 0; 14 | } 15 | 16 | .manage-wrapper .q-tab-panel, .q-item { 17 | padding: 0; 18 | } 19 | 20 | .manage-wrapper .mt { 21 | margin-top: 8px; 22 | } 23 | 24 | .manage-wrapper .separator { 25 | height: 5px; 26 | } 27 | 28 | .manage-wrapper .q-list { 29 | margin-bottom: 20px; 30 | } 31 | 32 | .manage-wrapper .q-item { 33 | margin: 3px 0 34 | } 35 | 36 | .manage-wrapper h6 { 37 | margin: 20px 0 10px 0 38 | } 39 | 40 | .open-dialog { 41 | cursor: pointer; 42 | } 43 | 44 | .bottom-bar { 45 | text-align: right; 46 | margin-top: 20px; 47 | } 48 | 49 | 50 | .editor { 51 | width: 100%; 52 | min-height: 400px; 53 | margin-top: 10px; 54 | } 55 | 56 | .editor-desc{ 57 | display: flex; 58 | } 59 | .editor-desc .badge{ 60 | margin: 0 5px; 61 | } 62 | -------------------------------------------------------------------------------- /frontend/src/css/modules.css: -------------------------------------------------------------------------------- 1 | /*scrollbar*/ 2 | ::-webkit-scrollbar { 3 | width: 2px; 4 | height: 2px; 5 | } 6 | 7 | ::-webkit-scrollbar-thumb { 8 | background: rgba(255, 255, 255, 0.5); 9 | border-radius: 3px; 10 | transition: opacity .3s; 11 | will-change: opacity; 12 | cursor: grab; 13 | } 14 | 15 | ::-webkit-scrollbar-thumb:hover { 16 | background: rgba(255, 255, 255, 0.3); 17 | background-clip: content-box; 18 | } 19 | 20 | ::-webkit-scrollbar-thumb:active { 21 | background: rgba(255, 255, 255, 0.5); 22 | } 23 | 24 | /*context-menu*/ 25 | .dark { 26 | --mx-menu-backgroud: var(--color-1) !important; 27 | --mx-menu-hover-backgroud: var(--color-2) !important;; 28 | --mx-menu-active-backgroud: var(--color-3) !important;; 29 | --mx-menu-divider: var(--color-0) !important; 30 | } 31 | 32 | /* modules */ 33 | .q-menu { 34 | border: 1px solid var(--color-3); 35 | box-shadow: 0 0 10px var(--color-1); 36 | max-height: max-content !important; 37 | background-color: var(--color-1); 38 | } 39 | 40 | .q-card { 41 | background: var(--color-1); 42 | } 43 | 44 | .q-tab-panels { 45 | background: var(--color-1); 46 | } 47 | 48 | .q-chip { 49 | background: var(--color-1); 50 | } 51 | 52 | .q-editor { 53 | background: var(--color-1); 54 | } 55 | 56 | .q-banner { 57 | background: var(--color-1); 58 | } 59 | 60 | .q-badge { 61 | color: var(--color-text); 62 | } 63 | 64 | .q-field__label { 65 | color: var(--color-text) !important; 66 | } 67 | 68 | .q-field--highlighted .q-field__control{ 69 | background: var(--color-2)!important; 70 | } 71 | 72 | .q-checkbox--dark .q-checkbox__inner--truthy{ 73 | color: var(--q-primary)!important; 74 | } 75 | 76 | .q-checkbox--dark .q-checkbox__svg{ 77 | color: var(--color-text)!important; 78 | } 79 | 80 | .q-checkbox--dark .text-primary{ 81 | background: none; 82 | } 83 | 84 | .q-checkbox--dark .q-checkbox__bg{ 85 | color: inherit; 86 | } 87 | 88 | .q-toggle--dark .q-toggle__inner--truthy .q-toggle__thumb { 89 | color: var(--q-primary)!important; 90 | } 91 | 92 | .q-toggle--dark .q-toggle__inner--truthy .q-toggle__track { 93 | background: var(--q-primary)!important; 94 | } 95 | 96 | .q-table--dark{ 97 | background: var(--color-2); 98 | } 99 | 100 | .q-table--dark .q-table__top{ 101 | background: var(--color-1); 102 | } 103 | .q-table--dark .q-table__title{ 104 | color: var(--color-8); 105 | } 106 | 107 | .vue-star-rating-star{ 108 | margin: 0 2px; 109 | } 110 | 111 | 112 | .q-select-content{ 113 | max-height: calc(100vh - 200px)!important; 114 | } 115 | 116 | .q-select-content-min{ 117 | max-height: 50vh!important; 118 | } 119 | -------------------------------------------------------------------------------- /frontend/src/css/page/platform.css: -------------------------------------------------------------------------------- 1 | .left-wrapper { 2 | min-height: 500px; 3 | background: var(--color-2); 4 | margin-right: 10px; 5 | } 6 | 7 | .platform-scroll { 8 | height: 500px; 9 | } 10 | 11 | .right-wrapper { 12 | background: var(--color-2); 13 | } 14 | 15 | .q-tab-panel { 16 | padding: 0 15px !important; 17 | } 18 | 19 | .platform-list .q-item { 20 | padding: 10px; 21 | margin: 0 22 | } 23 | 24 | .editor { 25 | width: 100%; 26 | min-height: 460px; 27 | margin-top: 10px; 28 | } 29 | 30 | .editor-desc{ 31 | display: flex; 32 | } 33 | .editor-desc .badge{ 34 | margin: 0 5px; 35 | } 36 | 37 | .platform-active { 38 | color: var(--color-text-brand) !important; 39 | background: var(--q-primary); 40 | } 41 | 42 | :deep(.q-editor__content img) { 43 | max-width: 200px; 44 | } 45 | 46 | .q-banner { 47 | margin-top: 8px; 48 | font-size: 12px; 49 | } 50 | 51 | .tag-label-wrapper { 52 | max-height: 200px; 53 | overflow: auto; 54 | border: 1px solid var(--color-3); 55 | width: 100%; 56 | } 57 | -------------------------------------------------------------------------------- /frontend/src/css/playnite/layout.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --content-backgorund-image: ""; 3 | --content-backgorund-mask: ""; 4 | --content-backgorund-size: ""; 5 | --content-backgorund-repeat: ""; 6 | --base-font-size: ""; 7 | } 8 | 9 | .bg { 10 | background-color: var(--color-2); 11 | background-image: var(--content-backgorund-image); 12 | background-repeat: var(--content-backgorund-repeat); 13 | background-size: var(--content-backgorund-size); 14 | transition: background-image 0.2s linear; 15 | font-size: var(--base-font-size); 16 | } 17 | 18 | .bg:before { 19 | content: ""; 20 | display: block; 21 | position: absolute; 22 | width: 100%; 23 | height: calc(100vh); 24 | transition: background-image 0.5s linear; 25 | } 26 | 27 | .bg-mask { 28 | background-image: var(--content-backgorund-mask); 29 | background-repeat: repeat; 30 | position: absolute; 31 | top: 0; 32 | left: 0; 33 | width: 100%; 34 | height: 100%; 35 | z-index: 0; 36 | } 37 | 38 | .fuzzy1:before { 39 | backdrop-filter: blur(1px); 40 | } 41 | 42 | .fuzzy3:before { 43 | backdrop-filter: blur(3px); 44 | } 45 | 46 | .fuzzy5:before { 47 | backdrop-filter: blur(5px); 48 | } 49 | 50 | .fuzzy7:before { 51 | backdrop-filter: blur(7px); 52 | } 53 | 54 | .fuzzy9:before { 55 | backdrop-filter: blur(9px); 56 | } 57 | 58 | .fuzzy15:before { 59 | backdrop-filter: blur(15px); 60 | } 61 | 62 | .fuzzy30:before { 63 | backdrop-filter: blur(30px); 64 | } 65 | 66 | .fuzzy45:before { 67 | backdrop-filter: blur(45px); 68 | } 69 | 70 | -------------------------------------------------------------------------------- /frontend/src/css/playnite/platform.css: -------------------------------------------------------------------------------- 1 | .close-btn { 2 | position: absolute; 3 | top: 10px; 4 | right: 10px; 5 | width: 80px; 6 | height: 80px; 7 | font-size: 24px; 8 | border-radius: 50%; 9 | background: var(--color-1); 10 | color: #FFF; 11 | z-index: 3; 12 | } 13 | 14 | 15 | .wrapper { 16 | width: 100%; 17 | height: 100vh; 18 | background-color: rgba(0, 0, 0, 0.9); 19 | } 20 | 21 | /* 平台栏 */ 22 | .platform-scroll-area{ 23 | width: 100%; 24 | height: 100%; 25 | } 26 | 27 | .platform-list { 28 | width: 100%; 29 | height: 100%; 30 | display: flex; 31 | justify-content: center; 32 | } 33 | 34 | .platform-item { 35 | width: 200px; 36 | height: 80vh; 37 | display: block; 38 | text-align: center; 39 | margin-top: 10vh; 40 | padding: 0; 41 | position: relative; 42 | } 43 | 44 | .platform-ico-active { 45 | animation: zoomInOut 2s infinite; /* 动画名称,每次动画持续2秒,并且无限循环 */ 46 | } 47 | 48 | .platform-item { 49 | display: block; 50 | } 51 | 52 | .platform-title { 53 | font-size: 36px; 54 | text-shadow: 2px 0 2px #000; 55 | font-weight: bolder; 56 | } 57 | 58 | .platform-logo { 59 | width: 72px; 60 | height: 72px; 61 | } 62 | 63 | .platform-active { 64 | background-color: var(--color-2); 65 | border-radius: 15px; 66 | background-repeat: repeat; 67 | background-size: cover; 68 | background-position: center center; 69 | position: relative; 70 | background-image: var(--content-backgorund-image); 71 | overflow: hidden; 72 | } 73 | 74 | .platform-content{ 75 | position: absolute; 76 | z-index: 2; 77 | width: 100%; 78 | height: 100%; 79 | margin-top: 10vh; 80 | 81 | } 82 | 83 | .platform-bg{ 84 | position: absolute; 85 | z-index: 1; 86 | width: 100%; 87 | height: 100%; 88 | backdrop-filter: blur(5px); 89 | } 90 | 91 | 92 | .menu-scroll-area { 93 | margin-top: 6vh; 94 | height: 53vh; 95 | } 96 | 97 | .menu-list { 98 | display: block; 99 | width: 100%; 100 | } 101 | 102 | .menu-list .q-item { 103 | text-align: center !important; 104 | display: block; 105 | padding: 0; 106 | height: 60px; 107 | line-height: 60px; 108 | font-size: 16px; 109 | } 110 | 111 | .menu-active { 112 | background: var(--q-primary); 113 | color: #FFF; 114 | } 115 | -------------------------------------------------------------------------------- /frontend/src/css/playnite/romlistBar.css: -------------------------------------------------------------------------------- 1 | 2 | 3 | .rom-wrapper { 4 | } 5 | 6 | .rom-scroll { 7 | height: calc(100vh - 30px) !important; 8 | width: 100%; 9 | max-width: 100%; 10 | } 11 | 12 | .active { 13 | background: rgba(255,255,255,0.2); 14 | } 15 | 16 | 17 | .module-title { 18 | text-align: center; 19 | word-break: break-all; 20 | white-space: pre-wrap; 21 | background: var(--color-1); 22 | } 23 | 24 | .image-error { 25 | background: var(--color-0); 26 | color: var(--color-6); 27 | } 28 | 29 | .rom-list { 30 | 31 | } 32 | 33 | .active { 34 | background: var(--q-primary); 35 | } 36 | 37 | .rom-list .rom-img{ 38 | width: 20px; 39 | } 40 | 41 | .rom-block { 42 | display: flex; 43 | flex-wrap: wrap; 44 | } 45 | 46 | 47 | .img-direction-0 { 48 | } 49 | 50 | .img-direction-1 { 51 | aspect-ratio: 4/3; 52 | } 53 | 54 | .img-direction-2 { 55 | aspect-ratio: 3/4; 56 | } 57 | 58 | .img-direction-3 { 59 | aspect-ratio: 16/9; 60 | } 61 | 62 | .img-direction-4 { 63 | aspect-ratio: 9/16; 64 | } 65 | 66 | .img-direction-5 { 67 | aspect-ratio: 3/2; 68 | } 69 | 70 | .img-direction-6 { 71 | aspect-ratio: 2/3; 72 | } 73 | 74 | .img-direction-7 { 75 | aspect-ratio: 1/1; 76 | } 77 | -------------------------------------------------------------------------------- /frontend/src/css/romManage.css: -------------------------------------------------------------------------------- 1 | 2 | .wrapper{ 3 | margin: 0 auto; 4 | } 5 | 6 | :deep(::-webkit-scrollbar) { 7 | width: 20px !important; 8 | height: 10px !important;; 9 | } 10 | 11 | .rom-list { 12 | background: var(--color-1); 13 | } 14 | 15 | :deep(.q-table th) { 16 | text-align: left; 17 | font-weight: bold; 18 | color: var(--color-9); 19 | } 20 | 21 | :deep(.q-table td) { 22 | text-align: left; 23 | } 24 | 25 | :deep(.q-table th:first-child) { 26 | position: sticky; 27 | left: 0; 28 | z-index: 1; 29 | background: var(--color-3); 30 | color: var(--color-15); 31 | } 32 | 33 | :deep(.q-table td:first-child) { 34 | position: sticky; 35 | left: 0; 36 | z-index: 1; 37 | background: var(--color-3); 38 | color: var(--color-15); 39 | } 40 | -------------------------------------------------------------------------------- /frontend/src/css/tiny/layout.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --content-backgorund-image: ""; 3 | --content-backgorund-size: ""; 4 | --content-backgorund-repeat: ""; 5 | --content-backgorund-mask: ""; 6 | --base-font-size: ""; 7 | } 8 | 9 | .bg { 10 | background-color: var(--color-2); 11 | background-image: var(--content-backgorund-image); 12 | background-repeat: var(--content-backgorund-repeat); 13 | background-size: var(--content-backgorund-size); 14 | transition: background-image 0.2s linear; 15 | } 16 | 17 | .bg:before { 18 | content: ""; 19 | display: block; 20 | position: absolute; 21 | width: 100%; 22 | height: calc(100vh); 23 | transition: background-image 0.5s linear; 24 | } 25 | 26 | .bg-mask{ 27 | background-image: var(--content-backgorund-mask); 28 | background-repeat: repeat; 29 | position: absolute; 30 | top: 0; 31 | left: 0; 32 | width: 100%; 33 | height: 100%; 34 | z-index: 0; 35 | } 36 | 37 | .fuzzy1:before { 38 | backdrop-filter: blur(1px); 39 | } 40 | 41 | .fuzzy3:before { 42 | backdrop-filter: blur(3px); 43 | } 44 | 45 | .fuzzy5:before { 46 | backdrop-filter: blur(5px); 47 | } 48 | 49 | .fuzzy7:before { 50 | backdrop-filter: blur(7px); 51 | } 52 | 53 | .fuzzy9:before { 54 | backdrop-filter: blur(9px); 55 | } 56 | 57 | .fuzzy15:before { 58 | backdrop-filter: blur(15px); 59 | } 60 | 61 | .fuzzy30:before { 62 | backdrop-filter: blur(30px); 63 | } 64 | 65 | .fuzzy45:before { 66 | backdrop-filter: blur(45px); 67 | } 68 | 69 | -------------------------------------------------------------------------------- /frontend/src/css/tiny/platform.css: -------------------------------------------------------------------------------- 1 | .close-btn { 2 | position: absolute; 3 | top: 10px; 4 | right: 10px; 5 | width: 80px; 6 | height: 80px; 7 | font-size: 24px; 8 | border-radius: 50%; 9 | background: var(--color-1); 10 | color: #FFF; 11 | z-index: 3; 12 | } 13 | 14 | 15 | .wrapper { 16 | width: 100%; 17 | height: 100vh; 18 | background-color: rgba(0, 0, 0, 0.9); 19 | } 20 | 21 | /* 平台栏 */ 22 | .platform-scroll-area{ 23 | width: 100%; 24 | height: 100%; 25 | } 26 | 27 | .platform-list { 28 | width: 100%; 29 | height: 100%; 30 | display: flex; 31 | justify-content: center; 32 | } 33 | 34 | .platform-item { 35 | width: 200px; 36 | height: 80vh; 37 | display: block; 38 | text-align: center; 39 | margin-top: 10vh; 40 | padding: 0; 41 | position: relative; 42 | } 43 | 44 | .platform-ico-active { 45 | animation: zoomInOut 2s infinite; /* 动画名称,每次动画持续2秒,并且无限循环 */ 46 | } 47 | 48 | .platform-item { 49 | display: block; 50 | } 51 | 52 | .platform-title { 53 | font-size: 36px; 54 | text-shadow: 2px 0 2px #000; 55 | font-weight: bolder; 56 | } 57 | 58 | .platform-logo { 59 | width: 72px; 60 | height: 72px; 61 | } 62 | 63 | .platform-active { 64 | background-color: var(--color-2); 65 | border-radius: 15px; 66 | background-repeat: repeat; 67 | background-size: cover; 68 | background-position: center center; 69 | position: relative; 70 | background-image: var(--content-backgorund-image); 71 | overflow: hidden; 72 | } 73 | 74 | .platform-content{ 75 | position: absolute; 76 | z-index: 2; 77 | width: 100%; 78 | height: 100%; 79 | margin-top: 10vh; 80 | } 81 | 82 | .platform-bg{ 83 | position: absolute; 84 | z-index: 1; 85 | width: 100%; 86 | height: 100%; 87 | backdrop-filter: blur(5px); 88 | } 89 | 90 | 91 | .menu-scroll-area { 92 | margin-top: 6vh; 93 | height: 53vh; 94 | } 95 | 96 | .menu-list { 97 | display: block; 98 | width: 100%; 99 | } 100 | 101 | .menu-list .q-item { 102 | text-align: center !important; 103 | display: block; 104 | padding: 0; 105 | height: 60px; 106 | line-height: 60px; 107 | font-size: 16px; 108 | } 109 | 110 | .menu-active { 111 | background: var(--q-primary); 112 | color: #FFF; 113 | } 114 | -------------------------------------------------------------------------------- /frontend/src/css/tiny/romlistBar.css: -------------------------------------------------------------------------------- 1 | .platform-title { 2 | position: absolute; 3 | right: 30px; 4 | top: 40px; 5 | line-height: 1em; 6 | font-size: 48px; 7 | opacity: 0.3; 8 | font-weight: bolder; 9 | } 10 | 11 | .rom-wrapper { 12 | width: 100%; 13 | height: calc(100vh - 30px); 14 | display: flex; 15 | align-items: center; 16 | justify-content: center; 17 | margin: 0 auto; 18 | position: relative; 19 | } 20 | 21 | .thumb-box { 22 | width: 30%; 23 | height: 80%; 24 | display: flex; 25 | align-content: center; 26 | justify-items: right; 27 | justify-content: right; 28 | justify-self: right; 29 | } 30 | 31 | 32 | .carousel { 33 | background: none !important; 34 | background-position: top center; 35 | width: 100%; 36 | height: 100%; 37 | } 38 | 39 | .carousel .q-carousel__slide { 40 | background-size: contain !important; 41 | background: no-repeat center center transparent; 42 | height: 200px; 43 | width: 100%; 44 | padding: 0; 45 | display: flex; 46 | align-items: center; 47 | justify-content: center; 48 | } 49 | 50 | .carousel-img { 51 | width: 100%; 52 | height: 100%; 53 | max-width: 800px; 54 | } 55 | 56 | .no-carousel { 57 | text-align: center; 58 | color: var(--color-6); 59 | right: 50px; 60 | opacity: 0.8; 61 | width: 100%; 62 | height: 100%; 63 | display: flex; 64 | align-items: center; 65 | justify-content: center; 66 | background: rgba(0, 0, 0, 0.4); 67 | } 68 | 69 | .page-box { 70 | margin: 0 20px; 71 | display: flex; 72 | flex-wrap: wrap; 73 | justify-content: center; 74 | align-items: center; 75 | text-align: center; 76 | } 77 | 78 | .page-box .page-num { 79 | width: 100%; 80 | line-height: 2em; 81 | font-size: 18px; 82 | } 83 | 84 | .page-box .page-num.top { 85 | border-bottom: 1px solid #FFF; 86 | } 87 | 88 | .rom-box { 89 | width: 30%; 90 | height: 80%; 91 | } 92 | 93 | .rom-scroll { 94 | height: 100%; 95 | width: 100%; 96 | max-width: 100%; 97 | } 98 | 99 | .image-error { 100 | background: var(--color-0); 101 | color: var(--color-6); 102 | } 103 | 104 | .rom-title{} 105 | 106 | .rom-title .master-title { 107 | font-size: var(--base-font-size); 108 | white-space: nowrap; 109 | overflow: hidden; 110 | text-overflow: ellipsis; 111 | } 112 | 113 | .rom-title .sub-title { 114 | font-size: calc(var(--base-font-size) - 5px); 115 | color: var(--color-6); 116 | white-space: nowrap; 117 | overflow: hidden; 118 | text-overflow: ellipsis; 119 | display: block; 120 | } 121 | 122 | .rom-list .active { 123 | background: rgba(255, 255, 255, 0.4); 124 | } 125 | 126 | .rom-list .active .sub-title { 127 | color: var(--color-2); 128 | } 129 | 130 | .rom-list .rom-img { 131 | width: 48px; 132 | height: 48px; 133 | } 134 | 135 | .rom-list .q-item{ 136 | padding: 10px; 137 | height: 65px; 138 | } 139 | 140 | .empty-romlist{ 141 | font-size: 28px; 142 | opacity: 0.5; 143 | } -------------------------------------------------------------------------------- /frontend/src/css/transitions.css: -------------------------------------------------------------------------------- 1 | /* scale */ 2 | .scale-enter-active, .scale-leave-active { 3 | transition: all 0.5s ease; 4 | } 5 | .scale-enter-from, .scale-leave-to { 6 | opacity: 0; 7 | transform: scale(0.9); 8 | } 9 | 10 | /* scale-slide */ 11 | .scale-slide-enter-active, .scale-slide-leave-active { 12 | position: absolute; 13 | transition: all 0.85s ease; 14 | } 15 | .scale-slide-enter-from { 16 | left: -100%; 17 | } 18 | .scale-slide-enter-to { 19 | left: 0; 20 | } 21 | .scale-slide-leave-from { 22 | transform: scale(1); 23 | } 24 | .scale-slide-leave-to { 25 | transform: scale(0.8); 26 | } 27 | 28 | /*fade*/ 29 | .fade-enter-active, .fade-leave-active { 30 | transition: opacity 0.5s ease; 31 | } 32 | .fade-enter-from, .fade-leave-to { 33 | opacity: 0; 34 | } 35 | 36 | /* slide */ 37 | /*.slide-enter-active, .slide-leave-active { 38 | transition: all 0.75s ease-out; 39 | } 40 | .slide-enter-to { 41 | position: absolute; 42 | right: 0; 43 | } 44 | .slide-enter-from { 45 | position: absolute; 46 | right: -100%; 47 | } 48 | .slide-leave-to { 49 | position: absolute; 50 | left: -100%; 51 | } 52 | .slide-leave-from { 53 | position: absolute; 54 | left: 0; 55 | }*/ 56 | 57 | 58 | 59 | 60 | .fade-enter-active, 61 | .fade-leave-active { 62 | transition: opacity 0.75s ease; 63 | } 64 | .fade-enter, 65 | .fade-leave-active { 66 | opacity: 0; 67 | } 68 | .child-view { 69 | position: absolute; 70 | transition: all 0.75s cubic-bezier(0.55, 0, 0.1, 1); 71 | } 72 | .slide-left-enter, 73 | .slide-right-leave-active { 74 | opacity: 0; 75 | -webkit-transform: translate(30px, 0); 76 | transform: translate(30px, 0); 77 | } 78 | .slide-left-leave-active, 79 | .slide-right-enter { 80 | opacity: 0; 81 | -webkit-transform: translate(-30px, 0); 82 | transform: translate(-30px, 0); 83 | } 84 | 85 | @keyframes zoomInOut { 86 | 0%, 100% { 87 | transform: scale(1); 88 | } 89 | 50% { 90 | transform: scale(1.2); 91 | } 92 | } -------------------------------------------------------------------------------- /frontend/src/env.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | 3 | declare namespace NodeJS { 4 | interface ProcessEnv { 5 | NODE_ENV: string; 6 | VUE_ROUTER_MODE: 'hash' | 'history' | 'abstract' | undefined; 7 | VUE_ROUTER_BASE: string | undefined; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /frontend/src/i18n/en-US/index.ts: -------------------------------------------------------------------------------- 1 | // This is just an example, 2 | // so you can safely delete all index props below 3 | 4 | export default { 5 | failed: 'Action failed', 6 | success: 'Action was successful' 7 | }; 8 | -------------------------------------------------------------------------------- /frontend/src/i18n/index.ts: -------------------------------------------------------------------------------- 1 | import enUS from './en-US'; 2 | 3 | export default { 4 | 'en-US': enUS 5 | }; 6 | -------------------------------------------------------------------------------- /frontend/src/pages/ErrorNotFound.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 28 | -------------------------------------------------------------------------------- /frontend/src/pages/classic/HeaderBarLogo.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 45 | 46 | 90 | -------------------------------------------------------------------------------- /frontend/src/pages/configPlatform/RombaseAlias.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 83 | -------------------------------------------------------------------------------- /frontend/src/pages/home/Layout.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 70 | 71 | 80 | -------------------------------------------------------------------------------- /frontend/src/pages/playnite/modules/EventKeyboard.vue: -------------------------------------------------------------------------------- 1 | 4 | 48 | 49 | 51 | -------------------------------------------------------------------------------- /frontend/src/pages/romManage/Layout.vue: -------------------------------------------------------------------------------- 1 | 52 | 53 | 67 | -------------------------------------------------------------------------------- /frontend/src/pages/test/Layout.vue: -------------------------------------------------------------------------------- 1 | 12 | 89 | 90 | 96 | -------------------------------------------------------------------------------- /frontend/src/pages/tiny/configUI/UiConst.vue: -------------------------------------------------------------------------------- 1 | 65 | -------------------------------------------------------------------------------- /frontend/src/pages/tiny/modules/EventKeyboard.vue: -------------------------------------------------------------------------------- 1 | 4 | 35 | 36 | 38 | -------------------------------------------------------------------------------- /frontend/src/quasar.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | 3 | // Forces TS to apply `@quasar/app-vite` augmentations of `quasar` package 4 | // Removing this would break `quasar/wrappers` imports as those typings are declared 5 | // into `@quasar/app-vite` 6 | // As a side effect, since `@quasar/app-vite` reference `quasar` to augment it, 7 | // this declaration also apply `quasar` own 8 | // augmentations (eg. adds `$q` into Vue component context) 9 | /// 10 | -------------------------------------------------------------------------------- /frontend/src/router/index.ts: -------------------------------------------------------------------------------- 1 | import { route } from 'quasar/wrappers'; 2 | import { 3 | createMemoryHistory, 4 | createRouter, 5 | createWebHashHistory, 6 | createWebHistory, 7 | } from 'vue-router'; 8 | 9 | import routes from './routes'; 10 | 11 | /* 12 | * If not building with SSR mode, you can 13 | * directly export the Router instantiation; 14 | * 15 | * The function below can be async too; either use 16 | * async/await or return a Promise which resolves 17 | * with the Router instance. 18 | */ 19 | 20 | export default route(function (/* { store, ssrContext } */) { 21 | const createHistory = process.env.SERVER 22 | ? createMemoryHistory 23 | : (process.env.VUE_ROUTER_MODE === 'history' ? createWebHistory : createWebHashHistory); 24 | 25 | const Router = createRouter({ 26 | scrollBehavior: () => ({ left: 0, top: 0 }), 27 | routes, 28 | 29 | // Leave this as is and make changes in quasar.conf.js instead! 30 | // quasar.conf.js -> build -> vueRouterMode 31 | // quasar.conf.js -> build -> publicPath 32 | history: createHistory(process.env.VUE_ROUTER_BASE), 33 | }); 34 | 35 | return Router; 36 | }); 37 | -------------------------------------------------------------------------------- /frontend/src/router/routes.ts: -------------------------------------------------------------------------------- 1 | import {RouteRecordRaw} from 'vue-router'; 2 | 3 | const routes: RouteRecordRaw[] = [ 4 | { 5 | path: '/', 6 | component: () => import('pages/home/Layout.vue'), 7 | meta: { 8 | refreshPage: true 9 | } 10 | }, 11 | { 12 | path: '/default', 13 | component: () => import('pages/classic/Layout.vue'), 14 | meta: { 15 | refreshPage: true 16 | } 17 | }, 18 | { 19 | path: '/playnite', 20 | component: () => import('pages/playnite/Layout.vue'), 21 | meta: { 22 | refreshPage: true 23 | } 24 | }, 25 | { 26 | path: '/tiny', 27 | component: () => import('pages/tiny/Layout.vue'), 28 | meta: { 29 | refreshPage: true 30 | } 31 | }, 32 | { 33 | path: '/config', 34 | component: () => import('pages/config/Layout.vue'), 35 | meta: { 36 | refreshPage: true 37 | }, 38 | }, 39 | { 40 | path: '/platform', 41 | component: () => import('pages/configPlatform/Layout.vue'), 42 | meta: { 43 | refreshPage: true 44 | }, 45 | }, 46 | { 47 | path: '/classic/ui', 48 | component: () => import('pages/classic/configUI/Layout.vue'), 49 | meta: { 50 | refreshPage: true 51 | }, 52 | }, 53 | { 54 | path: '/playnite/ui', 55 | component: () => import('pages/playnite/configUI/Layout.vue'), 56 | meta: { 57 | refreshPage: true 58 | }, 59 | }, 60 | { 61 | path: '/tiny/ui', 62 | component: () => import('pages/tiny/configUI/Layout.vue'), 63 | meta: { 64 | refreshPage: true 65 | }, 66 | }, 67 | { 68 | path: '/romManage', 69 | component: () => import('pages/romManage/Layout.vue'), 70 | meta: { 71 | refreshPage: true 72 | }, 73 | }, 74 | { 75 | path: '/test', 76 | component: () => import('pages/test/Layout.vue'), 77 | meta: { 78 | refreshPage: true 79 | }, 80 | }, 81 | // Always leave this as last one, 82 | // but you can also remove it 83 | { 84 | path: '/:catchAll(.*)*', 85 | component: () => import('pages/ErrorNotFound.vue'), 86 | }, 87 | ]; 88 | 89 | export default routes; 90 | -------------------------------------------------------------------------------- /frontend/src/shims-vue.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | 3 | /// 4 | 5 | // Mocks all files ending in `.vue` showing them as plain Vue instances 6 | declare module '*.vue' { 7 | import type { DefineComponent } from 'vue'; 8 | const component: DefineComponent<{}, {}, any>; 9 | export default component; 10 | } 11 | -------------------------------------------------------------------------------- /frontend/src/stores/example-store.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia'; 2 | 3 | export const useCounterStore = defineStore('counter', { 4 | state: () => ({ 5 | counter: 0, 6 | }), 7 | getters: { 8 | doubleCount: (state) => state.counter * 2, 9 | }, 10 | actions: { 11 | increment() { 12 | this.counter++; 13 | }, 14 | }, 15 | }); 16 | -------------------------------------------------------------------------------- /frontend/src/stores/index.ts: -------------------------------------------------------------------------------- 1 | import { store } from 'quasar/wrappers' 2 | import { createPinia } from 'pinia' 3 | import { Router } from 'vue-router'; 4 | 5 | /* 6 | * When adding new properties to stores, you should also 7 | * extend the `PiniaCustomProperties` interface. 8 | * @see https://pinia.vuejs.org/core-concepts/plugins.html#typing-new-store-properties 9 | */ 10 | declare module 'pinia' { 11 | export interface PiniaCustomProperties { 12 | readonly router: Router; 13 | } 14 | } 15 | 16 | /* 17 | * If not building with SSR mode, you can 18 | * directly export the Store instantiation; 19 | * 20 | * The function below can be async too; either use 21 | * async/await or return a Promise which resolves 22 | * with the Store instance. 23 | */ 24 | 25 | export default store((/* { ssrContext } */) => { 26 | const pinia = createPinia() 27 | 28 | // You can add Pinia plugins here 29 | // pinia.use(SomePiniaPlugin) 30 | 31 | return pinia 32 | }) 33 | -------------------------------------------------------------------------------- /frontend/src/stores/store-flag.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | // THIS FEATURE-FLAG FILE IS AUTOGENERATED, 3 | // REMOVAL OR CHANGES WILL CAUSE RELATED TYPES TO STOP WORKING 4 | import "quasar/dist/types/feature-flag"; 5 | 6 | declare module "quasar/dist/types/feature-flag" { 7 | interface QuasarFeatureFlags { 8 | store: true; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@quasar/app-vite/tsconfig-preset", 3 | "compilerOptions": { 4 | "baseUrl": "." 5 | } 6 | } -------------------------------------------------------------------------------- /frontend/vite.config.js: -------------------------------------------------------------------------------- 1 | import {defineConfig} from 'vite' 2 | import vue from '@vitejs/plugin-vue' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [vue()] 7 | }) 8 | 9 | /* 10 | export index ({ 11 | build:{ 12 | rollupOptions:{ 13 | external(source, importer, isResolved) { 14 | console.log(source, importer. isResolved); 15 | // 判断是否是/assets-’开头的路径,如果是则认为是外部依赖 16 | console.log("inininininiini") 17 | console.log(source) 18 | return source.startswith("assets-"); 19 | 20 | } 21 | } 22 | } 23 | })*/ 24 | -------------------------------------------------------------------------------- /frontend/vue.config.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frontlon/simUI-pulsar/2822bcf2b0f630925a29b4d929d6b846af63db41/frontend/vue.config.js -------------------------------------------------------------------------------- /main_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "path/filepath" 7 | "simUI/config" 8 | "simUI/constant" 9 | "simUI/db" 10 | "simUI/utils" 11 | "strings" 12 | "testing" 13 | ) 14 | 15 | func Program() { 16 | 17 | aa := "/ASS2ET/ASDFASDF" 18 | fmt.Println(strings.HasPrefix(aa, "/ASSET")) 19 | } 20 | 21 | func TestMain(m *testing.M) { 22 | 23 | rootpath, _ := filepath.Abs(filepath.Dir(os.Args[0])) 24 | 25 | //测试环境配置 26 | if utils.FileExists(".env") { 27 | rootpath, _ = utils.ReadFile(".env", true) 28 | constant.DEV = true 29 | } 30 | 31 | rootpath = strings.ReplaceAll(rootpath, "\\", "/") 32 | constant.ROOT_PATH = rootpath + "/" //当前软件的绝对路径 33 | constant.CACHE_PATH = rootpath + "/cache/" //缓存路径 34 | constant.CACHE_UNZIP_PATH = rootpath + "/cache/unzip/" //解压缓存路径 35 | constant.LANG_PATH = rootpath + "/language/" //语言目录 36 | constant.CACHE_UNOWNED_PATH = constant.CACHE_PATH + "unowned/" //无效资源备份目录 37 | 38 | //创建数据库文件 39 | if err := db.CreateDbFile(); err != nil { 40 | //系统alert提示 41 | utils.WriteLog(err.Error()) 42 | utils.DialogError("Error", err.Error()) 43 | return 44 | } 45 | 46 | //连接数据库 47 | if err := db.Conn(); err != nil { 48 | //系统alert提示 49 | utils.WriteLog("database connect faild!") 50 | utils.DialogError("Error", err.Error()) 51 | return 52 | } 53 | 54 | //初始化配置 55 | config.Cfg = &config.ConfStruct{} 56 | errConf := config.InitConf() 57 | if errConf != nil { 58 | utils.DialogError("Error", errConf.Error()) 59 | fmt.Println(errConf) 60 | os.Exit(1) 61 | return 62 | } 63 | 64 | //数据库升级 65 | //modules.UpgradeDB() 66 | 67 | if len(config.Cfg.Lang) == 0 { 68 | utils.WriteLog("没有找到语言文件或语言文件为空\n language files is not exists") 69 | utils.DialogError("Error", "没有找到语言文件或语言文件为空\n language files is not exists") 70 | return 71 | } 72 | 73 | //游戏手柄 74 | //modules.CheckJoystick() 75 | 76 | //软件启动时检测升级 77 | //modules.BootCheckUpgrade() 78 | 79 | //读软件配置 80 | 81 | Program() 82 | 83 | } 84 | -------------------------------------------------------------------------------- /modules/audio.go: -------------------------------------------------------------------------------- 1 | package modules 2 | 3 | // 读取rom展示图列表 4 | /*func GetGameAudios(id uint64) ([]GameAudio, error) { 5 | 6 | //游戏游戏详细数据 7 | info, err := (&db.Rom{}).GetById(id) 8 | lists := []GameAudio{} 9 | if err != nil { 10 | return lists, err 11 | } 12 | 13 | //资源路径 14 | resPath := components.GetResPathByType("audio", info.Platform) 15 | 16 | //音频列表 17 | files, _ := components.GetAudioRes(resPath, info.RomName) 18 | 19 | //子资源文件名排序 20 | sort.Slice(files, func(i, j int) bool { 21 | return strings.ToLower(files[i]) < strings.ToLower(files[j]) 22 | }) 23 | 24 | if len(files) > 0 { 25 | for _, v := range files { 26 | s := GameAudio{ 27 | Name: utils.GetFileName(v), 28 | Path: utils.WailsPathEncode(v), 29 | } 30 | lists = append(lists, s) 31 | } 32 | } 33 | 34 | return lists, nil 35 | }*/ 36 | 37 | /** 38 | * 编辑音频 39 | **/ 40 | /*func UpdateGameAudio(paths []string) ([]string, error) { 41 | return nil, nil 42 | } 43 | */ 44 | /** 45 | * 添加音频 46 | **/ 47 | /*func AddGameAudio(id uint64, paths []string) ([]string, error) { 48 | vo, _ := (&db.Rom{}).GetById(id) 49 | platformInfo := (&db.Platform{}).GetVOById(vo.Platform, false) 50 | 51 | if platformInfo.AudioPath == "" { 52 | return nil, errors.New(config.Cfg.Lang["AudioMenuCanNotBeEmpty"]) 53 | } 54 | 55 | //检查创建目录 56 | _ = utils.CreateDir(platformInfo.AudioPath) 57 | 58 | result := []string{} 59 | for _, p := range paths { 60 | p = utils.ToAbsPath(p, "") 61 | 62 | if utils.FileExists(p) { 63 | continue 64 | } 65 | 66 | name := utils.GetFileNameAndExt(p) 67 | dst := platformInfo.AudioPath + "/" + vo.RomName + "/" + name 68 | if err := utils.FileCopy(p, dst); err != nil { 69 | return nil, err 70 | } 71 | result = append(result, utils.WailsPathEncode(dst)) 72 | } 73 | 74 | return result, nil 75 | }*/ 76 | 77 | /** 78 | * 音频改名 79 | **/ 80 | /*func RenameGameAudio(pth string, newName string) (string, error) { 81 | src := utils.WailsPathDecode(pth) 82 | dst := utils.ReplaceFileNameByPath(src, newName) 83 | if utils.FileExists(dst) { 84 | return "", errors.New("文件已存在") 85 | } 86 | err := utils.FileMove(src, dst) 87 | if err != nil { 88 | return "", err 89 | } 90 | return dst, nil 91 | } 92 | */ 93 | /** 94 | * 删除音频 95 | **/ 96 | /*func DelGameAudio(pth string) error { 97 | f := utils.WailsPathDecode(pth) 98 | return utils.FileDelete(f) 99 | 100 | }*/ 101 | -------------------------------------------------------------------------------- /modules/image.go: -------------------------------------------------------------------------------- 1 | package modules 2 | 3 | import ( 4 | "errors" 5 | "simUI/components" 6 | "simUI/db" 7 | "simUI/utils" 8 | "strings" 9 | "time" 10 | ) 11 | 12 | var imageWidth = 320 13 | var quality = 80 14 | 15 | func CreateRomResByBase64(id uint64, resType string, slaveRes uint8, fileType string, base64Str string) (string, error) { 16 | 17 | fileTypes := strings.Split(fileType, "/") 18 | if fileTypes[0] != "image" && fileTypes[0] != "video" { 19 | return "", errors.New("文件类型错误") 20 | } 21 | 22 | rom, err := (&db.Rom{}).GetById(id) 23 | if err != nil { 24 | return "", err 25 | } 26 | 27 | resPath := components.GetResPathByType(resType, rom.Platform) 28 | pth := "" 29 | tm := utils.ToString(time.Now().Unix()) 30 | if slaveRes == 0 { 31 | pth = resPath + "/" + rom.RomName + "." + fileTypes[1] 32 | } else { 33 | pth = resPath + "/" + rom.RomName + "/" + tm + "." + fileTypes[1] 34 | } 35 | 36 | if err = utils.Base64ToFile(base64Str, pth); err != nil { 37 | return "", err 38 | } 39 | 40 | //return utils.WailsPathEncode(pth), nil 41 | return pth, nil 42 | 43 | } 44 | 45 | func CreateImg() { 46 | /** 判断是不是图片 */ 47 | /*in := "/Users/frontlon/go/src/wails/sim-ui-pulsar/in.jpg" 48 | format := utils.GetFileExt(in) 49 | out := "/Users/frontlon/go/src/wails/sim-ui-pulsar/out" + format 50 | 51 | err := utils.CreateThumbnail(in, out) 52 | 53 | fmt.Println("-=-=-") 54 | fmt.Println(err) 55 | 56 | if err != nil { 57 | fmt.Println(err) 58 | } 59 | */ 60 | } 61 | 62 | //func CreateOptimizedImage(platform uint32, opt string) error { 63 | // 64 | // paths := components.GetResPath(platform, 0) 65 | // path := paths[opt] 66 | // 67 | // platformInfo := (&db.Platform{}).GetVOById(platform, false) 68 | // outputPath := platformInfo.OptimizedPath 69 | // 70 | // //先删除原文件 71 | // utils.DeleteDir(outputPath) 72 | // utils.CreateDir(outputPath) 73 | // 74 | // //读取文件总数 75 | // //files, _ := ioutil.ReadDir(path) 76 | // //fileCount := len(files) 77 | // i := 0 78 | // filepath.Walk(path, func(p string, f os.FileInfo, err error) error { 79 | // if f == nil { 80 | // return err 81 | // } 82 | // if f.IsDir() { /** 是否是目录 */ 83 | // return nil 84 | // } 85 | // 86 | // /** 判断是不是图片 */ 87 | // format := utils.GetFileExt(p) 88 | // 89 | // outputPath := outputPath + "/" + utils.GetFileNameAndExt(p) 90 | // if p != "" { 91 | // err := utils.ImageCompress( 92 | // func() (io.Reader, error) { 93 | // return os.Open(p) 94 | // }, 95 | // func() (*os.File, error) { 96 | // return os.Open(p) 97 | // }, 98 | // outputPath, 99 | // quality, 100 | // imageWidth, 101 | // format) 102 | // 103 | // if err != nil { 104 | // return err 105 | // } 106 | // } 107 | // 108 | // /*if i%10 == 0 { 109 | // utils.Loading("[2/3]已生成("+utils.ToString(i)+" / "+utils.ToString(fileCount)+")", config.Cfg.Platform[platform].Name) 110 | // }*/ 111 | // 112 | // i++ 113 | // return nil 114 | // }) 115 | // 116 | // //数据更新完成后,页面回调,更新页面DOM 117 | // /*if _, err := utils.Window.Call("CB_createOptimizedCache"); err != nil { 118 | // fmt.Print(err) 119 | // }*/ 120 | // 121 | // return nil 122 | //} 123 | -------------------------------------------------------------------------------- /modules/others.go: -------------------------------------------------------------------------------- 1 | package modules 2 | 3 | import ( 4 | "simUI/server" 5 | ) 6 | 7 | // 启动上传服务 8 | func StartUploadServer() string { 9 | if server.Addr == "" { 10 | server.StartHttpServer() 11 | } 12 | return server.Addr 13 | } 14 | -------------------------------------------------------------------------------- /modules/romConfig.go: -------------------------------------------------------------------------------- 1 | package modules 2 | 3 | import ( 4 | "simUI/config" 5 | "simUI/db" 6 | ) 7 | 8 | // 设为隐藏 9 | func SetHide(id uint64, hide uint8) error { 10 | 11 | //数据库中读取rom详情 12 | rom, err := (&db.Rom{}).GetById(id) 13 | if err != nil { 14 | return err 15 | } 16 | 17 | //更新数据 18 | if err = config.SetRomSettingOneField(rom.Platform, rom.RomName, "Hide", hide, true); err != nil { 19 | return err 20 | } 21 | 22 | err = (&db.Rom{RomName: rom.RomName, Hide: hide}).UpdateHide() 23 | if err != nil { 24 | return err 25 | } 26 | 27 | return nil 28 | } 29 | 30 | // 设为喜爱 31 | func SetFavorite(id uint64, fav uint8) error { 32 | 33 | //数据库中读取rom详情 34 | rom, err := (&db.Rom{}).GetById(id) 35 | if err != nil { 36 | return err 37 | } 38 | 39 | //更新数据 40 | if err = config.SetRomSettingOneField(rom.Platform, rom.RomName, "Favorite", fav, true); err != nil { 41 | return err 42 | } 43 | 44 | err = (&db.Rom{RomName: rom.RomName, Favorite: fav}).UpdateFavorite() 45 | if err != nil { 46 | return err 47 | } 48 | 49 | return nil 50 | } 51 | -------------------------------------------------------------------------------- /modules/romSimSetting.go: -------------------------------------------------------------------------------- 1 | package modules 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | "simUI/config" 8 | "simUI/db" 9 | "simUI/utils" 10 | ) 11 | 12 | // 读取一个ROM模拟器配置 13 | func GetRomSimSettingById(romId uint64, simId uint32) (db.RomSimSetting, error) { 14 | 15 | resp := db.RomSimSetting{} 16 | 17 | romInfo, _ := (&db.Rom{}).GetById(romId) 18 | if romInfo == nil { 19 | return resp, errors.New(config.Cfg.Lang["romIsNotExists"]) 20 | } 21 | 22 | simInfo, _ := (&db.Simulator{}).GetById(simId) 23 | 24 | if simInfo == nil { 25 | return resp, errors.New(config.Cfg.Lang["simIsNotExists"]) 26 | } 27 | 28 | simName := utils.GetFileName(simInfo.Path) 29 | 30 | data := (&db.Rom{}).ConvertRomSimple(romInfo, []*db.Rom{}) 31 | 32 | if _, ok := data.SimSetting[simName]; ok { 33 | resp = db.RomSimSetting{ 34 | Cmd: data.SimSetting[simName].Cmd, 35 | RunBefore: data.SimSetting[simName].RunBefore, 36 | RunAfter: data.SimSetting[simName].RunAfter, 37 | Unzip: data.SimSetting[simName].Unzip, 38 | } 39 | } 40 | 41 | return resp, nil 42 | } 43 | 44 | // 更新ROM模拟器配置 45 | func UpdateRomSimSetting(romId uint64, simId uint32, data *db.RomSimSetting) error { 46 | 47 | data.RunBefore = utils.ToRelPath(data.RunBefore, "") 48 | data.RunAfter = utils.ToRelPath(data.RunAfter, "") 49 | 50 | //读rom信息 51 | romInfo, _ := (&db.Rom{}).GetById(romId) 52 | if romInfo == nil { 53 | return errors.New(config.Cfg.Lang["romIsNotExists"]) 54 | } 55 | romVo := (&db.Rom{}).ConvertRomSimple(romInfo, []*db.Rom{}) 56 | romSetting := map[uint32]db.RomSimSetting{} 57 | if romInfo.SimSetting != "" { 58 | json.Unmarshal([]byte(romInfo.SimSetting), &romSetting) 59 | } 60 | 61 | //读模拟器信息 62 | simInfo, _ := (&db.Simulator{}).GetById(simId) 63 | if simInfo == nil { 64 | return errors.New(config.Cfg.Lang["simIsNotExists"]) 65 | } 66 | 67 | simName := utils.GetFileName(simInfo.Path) 68 | romVo.SimSetting[simName] = data 69 | 70 | //清除空数据 71 | for k, v := range romVo.SimSetting { 72 | if v.RunBefore == "" && v.RunAfter == "" && v.Cmd == "" && v.Unzip == "" { 73 | delete(romVo.SimSetting, k) 74 | } 75 | } 76 | 77 | create, _ := json.Marshal(romVo.SimSetting) 78 | if err := (&db.Rom{}).UpdateSimSettingById(romId, string(create)); err != nil { 79 | fmt.Println("UpdateRomSimSetting", err) 80 | return err 81 | } 82 | 83 | //更新到配置文件 84 | config.SetRomSettingOneField(romInfo.Platform, romInfo.RomName, "SimSetting", string(create), true) 85 | 86 | return nil 87 | 88 | } 89 | -------------------------------------------------------------------------------- /modules/rombaseAlias.go: -------------------------------------------------------------------------------- 1 | package modules 2 | 3 | import ( 4 | "simUI/config" 5 | "simUI/db" 6 | ) 7 | 8 | // 这些是必须返回出一些别名配置 9 | var rombaseAliasList = []string{"OtherA", "OtherB", "OtherC", "OtherD"} 10 | 11 | // 根据类型读取数据 12 | func GetRomBaseAliasByPlatform(platform uint32) (map[string]string, error) { 13 | voMap, err := (&db.RombaseAlias{}).GetByPlatform(platform) 14 | if err != nil { 15 | voMap = map[string]string{} 16 | } 17 | 18 | for _, typ := range rombaseAliasList { 19 | if _, ok := voMap[typ]; !ok { 20 | voMap[typ] = config.Cfg.Lang["base"+typ] 21 | } 22 | } 23 | 24 | return voMap, nil 25 | 26 | } 27 | 28 | // 更新数据 29 | func UpdateRomBaseAlias(platform uint32, data map[string]string) error { 30 | 31 | existMap, _ := (&db.RombaseAlias{}).GetByPlatform(platform) 32 | 33 | for typ, alias := range data { 34 | 35 | //删除记录 36 | if alias == "" { 37 | (&db.RombaseAlias{ 38 | Platform: platform, 39 | Type: typ, 40 | }).DeleteByType() 41 | continue 42 | } 43 | 44 | if _, ok := existMap[typ]; ok { 45 | //修改记录 46 | (&db.RombaseAlias{ 47 | Platform: platform, 48 | Type: typ, 49 | Alias: alias, 50 | }).UpdateByType() 51 | } else { 52 | //新增记录 53 | (&db.RombaseAlias{ 54 | Platform: platform, 55 | Type: typ, 56 | Alias: alias, 57 | }).Add() 58 | } 59 | } 60 | return nil 61 | } 62 | -------------------------------------------------------------------------------- /modules/rombaseEnum.go: -------------------------------------------------------------------------------- 1 | package modules 2 | 3 | import ( 4 | "simUI/db" 5 | "strings" 6 | ) 7 | 8 | // 读取rombase枚举 9 | func UpdateRomBaseEnum(t string, data []string) error { 10 | 11 | //先删除记录 12 | (&db.RombaseEnum{Type: t}).DeleteByType() 13 | 14 | if len(data) == 0 { 15 | return nil 16 | } 17 | 18 | create := []*db.RombaseEnum{} 19 | for k, v := range data { 20 | if strings.Trim(v, " ") == "" { 21 | continue 22 | } 23 | c := &db.RombaseEnum{} 24 | c.Type = t 25 | c.Name = v 26 | c.Sort = uint32(k) + 1 27 | create = append(create, c) 28 | } 29 | if err := (&db.RombaseEnum{}).BatchAdd(create); err != nil { 30 | return err 31 | } 32 | return nil 33 | } 34 | 35 | // 读取全部枚举数据 36 | func GetRomBaseEnum() (map[string][]string, error) { 37 | lists, err := (&db.RombaseEnum{}).GetAll() 38 | if err != nil { 39 | return nil, err 40 | } 41 | result := map[string][]string{} 42 | 43 | result["type"] = []string{} 44 | result["year"] = []string{} 45 | result["producer"] = []string{} 46 | result["publisher"] = []string{} 47 | result["country"] = []string{} 48 | result["translate"] = []string{} 49 | result["version"] = []string{} 50 | 51 | for _, v := range lists { 52 | result[v.Type] = append(result[v.Type], v.Name) 53 | } 54 | return result, nil 55 | } 56 | 57 | // 根据类型读取枚举数据 58 | func GetRomBaseEnumByType(t string) ([]string, error) { 59 | lists, err := (&db.RombaseEnum{}).GetByType(t) 60 | if err != nil { 61 | return nil, err 62 | } 63 | result := []string{} 64 | for _, v := range lists { 65 | result = append(result, v.Name) 66 | } 67 | return result, nil 68 | } 69 | -------------------------------------------------------------------------------- /modules/shortcut.go: -------------------------------------------------------------------------------- 1 | package modules 2 | 3 | import ( 4 | "simUI/db" 5 | "simUI/utils" 6 | "strings" 7 | ) 8 | 9 | // 读取快捷工具 10 | func GetShortcuts(isAbs bool) ([]*db.Shortcut, error) { 11 | volist, err := (&db.Shortcut{}).GetAll() 12 | if err != nil { 13 | utils.WriteLog(err.Error()) 14 | return nil, err 15 | } 16 | 17 | if isAbs { 18 | for k, v := range volist { 19 | volist[k].Path = utils.ToAbsPath(v.Path, "") 20 | } 21 | } 22 | return volist, nil 23 | } 24 | 25 | // 更新快捷工具 26 | func UpdateShortcut(data []*db.Shortcut) ([]*db.Shortcut, error) { 27 | m := &db.Shortcut{} 28 | volist, err := m.GetAll() 29 | if err != nil { 30 | utils.WriteLog(err.Error()) 31 | return nil, err 32 | } 33 | voMap := map[uint32]*db.Shortcut{} 34 | for _, v := range volist { 35 | voMap[v.Id] = v 36 | } 37 | 38 | for _, v := range data { 39 | 40 | if v.Name == "" && v.Path == "" { 41 | continue 42 | } 43 | 44 | v.Path = utils.ToRelPath(v.Path, "") 45 | v.Name = strings.TrimSpace(v.Name) 46 | //没有找到则添加 47 | if _, ok := voMap[v.Id]; !ok { 48 | v.Add() 49 | continue 50 | } 51 | 52 | exist := voMap[v.Id] 53 | //有差异则更新数据 54 | if v.Name != exist.Name || v.Path != exist.Path || v.Sort != exist.Sort { 55 | v.UpdateById() 56 | } 57 | } 58 | 59 | //检查删除 60 | dataMap := map[uint32]*db.Shortcut{} 61 | for _, v := range data { 62 | dataMap[v.Id] = v 63 | } 64 | for _, v := range volist { 65 | if _, ok := dataMap[v.Id]; !ok { 66 | v.DeleteById() 67 | } 68 | } 69 | 70 | return volist, nil 71 | } 72 | 73 | // 更新快捷软件排序 74 | func UpdateShortcutSort(lists []uint32) error { 75 | if len(lists) == 0 { 76 | return nil 77 | } 78 | for k, pfId := range lists { 79 | shortcut := &db.Shortcut{ 80 | Id: pfId, 81 | Sort: uint32(k + 1), 82 | } 83 | err := shortcut.UpdateSortById() 84 | if err != nil { 85 | utils.WriteLog(err.Error()) 86 | return err 87 | } 88 | } 89 | return nil 90 | } 91 | -------------------------------------------------------------------------------- /modules/simulator.go: -------------------------------------------------------------------------------- 1 | package modules 2 | 3 | import ( 4 | "simUI/config" 5 | "simUI/db" 6 | "simUI/utils" 7 | ) 8 | 9 | // 读取所有模拟器 10 | func GetAllSimulator() (map[uint32][]*db.Simulator, error) { 11 | dolist, _ := (&db.Simulator{}).GetAll() 12 | data := map[uint32][]*db.Simulator{} 13 | 14 | for _, v := range dolist { 15 | if _, ok := data[v.Platform]; ok { 16 | data[v.Platform] = append(data[v.Platform], v) 17 | } else { 18 | data[v.Platform] = []*db.Simulator{v} 19 | } 20 | } 21 | return data, nil 22 | } 23 | 24 | // 读取一个平台下的所有模拟器 25 | func GetSimulatorByPlatform(id uint32) ([]*db.Simulator, error) { 26 | return (&db.Simulator{}).GetByPlatform(id) 27 | } 28 | 29 | // 读取一个模拟器 30 | func GetSimulatorById(id uint32) (*db.Simulator, error) { 31 | return (&db.Simulator{}).GetById(id) 32 | } 33 | 34 | // 添加模拟器 35 | func AddSimulator(platformId uint32, name string) (*db.Simulator, error) { 36 | 37 | sim := &db.Simulator{ 38 | Name: name, 39 | Platform: platformId, 40 | } 41 | id, err := sim.Add() 42 | if err != nil { 43 | return nil, err 44 | } 45 | sim.Id = id 46 | return sim, nil 47 | } 48 | 49 | // 更新模拟器 50 | func UpdateSimulator(data db.Simulator) (*db.Simulator, error) { 51 | sim := &db.Simulator{ 52 | Id: data.Id, 53 | Name: data.Name, 54 | Platform: data.Platform, 55 | Path: data.Path, 56 | Cmd: data.Cmd, 57 | RunBefore: data.RunBefore, 58 | RunAfter: data.RunAfter, 59 | } 60 | 61 | //更新模拟器 62 | if err := sim.UpdateById(); err != nil { 63 | return sim, err 64 | } 65 | 66 | return sim, nil 67 | } 68 | 69 | // 更新模拟器排序 70 | func UpdateSimulatorSort(lists []uint32) error { 71 | if len(lists) == 0 { 72 | return nil 73 | } 74 | for k, pfId := range lists { 75 | simulator := &db.Simulator{ 76 | Id: pfId, 77 | Sort: uint32(k + 1), 78 | } 79 | err := simulator.UpdateSortById() 80 | if err != nil { 81 | utils.WriteLog(err.Error()) 82 | return err 83 | } 84 | } 85 | return nil 86 | } 87 | 88 | // 删除模拟器 89 | func DelSimulator(id uint32) error { 90 | if err := (&db.Simulator{Id: id}).DeleteById(); err != nil { 91 | utils.WriteLog(err.Error()) 92 | return err 93 | } 94 | return nil 95 | } 96 | 97 | // 设置一个rom的模拟器id 98 | func SetRomSimId(romId uint64, simId uint32) error { 99 | 100 | if err := (&db.Rom{}).UpdateOneField(romId, "sim_id", simId); err != nil { 101 | return err 102 | } 103 | 104 | rom, _ := (&db.Rom{}).GetById(romId) 105 | 106 | //更新csv 107 | go func() { 108 | config.SetRomSettingOneField(rom.Platform, rom.RomName, "SimId", simId, true) 109 | }() 110 | 111 | return nil 112 | } 113 | 114 | // 批量设置rom的模拟器id 115 | func BatchSetRomSimId(romIds []uint64, simId uint32) error { 116 | 117 | err := (&db.Rom{}).UpdateSimIdByIds(romIds, simId) 118 | if err != nil { 119 | return err 120 | } 121 | 122 | roms, _ := (&db.Rom{}).GetByIds(romIds) 123 | if len(roms) == 0 { 124 | return nil 125 | } 126 | platform := roms[0].Platform 127 | 128 | //更新csv 129 | go func() { 130 | for _, v := range roms { 131 | config.SetRomSettingOneField(platform, v.RomName, "SimId", simId, true) 132 | } 133 | config.FlushRomSetting(platform) 134 | }() 135 | 136 | return nil 137 | } 138 | -------------------------------------------------------------------------------- /modules/strategyFiles.go: -------------------------------------------------------------------------------- 1 | package modules 2 | 3 | import ( 4 | "errors" 5 | "simUI/components" 6 | "simUI/config" 7 | "simUI/db" 8 | "simUI/utils" 9 | "sort" 10 | "strings" 11 | ) 12 | 13 | type GameStrategyFile struct { 14 | Name string //文件名称 15 | Path string //文件路径 16 | } 17 | 18 | // 读取攻略文件 19 | func GetStrategyFiles(id uint64) ([]GameStrategyFile, error) { 20 | 21 | //游戏游戏详细数据 22 | info, err := (&db.Rom{}).GetById(id) 23 | lists := []GameStrategyFile{} 24 | if err != nil { 25 | return lists, err 26 | } 27 | 28 | //资源路径 29 | resPath := components.GetResPathByType("file", info.Platform) 30 | 31 | //音频列表 32 | files, _ := components.GetFileRes(resPath, info.RomName) 33 | 34 | //文件名排序 35 | sort.Slice(files, func(i, j int) bool { 36 | return strings.ToLower(files[i]) < strings.ToLower(files[j]) 37 | }) 38 | 39 | if len(files) > 0 { 40 | for _, v := range files { 41 | s := GameStrategyFile{ 42 | Name: utils.GetFileName(v), 43 | Path: utils.ToRelPath(v, ""), 44 | } 45 | lists = append(lists, s) 46 | } 47 | } 48 | return lists, nil 49 | } 50 | 51 | // 更新攻略文件 52 | func UpdateStrategyFiles(id uint64, data []GameStrategyFile) ([]GameStrategyFile, error) { 53 | 54 | //游戏游戏详细数据 55 | info, err := (&db.Rom{}).GetById(id) 56 | if err != nil { 57 | return nil, err 58 | } 59 | 60 | //游戏游戏详细数据 61 | platformInfo := (&db.Platform{}).GetVOById(info.Platform, false) 62 | 63 | if platformInfo.FilesPath == "" { 64 | return nil, errors.New(config.Cfg.Lang["platformPathNotFound"]) 65 | } 66 | 67 | //资源路径 68 | resPath := components.GetResPathByType("file", info.Platform) 69 | 70 | //攻略列表 71 | files, _ := components.GetFileRes(resPath, info.RomName) 72 | existMap := map[string]string{} 73 | for _, v := range files { 74 | existMap[utils.GetFileName(v)] = v 75 | } 76 | 77 | newData := map[string]string{} 78 | for _, v := range data { 79 | 80 | if v.Path == "" { 81 | continue 82 | } 83 | 84 | if v.Name == "" { 85 | v.Name = utils.GetFileName(v.Path) 86 | } 87 | 88 | absPath := utils.ToAbsPath(v.Path, "") 89 | v.Name = strings.TrimSpace(v.Name) 90 | pathName := utils.GetFileName(v.Path) 91 | pathExt := utils.GetFileExt(v.Path) 92 | inPath := platformInfo.FilesPath + "/" + info.RomName + "/" + v.Name + pathExt 93 | 94 | if !utils.FileExists(inPath) { 95 | //文件不存在,复制进来 96 | err = utils.FileCopy(absPath, inPath) 97 | if err != nil { 98 | return nil, err 99 | } 100 | } else if utils.FileExists(absPath) && pathName != v.Name { 101 | //文件存在,但是需要改名 102 | _, err = utils.FileRename(absPath, v.Name) 103 | if err != nil { 104 | return nil, err 105 | } 106 | } 107 | newData[v.Name] = absPath 108 | } 109 | 110 | //重新读一下音频列表,检查不存在则删除 111 | files, _ = components.GetFileRes(resPath, info.RomName) 112 | 113 | for _, p := range files { 114 | nfile := utils.GetFileName(p) 115 | if _, ok := newData[nfile]; !ok { 116 | utils.FileDelete(p) 117 | continue 118 | } 119 | } 120 | return GetStrategyFiles(id) 121 | } 122 | -------------------------------------------------------------------------------- /readme/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frontlon/simUI-pulsar/2822bcf2b0f630925a29b4d929d6b846af63db41/readme/1.jpg -------------------------------------------------------------------------------- /readme/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frontlon/simUI-pulsar/2822bcf2b0f630925a29b4d929d6b846af63db41/readme/2.jpg -------------------------------------------------------------------------------- /readme/3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frontlon/simUI-pulsar/2822bcf2b0f630925a29b4d929d6b846af63db41/readme/3.jpg -------------------------------------------------------------------------------- /readme/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frontlon/simUI-pulsar/2822bcf2b0f630925a29b4d929d6b846af63db41/readme/logo.png -------------------------------------------------------------------------------- /request/input.go: -------------------------------------------------------------------------------- 1 | package request 2 | 3 | /** 4 | * 导出(分享)rom OutputRom 5 | **/ 6 | type OutputRom struct { 7 | Save string `json:"save"` //zip文件保存路径 8 | Platform uint32 `json:"platform"` //平台id 9 | Opt string `json:"opt"` //导出类型 10 | Options []string `json:"options"` //导出选项 11 | Menus []string `json:"menus"` //目录列表 12 | Roms []uint64 `json:"roms"` //rom列表 13 | Simulators []uint32 `json:"simulators"` //模拟器列表 14 | } 15 | -------------------------------------------------------------------------------- /request/platform.go: -------------------------------------------------------------------------------- 1 | package request 2 | 3 | type UpdatePlatform struct { 4 | Id uint32 `json:"id"` 5 | Name string `json:"name"` 6 | Icon string `json:"icon"` 7 | Tag string `json:"tag"` 8 | RomExts []string `json:"romExts"` 9 | RootPath string `json:"rootPath"` 10 | RomPath []string `json:"romPath"` 11 | HideName uint8 `json:"hideName"` 12 | } 13 | -------------------------------------------------------------------------------- /request/rom.go: -------------------------------------------------------------------------------- 1 | package request 2 | 3 | /** 4 | * 读取游戏列表 GetGameList 5 | **/ 6 | type GetGameList struct { 7 | Theme string `json:"theme"` //主题 8 | ShowHide uint8 `json:"showHide"` //是否隐藏 9 | ShowSubGame uint8 `json:"showSubGame"` //是否显示子游戏 10 | Platform uint32 `json:"platform"` //平台 11 | Catname string `json:"catname"` //分类 12 | CatnameLike int `json:"catnameLike"` //分类模糊搜索 0精确查找 1模糊查找 13 | Keyword string `json:"keyword"` //关键字 14 | Letter string `json:"letter"` //字母索引 15 | Page int `json:"page"` //分页数 16 | BaseType string `json:"baseType"` //资料 - 游戏类型 17 | BasePublisher string `json:"basePublisher"` //资料 - 发布者 18 | BaseYear string `json:"baseYear"` //资料 - 发型年份 19 | BaseCountry string `json:"baseCountry"` //资料 - 国家 20 | BaseTranslate string `json:"baseTranslate"` //资料 - 语言 21 | BaseVersion string `json:"baseVersion"` //资料 - 版本 22 | BaseProducer string `json:"baseProducer"` //资料 - 制作商 23 | Score string `json:"score"` //评分 24 | Complete string `json:"complete"` //是否通关 25 | SimpleMode string `json:"simpleModel"` //返回的数据结构类型 26 | } 27 | -------------------------------------------------------------------------------- /utils/alert.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import "github.com/ncruces/zenity" 4 | 5 | // 调用系统dialog框 - 错误 6 | func DialogError(title, msg string) { 7 | zenity.Error(msg, zenity.Title(title), zenity.ErrorIcon) 8 | } 9 | 10 | // 调用系统dialog框 - 信息 11 | func DialogInfo(title, msg string) { 12 | zenity.Info(msg, zenity.Title(title), zenity.InfoIcon) 13 | 14 | } 15 | 16 | // 调用系统dialog框 - 警告 17 | func DialogWarn(title, msg string) { 18 | zenity.Warning(msg, zenity.Title(title), zenity.WarningIcon) 19 | } 20 | -------------------------------------------------------------------------------- /utils/args.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | var args map[string]string 4 | 5 | // 解析并读取命令行参数 6 | func GetCmdArgs(name string) string { 7 | /*if len(args) == 0 { 8 | var db string 9 | var dev string 10 | flag.StringVar(&db, "db", "", "") 11 | flag.StringVar(&dev, "dev", "", "") 12 | flag.Parse() 13 | 14 | fmt.Println("-------------") 15 | fmt.Println(dev) 16 | args = map[string]string{ 17 | "db": db, 18 | "dev": "", 19 | } 20 | } 21 | if _, ok := args[name]; ok { 22 | return args[name] 23 | } else { 24 | return "" 25 | }*/ 26 | return "" 27 | } 28 | -------------------------------------------------------------------------------- /utils/calljs.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | // 调用alert框 4 | func ErrorMsg(err string) error { 5 | 6 | /*if _, err := Window.Call("errorBox", sciter.NewValue(err)); err != nil { 7 | }*/ 8 | return nil 9 | } 10 | 11 | // 调用loading框 12 | func Loading(str string, platform string) error { 13 | /*if _, err := Window.Call("startLoading", sciter.NewValue(str), sciter.NewValue(platform)); err != nil { 14 | } 15 | return sciter.NullValue()*/ 16 | return nil 17 | } 18 | 19 | // 检查当前窗口激活状态 20 | func CheckWinActive() bool { 21 | /*active, err := Window.Call("checkWinActive") 22 | if err != nil { 23 | fmt.Println(err) 24 | } 25 | return active.Bool()*/ 26 | return false 27 | } 28 | 29 | // 调用视图中的方向控制【手柄】 30 | func ViewDirection(dir int) bool { 31 | /*active, err := Window.Call("joystickDirection", sciter.NewValue(dir)) 32 | if err != nil { 33 | fmt.Println(err) 34 | } 35 | return active.Bool()*/ 36 | return false 37 | } 38 | 39 | // 调用视图中的按钮控制【手柄】 40 | func ViewButton(btn string) bool { 41 | /*active, err := Window.Call("joystickButton", sciter.NewValue(btn)) 42 | if err != nil { 43 | fmt.Println(err) 44 | } 45 | return active.Bool()*/ 46 | return false 47 | } 48 | -------------------------------------------------------------------------------- /utils/conv.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "strconv" 5 | ) 6 | 7 | /* 8 | 传入任意类型,将数据转换为字符串 9 | */ 10 | func ToString(str interface{}) string { 11 | var val string 12 | switch t := str.(type) { 13 | case string: 14 | val = t 15 | case int: 16 | val = strconv.Itoa(t) 17 | case int8: 18 | val = strconv.Itoa(int(t)) 19 | case int16: 20 | val = strconv.Itoa(int(t)) 21 | case int32: 22 | val = strconv.Itoa(int(t)) 23 | case int64: 24 | val = strconv.Itoa(int(t)) 25 | case uint: 26 | val = strconv.Itoa(int(t)) 27 | case uint8: 28 | val = strconv.Itoa(int(t)) 29 | case uint16: 30 | val = strconv.Itoa(int(t)) 31 | case uint32: 32 | val = strconv.Itoa(int(t)) 33 | case uint64: 34 | val = strconv.Itoa(int(t)) 35 | case float32: 36 | val = strconv.FormatFloat(float64(t), 'f', -1, 64) 37 | case float64: 38 | val = strconv.FormatFloat(t, 'f', -1, 64) 39 | case []uint8: //[]byte类型 40 | val = string(t) 41 | case bool: 42 | if t == true { 43 | val = "true" 44 | } else { 45 | val = "false" 46 | } 47 | default: 48 | val = t.(string) 49 | } 50 | return val 51 | } 52 | 53 | /* 54 | * 55 | 56 | 传入任意类型,将数据转换为整型 57 | 58 | * 59 | */ 60 | func ToInt(str interface{}) int { 61 | var val int 62 | switch t := str.(type) { 63 | case int: 64 | val = int(t) 65 | case int8: 66 | val = int(t) 67 | case int16: 68 | val = int(t) 69 | case int32: 70 | val = int(t) 71 | case int64: 72 | val = int(t) 73 | case uint: 74 | val = int(t) 75 | case uint8: 76 | val = int(t) 77 | case uint16: 78 | val = int(t) 79 | case uint32: 80 | val = int(t) 81 | case uint64: 82 | val = int(t) 83 | case float32: 84 | val = int(t) 85 | case float64: 86 | val = int(t) 87 | case string: 88 | val, _ = strconv.Atoi(str.(string)) 89 | case bool: 90 | if str.(bool) == true { 91 | val = 1 92 | } else { 93 | val = 0 94 | } 95 | default: 96 | val = 0 97 | } 98 | return val 99 | } 100 | 101 | func ToFloat64(str interface{}) float64 { 102 | var val float64 103 | switch t := str.(type) { 104 | case int: 105 | val = float64(t) 106 | case int8: 107 | val = float64(t) 108 | case int16: 109 | val = float64(t) 110 | case int32: 111 | val = float64(t) 112 | case int64: 113 | val = float64(t) 114 | case uint: 115 | val = float64(t) 116 | case uint8: 117 | val = float64(t) 118 | case uint16: 119 | val = float64(t) 120 | case uint32: 121 | val = float64(t) 122 | case uint64: 123 | val = float64(t) 124 | case float32: 125 | val = float64(t) 126 | case float64: 127 | val = float64(t) 128 | case string: 129 | val, _ = strconv.ParseFloat(t, 64) 130 | case bool: 131 | if str.(bool) == true { 132 | val = 1 133 | } else { 134 | val = 0 135 | } 136 | default: 137 | val = 0 138 | } 139 | return val 140 | } 141 | -------------------------------------------------------------------------------- /utils/convert.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "github.com/axgle/mahonia" 5 | "golang.org/x/text/encoding/simplifiedchinese" 6 | ) 7 | 8 | //转换为utf8 9 | func ToUTF8(str string) string { 10 | if str == "" { 11 | return str 12 | } 13 | srcCoder := mahonia.NewDecoder("gbk") 14 | srcResult := srcCoder.ConvertString(str) 15 | tagCoder := mahonia.NewDecoder("utf-8") 16 | _, cdata, _ := tagCoder.Translate([]byte(srcResult), true) 17 | result := string(cdata) 18 | return result 19 | } 20 | 21 | //检查内容是不是utf8 22 | func IsUTF8(str string) bool { 23 | buf := []byte(str) 24 | nBytes := 0 25 | for i := 0; i < len(buf); i++ { 26 | if nBytes == 0 { 27 | if (buf[i] & 0x80) != 0 { //与操作之后不为0,说明首位为1 28 | for (buf[i] & 0x80) != 0 { 29 | buf[i] <<= 1 //左移一位 30 | nBytes++ //记录字符共占几个字节 31 | } 32 | if nBytes < 2 || nBytes > 6 { //因为UTF8编码单字符最多不超过6个字节 33 | return false 34 | } 35 | nBytes-- //减掉首字节的一个计数 36 | } 37 | } else { //处理多字节字符 38 | if buf[i]&0xc0 != 0x80 { //判断多字节后面的字节是否是10开头 39 | return false 40 | } 41 | nBytes-- 42 | } 43 | } 44 | return nBytes == 0 45 | } 46 | 47 | //utf8编码转gbk编码 48 | func Utf8ToGbk(str string) string { 49 | h, _ := simplifiedchinese.GBK.NewEncoder().Bytes([]byte(str)) 50 | return string(h) 51 | } 52 | -------------------------------------------------------------------------------- /utils/csv.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "encoding/csv" 5 | "fmt" 6 | "io" 7 | "os" 8 | ) 9 | 10 | //读取csv 11 | func ReadCsv(filename string) ([][]string, error) { 12 | 13 | if filename == "" { 14 | return nil, nil 15 | } 16 | 17 | file, err := os.Open(filename) 18 | if err != nil { 19 | fmt.Println("Error:", err) 20 | return nil, err 21 | } 22 | defer file.Close() 23 | reader := csv.NewReader(file) 24 | data := [][]string{} 25 | for { 26 | record, err := reader.Read() 27 | if err == io.EOF { 28 | break 29 | } 30 | data = append(data, record) 31 | } 32 | return data, nil 33 | } -------------------------------------------------------------------------------- /utils/encode.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "encoding/base64" 5 | "fmt" 6 | "os" 7 | "strings" 8 | ) 9 | 10 | // base64编码 11 | func Base64Encode(s string) string { 12 | if s == "" { 13 | return "" 14 | } 15 | encodeString := base64.StdEncoding.EncodeToString([]byte(s)) 16 | return strings.Replace(encodeString, "=", "_", -1) 17 | } 18 | 19 | // base64解码 20 | func Base64Decode(s string) string { 21 | if s == "" { 22 | return "" 23 | } 24 | 25 | s = strings.Replace(s, "_", "=", -1) 26 | decodeBytes, err := base64.StdEncoding.DecodeString(s) 27 | if err != nil { 28 | return "" 29 | } 30 | return string(decodeBytes) 31 | } 32 | 33 | // base64转换为文件 34 | func Base64ToFile(base64Str, outputPath string) error { 35 | // 提取base64数据部分 36 | data := base64Str 37 | if idx := strings.Index(base64Str, "base64,"); idx != -1 { 38 | data = base64Str[idx+7:] 39 | fmt.Println("=========", idx, idx+7) 40 | 41 | } 42 | 43 | // 解码base64内容 44 | decodedData, err := base64.StdEncoding.DecodeString(data) 45 | if err != nil { 46 | return fmt.Errorf("decode base64 string: %v", err) 47 | } 48 | 49 | // 写入文件 50 | err = os.WriteFile(outputPath, decodedData, 0666) 51 | if err != nil { 52 | return fmt.Errorf("write file: %v", err) 53 | } 54 | 55 | return nil 56 | } 57 | -------------------------------------------------------------------------------- /utils/html.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "regexp" 5 | ) 6 | 7 | //从html中抽取img地址 8 | func GetImgPathByHtml(htmls string) []string { 9 | var imgRE = regexp.MustCompile(`]+\bsrc=["']([^"']+)["']`) 10 | imgs := imgRE.FindAllStringSubmatch(htmls, -1) 11 | out := make([]string, len(imgs)) 12 | for i := range out { 13 | out[i] = imgs[i][1] 14 | } 15 | return out 16 | } 17 | -------------------------------------------------------------------------------- /utils/http.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | "io/ioutil" 8 | "net/http" 9 | "net/url" 10 | "os" 11 | "time" 12 | ) 13 | 14 | const HttpTimeout = 3 * time.Second 15 | 16 | // get请求 17 | func HttpGet(uri string, headers map[string]string) ([]byte, error) { 18 | // 表单数据 19 | 20 | // 创建一个新的请求对象 21 | req, err := http.NewRequest("GET", uri, nil) 22 | if err != nil { 23 | fmt.Println(err) 24 | return nil, err 25 | } 26 | 27 | // 设置请求头部 28 | if headers != nil && len(headers) > 0 { 29 | for k, v := range headers { 30 | req.Header.Set(k, v) 31 | } 32 | } 33 | // 发送请求 34 | client := &http.Client{Timeout: HttpTimeout} 35 | resp, err := client.Do(req) 36 | if err != nil { 37 | fmt.Println(err) 38 | return nil, err 39 | } 40 | defer resp.Body.Close() 41 | 42 | bodyBytes, err := ioutil.ReadAll(resp.Body) 43 | if err != nil { 44 | fmt.Println(err) 45 | return nil, err 46 | } 47 | return bodyBytes, nil 48 | } 49 | 50 | // post form 请求 51 | func HttpPostForm(uri string, formData, headers map[string]string) ([]byte, error) { 52 | // 表单数据 53 | data := url.Values{} 54 | if formData != nil && len(formData) > 0 { 55 | for k, v := range formData { 56 | data.Set(k, v) 57 | } 58 | } 59 | 60 | // 创建一个缓冲区来存储表单编码后的数据 61 | buf := bytes.NewBufferString(data.Encode()) 62 | 63 | // 创建一个新的请求对象 64 | req, err := http.NewRequest("POST", uri, buf) 65 | if err != nil { 66 | fmt.Println(err) 67 | return nil, err 68 | } 69 | 70 | // 设置请求头部 71 | req.Header.Set("Content-Type", "application/x-www-form-urlencoded") 72 | if headers != nil && len(headers) > 0 { 73 | for k, v := range headers { 74 | req.Header.Set(k, v) 75 | } 76 | } 77 | // 发送请求 78 | client := &http.Client{Timeout: HttpTimeout} 79 | resp, err := client.Do(req) 80 | if err != nil { 81 | fmt.Println(err) 82 | return nil, err 83 | } 84 | defer resp.Body.Close() 85 | 86 | bodyBytes, err := io.ReadAll(resp.Body) 87 | if err != nil { 88 | fmt.Println(err) 89 | return nil, err 90 | } 91 | return bodyBytes, nil 92 | } 93 | 94 | // 下载文件到本地 95 | func DownloadFile(httpUrl string, localPath string) error { 96 | 97 | //下载文件 98 | response, err := http.Get(httpUrl) 99 | if err != nil { 100 | return err 101 | } 102 | 103 | dir := GetFilePath(localPath) 104 | 105 | if err = CreateDir(dir); err != nil { 106 | return err 107 | } 108 | 109 | f, err := os.Create(localPath) 110 | defer f.Close() 111 | 112 | if err != nil { 113 | return err 114 | } 115 | 116 | if _, err := io.Copy(f, response.Body); err != nil { 117 | return err 118 | } 119 | 120 | return nil 121 | } 122 | -------------------------------------------------------------------------------- /utils/image.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "errors" 5 | "github.com/nfnt/resize" 6 | "image" 7 | "image/jpeg" 8 | "os" 9 | "strings" 10 | ) 11 | 12 | func CreateThumbnail(src string, dst string, imgWidth int, quality int) error { 13 | 14 | format := strings.ToLower(GetFileExt(src)) 15 | 16 | //检查图片格式是否支持 17 | allowFormats := map[string]uint8{".gif": 1, ".jpg": 1, ".jpeg": 1, ".png": 1} 18 | if _, ok := allowFormats[format]; !ok { 19 | return nil 20 | } 21 | 22 | if !FileExists(src) { 23 | return errors.New("file not exists") 24 | } 25 | 26 | //读取文件 27 | fa, err := os.Open(src) 28 | if err != nil { 29 | return err 30 | } 31 | defer fa.Close() 32 | 33 | config, _, err := image.DecodeConfig(fa) 34 | if err != nil { 35 | return err 36 | } 37 | 38 | if config.Width < imgWidth { 39 | return errors.New("no need dst scale") 40 | } 41 | 42 | fb, err := os.Open(src) 43 | if err != nil { 44 | return err 45 | } 46 | defer fb.Close() 47 | origin, _, err := image.Decode(fb) 48 | if err != nil { 49 | return err 50 | } 51 | 52 | // 做等比缩放 53 | width := uint(imgWidth) 54 | height := uint(imgWidth * config.Height / config.Width) 55 | 56 | canvas := resize.Thumbnail(width, height, origin, resize.Lanczos3) 57 | 58 | if FileExists(dst) { 59 | FileDelete(dst) 60 | } 61 | 62 | file_out, err := os.Create(dst) 63 | if err != nil { 64 | return err 65 | } 66 | defer file_out.Close() 67 | 68 | //生成图片 69 | err = jpeg.Encode(file_out, canvas, &jpeg.Options{quality}) 70 | if err != nil { 71 | return err 72 | } 73 | 74 | return nil 75 | } 76 | -------------------------------------------------------------------------------- /utils/log.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "io" 5 | "os" 6 | "runtime" 7 | "strconv" 8 | "time" 9 | ) 10 | 11 | /* 12 | 写日志 13 | */ 14 | func WriteLog(str string) { 15 | fileName := "log.txt" 16 | 17 | cachePath := "./cache/" 18 | if err := CreateDir(cachePath); err != nil { 19 | return 20 | } 21 | 22 | f, _ := os.OpenFile(cachePath+fileName, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) 23 | 24 | c_date := time.Now().Format("2006-01-02 15:04:05") 25 | _, c_file, c_line, _ := runtime.Caller(1) 26 | 27 | content := c_date + "\t" //日期 28 | content += c_file + "\t" //文件 29 | content += strconv.Itoa(c_line) + "\t" //行号 30 | content += str + "\r\n" //内容 31 | 32 | if _, err := io.WriteString(f, content); err != nil { 33 | return 34 | } 35 | 36 | defer f.Close() 37 | return 38 | } 39 | -------------------------------------------------------------------------------- /utils/pinyin/.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.11.x 5 | 6 | os: 7 | - linux 8 | - osx 9 | - windows 10 | 11 | before_install: 12 | - go get -v ./... 13 | 14 | script: 15 | - go test ./... -race -coverprofile=coverage.txt -covermode=atomic 16 | 17 | after_success: 18 | - bash <(curl -s https://codecov.io/bash) -------------------------------------------------------------------------------- /utils/pinyin/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Chain Zhang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /utils/pinyin/Makefile: -------------------------------------------------------------------------------- 1 | PKG_LIST := $(shell go list ./... | grep -v /vendor/) 2 | 3 | test: 4 | @go test -short -cover ${PKG_LIST} -------------------------------------------------------------------------------- /utils/pinyin/README.md: -------------------------------------------------------------------------------- 1 | # pinyin 2 | 3 | [![Build Status](https://travis-ci.com/Chain-Zhang/pinyin.svg?branch=master)](https://travis-ci.com/Chain-Zhang/pinyin) 4 | [![codecov](https://codecov.io/gh/Chain-Zhang/pinyin/branch/master/graph/badge.svg)](https://codecov.io/gh/Chain-Zhang/pinyin) 5 | 6 | golang实现中文汉字转拼音 7 | 8 | demo 9 | 10 | ```go 11 | package main 12 | 13 | import( 14 | "fmt" 15 | "github.com/chain-zhang/pinyin" 16 | ) 17 | 18 | func main() { 19 | str, err := pinyin.New("我是中国人").Split("").Mode(InitialsInCapitals).Convert() 20 | if err != nil { 21 | // 错误处理 22 | }else{ 23 | fmt.Println(str) 24 | } 25 | 26 | str, err = pinyin.New("我是中国人").Split(" ").Mode(pinyin.WithoutTone).Convert() 27 | if err != nil { 28 | // 错误处理 29 | }else{ 30 | fmt.Println(str) 31 | } 32 | 33 | str, err = pinyin.New("我是中国人").Split("-").Mode(pinyin.Tone).Convert() 34 | if err != nil { 35 | // 错误处理 36 | }else{ 37 | fmt.Println(str) 38 | } 39 | 40 | str, err = pinyin.New("我是中国人").Convert() 41 | if err != nil { 42 | // 错误处理 43 | }else{ 44 | fmt.Println(str) 45 | } 46 | } 47 | ``` 48 | 49 | 输出 50 | 51 | ```bash 52 | WoShiZhongGuoRen 53 | wo shi zhong guo ren 54 | wǒ-shì-zhōng-guó-rén 55 | wo shi zhong guo ren 56 | ``` 57 | 58 | Mode 介绍 59 | 60 | * `InitialsInCapitals`: 首字母大写, 不带音调 61 | * `WithoutTone`: 全小写,不带音调 62 | * `Tone`: 全小写带音调 63 | 64 | Split 介绍 65 | 66 | split 方法是两个汉字之间的分隔符. -------------------------------------------------------------------------------- /utils/pinyin/codecov.yml: -------------------------------------------------------------------------------- 1 | codecov: 2 | token: f3bed817-d998-44c1-b474-168d03f438bd -------------------------------------------------------------------------------- /utils/pinyin/error.go: -------------------------------------------------------------------------------- 1 | package pinyin 2 | 3 | import "errors" 4 | 5 | var ( 6 | ErrInitialize = errors.New("not yet initialized") 7 | ) 8 | -------------------------------------------------------------------------------- /utils/pinyin/pinyin_test.go: -------------------------------------------------------------------------------- 1 | package pinyin 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestConvert(t *testing.T) { 8 | str, err := New("hi,我是中国人").Split("").Mode(InitialsInCapitals).Convert() 9 | if err != nil { 10 | t.Error(err) 11 | } else { 12 | t.Log(str) 13 | } 14 | 15 | str, err = New("hi, 我是中国人").Split(" ").Mode(WithoutTone).Convert() 16 | if err != nil { 17 | t.Error(err) 18 | } else { 19 | t.Log(str) 20 | } 21 | 22 | str, err = New("我是hahah中国人").Split("-").Mode(Tone).Convert() 23 | if err != nil { 24 | t.Error(err) 25 | } else { 26 | t.Log(str) 27 | } 28 | 29 | str, err = New("我是h中国人a").Convert() 30 | if err != nil { 31 | t.Error(err) 32 | } else { 33 | t.Log(str) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /utils/rom.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | var prefix = "t0Ic" //前缀 8 | 9 | // 创建rom唯一id 10 | func CreateRomUniqId(t string, s int64) string { 11 | return prefix + "_" + t + "_" + ToString(s) 12 | } 13 | 14 | // 分割rom唯一id 15 | func SplitRomUniqId(uniqId string) (string, string) { 16 | if uniqId == "" { 17 | return "", "" 18 | } 19 | sli := strings.Split(uniqId, "_") 20 | return sli[1], sli[2] //返回 时间,大小 21 | } 22 | 23 | // 检查是不是唯一ID 24 | func HasRomUniqId(uniqId string) bool { 25 | if uniqId == "" { 26 | return false 27 | } 28 | sli := strings.Split(uniqId, "_") 29 | 30 | if sli[0] == prefix { 31 | return true 32 | } 33 | return false 34 | } 35 | 36 | // 读取rom的目录 37 | func getSplitRomMenu(rel string) string { 38 | p := GetFilePath(rel) 39 | parr := strings.Split(p, "/") 40 | parr = SliceRemoveEmpty(parr) 41 | menu := "_7b9" 42 | if len(parr) > 0 { 43 | menu = menu + "/" + parr[0] 44 | } 45 | return menu 46 | } 47 | 48 | // 处理rom路径为2级 49 | func HandleRomMenu(p string) string { 50 | rel := GetFilePath(p) 51 | arr := strings.Split(rel, "/") 52 | if len(arr) > 3 { 53 | arr = arr[0:3] 54 | rel = strings.Join(arr, "/") + "/" 55 | } 56 | return rel 57 | } 58 | 59 | // 读取map中第一个key和value 60 | func MapGetFirst[K, V comparable](mp map[K]V) (K, V) { 61 | var key K 62 | var val V 63 | 64 | if len(mp) == 0 { 65 | return key, val 66 | } 67 | 68 | for k, v := range mp { 69 | return k, v 70 | } 71 | return key, val 72 | } 73 | -------------------------------------------------------------------------------- /utils/string.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "bytes" 5 | "crypto/md5" 6 | "fmt" 7 | "math/rand" 8 | "regexp" 9 | "simUI/utils/pinyin" 10 | "strings" 11 | "time" 12 | ) 13 | 14 | /** 15 | * 字符串MD5 16 | **/ 17 | func Md5(str string) string { 18 | data := []byte(str) 19 | has := md5.Sum(data) 20 | return fmt.Sprintf("%x", has) 21 | } 22 | 23 | /** 24 | * 字符转拼音 25 | **/ 26 | func TextToPinyin(str string) string { 27 | str, err := pinyin.New(str).Split("").Mode(pinyin.WithoutTone).Convert() 28 | if err != nil { 29 | // 错误处理 30 | } 31 | str = strings.ToLower(str) 32 | str = strings.ReplaceAll(str, " ", "") 33 | return str 34 | } 35 | 36 | func Addslashes(str string) string { 37 | var buf bytes.Buffer 38 | for _, char := range str { 39 | switch char { 40 | case '\'', '"', '\\': 41 | buf.WriteRune('\\') 42 | } 43 | buf.WriteRune(char) 44 | } 45 | return buf.String() 46 | } 47 | 48 | // 输入一个范围,生成范围随机数 49 | func RandInt(min, max int) int { 50 | rand.Seed(time.Now().UnixNano()) 51 | return rand.Intn(max-min+1) + min 52 | } 53 | 54 | // 输入一个长度,返回一个随机字符串 55 | func RandStr(min, max int) string { 56 | rand.Seed(time.Now().UnixNano()) 57 | 58 | length := rand.Intn(max-min+1) + min 59 | result := make([]byte, length) 60 | 61 | for i := 0; i < length; i++ { 62 | result[i] = byte(rand.Intn(26) + 97) 63 | } 64 | 65 | return string(result) 66 | } 67 | 68 | // 检查一个字符串是不是纯英文 69 | func IsEnglish(str string) bool { 70 | reg := regexp.MustCompile(`[^a-zA-Z0-9!"# $%&'()*+,-./:;<=>?@[\\\]^_` + "`" + `{|}~]+`) 71 | enStr := reg.ReplaceAllString(str, "") 72 | return len(enStr) == len(str) 73 | } 74 | 75 | // 首字母大写 76 | func ToTitleCase(str string) string { 77 | if str == "" { 78 | return "" 79 | } 80 | return strings.ToUpper(string(str[0])) + str[1:] 81 | } 82 | -------------------------------------------------------------------------------- /utils/struct.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "errors" 5 | "reflect" 6 | ) 7 | 8 | // 根据Key,读取struct value 9 | // T是值或指针都可以 10 | func GetStructValue[T, V comparable](stu T, k string) V { 11 | var value V 12 | switch reflect.TypeOf(stu).Kind() { 13 | case reflect.Struct: 14 | value = reflect.ValueOf(stu).FieldByName(k).Interface().(V) 15 | case reflect.Ptr: 16 | isset := reflect.ValueOf(stu).Elem().FieldByName(k) 17 | if isset.IsValid() { 18 | value = reflect.ValueOf(stu).Elem().FieldByName(k).Interface().(V) 19 | } 20 | } 21 | return value 22 | } 23 | 24 | // 根据Key,设置struct value 25 | // T 必须是指针 26 | func SetStructValue[T comparable](stu T, k string, val any) error { 27 | 28 | structValue := reflect.ValueOf(stu).Elem() 29 | field := structValue.FieldByName(k) 30 | 31 | if !field.IsValid() { 32 | return errors.New("no such field") 33 | } 34 | 35 | if !field.CanSet() { 36 | return errors.New("cannot set this field") 37 | } 38 | 39 | value := ToString(val) 40 | field.Set(reflect.ValueOf(value)) 41 | return nil 42 | } 43 | -------------------------------------------------------------------------------- /utils/system.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | ) 7 | 8 | // 检查端口是否占用 9 | func IsPortInUse(port int) bool { 10 | ln, err := net.Listen("tcp", ":"+fmt.Sprint(port)) 11 | if err != nil { 12 | return true 13 | } 14 | defer ln.Close() 15 | return false 16 | } 17 | -------------------------------------------------------------------------------- /utils/translate.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "encoding/json" 5 | ) 6 | 7 | type BaiduTrans struct { 8 | From string `json:"from"` 9 | To string `json:"to"` 10 | TransResult []struct { 11 | Src string `json:"src"` 12 | Dst string `json:"dst"` 13 | } `json:"trans_result"` 14 | } 15 | 16 | // 百度翻译 to: zh/en/cht 17 | func BaiduTranslate(keyword string, to string) string { 18 | postUrl := "https://fanyi-api.baidu.com/api/trans/vip/translate?" 19 | appId := "20230421001650804" 20 | scriet := "FzLUusAtVas4v1FJJphE" 21 | salt := ToString(RandInt(8, 8)) 22 | sign := appId + keyword + salt + scriet 23 | 24 | data := map[string]string{ 25 | "q": keyword, 26 | "from": "auto", 27 | "to": to, 28 | "appid": appId, 29 | "salt": salt, 30 | "sign": Md5(sign), 31 | } 32 | resp, err := HttpPostForm(postUrl, data, nil) 33 | if err != nil { 34 | WriteLog("BaiduTranslate Error:" + err.Error()) 35 | return "" 36 | } 37 | 38 | var body BaiduTrans 39 | json.Unmarshal(resp, &body) 40 | if len(body.TransResult) == 0 { 41 | return "" 42 | } 43 | 44 | return body.TransResult[0].Dst 45 | } 46 | -------------------------------------------------------------------------------- /utils/uuid.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import "github.com/google/uuid" 4 | 5 | // 生成uuid 6 | func CreateUUid() string { 7 | return uuid.New().String() 8 | } 9 | -------------------------------------------------------------------------------- /utils/version.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | // 比较版本号 9 | func CompareVersion(v1, v2 string) int { 10 | ver1 := strings.Split(v1, ".") // 将版本号按照"."分割为切片 11 | ver2 := strings.Split(v2, ".") 12 | 13 | for i := 0; i < len(ver1) || i < len(ver2); i++ { 14 | num1 := 0 15 | num2 := 0 16 | 17 | if i < len(ver1) { 18 | fmt.Sscanf(ver1[i], "%d", &num1) 19 | } 20 | 21 | if i < len(ver2) { 22 | fmt.Sscanf(ver2[i], "%d", &num2) 23 | } 24 | 25 | if num1 > num2 { 26 | return 1 27 | } else if num1 < num2 { 28 | return -1 29 | } 30 | } 31 | 32 | return 0 33 | } 34 | -------------------------------------------------------------------------------- /utils/wails.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | const wailsPathPrefixHead = "/ASSET/" 8 | const wailsPathPrefixEnd = ".asset" 9 | const WailsPathPattern = `(/ASSET/.+?\.asset)` //正则 10 | 11 | // wails 路径编码 12 | func WailsPathEncode(p string, toAbs bool) string { 13 | if p == "" { 14 | return p 15 | } 16 | 17 | if strings.HasPrefix(p, wailsPathPrefixHead) { 18 | return p 19 | } 20 | 21 | if p == "" { 22 | return "" 23 | } 24 | if toAbs && !IsAbsPath(p) { 25 | p = ToAbsPath(p, "") 26 | } 27 | return wailsPathPrefixHead + Base64Encode(p) + wailsPathPrefixEnd 28 | } 29 | 30 | // wails 路径解码 31 | func WailsPathDecode(p string) string { 32 | if p == "" { 33 | return p 34 | } 35 | if !strings.HasPrefix(p, wailsPathPrefixHead) { 36 | return p 37 | } 38 | 39 | p = strings.Replace(p, wailsPathPrefixHead, "", 1) 40 | p = strings.Replace(p, wailsPathPrefixEnd, "", 1) 41 | return Base64Decode(p) 42 | } 43 | 44 | // 检查是否为 wails路径 true是,false不是 45 | func WailsPathCheck(p string) bool { 46 | if p == "" { 47 | return false 48 | } 49 | return strings.Contains(p, wailsPathPrefixHead) 50 | } 51 | -------------------------------------------------------------------------------- /wails.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "simui", 3 | "outputfilename": "simui", 4 | "frontend:install": "npm install", 5 | "frontend:build": "npm run build", 6 | "frontend:dev:watcher": "npm run dev", 7 | "author": { 8 | "name": "frontlon", 9 | "email": "front_diablo@163.com" 10 | } 11 | } --------------------------------------------------------------------------------