diff --git a/configs/config.yaml b/configs/config.yaml index e247fd72..046a6040 100644 --- a/configs/config.yaml +++ b/configs/config.yaml @@ -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: diff --git a/go.mod b/go.mod index 069e1be9..2a57d172 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index e8b40891..e50d9ed2 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/pkg/config/emulator/config.go b/pkg/config/emulator/config.go index bf0d1658..97b64ba6 100644 --- a/pkg/config/emulator/config.go +++ b/pkg/config/emulator/config.go @@ -12,6 +12,7 @@ type Emulator struct { Width int Height int } + Storage string Libretro LibretroConfig } diff --git a/pkg/config/worker/config.go b/pkg/config/worker/config.go index 1006060d..f4897d8f 100644 --- a/pkg/config/worker/config.go +++ b/pkg/config/worker/config.go @@ -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) + } + } } diff --git a/pkg/emulator/libretro/nanoarch/naemulator.go b/pkg/emulator/libretro/nanoarch/naemulator.go index d3de3066..7a4ffc3a 100644 --- a/pkg/emulator/libretro/nanoarch/naemulator.go +++ b/pkg/emulator/libretro/nanoarch/naemulator.go @@ -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 diff --git a/pkg/emulator/libretro/nanoarch/nanoarch.go b/pkg/emulator/libretro/nanoarch/nanoarch.go index b2987797..17fe3e02 100644 --- a/pkg/emulator/libretro/nanoarch/nanoarch.go +++ b/pkg/emulator/libretro/nanoarch/nanoarch.go @@ -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() { diff --git a/pkg/emulator/libretro/nanoarch/nanoarch_test.go b/pkg/emulator/libretro/nanoarch/nanoarch_test.go index 0da7d5ef..5ee0fb1a 100644 --- a/pkg/emulator/libretro/nanoarch/nanoarch_test.go +++ b/pkg/emulator/libretro/nanoarch/nanoarch_test.go @@ -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) diff --git a/pkg/emulator/libretro/nanoarch/persistence.go b/pkg/emulator/libretro/nanoarch/persistence.go new file mode 100644 index 00000000..a29726be --- /dev/null +++ b/pkg/emulator/libretro/nanoarch/persistence.go @@ -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 + } +} diff --git a/pkg/emulator/libretro/nanoarch/savestates_test.go b/pkg/emulator/libretro/nanoarch/persistence_test.go similarity index 100% rename from pkg/emulator/libretro/nanoarch/savestates_test.go rename to pkg/emulator/libretro/nanoarch/persistence_test.go diff --git a/pkg/emulator/libretro/nanoarch/savestates.go b/pkg/emulator/libretro/nanoarch/savestates.go deleted file mode 100644 index 3edd4cdd..00000000 --- a/pkg/emulator/libretro/nanoarch/savestates.go +++ /dev/null @@ -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 - } -} diff --git a/pkg/emulator/libretro/nanoarch/state.go b/pkg/emulator/libretro/nanoarch/state.go new file mode 100644 index 00000000..cb655be3 --- /dev/null +++ b/pkg/emulator/libretro/nanoarch/state.go @@ -0,0 +1,100 @@ +package nanoarch + +/* +#include "libretro.h" +#include + +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} +} diff --git a/pkg/environment/env.go b/pkg/environment/env.go index 106fb74f..2f3d9b45 100644 --- a/pkg/environment/env.go +++ b/pkg/environment/env.go @@ -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 +} diff --git a/pkg/util/codec.go b/pkg/util/codec.go index 4879c132..ac850077 100644 --- a/pkg/util/codec.go +++ b/pkg/util/codec.go @@ -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 diff --git a/pkg/worker/handlers.go b/pkg/worker/handlers.go index 667c402a..18ab6043 100644 --- a/pkg/worker/handlers.go +++ b/pkg/worker/handlers.go @@ -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) } } diff --git a/pkg/worker/room/room.go b/pkg/worker/room/room.go index acb7fa76..c20b6826 100644 --- a/pkg/worker/room/room.go +++ b/pkg/worker/room/room.go @@ -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