Skip to content

Commit 4c6970c

Browse files
Merge pull request #151 from rommapp/v4.7.0.0
v4.7.0.0
2 parents 72cf2c8 + a9cdd89 commit 4c6970c

103 files changed

Lines changed: 7284 additions & 3598 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/CHANGELOG.md

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,52 @@
1+
# v4.7.0.0
2+
3+
## New Features
4+
5+
- **Save Sync Rebuilt**: Save Sync has been completely rebuilt from the ground up with device-aware syncing powered by RomM's new save sync API (#135)
6+
- **Conflict Resolution**: New conflict resolution screen when both local and remote saves have changed since last sync
7+
- **Save Slots**: User-configurable save slots per game, with slot selection during first sync and manageable from the Synced Games screen
8+
- **Backup Retention**: Configurable backup retention policy for save files (keep all, last 5, 10, or 15)
9+
- **Sync History**: View a chronological log (stored locally) of all sync actions (uploads, downloads) for this device
10+
- **Synced Games Browser**: Browse all synced games grouped by platform, view save details, and manage save slots
11+
- **Sync Menu**: New dedicated sync menu accessible via `Y` from the main menu
12+
- **Device Registration**: Device registration is performed when setting up save sync, can change device name anytime in settings (#134)
13+
- **Batocera Support**: Preliminary support for Batocera CFW (#157) - @from-nibly
14+
- **Server Settings Reconfiguration**: Reconfigure server host, port, protocol, and SSL verification without logging out (#146)
15+
- **Flip Face Buttons**: New setting to swap face buttons (A <-> B, X <-> Y) for devices where the layout doesn't match expectations (#153)
16+
- **Cache Purging**: Local cache now purges deleted items using RomM's new identifier endpoints, keeping the cache in sync with the server more efficiently (#83)
17+
- **muOS Jacaranda Icon Support**: Grout icons now display correctly on muOS Jacaranda and prior versions - @pawndev
18+
19+
## Improvements
20+
21+
- **Save Sync Platform Mapping**: Restored save sync platform mapping for manual directory configuration
22+
- **muOS Splash & Preview Options**: Configuration for downloading screenshot, title, and marquee artwork (#86) - @pawndev
23+
- **Gamelist.xml Support**: Added miyoogamelist.xml support for Spruce and Allium (#140) - @pawndev
24+
- **Trimui Stock Save Sync**: Added save directory mappings for Trimui stock OS (#128) - @malkavi
25+
- Removed state machine from search for cleaner navigation
26+
- Updated translations across all supported languages (#123)
27+
28+
## Bug Fixes
29+
30+
- Fixed save mapping for muOS NDS
31+
- Fixed launch scripts and binaries not being executable in zip files (#155)
32+
- Fixed Spruce launch script - @pawndev
33+
- Fixed initial device registration flow
34+
- Fixed Trimui packaging - @malkavi
35+
36+
## Documentation
37+
38+
- Added Anbernic RG34XX as tested on ROCKNIX - @SethBarberee
39+
- Added Anbernic RG34XXSP to muOS installation guide
40+
- Added Anbernic RG CUBE to muOS compatibility list
41+
- Added RGB30 to ROCKNIX installation guide - @pawndev
42+
- Updated Knulli tested devices
43+
44+
## Contributors
45+
46+
Thanks to @pawndev, @from-nibly, @malkavi, @SethBarberee, and @ivan for their contributions to this release! :heart:
47+
48+
---
49+
150
# v4.6.1.0
251

352
## New Features

.github/workflows/release.yml

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -49,11 +49,11 @@ jobs:
4949
pak.json > pak.json.tmp && mv pak.json.tmp pak.json
5050
5151
# Update build pak.json (will be copied during build)
52-
mkdir -p build/Grout.pak
52+
mkdir -p build64/Grout.pak
5353
jq --arg version "${{ steps.version.outputs.value }}" \
5454
--arg changelog "${{ inputs.changelog }}" \
5555
'.version = $version | .changelog[$version] = $changelog' \
56-
pak.json > build/Grout.pak/pak.json
56+
pak.json > build64/Grout.pak/pak.json
5757
else
5858
echo "Version is already ${{ steps.version.outputs.value }}, skipping pak.json update"
5959
fi
@@ -78,41 +78,49 @@ jobs:
7878
version: 3.x
7979
repo-token: ${{ secrets.GITHUB_TOKEN }}
8080

81+
- name: Set up Docker Buildx
82+
uses: docker/setup-buildx-action@v3
83+
8184
- name: Build and Package
82-
run: task build extract package-next package-muos package-knulli package-spruce package-rocknix package-trimui
85+
run: task build extract package-next package-muos package-knulli package-spruce package-rocknix package-trimui package-batocera
8386

8487
- name: Create NextUI distribution
8588
run: |
86-
cd build/Grout.pak
89+
cd build64/Grout.pak
8790
zip -r ../Grout.pak.zip .
8891
8992
- name: Create muOS distribution
9093
run: |
91-
cd build/muOS
94+
cd build64/muOS
9295
zip -r Grout.muxapp Grout
9396
mv Grout.muxapp ../Grout.muxapp
9497
9598
- name: Create Knulli distribution
9699
run: |
97-
cd build/Knulli
100+
cd build64/Knulli
98101
zip -r ../Grout-Knulli.zip Grout
99102
100103
- name: Create spruce distribution
101104
run: |
102-
cd build/Spruce
105+
cd build64/Spruce
103106
zip -r Grout.spruce.zip Grout
104107
mv Grout.spruce.zip ../Grout.spruce.zip
105108
106109
- name: Create ROCKNIX distribution
107110
run: |
108-
cd build/ROCKNIX
111+
cd build64/ROCKNIX
109112
zip -r ../Grout-ROCKNIX.zip Grout.sh Grout
110113
111114
- name: Create Trimui distribution
112115
run: |
113-
cd build/Trimui
116+
cd build64/Trimui
114117
zip -r ../Grout-Trimui.zip Grout
115118
119+
- name: Create Batocera distribution
120+
run: |
121+
cd build/Batocera
122+
zip -r ../Grout-Batocera.zip Grout.sh Grout
123+
116124
- name: Create GitHub Release
117125
uses: softprops/action-gh-release@v2
118126
with:
@@ -125,12 +133,13 @@ jobs:
125133
126134
${{ inputs.beta && format('Built from branch: `{0}`', github.ref_name) || '' }}
127135
files: |
128-
build/Grout.pak.zip
129-
build/Grout.muxapp
130-
build/Grout.spruce.zip
131-
build/Grout-Knulli.zip
132-
build/Grout-ROCKNIX.zip
133-
build/Grout-Trimui.zip
134-
build/grout
136+
build64/Grout.pak.zip
137+
build64/Grout.muxapp
138+
build64/Grout.spruce.zip
139+
build64/Grout-Knulli.zip
140+
build64/Grout-ROCKNIX.zip
141+
build64/Grout-Trimui.zip
142+
build64/Grout-Batocera.zip
143+
build64/grout
135144
draft: false
136145
prerelease: ${{ inputs.beta }}

.gitignore

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@
66
*.iml
77

88
# Build
9-
build/
9+
build64/
1010
build32/
1111
build-miyoo/
1212
grout
1313
app/app
1414
es_restart_request
15+
/save-sync-preview
1516

1617
# Local config
1718
.env
@@ -36,3 +37,6 @@ __pycache__/
3637

3738
# MkDocs build output
3839
site/
40+
41+
/save_slots.json
42+
/create-conflict

app/grout.go

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@ import (
66

77
_ "github.com/BrandonKowalski/certifiable"
88
gaba "github.com/BrandonKowalski/gabagool/v2/pkg/gabagool"
9-
"github.com/BrandonKowalski/gabagool/v2/pkg/gabagool/i18n"
10-
goi18n "github.com/nicksnyder/go-i18n/v2/i18n"
119
)
1210

1311
func main() {
@@ -30,18 +28,6 @@ func main() {
3028
}
3129

3230
func cleanup() {
33-
if currentAppState != nil && currentAppState.AutoSync != nil && currentAppState.AutoSync.IsRunning() {
34-
gaba.GetLogger().Info("Waiting for auto-sync to complete before exiting...")
35-
gaba.ProcessMessage(
36-
i18n.Localize(&goi18n.Message{ID: "auto_sync_waiting", Other: "Waiting for save sync to complete..."}, nil),
37-
gaba.ProcessMessageOptions{},
38-
func() (interface{}, error) {
39-
currentAppState.AutoSync.Wait()
40-
return nil, nil
41-
},
42-
)
43-
}
44-
4531
if err := os.RemoveAll(".tmp"); err != nil {
4632
gaba.GetLogger().Error("Failed to clean .tmp directory", "error", err)
4733
}

app/helpers.go

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,6 @@ func savePlatformOrder(state *AppState, platforms []romm.Platform) {
3434
internal.SaveConfig(state.Config)
3535
}
3636

37-
func triggerAutoSyncRouter(state *AppState) {
38-
if state.AutoSync != nil {
39-
state.AutoSync.Trigger()
40-
}
41-
}
42-
4337
func executeDownloadUI(state *AppState, r ui.GameDetailsOutput, stack *router.Stack) {
4438
entry := stack.Peek()
4539
var allGames []romm.Rom

app/router.go

Lines changed: 44 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import (
55
"grout/cfw"
66
"grout/internal"
77
"grout/romm"
8-
"grout/sync"
98
"grout/ui"
109
"grout/update"
1110

@@ -38,7 +37,7 @@ func runWithRouter(config *internal.Config, currentCFW cfw.CFW, platforms []romm
3837
Platforms: &state.Platforms,
3938
QuitOnBack: quitOnBack,
4039
ShowCollections: showCollections,
41-
ShowSaveSync: computeShowSaveSync(state),
40+
ShowSaveSync: state.Host.DeviceID != "",
4241
}
4342

4443
return r.Run(ScreenPlatformSelection, initialInput)
@@ -81,24 +80,12 @@ func registerScreens(r *router.Router, state *AppState) {
8180
r.Register(ScreenPlatformSelection, func(input any) (any, error) {
8281
in := input.(ui.PlatformSelectionInput)
8382

84-
if state.Config.SaveSyncMode == internal.SaveSyncModeAutomatic {
85-
state.autoSyncOnce.Do(func() {
86-
state.AutoSync = sync.NewAutoSync(state.Host, state.Config)
87-
ui.AddStatusBarIcon(state.AutoSync.Icon())
88-
state.AutoSync.Start()
89-
})
90-
}
91-
9283
state.autoUpdateOnce.Do(func() {
9384
state.AutoUpdate = update.NewAutoUpdate(state.CFW, state.Config.ReleaseChannel, &state.Host)
9485
ui.AddStatusBarIcon(state.AutoUpdate.Icon())
9586
state.AutoUpdate.Start()
9687
})
9788

98-
if in.ShowSaveSync == nil {
99-
in.ShowSaveSync = computeShowSaveSync(state)
100-
}
101-
10289
screen := ui.NewPlatformSelectionScreen()
10390
return screen.Draw(in)
10491
})
@@ -163,11 +150,6 @@ func registerScreens(r *router.Router, state *AppState) {
163150
return screen.Draw(input.(ui.PlatformMappingInput))
164151
})
165152

166-
r.Register(ScreenSaveSyncSettings, func(input any) (any, error) {
167-
screen := ui.NewSaveSyncSettingsScreen()
168-
return screen.Draw(input.(ui.SaveSyncSettingsInput))
169-
})
170-
171153
r.Register(ScreenInfo, func(input any) (any, error) {
172154
screen := ui.NewInfoScreen()
173155
return screen.Draw(input.(ui.InfoInput))
@@ -185,11 +167,6 @@ func registerScreens(r *router.Router, state *AppState) {
185167
return screen.Draw(in)
186168
})
187169

188-
r.Register(ScreenSaveSync, func(input any) (any, error) {
189-
screen := ui.NewSaveSyncScreen()
190-
return screen.Draw(input.(ui.SaveSyncInput))
191-
})
192-
193170
r.Register(ScreenBIOSDownload, func(input any) (any, error) {
194171
in := input.(ui.BIOSDownloadInput)
195172
screen := ui.NewBIOSDownloadScreen()
@@ -213,4 +190,47 @@ func registerScreens(r *router.Router, state *AppState) {
213190
screen := ui.NewGameFiltersScreen()
214191
return screen.Draw(input.(ui.GameFiltersInput))
215192
})
193+
194+
r.Register(ScreenSaveSync, func(input any) (any, error) {
195+
in := input.(ui.SaveSyncInput)
196+
screen := ui.NewSaveSyncScreen()
197+
return screen.Execute(in), nil
198+
})
199+
200+
r.Register(ScreenSaveConflict, func(input any) (any, error) {
201+
screen := ui.NewSaveConflictScreen()
202+
return screen.Draw(input.(ui.SaveConflictInput))
203+
})
204+
205+
r.Register(ScreenSaveSyncSettings, func(input any) (any, error) {
206+
in := input.(ui.SaveSyncSettingsInput)
207+
screen := ui.NewSaveSyncSettingsScreen()
208+
return screen.Draw(in)
209+
})
210+
211+
r.Register(ScreenSyncMenu, func(input any) (any, error) {
212+
screen := ui.NewSyncMenuScreen()
213+
return screen.Draw(input.(ui.SyncMenuInput))
214+
})
215+
216+
r.Register(ScreenSyncedGames, func(input any) (any, error) {
217+
screen := ui.NewSyncedGamesScreen()
218+
return screen.Draw(input.(ui.SyncedGamesInput))
219+
})
220+
221+
r.Register(ScreenSyncHistory, func(input any) (any, error) {
222+
screen := ui.NewSyncHistoryScreen()
223+
return screen.Draw(input.(ui.SyncHistoryInput))
224+
})
225+
226+
r.Register(ScreenSaveMapping, func(input any) (any, error) {
227+
screen := ui.NewSaveMappingScreen()
228+
return screen.Draw(input.(ui.SaveMappingInput))
229+
})
230+
231+
r.Register(ScreenServerAddress, func(input any) (any, error) {
232+
screen := ui.NewServerAddressScreen()
233+
return screen.Draw(input.(ui.ServerAddressInput))
234+
})
235+
216236
}

app/screens.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,19 @@ const (
2121
ScreenCollectionsSettings
2222
ScreenAdvancedSettings
2323
ScreenPlatformMapping
24-
ScreenSaveSyncSettings
2524
ScreenInfo
2625
ScreenLogoutConfirmation
2726
ScreenRebuildCache
28-
ScreenSaveSync
2927
ScreenBIOSDownload
3028
ScreenArtworkSync
3129
ScreenUpdateCheck
3230
ScreenGameFilters
31+
ScreenSaveSync
32+
ScreenSaveSyncSettings
33+
ScreenSaveConflict
34+
ScreenSyncMenu
35+
ScreenSyncedGames
36+
ScreenSyncHistory
37+
ScreenSaveMapping
38+
ScreenServerAddress
3339
)

app/setup.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,11 @@ func setup() SetupResult {
5656
}
5757
}
5858

59+
// Pre-load config to apply settings that must be set before Init()
60+
if preConfig, err := internal.LoadConfig(); err == nil {
61+
gaba.SetFlipFaceButtons(preConfig.SwapFaceButtons)
62+
}
63+
5964
gaba.Init(gaba.Options{
6065
WindowTitle: "Grout",
6166
PrimaryThemeColorHex: 0x007C77,
@@ -145,6 +150,7 @@ func setup() SetupResult {
145150
}
146151

147152
internal.InitKidMode(config)
153+
gaba.SetFlipFaceButtons(config.SwapFaceButtons)
148154

149155
if internal.IsKidModeEnabled() {
150156
splashBytes, _ := resources.GetSplashImageBytes()

app/state.go

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import (
55
"grout/cfw"
66
"grout/internal"
77
"grout/romm"
8-
"grout/sync"
98
"grout/update"
109
gosync "sync"
1110
"sync/atomic"
@@ -21,24 +20,8 @@ type AppState struct {
2120

2221
RommVersion atomic.Value // string
2322

24-
AutoSync *sync.AutoSync
2523
AutoUpdate *update.AutoUpdate
2624
CacheSync *cache.BackgroundSync
2725

28-
autoSyncOnce gosync.Once
2926
autoUpdateOnce gosync.Once
3027
}
31-
32-
func computeShowSaveSync(state *AppState) *atomic.Bool {
33-
switch state.Config.SaveSyncMode {
34-
case internal.SaveSyncModeManual:
35-
showSaveSync := &atomic.Bool{}
36-
showSaveSync.Store(true)
37-
return showSaveSync
38-
case internal.SaveSyncModeAutomatic:
39-
if state.AutoSync != nil {
40-
return state.AutoSync.ShowButton()
41-
}
42-
}
43-
return nil
44-
}

0 commit comments

Comments
 (0)