mirror of
https://github.com/giongto35/cloud-game.git
synced 2026-07-25 19:13:52 +00:00
Add experimental core's SRAM save/load (#273)
* Add experimental core's SRAM save/load * Clean room.go * Update Save RAM states * Update dependencies * Extract emulator states into a separate module * Google Cloud saves don't support save RAM persistence, but save state (.dat) files only. * Add emulator storage path into the configuration file * Verify offline storage path
This commit is contained in:
parent
6584d8219c
commit
7bc9661b3d
16 changed files with 296 additions and 212 deletions
4
configs/config.yaml
vendored
4
configs/config.yaml
vendored
|
|
@ -73,6 +73,10 @@ emulator:
|
|||
width: 320
|
||||
height: 240
|
||||
|
||||
# save directory for emulator states
|
||||
# special tag {user} will be replaced with current user's home dir
|
||||
storage: "{user}/.cr/save"
|
||||
|
||||
libretro:
|
||||
cores:
|
||||
paths:
|
||||
|
|
|
|||
15
go.mod
vendored
15
go.mod
vendored
|
|
@ -13,6 +13,7 @@ require (
|
|||
github.com/gofrs/flock v0.8.0
|
||||
github.com/gofrs/uuid v4.0.0+incompatible
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b
|
||||
github.com/google/uuid v1.2.0 // indirect
|
||||
github.com/gorilla/mux v1.8.0
|
||||
github.com/gorilla/websocket v1.4.2
|
||||
github.com/kkyr/fig v0.2.0
|
||||
|
|
@ -21,20 +22,22 @@ require (
|
|||
github.com/pion/ice/v2 v2.0.15 // indirect
|
||||
github.com/pion/interceptor v0.0.9
|
||||
github.com/pion/rtp v1.6.2
|
||||
github.com/pion/sdp/v3 v3.0.4 // indirect
|
||||
github.com/pion/webrtc/v3 v3.0.3
|
||||
github.com/pion/webrtc/v3 v3.0.4
|
||||
github.com/prometheus/client_golang v1.9.0
|
||||
github.com/prometheus/procfs v0.3.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/veandco/go-sdl2 v0.4.5
|
||||
go.opencensus.io v0.22.6 // indirect
|
||||
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad
|
||||
golang.org/x/image v0.0.0-20201208152932-35266b937fa6
|
||||
golang.org/x/mod v0.4.1 // indirect
|
||||
golang.org/x/oauth2 v0.0.0-20210113205817-d3ed898aa8a3 // indirect
|
||||
golang.org/x/sys v0.0.0-20210113181707-4bcb84eeeb78 // indirect
|
||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777 // indirect
|
||||
golang.org/x/oauth2 v0.0.0-20210126194326-f9ce19ea3013 // indirect
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c // indirect
|
||||
golang.org/x/text v0.3.5 // indirect
|
||||
golang.org/x/tools v0.0.0-20210115202250-e0d201561e39 // indirect
|
||||
google.golang.org/genproto v0.0.0-20210114201628-6edceaf6022f // indirect
|
||||
golang.org/x/tools v0.1.0 // indirect
|
||||
google.golang.org/api v0.38.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20210126160654-44e461bb6506 // indirect
|
||||
google.golang.org/grpc v1.35.0 // indirect
|
||||
gopkg.in/hraban/opus.v2 v2.0.0-20201025103112-d779bb1cc5a2
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
|
|
|
|||
44
go.sum
vendored
44
go.sum
vendored
|
|
@ -16,6 +16,7 @@ cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHOb
|
|||
cloud.google.com/go v0.66.0 h1:DZeAkuQGQqnm9Xv36SbMJEU8aFBz4wL04UpMWPWwjzg=
|
||||
cloud.google.com/go v0.66.0/go.mod h1:dgqGAjKCDxyhGTtC9dAREQGUJpkceNm1yt590Qno0Ko=
|
||||
cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
|
||||
cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
|
||||
cloud.google.com/go v0.75.0 h1:XgtDnVJRCPEUG21gjFiRPz4zI1Mjg16R+NYQjfmU4XY=
|
||||
cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY=
|
||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||
|
|
@ -183,6 +184,7 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
|||
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M=
|
||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
|
|
@ -201,6 +203,7 @@ github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hf
|
|||
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200905233945-acf8798be1f7/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
|
|
@ -208,6 +211,8 @@ github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
|
|||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.1.5 h1:kxhtnfFVi+rYdOALN0B3k9UT86zVJKfBimRaciULW4I=
|
||||
github.com/google/uuid v1.1.5/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
|
||||
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
|
|
@ -369,8 +374,6 @@ github.com/pion/sctp v1.7.10 h1:o3p3/hZB5Cx12RMGyWmItevJtZ6o2cpuxaw6GOS4x+8=
|
|||
github.com/pion/sctp v1.7.10/go.mod h1:EhpTUQu1/lcK3xI+eriS6/96fWetHGCvBi9MSsnaBN0=
|
||||
github.com/pion/sctp v1.7.11 h1:UCnj7MsobLKLuP/Hh+JMiI/6W5Bs/VF45lWKgHFjSIE=
|
||||
github.com/pion/sctp v1.7.11/go.mod h1:EhpTUQu1/lcK3xI+eriS6/96fWetHGCvBi9MSsnaBN0=
|
||||
github.com/pion/sdp/v3 v3.0.3 h1:gJK9hk+JFD2NGIM1nXmqNCq1DkVaIZ9dlA3u3otnkaw=
|
||||
github.com/pion/sdp/v3 v3.0.3/go.mod h1:bNiSknmJE0HYBprTHXKPQ3+JjacTv5uap92ueJZKsRk=
|
||||
github.com/pion/sdp/v3 v3.0.4 h1:2Kf+dgrzJflNCSw3TV5v2VLeI0s/qkzy2r5jlR0wzf8=
|
||||
github.com/pion/sdp/v3 v3.0.4/go.mod h1:bNiSknmJE0HYBprTHXKPQ3+JjacTv5uap92ueJZKsRk=
|
||||
github.com/pion/srtp/v2 v2.0.1 h1:kgfh65ob3EcnFYA4kUBvU/menCp9u7qaJLXwWgpobzs=
|
||||
|
|
@ -390,8 +393,8 @@ github.com/pion/turn/v2 v2.0.5 h1:iwMHqDfPEDEOFzwWKT56eFmh6DYC6o/+xnLAEzgISbA=
|
|||
github.com/pion/turn/v2 v2.0.5/go.mod h1:APg43CFyt/14Uy7heYUOGWdkem/Wu4PhCO/bjyrTqMw=
|
||||
github.com/pion/udp v0.1.0 h1:uGxQsNyrqG3GLINv36Ff60covYmfrLoxzwnCsIYspXI=
|
||||
github.com/pion/udp v0.1.0/go.mod h1:BPELIjbwE9PRbd/zxI/KYBnbo7B6+oA6YuEaNE8lths=
|
||||
github.com/pion/webrtc/v3 v3.0.3 h1:nAXJ5niRFRRMneuhM56xsh3J76sv+HAHCNuBmab3YR0=
|
||||
github.com/pion/webrtc/v3 v3.0.3/go.mod h1:DZonLDfkjMlsY/IGixAbcq8izHu0zJIk04DYx51KUvk=
|
||||
github.com/pion/webrtc/v3 v3.0.4 h1:Tiw3H9fpfcwkvaxonB+Gv1DG9tmgYBQaM1vBagDHP40=
|
||||
github.com/pion/webrtc/v3 v3.0.4/go.mod h1:1TmFSLpPYFTFXFHPtoq9eGP1ASTa9LC6FBh7sUY8cd4=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
|
|
@ -465,6 +468,8 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P
|
|||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
||||
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||
|
|
@ -488,6 +493,8 @@ go.opencensus.io v0.22.4 h1:LYy1Hy3MJdrCdMwwzxA/dRok4ejH+RwNGbuoD9fCjto=
|
|||
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.5 h1:dntmOdLpSpHlVqbW5Eay97DelsZHe+55D+xC6i0dDS0=
|
||||
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
|
||||
go.opencensus.io v0.22.6 h1:BdkrbWrzDlV9dnbzoP7sfN+dHheJ4J9JOaYxcUDL+ok=
|
||||
go.opencensus.io v0.22.6/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
|
||||
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||
|
|
@ -591,10 +598,14 @@ golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwY
|
|||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102 h1:42cLlJJdEh+ySyeUUbEQ5bsTiq8voBeTuweGVkY6Puw=
|
||||
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201201195509-5d6afe98e0b7 h1:3uJsdck53FDIpWwLeAXlia9p4C8j0BO2xZrqzKpL0D8=
|
||||
golang.org/x/net v0.0.0-20201201195509-5d6afe98e0b7/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20201224014010-6772e930b67b h1:iFwSg7t5GZmB/Q5TjiEAsdoLDrdJRC1RiF2WhuV29Qw=
|
||||
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777 h1:003p0dJM77cxMSyCPFphvZf/Y5/NXf5fzg6ufd1/Oew=
|
||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0=
|
||||
|
|
@ -606,8 +617,8 @@ golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ
|
|||
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5 h1:Lm4OryKCca1vehdsWogr9N4t7NfZxLbJoc/H0w4K4S4=
|
||||
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210113205817-d3ed898aa8a3 h1:BaN3BAqnopnKjvl+15DYP6LLrbBHfbfmlFYzmFj/Q9Q=
|
||||
golang.org/x/oauth2 v0.0.0-20210113205817-d3ed898aa8a3/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210126194326-f9ce19ea3013 h1:55H5j7lotzuFCEOKDsMch+fRNUQ9DgtyHOUP31FNqKc=
|
||||
golang.org/x/oauth2 v0.0.0-20210126194326-f9ce19ea3013/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
|
|
@ -617,6 +628,7 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ
|
|||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
|
|
@ -667,8 +679,10 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201214210602-f9fddec55a1e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210113181707-4bcb84eeeb78 h1:nVuTkr9L6Bq62qpUqKo/RnZCFfzDBL0bYo6w9OJUqZY=
|
||||
golang.org/x/sys v0.0.0-20210113181707-4bcb84eeeb78/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c h1:VwygUrnw9jn88c4u8GD3rZQbqrP/tgas88tPUbBxQrk=
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
|
|
@ -740,9 +754,11 @@ golang.org/x/tools v0.0.0-20200918232735-d647fc253266 h1:k7tVuG0g1JwmD3Jh8oAl1vQ
|
|||
golang.org/x/tools v0.0.0-20200918232735-d647fc253266/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
|
||||
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210115202250-e0d201561e39 h1:BTs2GMGSMWpgtCpv1CE7vkJTv7XcHdcLLnAMu7UbgTY=
|
||||
golang.org/x/tools v0.0.0-20210115202250-e0d201561e39/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.0 h1:po9/4sTYwZU9lPhi1tOrb4hCv3qrhiQ77LZfGa2OjwY=
|
||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
|
|
@ -773,6 +789,8 @@ google.golang.org/api v0.32.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ
|
|||
google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
|
||||
google.golang.org/api v0.36.0 h1:l2Nfbl2GPXdWorv+dT2XfinX2jOOw4zv1VhLstx+6rE=
|
||||
google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
|
||||
google.golang.org/api v0.38.0 h1:vDyWk6eup8eQAidaZ31sNWIn8tZEL8qpbtGkBD4ytQo=
|
||||
google.golang.org/api v0.38.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
|
|
@ -820,9 +838,11 @@ google.golang.org/genproto v0.0.0-20200914193844-75d14daec038/go.mod h1:FWY/as6D
|
|||
google.golang.org/genproto v0.0.0-20200921151605-7abf4a1a14d5/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210114201628-6edceaf6022f h1:izedQ6yVIc5mZsRuXzmSreCOlzI0lCU1HpG8yEdMiKw=
|
||||
google.golang.org/genproto v0.0.0-20210114201628-6edceaf6022f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210126160654-44e461bb6506 h1:uLBY0yHDCj2PMQ98KWDSIDFwn9zK2zh+tgWtbvPPBjI=
|
||||
google.golang.org/genproto v0.0.0-20210126160654-44e461bb6506/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM=
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ type Emulator struct {
|
|||
Width int
|
||||
Height int
|
||||
}
|
||||
Storage string
|
||||
Libretro LibretroConfig
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ package worker
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"github.com/giongto35/cloud-game/v2/pkg/config"
|
||||
"github.com/giongto35/cloud-game/v2/pkg/config/emulator"
|
||||
|
|
@ -9,6 +11,7 @@ import (
|
|||
"github.com/giongto35/cloud-game/v2/pkg/config/monitoring"
|
||||
"github.com/giongto35/cloud-game/v2/pkg/config/shared"
|
||||
webrtcConfig "github.com/giongto35/cloud-game/v2/pkg/config/webrtc"
|
||||
"github.com/giongto35/cloud-game/v2/pkg/environment"
|
||||
flag "github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
|
|
@ -35,6 +38,7 @@ func NewConfig() (conf Config) {
|
|||
if err := config.LoadConfig(&conf, configPath); err == nil {
|
||||
conf.Loaded = true
|
||||
}
|
||||
conf.expandSpecialTags()
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -65,4 +69,21 @@ func (c *Config) Deserialize(data []byte) {
|
|||
if err := json.Unmarshal(data, c); err == nil {
|
||||
c.Loaded = true
|
||||
}
|
||||
c.expandSpecialTags()
|
||||
}
|
||||
|
||||
// expandSpecialTags replaces all the special tags in the config.
|
||||
func (c *Config) expandSpecialTags() {
|
||||
// home dir
|
||||
dir := c.Emulator.Storage
|
||||
if dir != "" {
|
||||
tag := "{user}"
|
||||
if strings.Contains(dir, tag) {
|
||||
userHomeDir, err := environment.GetUserHome()
|
||||
if err != nil {
|
||||
log.Fatalln("couldn't read user home directory", err)
|
||||
}
|
||||
c.Emulator.Storage = strings.Replace(dir, tag, userHomeDir, -1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ import (
|
|||
|
||||
config "github.com/giongto35/cloud-game/v2/pkg/config/emulator"
|
||||
"github.com/giongto35/cloud-game/v2/pkg/emulator"
|
||||
"github.com/giongto35/cloud-game/v2/pkg/util"
|
||||
)
|
||||
|
||||
/*
|
||||
|
|
@ -72,11 +71,22 @@ type naEmulator struct {
|
|||
roomID string
|
||||
gameName string
|
||||
isSavingLoading bool
|
||||
storage Storage
|
||||
|
||||
controllersMap map[string][]controllerState
|
||||
done chan struct{}
|
||||
}
|
||||
|
||||
type Storage struct {
|
||||
// save path without the dir slash in the end
|
||||
Path string
|
||||
// contains the name of the main save file
|
||||
// e.g. abc<...>293.dat
|
||||
// needed for Google Cloud save/restore which
|
||||
// doesn't support multiple files
|
||||
MainSave string
|
||||
}
|
||||
|
||||
// VideoExporter produces image frame to unix socket
|
||||
type VideoExporter struct {
|
||||
sock net.Conn
|
||||
|
|
@ -103,7 +113,7 @@ const maxPort = 8
|
|||
const SocketAddrTmpl = "/tmp/cloudretro-retro-%s.sock"
|
||||
|
||||
// NAEmulator implements CloudEmulator interface based on NanoArch(golang RetroArch)
|
||||
func NewNAEmulator(roomID string, inputChannel <-chan InputEvent, conf config.LibretroCoreConfig) (*naEmulator, chan GameFrame, chan []int16) {
|
||||
func NewNAEmulator(roomID string, inputChannel <-chan InputEvent, storage Storage, conf config.LibretroCoreConfig) (*naEmulator, chan GameFrame, chan []int16) {
|
||||
imageChannel := make(chan GameFrame, 30)
|
||||
audioChannel := make(chan []int16, 30)
|
||||
|
||||
|
|
@ -117,6 +127,7 @@ func NewNAEmulator(roomID string, inputChannel <-chan InputEvent, conf config.Li
|
|||
HasMultitap: conf.HasMultitap,
|
||||
AutoGlContext: conf.AutoGlContext,
|
||||
},
|
||||
storage: storage,
|
||||
imageChannel: imageChannel,
|
||||
audioChannel: audioChannel,
|
||||
inputChannel: inputChannel,
|
||||
|
|
@ -158,17 +169,17 @@ func NewVideoExporter(roomID string, imgChannel chan GameFrame) *VideoExporter {
|
|||
|
||||
// Init initialize new RetroArch cloud emulator
|
||||
// withImageChan returns an image stream as Channel for output else it will write to unix socket
|
||||
func Init(roomID string, withImageChannel bool, inputChannel <-chan InputEvent, config config.LibretroCoreConfig) (*naEmulator, chan GameFrame, chan []int16) {
|
||||
emulator, imageChannel, audioChannel := NewNAEmulator(roomID, inputChannel, config)
|
||||
func Init(roomID string, withImageChannel bool, inputChannel <-chan InputEvent, storage Storage, config config.LibretroCoreConfig) (*naEmulator, chan GameFrame, chan []int16) {
|
||||
emu, imageChannel, audioChannel := NewNAEmulator(roomID, inputChannel, storage, config)
|
||||
// Set to global NAEmulator
|
||||
NAEmulator = emulator
|
||||
NAEmulator = emu
|
||||
if !withImageChannel {
|
||||
NAEmulator.videoExporter = NewVideoExporter(roomID, imageChannel)
|
||||
}
|
||||
|
||||
go NAEmulator.listenInput()
|
||||
|
||||
return emulator, imageChannel, audioChannel
|
||||
return emu, imageChannel, audioChannel
|
||||
}
|
||||
|
||||
func (na *naEmulator) listenInput() {
|
||||
|
|
@ -268,9 +279,9 @@ func (na *naEmulator) ToggleMultitap() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (na *naEmulator) GetHashPath() string {
|
||||
return util.GetSavePath(na.roomID)
|
||||
}
|
||||
func (na *naEmulator) GetHashPath() string { return na.storage.Path + "/" + na.storage.MainSave }
|
||||
|
||||
func (na *naEmulator) GetSRAMPath() string { return na.storage.Path + "/" + na.roomID + ".srm" }
|
||||
|
||||
func (*naEmulator) GetViewport() interface{} {
|
||||
return outputImg
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ package nanoarch
|
|||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"log"
|
||||
"math/rand"
|
||||
"os"
|
||||
|
|
@ -39,11 +38,6 @@ void bridge_retro_set_audio_sample_batch(void *f, void *callback);
|
|||
bool bridge_retro_load_game(void *f, struct retro_game_info *gi);
|
||||
void bridge_retro_unload_game(void *f);
|
||||
void bridge_retro_run(void *f);
|
||||
size_t bridge_retro_get_memory_size(void *f, unsigned id);
|
||||
void* bridge_retro_get_memory_data(void *f, unsigned id);
|
||||
bool bridge_retro_serialize(void *f, void *data, size_t size);
|
||||
bool bridge_retro_unserialize(void *f, void *data, size_t size);
|
||||
size_t bridge_retro_serialize_size(void *f);
|
||||
void bridge_retro_set_controller_port_device(void *f, unsigned port, unsigned device);
|
||||
|
||||
bool coreEnvironment_cgo(unsigned cmd, void *data);
|
||||
|
|
@ -392,25 +386,24 @@ func deinitVideo() {
|
|||
video.autoGlContext = false
|
||||
}
|
||||
|
||||
var retroHandle unsafe.Pointer
|
||||
var retroInit unsafe.Pointer
|
||||
var retroDeinit unsafe.Pointer
|
||||
var retroAPIVersion unsafe.Pointer
|
||||
var retroGetSystemInfo unsafe.Pointer
|
||||
var retroGetSystemAVInfo unsafe.Pointer
|
||||
var retroSetEnvironment unsafe.Pointer
|
||||
var retroSetVideoRefresh unsafe.Pointer
|
||||
var retroSetInputPoll unsafe.Pointer
|
||||
var retroSetInputState unsafe.Pointer
|
||||
var retroSetAudioSample unsafe.Pointer
|
||||
var retroSetAudioSampleBatch unsafe.Pointer
|
||||
var retroRun unsafe.Pointer
|
||||
var retroLoadGame unsafe.Pointer
|
||||
var retroUnloadGame unsafe.Pointer
|
||||
var retroSerializeSize unsafe.Pointer
|
||||
var retroSerialize unsafe.Pointer
|
||||
var retroUnserialize unsafe.Pointer
|
||||
var retroSetControllerPortDevice unsafe.Pointer
|
||||
var (
|
||||
retroAPIVersion unsafe.Pointer
|
||||
retroDeinit unsafe.Pointer
|
||||
retroGetSystemAVInfo unsafe.Pointer
|
||||
retroGetSystemInfo unsafe.Pointer
|
||||
retroHandle unsafe.Pointer
|
||||
retroInit unsafe.Pointer
|
||||
retroLoadGame unsafe.Pointer
|
||||
retroRun unsafe.Pointer
|
||||
retroSetAudioSample unsafe.Pointer
|
||||
retroSetAudioSampleBatch unsafe.Pointer
|
||||
retroSetControllerPortDevice unsafe.Pointer
|
||||
retroSetEnvironment unsafe.Pointer
|
||||
retroSetInputPoll unsafe.Pointer
|
||||
retroSetInputState unsafe.Pointer
|
||||
retroSetVideoRefresh unsafe.Pointer
|
||||
retroUnloadGame unsafe.Pointer
|
||||
)
|
||||
|
||||
func coreLoad(meta emulator.Metadata) {
|
||||
isGlAllowed = meta.IsGlAllowed
|
||||
|
|
@ -458,6 +451,8 @@ func coreLoad(meta emulator.Metadata) {
|
|||
retroSerialize = loadFunction(retroHandle, "retro_serialize")
|
||||
retroUnserialize = loadFunction(retroHandle, "retro_unserialize")
|
||||
retroSetControllerPortDevice = loadFunction(retroHandle, "retro_set_controller_port_device")
|
||||
retroGetMemorySize = loadFunction(retroHandle, "retro_get_memory_size")
|
||||
retroGetMemoryData = loadFunction(retroHandle, "retro_get_memory_data")
|
||||
|
||||
mu.Unlock()
|
||||
|
||||
|
|
@ -593,41 +588,6 @@ func toggleMultitap() {
|
|||
}
|
||||
}
|
||||
|
||||
// serializeSize returns the amount of data the implementation requires to serialize
|
||||
// internal state (save states).
|
||||
// Between calls to retro_load_game() and retro_unload_game(), the
|
||||
// returned size is never allowed to be larger than a previous returned
|
||||
// value, to ensure that the frontend can allocate a save state buffer once.
|
||||
func serializeSize() uint {
|
||||
return uint(C.bridge_retro_serialize_size(retroSerializeSize))
|
||||
}
|
||||
|
||||
// Serializes internal state and returns the state as a byte slice.
|
||||
func serialize(size uint) ([]byte, error) {
|
||||
data := C.malloc(C.size_t(size))
|
||||
defer C.free(data)
|
||||
|
||||
ok := bool(C.bridge_retro_serialize(retroSerialize, data, C.size_t(size)))
|
||||
if !ok {
|
||||
return nil, errors.New("retro_serialize failed")
|
||||
}
|
||||
|
||||
bytes := C.GoBytes(data, C.int(size))
|
||||
return bytes, nil
|
||||
}
|
||||
|
||||
// unserialize deserializes internal state from a byte slice.
|
||||
func unserialize(bytes []byte, size uint) error {
|
||||
if len(bytes) == 0 {
|
||||
return nil
|
||||
}
|
||||
ok := bool(C.bridge_retro_unserialize(retroUnserialize, unsafe.Pointer(&bytes[0]), C.size_t(size)))
|
||||
if !ok {
|
||||
return errors.New("retro_unserialize failed")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func nanoarchShutdown() {
|
||||
if usesLibCo {
|
||||
thread.MainMaybe(func() {
|
||||
|
|
|
|||
|
|
@ -70,12 +70,18 @@ func GetEmulatorMock(room string, system string) *EmulatorMock {
|
|||
audio := make(chan []int16, 30)
|
||||
inputs := make(chan InputEvent, 100)
|
||||
|
||||
store := Storage{
|
||||
Path: os.TempDir(),
|
||||
MainSave: room + ".dat",
|
||||
}
|
||||
|
||||
// an emu
|
||||
emu := &EmulatorMock{
|
||||
naEmulator: naEmulator{
|
||||
imageChannel: images,
|
||||
audioChannel: audio,
|
||||
inputChannel: inputs,
|
||||
storage: store,
|
||||
|
||||
meta: emulator.Metadata{
|
||||
LibPath: meta.Lib,
|
||||
|
|
@ -129,15 +135,14 @@ func GetDefaultEmulatorMock(room string, system string, rom string) *EmulatorMoc
|
|||
// The rom will be loaded from emulators' games path.
|
||||
func (emu *EmulatorMock) loadRom(game string) {
|
||||
fmt.Printf("%v %v\n", emu.paths.cores, emu.core)
|
||||
coreLoad(emulator.Metadata{
|
||||
LibPath: emu.paths.cores + emu.core,
|
||||
})
|
||||
coreLoad(emulator.Metadata{LibPath: emu.paths.cores + emu.core})
|
||||
coreLoadGame(emu.paths.games + game)
|
||||
}
|
||||
|
||||
// shutdownEmulator closes the emulator and cleans its resources.
|
||||
func (emu *EmulatorMock) shutdownEmulator() {
|
||||
_ = os.Remove(emu.GetHashPath())
|
||||
_ = os.Remove(emu.GetSRAMPath())
|
||||
|
||||
close(emu.imageChannel)
|
||||
close(emu.audioChannel)
|
||||
|
|
@ -198,7 +203,7 @@ func (emu *EmulatorMock) dumpState() (string, string) {
|
|||
// Locks the emulator.
|
||||
func (emu *EmulatorMock) getStateHash() string {
|
||||
emu.Lock()
|
||||
state, _ := getState()
|
||||
state, _ := getSaveState()
|
||||
emu.Unlock()
|
||||
|
||||
return getHash(state)
|
||||
|
|
|
|||
47
pkg/emulator/libretro/nanoarch/persistence.go
Normal file
47
pkg/emulator/libretro/nanoarch/persistence.go
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
package nanoarch
|
||||
|
||||
import "io/ioutil"
|
||||
|
||||
// Save writes the current state to the filesystem.
|
||||
// Deadlock warning: locks the emulator.
|
||||
func (na *naEmulator) Save() (err error) {
|
||||
na.Lock()
|
||||
defer na.Unlock()
|
||||
|
||||
if sramState := getSaveRAM(); sramState != nil {
|
||||
err = toFile(na.GetSRAMPath(), sramState)
|
||||
}
|
||||
if saveState, err := getSaveState(); err == nil {
|
||||
err = toFile(na.GetHashPath(), saveState)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Load restores the state from the filesystem.
|
||||
// Deadlock warning: locks the emulator.
|
||||
func (na *naEmulator) Load() (err error) {
|
||||
na.Lock()
|
||||
defer na.Unlock()
|
||||
|
||||
if sramState, err := fromFile(na.GetSRAMPath()); err == nil {
|
||||
restoreSaveRAM(sramState)
|
||||
}
|
||||
if saveState, err := fromFile(na.GetHashPath()); err == nil {
|
||||
err = restoreSaveState(saveState)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// toFile writes the state to a file with the path.
|
||||
func toFile(path string, data []byte) error {
|
||||
return ioutil.WriteFile(path, data, 0644)
|
||||
}
|
||||
|
||||
// fromFile reads the state from a file with the path.
|
||||
func fromFile(path string) ([]byte, error) {
|
||||
if bytes, err := ioutil.ReadFile(path); err == nil {
|
||||
return bytes, nil
|
||||
} else {
|
||||
return []byte{}, err
|
||||
}
|
||||
}
|
||||
|
|
@ -1,60 +0,0 @@
|
|||
package nanoarch
|
||||
|
||||
import "io/ioutil"
|
||||
|
||||
type state []byte
|
||||
|
||||
// Save writes the current state to the filesystem.
|
||||
// Deadlock warning: locks the emulator.
|
||||
func (na *naEmulator) Save() error {
|
||||
na.Lock()
|
||||
defer na.Unlock()
|
||||
|
||||
if state, err := getState(); err == nil {
|
||||
return state.toFile(na.GetHashPath())
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Load restores the state from the filesystem.
|
||||
// Deadlock warning: locks the emulator.
|
||||
func (na *naEmulator) Load() error {
|
||||
na.Lock()
|
||||
defer na.Unlock()
|
||||
|
||||
path := na.GetHashPath()
|
||||
if state, err := fromFile(path); err == nil {
|
||||
return restoreState(state)
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// getState returns the current emulator state.
|
||||
func getState() (state, error) {
|
||||
if dat, err := serialize(serializeSize()); err == nil {
|
||||
return dat, nil
|
||||
} else {
|
||||
return state{}, err
|
||||
}
|
||||
}
|
||||
|
||||
// restoreState restores an emulator state.
|
||||
func restoreState(dat state) error {
|
||||
return unserialize(dat, serializeSize())
|
||||
}
|
||||
|
||||
// toFile writes the state to a file with the path.
|
||||
func (st state) toFile(path string) error {
|
||||
return ioutil.WriteFile(path, st, 0644)
|
||||
}
|
||||
|
||||
// fromFile reads the state from a file with the path.
|
||||
func fromFile(path string) (state, error) {
|
||||
if bytes, err := ioutil.ReadFile(path); err == nil {
|
||||
return bytes, nil
|
||||
} else {
|
||||
return state{}, err
|
||||
}
|
||||
}
|
||||
100
pkg/emulator/libretro/nanoarch/state.go
Normal file
100
pkg/emulator/libretro/nanoarch/state.go
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
package nanoarch
|
||||
|
||||
/*
|
||||
#include "libretro.h"
|
||||
#include <stdlib.h>
|
||||
|
||||
size_t bridge_retro_get_memory_size(void *f, unsigned id);
|
||||
void* bridge_retro_get_memory_data(void *f, unsigned id);
|
||||
bool bridge_retro_serialize(void *f, void *data, size_t size);
|
||||
bool bridge_retro_unserialize(void *f, void *data, size_t size);
|
||||
size_t bridge_retro_serialize_size(void *f);
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"errors"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// !global emulator lib state
|
||||
var (
|
||||
retroGetMemoryData unsafe.Pointer
|
||||
retroGetMemorySize unsafe.Pointer
|
||||
retroSerialize unsafe.Pointer
|
||||
retroSerializeSize unsafe.Pointer
|
||||
retroUnserialize unsafe.Pointer
|
||||
)
|
||||
|
||||
// defines any memory state of the emulator
|
||||
type state []byte
|
||||
|
||||
type mem struct {
|
||||
ptr unsafe.Pointer
|
||||
size uint
|
||||
}
|
||||
|
||||
// saveStateSize returns the amount of data the implementation requires
|
||||
// to serialize internal state (save states).
|
||||
func saveStateSize() uint { return uint(C.bridge_retro_serialize_size(retroSerializeSize)) }
|
||||
|
||||
// getSaveState returns emulator internal state.
|
||||
func getSaveState() (state, error) {
|
||||
size := saveStateSize()
|
||||
data := C.malloc(C.size_t(size))
|
||||
defer C.free(data)
|
||||
if !bool(C.bridge_retro_serialize(retroSerialize, data, C.size_t(size))) {
|
||||
return nil, errors.New("retro_serialize failed")
|
||||
}
|
||||
return C.GoBytes(data, C.int(size)), nil
|
||||
}
|
||||
|
||||
// restoreSaveState restores emulator internal state.
|
||||
func restoreSaveState(st state) error {
|
||||
if len(st) == 0 {
|
||||
return nil
|
||||
}
|
||||
size := saveStateSize()
|
||||
if !bool(C.bridge_retro_unserialize(retroUnserialize, unsafe.Pointer(&st[0]), C.size_t(size))) {
|
||||
return errors.New("retro_unserialize failed")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// getSaveRAM returns the game save RAM (cartridge) data or a nil slice.
|
||||
func getSaveRAM() state {
|
||||
mem := ptSaveRAM()
|
||||
if mem == nil {
|
||||
return nil
|
||||
}
|
||||
return C.GoBytes(mem.ptr, C.int(mem.size))
|
||||
}
|
||||
|
||||
// restoreSaveRAM restores game save RAM.
|
||||
func restoreSaveRAM(st state) {
|
||||
if len(st) == 0 {
|
||||
return
|
||||
}
|
||||
if mem := ptSaveRAM(); mem != nil {
|
||||
sram := (*[1 << 30]byte)(mem.ptr)[:mem.size:mem.size]
|
||||
copy(sram, st)
|
||||
}
|
||||
}
|
||||
|
||||
// getMemorySize returns memory region size.
|
||||
func getMemorySize(id uint) uint {
|
||||
return uint(C.bridge_retro_get_memory_size(retroGetMemorySize, C.uint(id)))
|
||||
}
|
||||
|
||||
// getMemoryData returns a pointer to memory data.
|
||||
func getMemoryData(id uint) unsafe.Pointer {
|
||||
return C.bridge_retro_get_memory_data(retroGetMemoryData, C.uint(id))
|
||||
}
|
||||
|
||||
// ptSaveRam return SRAM memory pointer if core supports it or nil.
|
||||
func ptSaveRAM() *mem {
|
||||
ptr, size := getMemoryData(C.RETRO_MEMORY_SAVE_RAM), getMemorySize(C.RETRO_MEMORY_SAVE_RAM)
|
||||
if ptr == nil || size == 0 {
|
||||
return nil
|
||||
}
|
||||
return &mem{ptr: ptr, size: size}
|
||||
}
|
||||
|
|
@ -1,5 +1,7 @@
|
|||
package environment
|
||||
|
||||
import "os/user"
|
||||
|
||||
type Env string
|
||||
|
||||
const (
|
||||
|
|
@ -16,3 +18,11 @@ func (env *Env) AnyOf(what ...Env) bool {
|
|||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func GetUserHome() (string, error) {
|
||||
me, err := user.Current()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return me.HomeDir, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,8 +3,6 @@ package util
|
|||
|
||||
import (
|
||||
"image"
|
||||
"log"
|
||||
"os/user"
|
||||
"unsafe"
|
||||
|
||||
"github.com/giongto35/cloud-game/v2/pkg/encoder"
|
||||
|
|
@ -65,25 +63,6 @@ func RgbaToYuvInplace(rgba *image.RGBA, yuv []byte, width, height int) {
|
|||
C.rgba2yuv(unsafe.Pointer(&yuv[0]), unsafe.Pointer(&rgba.Pix[0]), C.int(width), C.int(height), C.int(stride))
|
||||
}
|
||||
|
||||
var homeDir string
|
||||
|
||||
func init() {
|
||||
u, err := user.Current()
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
homeDir = u.HomeDir
|
||||
}
|
||||
|
||||
// GetSavePath returns save location of game based on roomID
|
||||
func GetSavePath(roomID string) string {
|
||||
return savePath(roomID)
|
||||
}
|
||||
|
||||
func savePath(hash string) string {
|
||||
return homeDir + "/.cr/save/" + hash + ".dat"
|
||||
}
|
||||
|
||||
// GetVideoEncoder returns video encoder based on some qualification.
|
||||
// Actually Android is only supporting VP8 but H264 has better encoding performance
|
||||
// TODO: Better use useragent attribute from frontend
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import (
|
|||
"log"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"time"
|
||||
|
||||
"github.com/giongto35/cloud-game/v2/pkg/config/worker"
|
||||
|
|
@ -14,7 +13,6 @@ import (
|
|||
"github.com/giongto35/cloud-game/v2/pkg/encoder"
|
||||
"github.com/giongto35/cloud-game/v2/pkg/environment"
|
||||
"github.com/giongto35/cloud-game/v2/pkg/games"
|
||||
"github.com/giongto35/cloud-game/v2/pkg/util"
|
||||
"github.com/giongto35/cloud-game/v2/pkg/webrtc"
|
||||
storage "github.com/giongto35/cloud-game/v2/pkg/worker/cloud-storage"
|
||||
"github.com/giongto35/cloud-game/v2/pkg/worker/room"
|
||||
|
|
@ -42,7 +40,7 @@ type Handler struct {
|
|||
// NewHandler returns a new server
|
||||
func NewHandler(cfg worker.Config, wrk *Worker) *Handler {
|
||||
// Create offline storage folder
|
||||
createOfflineStorage()
|
||||
createOfflineStorage(cfg.Emulator.Storage)
|
||||
|
||||
// Init online storage
|
||||
onlineStorage := storage.NewInitClient()
|
||||
|
|
@ -105,12 +103,7 @@ func setupCoordinatorConnection(host string, zone string, cfg worker.Config) (*C
|
|||
scheme = "ws"
|
||||
}
|
||||
|
||||
coordinatorURL := url.URL{
|
||||
Scheme: scheme,
|
||||
Host: host,
|
||||
Path: "/wso",
|
||||
RawQuery: "zone=" + zone,
|
||||
}
|
||||
coordinatorURL := url.URL{Scheme: scheme, Host: host, Path: "/wso", RawQuery: "zone=" + zone}
|
||||
log.Println("Worker connecting to coordinator:", coordinatorURL.String())
|
||||
|
||||
conn, err := createCoordinatorConnection(&coordinatorURL)
|
||||
|
|
@ -216,9 +209,10 @@ func (h *Handler) Close() {
|
|||
r.Close()
|
||||
}
|
||||
}
|
||||
func createOfflineStorage() {
|
||||
dir, _ := path.Split(util.GetSavePath("dummy"))
|
||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||
|
||||
func createOfflineStorage(path string) {
|
||||
log.Printf("Set storage: %v", path)
|
||||
if err := os.MkdirAll(path, 0755); err != nil {
|
||||
log.Println("Failed to create offline storage, err: ", err)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ import (
|
|||
"github.com/giongto35/cloud-game/v2/pkg/emulator/libretro/nanoarch"
|
||||
"github.com/giongto35/cloud-game/v2/pkg/encoder"
|
||||
"github.com/giongto35/cloud-game/v2/pkg/games"
|
||||
"github.com/giongto35/cloud-game/v2/pkg/util"
|
||||
"github.com/giongto35/cloud-game/v2/pkg/webrtc"
|
||||
storage "github.com/giongto35/cloud-game/v2/pkg/worker/cloud-storage"
|
||||
)
|
||||
|
|
@ -149,10 +148,14 @@ func NewRoom(roomID string, game games.GameMetadata, videoCodec encoder.VideoCod
|
|||
|
||||
// Check if room is on local storage, if not, pull from GCS to local storage
|
||||
go func(game games.GameMetadata, roomID string) {
|
||||
store := nanoarch.Storage{
|
||||
Path: cfg.Emulator.Storage,
|
||||
MainSave: roomID + ".dat",
|
||||
}
|
||||
|
||||
// Check room is on local or fetch from server
|
||||
savePath := util.GetSavePath(roomID)
|
||||
log.Println("Check ", savePath, " on online storage : ", room.isGameOnLocal(savePath))
|
||||
if err := room.saveOnlineRoomToLocal(roomID, savePath); err != nil {
|
||||
log.Printf("Check %s on online storage: %v", roomID, isGameOnLocal(store.MainSave))
|
||||
if err := room.saveOnlineRoomToLocal(roomID, store.MainSave); err != nil {
|
||||
log.Printf("Warn: Room %s is not in online storage, error %s", roomID, err)
|
||||
}
|
||||
|
||||
|
|
@ -166,13 +169,13 @@ func NewRoom(roomID string, game games.GameMetadata, videoCodec encoder.VideoCod
|
|||
if cfg.Encoder.WithoutGame {
|
||||
// Run without game, image stream is communicated over a unix socket
|
||||
imageChannel := NewVideoImporter(roomID)
|
||||
director, _, audioChannel := nanoarch.Init(roomID, false, inputChannel, libretroConfig)
|
||||
director, _, audioChannel := nanoarch.Init(roomID, false, inputChannel, store, libretroConfig)
|
||||
room.imageChannel = imageChannel
|
||||
room.director = director
|
||||
room.audioChannel = audioChannel
|
||||
} else {
|
||||
// Run without game, image stream is communicated over image channel
|
||||
director, imageChannel, audioChannel := nanoarch.Init(roomID, true, inputChannel, libretroConfig)
|
||||
director, imageChannel, audioChannel := nanoarch.Init(roomID, true, inputChannel, store, libretroConfig)
|
||||
room.imageChannel = imageChannel
|
||||
room.director = director
|
||||
room.audioChannel = audioChannel
|
||||
|
|
@ -244,8 +247,8 @@ func generateRoomID(gameName string) string {
|
|||
return roomID
|
||||
}
|
||||
|
||||
func (r *Room) isGameOnLocal(savePath string) bool {
|
||||
_, err := os.Open(savePath)
|
||||
func isGameOnLocal(path string) bool {
|
||||
_, err := os.Open(path)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
|
|
@ -375,12 +378,7 @@ func (r *Room) isRoomExisted() bool {
|
|||
if err == nil {
|
||||
return true
|
||||
}
|
||||
// Check if room is in local
|
||||
savePath := util.GetSavePath(r.ID)
|
||||
if r.isGameOnLocal(savePath) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
return isGameOnLocal(r.ID)
|
||||
}
|
||||
|
||||
// SaveGame will save game to local and trigger a callback to store game on onlineStorage, so the game can be accessed later
|
||||
|
|
@ -402,7 +400,8 @@ func (r *Room) SaveGame() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// saveOnlineRoomToLocal save online room to local
|
||||
// saveOnlineRoomToLocal save online room to local.
|
||||
// !Supports only one file of main save state.
|
||||
func (r *Room) saveOnlineRoomToLocal(roomID string, savePath string) error {
|
||||
log.Println("Check if game is on cloud storage")
|
||||
// If the game is not on local server
|
||||
|
|
@ -418,21 +417,11 @@ func (r *Room) saveOnlineRoomToLocal(roomID string, savePath string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (r *Room) LoadGame() error {
|
||||
err := r.director.LoadGame()
|
||||
func (r *Room) LoadGame() error { return r.director.LoadGame() }
|
||||
|
||||
return err
|
||||
}
|
||||
func (r *Room) ToggleMultitap() error { return r.director.ToggleMultitap() }
|
||||
|
||||
func (r *Room) ToggleMultitap() error {
|
||||
err := r.director.ToggleMultitap()
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *Room) IsEmpty() bool {
|
||||
return len(r.rtcSessions) == 0
|
||||
}
|
||||
func (r *Room) IsEmpty() bool { return len(r.rtcSessions) == 0 }
|
||||
|
||||
func (r *Room) IsRunningSessions() bool {
|
||||
// If there is running session
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue