Compare commits
35 Commits
v1.7.3
...
v1.7.4-pre
Author | SHA1 | Date | |
---|---|---|---|
![]() |
590a7829b1 | ||
![]() |
ae75fc1490 | ||
![]() |
0c645d04aa | ||
![]() |
13b3764fcf | ||
![]() |
57d4fe7cf9 | ||
![]() |
8519d4c745 | ||
![]() |
822a40b61b | ||
![]() |
b463bb6064 | ||
![]() |
3e8696e6e8 | ||
![]() |
d1f157ac08 | ||
![]() |
30be179367 | ||
![]() |
787bb47bd3 | ||
![]() |
ccf9afd561 | ||
![]() |
c7b6233ad6 | ||
![]() |
afb72c3d74 | ||
![]() |
299e24793f | ||
![]() |
de9b9bf706 | ||
![]() |
28bc50a82e | ||
![]() |
f9f65f3ffb | ||
![]() |
87f3a1aa89 | ||
![]() |
b3e7696292 | ||
![]() |
29c685c791 | ||
![]() |
f0020e5b1f | ||
![]() |
3897ed65ef | ||
![]() |
cebab38dd6 | ||
![]() |
fd7b354571 | ||
![]() |
be1d53b3b1 | ||
![]() |
e0cff36819 | ||
![]() |
cea654692e | ||
![]() |
c82ffb6c97 | ||
![]() |
7400a8c040 | ||
![]() |
eda171efa1 | ||
![]() |
f881459c0b | ||
![]() |
69da6ee346 | ||
![]() |
f85109abd2 |
@@ -9,9 +9,14 @@ on:
|
||||
jobs:
|
||||
docker:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
packages: write
|
||||
contents: read
|
||||
attestations: write
|
||||
id-token: write
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: 'true'
|
||||
- name: Install dotnet tool
|
||||
@@ -21,18 +26,37 @@ jobs:
|
||||
- name: Sign in to Nuget
|
||||
run: dotnet nuget add source --username michael-j-green --password ${{ secrets.NUGETKEY }} --store-password-in-clear-text --name github "https://nuget.pkg.github.com/gaseous-project/index.json"
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
uses: docker/setup-qemu-action@v3
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v2
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v4
|
||||
- name: Login to GitHub Package Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Build and push standard image
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
file: ./build/Dockerfile
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
tags: gaseousgames/gaseousserver:${{ github.ref_name}}
|
||||
tags: |
|
||||
gaseousgames/gaseousserver:${{ github.ref_name}}
|
||||
ghcr.io/gaseous-project/gaseousserver:${{ github.ref_name}}
|
||||
- name: Build and push image with embedded mariadb
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
file: ./build/Dockerfile-EmbeddedDB
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
tags: |
|
||||
gaseousgames/gaseousserver:${{ github.ref_name}}-embeddeddb
|
||||
ghcr.io/gaseous-project/gaseousserver:${{ github.ref_name}}-embeddeddb
|
||||
|
42
.github/workflows/BuildDockerOnTag-Release.yml
vendored
42
.github/workflows/BuildDockerOnTag-Release.yml
vendored
@@ -8,9 +8,14 @@ on:
|
||||
jobs:
|
||||
docker:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
packages: write
|
||||
contents: read
|
||||
attestations: write
|
||||
id-token: write
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: 'true'
|
||||
- name: Install dotnet tool
|
||||
@@ -20,18 +25,41 @@ jobs:
|
||||
- name: Sign in to Nuget
|
||||
run: dotnet nuget add source --username michael-j-green --password ${{ secrets.NUGETKEY }} --store-password-in-clear-text --name github "https://nuget.pkg.github.com/gaseous-project/index.json"
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
uses: docker/setup-qemu-action@v3
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v2
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v4
|
||||
- name: Login to GitHub Package Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Build and push standard image
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
file: ./build/Dockerfile
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
tags: gaseousgames/gaseousserver:latest,gaseousgames/gaseousserver:${{ github.ref_name}}
|
||||
tags: |
|
||||
gaseousgames/gaseousserver:latest
|
||||
gaseousgames/gaseousserver:${{ github.ref_name}}
|
||||
ghcr.io/gaseous-project/gaseousserver:latest
|
||||
ghcr.io/gaseous-project/gaseousserver:${{ github.ref_name}}
|
||||
- name: Build and push image with embedded mariadb
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
file: ./build/Dockerfile-EmbeddedDB
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
tags: |
|
||||
gaseousgames/gaseousserver:latest-embeddeddb
|
||||
gaseousgames/gaseousserver:${{ github.ref_name}}-embeddeddb
|
||||
ghcr.io/gaseous-project/gaseousserver:latest-embeddeddb
|
||||
ghcr.io/gaseous-project/gaseousserver:${{ github.ref_name}}-embeddeddb
|
||||
|
3
.vscode/launch.json
vendored
3
.vscode/launch.json
vendored
@@ -24,7 +24,8 @@
|
||||
},
|
||||
"sourceFileMap": {
|
||||
"/Views": "${workspaceFolder}/Views"
|
||||
}
|
||||
},
|
||||
"enableStepFiltering": false
|
||||
},
|
||||
{
|
||||
"name": ".NET Core Attach",
|
||||
|
20
Gaseous.sln
20
Gaseous.sln
@@ -1,4 +1,4 @@
|
||||
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 16
|
||||
VisualStudioVersion = 25.0.1704.4
|
||||
@@ -27,30 +27,18 @@ Global
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{08699C93-15CD-4E39-9053-D3C8056CE938}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{08699C93-15CD-4E39-9053-D3C8056CE938}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{08699C93-15CD-4E39-9053-D3C8056CE938}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{08699C93-15CD-4E39-9053-D3C8056CE938}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{FFCEC386-033F-4772-A45B-D33579F2E5EE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{FFCEC386-033F-4772-A45B-D33579F2E5EE}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{FFCEC386-033F-4772-A45B-D33579F2E5EE}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{FFCEC386-033F-4772-A45B-D33579F2E5EE}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{A01D2EFF-C82E-473B-84D7-7C25E736F5D2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{A01D2EFF-C82E-473B-84D7-7C25E736F5D2}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{A01D2EFF-C82E-473B-84D7-7C25E736F5D2}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{A01D2EFF-C82E-473B-84D7-7C25E736F5D2}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{B07A4655-A003-416B-A790-ADAA5B548E1A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{B07A4655-A003-416B-A790-ADAA5B548E1A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{B07A4655-A003-416B-A790-ADAA5B548E1A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{B07A4655-A003-416B-A790-ADAA5B548E1A}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {979BF092-AFBC-4F19-B55F-32E73959BD1A}
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{F1A847C7-57BC-4DA9-8F83-CD060A7F5122} = {17FA6F12-8532-420C-9489-CB8FDE42137C}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {979BF092-AFBC-4F19-B55F-32E73959BD1A}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
147
LICENSE
147
LICENSE
@@ -1,5 +1,5 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
Version 3, 19 November 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
@@ -7,17 +7,15 @@
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
The GNU Affero General Public License is a free, copyleft license for
|
||||
software and other kinds of works, specifically designed to ensure
|
||||
cooperation with the community in the case of network server software.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
our General Public Licenses are intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
software for all its users.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
@@ -26,44 +24,34 @@ them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
Developers that use our General Public Licenses protect your rights
|
||||
with two steps: (1) assert copyright on the software, and (2) offer
|
||||
you this License which gives you legal permission to copy, distribute
|
||||
and/or modify the software.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
A secondary benefit of defending all users' freedom is that
|
||||
improvements made in alternate versions of the program, if they
|
||||
receive widespread use, become available for other developers to
|
||||
incorporate. Many developers of free software are heartened and
|
||||
encouraged by the resulting cooperation. However, in the case of
|
||||
software used on network servers, this result may fail to come about.
|
||||
The GNU General Public License permits making a modified version and
|
||||
letting the public access it on a server without ever releasing its
|
||||
source code to the public.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
The GNU Affero General Public License is designed specifically to
|
||||
ensure that, in such cases, the modified source code becomes available
|
||||
to the community. It requires the operator of a network server to
|
||||
provide the source code of the modified version running there to the
|
||||
users of that server. Therefore, public use of a modified version, on
|
||||
a publicly accessible server, gives the public access to the source
|
||||
code of the modified version.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
An older license, called the Affero General Public License and
|
||||
published by Affero, was designed to accomplish similar goals. This is
|
||||
a different license, not a version of the Affero GPL, but Affero has
|
||||
released a new version of the Affero GPL which permits relicensing under
|
||||
this license.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
@@ -72,7 +60,7 @@ modification follow.
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
"This License" refers to version 3 of the GNU Affero General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
@@ -549,35 +537,45 @@ to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
13. Remote Network Interaction; Use with the GNU General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, if you modify the
|
||||
Program, your modified version must prominently offer all users
|
||||
interacting with it remotely through a computer network (if your version
|
||||
supports such interaction) an opportunity to receive the Corresponding
|
||||
Source of your version by providing access to the Corresponding Source
|
||||
from a network server at no charge, through some standard or customary
|
||||
means of facilitating copying of software. This Corresponding Source
|
||||
shall include the Corresponding Source for any work covered by version 3
|
||||
of the GNU General Public License that is incorporated pursuant to the
|
||||
following paragraph.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
under version 3 of the GNU General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
but the work with which it is combined will remain governed by version
|
||||
3 of the GNU General Public License.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
the GNU Affero General Public License from time to time. Such new versions
|
||||
will be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Program specifies that a certain numbered version of the GNU Affero General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
GNU Affero General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
versions of the GNU Affero General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
@@ -631,44 +629,33 @@ to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
Gaseous
|
||||
Copyright (C) 2023 Gaseous
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) 2023 Gaseous
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
If your software can interact with users remotely through a computer
|
||||
network, you should also make sure that it provides a way for users to
|
||||
get its source. For example, if your program is a web application, its
|
||||
interface could display a "Source" link that leads users to an archive
|
||||
of the code. There are many ways you could offer source, and different
|
||||
solutions will be better for different programs; see section 13 for the
|
||||
specific requirements.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
For more information on this, and how to apply and follow the GNU AGPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||
|
@@ -8,21 +8,34 @@ RUN echo "Target: $TARGETARCH"
|
||||
RUN echo "Build: $BUILDPLATFORM"
|
||||
|
||||
# Copy everything
|
||||
COPY . ./
|
||||
COPY .. ./
|
||||
# Restore as distinct layers
|
||||
RUN dotnet restore "gaseous-server/gaseous-server.csproj" -a $TARGETARCH
|
||||
# Build and publish a release
|
||||
RUN dotnet publish "gaseous-server/gaseous-server.csproj" --use-current-runtime --self-contained true -c Release -o out -a $TARGETARCH
|
||||
|
||||
# download and unzip EmulatorJS from CDN
|
||||
RUN apt-get update && apt-get install -y p7zip-full
|
||||
# disabled for 1.7.4 as the next version EmulatorJS is not yet available
|
||||
# # update apt-get
|
||||
# RUN apt-get update
|
||||
|
||||
# # download and unzip EmulatorJS from CDN
|
||||
# RUN apt-get install -y p7zip-full
|
||||
# RUN mkdir -p out/wwwroot/emulators/EmulatorJS
|
||||
# RUN wget https://cdn.emulatorjs.org/releases/4.0.12.7z
|
||||
# RUN 7z x -y -oout/wwwroot/emulators/EmulatorJS 4.0.12.7z
|
||||
RUN wget --recursive --no-parent https://cdn.emulatorjs.org/latest/
|
||||
RUN mkdir -p out/wwwroot/emulators/EmulatorJS
|
||||
RUN wget https://cdn.emulatorjs.org/releases/4.0.12.7z
|
||||
RUN 7z x -y -oout/wwwroot/emulators/EmulatorJS 4.0.12.7z
|
||||
RUN cp -fr cdn.emulatorjs.org/latest/* out/wwwroot/emulators/EmulatorJS
|
||||
RUN rm -Rf cdn.emulatorjs.org
|
||||
|
||||
# clean up apt-get
|
||||
RUN apt-get clean && rm -rf /var/lib/apt/lists
|
||||
|
||||
# Build runtime image
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:8.0
|
||||
ENV INDOCKER=1
|
||||
WORKDIR /App
|
||||
COPY --from=build-env /App/out .
|
||||
|
||||
# start gaseous-server
|
||||
ENTRYPOINT ["dotnet", "gaseous-server.dll"]
|
77
build/Dockerfile-EmbeddedDB
Normal file
77
build/Dockerfile-EmbeddedDB
Normal file
@@ -0,0 +1,77 @@
|
||||
FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:8.0 AS build-env
|
||||
ARG TARGETARCH
|
||||
ARG BUILDPLATFORM
|
||||
WORKDIR /App
|
||||
EXPOSE 80
|
||||
|
||||
RUN echo "Target: $TARGETARCH"
|
||||
RUN echo "Build: $BUILDPLATFORM"
|
||||
|
||||
# Copy everything
|
||||
COPY .. ./
|
||||
# Restore as distinct layers
|
||||
RUN dotnet restore "gaseous-server/gaseous-server.csproj" -a $TARGETARCH
|
||||
# Build and publish a release
|
||||
RUN dotnet publish "gaseous-server/gaseous-server.csproj" --use-current-runtime --self-contained true -c Release -o out -a $TARGETARCH
|
||||
|
||||
# disabled for 1.7.4 as the next version EmulatorJS is not yet available
|
||||
# # update apt-get
|
||||
# RUN apt-get update
|
||||
|
||||
# # download and unzip EmulatorJS from CDN
|
||||
# RUN apt-get install -y p7zip-full
|
||||
# RUN mkdir -p out/wwwroot/emulators/EmulatorJS
|
||||
# RUN wget https://cdn.emulatorjs.org/releases/4.0.12.7z
|
||||
# RUN 7z x -y -oout/wwwroot/emulators/EmulatorJS 4.0.12.7z
|
||||
|
||||
RUN wget --recursive --no-parent https://cdn.emulatorjs.org/latest/
|
||||
RUN mkdir -p out/wwwroot/emulators/EmulatorJS
|
||||
RUN cp -fr cdn.emulatorjs.org/latest/* out/wwwroot/emulators/EmulatorJS
|
||||
RUN rm -Rf cdn.emulatorjs.org
|
||||
|
||||
# Build runtime image
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:8.0
|
||||
ENV INDOCKER=1
|
||||
WORKDIR /App
|
||||
COPY --from=build-env /App/out .
|
||||
|
||||
# variables
|
||||
ARG PUID=1000
|
||||
ARG PGID=1000
|
||||
ARG dbhost=localhost
|
||||
ARG dbuser=root
|
||||
ARG dbpass=gaseous
|
||||
ARG MARIADB_ROOT_PASSWORD=$dbpass
|
||||
|
||||
ENV PUID=${PUID}
|
||||
ENV PGID=${PGID}
|
||||
ENV dbhost=${dbhost}
|
||||
ENV dbuser=${dbuser}
|
||||
ENV dbpass=${dbpass}
|
||||
ENV MARIADB_ROOT_PASSWORD=${dbpass}
|
||||
|
||||
# install mariadb
|
||||
RUN DEBIAN_FRONTEND=noninteractive && \
|
||||
apt-get update && apt-get install -y mariadb-server
|
||||
RUN mkdir -p /run/mysqld
|
||||
COPY ../build/mariadb.sh /usr/sbin/start-mariadb.sh
|
||||
RUN chmod +x /usr/sbin/start-mariadb.sh
|
||||
|
||||
# install supervisord
|
||||
RUN apt-get install -y supervisor
|
||||
COPY ../build/supervisord.conf /etc/supervisor/conf.d/supervisord.conf
|
||||
RUN mkdir -p /var/run/supervisord
|
||||
RUN mkdir -p /var/log/supervisord
|
||||
|
||||
# clean up apt-get
|
||||
RUN apt-get clean && rm -rf /var/lib/apt/lists
|
||||
|
||||
# copy entrypoint
|
||||
COPY ../build/entrypoint.sh /usr/sbin/entrypoint.sh
|
||||
RUN chmod +x /usr/sbin/entrypoint.sh
|
||||
|
||||
# volumes
|
||||
VOLUME /home/gaseous/.gaseous-server /var/lib/mysql
|
||||
|
||||
# start services
|
||||
ENTRYPOINT [ "/usr/sbin/entrypoint.sh" ]
|
12
build/entrypoint.sh
Normal file
12
build/entrypoint.sh
Normal file
@@ -0,0 +1,12 @@
|
||||
#!/bin/sh
|
||||
|
||||
# create the user
|
||||
echo "Creating user gaseous with UID ${PUID} and GID ${PGID}"
|
||||
groupadd -g ${PGID} gaseous
|
||||
useradd -u ${PUID} -g ${PGID} -m gaseous -G sudo
|
||||
usermod -p "*" gaseous
|
||||
mkdir -p /home/gaseous/.gaseous-server /var/lib/mysql
|
||||
chown -R ${PUID}:${PGID} /App /home/gaseous/.gaseous-server /var/lib/mysql /run/mysqld
|
||||
|
||||
# Start supervisord and services
|
||||
/usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf
|
17
build/mariadb.sh
Normal file
17
build/mariadb.sh
Normal file
@@ -0,0 +1,17 @@
|
||||
#!/bin/sh
|
||||
|
||||
# start the database server without network or grant tables
|
||||
/usr/sbin/mariadbd --datadir=/var/lib/mysql --skip-grant-tables --skip-networking &
|
||||
|
||||
# wait for the server to start
|
||||
sleep 5
|
||||
|
||||
# change the root password
|
||||
mariadb -u root -e "FLUSH PRIVILEGES; ALTER USER 'root'@'localhost' IDENTIFIED BY '$MARIADB_ROOT_PASSWORD'; FLUSH PRIVILEGES;"
|
||||
|
||||
# stop the server
|
||||
sleep 5
|
||||
killall mariadbd
|
||||
|
||||
# start the server normally
|
||||
/usr/sbin/mariadbd --datadir=/var/lib/mysql
|
35
build/supervisord.conf
Normal file
35
build/supervisord.conf
Normal file
@@ -0,0 +1,35 @@
|
||||
[supervisord]
|
||||
nodaemon=true
|
||||
logfile=/var/log/supervisord/supervisord.log
|
||||
logfile_maxbytes=50
|
||||
logfile_backups=5
|
||||
pidfile=/var/run/supervisord/supervisord.pid
|
||||
loglevel = info
|
||||
|
||||
[unix_http_server]
|
||||
file=/var/run/supervisord/supervisor.sock
|
||||
chmod=0700
|
||||
|
||||
[rpcinterface:supervisor]
|
||||
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
|
||||
|
||||
[supervisorctl]
|
||||
serverurl=unix:///var/run/supervisord/supervisor.sock
|
||||
|
||||
[program:mariadb]
|
||||
user=gaseous
|
||||
command=bash -c "/usr/sbin/start-mariadb.sh"
|
||||
autostart=true
|
||||
autorestart=true
|
||||
redirect_stderr=true
|
||||
stdout_logfile=/dev/fd/1
|
||||
stdout_logfile_maxbytes=0
|
||||
|
||||
[program:gaseous-server]
|
||||
user=gaseous
|
||||
command=dotnet /App/gaseous-server.dll
|
||||
autostart=true
|
||||
autorestart=true
|
||||
redirect_stderr=true
|
||||
stdout_logfile=/dev/fd/1
|
||||
stdout_logfile_maxbytes=0
|
@@ -1,5 +1,6 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Concurrent;
|
||||
using System.ComponentModel;
|
||||
using System.Data;
|
||||
using System.IO.Compression;
|
||||
using System.Reflection;
|
||||
using System.Security.Cryptography;
|
||||
@@ -19,7 +20,8 @@ namespace gaseous_server.Classes
|
||||
if (ObjectToCheck == null || ObjectToCheck == System.DBNull.Value)
|
||||
{
|
||||
return IfNullValue;
|
||||
} else
|
||||
}
|
||||
else
|
||||
{
|
||||
return ObjectToCheck;
|
||||
}
|
||||
@@ -43,11 +45,13 @@ namespace gaseous_server.Classes
|
||||
{
|
||||
var xmlStream = File.OpenRead(FileName);
|
||||
|
||||
Logging.Log(Logging.LogType.Information, "Hash File", "Generating MD5 hash for file: " + FileName);
|
||||
var md5 = MD5.Create();
|
||||
byte[] md5HashByte = md5.ComputeHash(xmlStream);
|
||||
string md5Hash = BitConverter.ToString(md5HashByte).Replace("-", "").ToLowerInvariant();
|
||||
_md5hash = md5Hash;
|
||||
|
||||
Logging.Log(Logging.LogType.Information, "Hash File", "Generating SHA1 hash for file: " + FileName);
|
||||
var sha1 = SHA1.Create();
|
||||
xmlStream.Position = 0;
|
||||
byte[] sha1HashByte = sha1.ComputeHash(xmlStream);
|
||||
|
@@ -80,7 +80,8 @@ namespace gaseous_server.Classes
|
||||
get
|
||||
{
|
||||
string logPath = Path.Combine(ConfigurationPath, "Logs");
|
||||
if (!Directory.Exists(logPath)) {
|
||||
if (!Directory.Exists(logPath))
|
||||
{
|
||||
Directory.CreateDirectory(logPath);
|
||||
}
|
||||
return logPath;
|
||||
@@ -131,7 +132,7 @@ namespace gaseous_server.Classes
|
||||
_config.DatabaseConfiguration.DatabaseName = (string)Common.GetEnvVar("dbname", _config.DatabaseConfiguration.DatabaseName);
|
||||
_config.DatabaseConfiguration.Port = int.Parse((string)Common.GetEnvVar("dbport", _config.DatabaseConfiguration.Port.ToString()));
|
||||
_config.MetadataConfiguration.MetadataSource = (HasheousClient.Models.MetadataModel.MetadataSources)Enum.Parse(typeof(HasheousClient.Models.MetadataModel.MetadataSources), (string)Common.GetEnvVar("metadatasource", _config.MetadataConfiguration.MetadataSource.ToString()));
|
||||
_config.MetadataConfiguration.SignatureSource = (HasheousClient.Models.MetadataModel.SignatureSources)Enum.Parse(typeof(HasheousClient.Models.MetadataModel.SignatureSources), (string)Common.GetEnvVar("signaturesource", _config.MetadataConfiguration.SignatureSource.ToString()));;
|
||||
_config.MetadataConfiguration.SignatureSource = (HasheousClient.Models.MetadataModel.SignatureSources)Enum.Parse(typeof(HasheousClient.Models.MetadataModel.SignatureSources), (string)Common.GetEnvVar("signaturesource", _config.MetadataConfiguration.SignatureSource.ToString())); ;
|
||||
_config.MetadataConfiguration.MaxLibraryScanWorkers = int.Parse((string)Common.GetEnvVar("maxlibraryscanworkers", _config.MetadataConfiguration.MaxLibraryScanWorkers.ToString()));
|
||||
_config.MetadataConfiguration.HasheousHost = (string)Common.GetEnvVar("hasheoushost", _config.MetadataConfiguration.HasheousHost);
|
||||
_config.IGDBConfiguration.ClientId = (string)Common.GetEnvVar("igdbclientid", _config.IGDBConfiguration.ClientId);
|
||||
@@ -427,7 +428,8 @@ namespace gaseous_server.Classes
|
||||
|
||||
public class Database
|
||||
{
|
||||
private static string _DefaultHostName {
|
||||
private static string _DefaultHostName
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!String.IsNullOrEmpty(Environment.GetEnvironmentVariable("dbhost")))
|
||||
|
@@ -20,7 +20,7 @@ namespace gaseous_server.Classes
|
||||
|
||||
Logging.Log(Logging.LogType.Information, "Database", "Checking for pre-upgrade for schema version " + TargetSchemaVersion);
|
||||
|
||||
switch(DatabaseType)
|
||||
switch (DatabaseType)
|
||||
{
|
||||
case Database.databaseType.MySql:
|
||||
switch (TargetSchemaVersion)
|
||||
@@ -69,7 +69,7 @@ namespace gaseous_server.Classes
|
||||
Dictionary<string, object> dbDict = new Dictionary<string, object>();
|
||||
DataTable data;
|
||||
|
||||
switch(DatabaseType)
|
||||
switch (DatabaseType)
|
||||
{
|
||||
case Database.databaseType.MySql:
|
||||
switch (TargetSchemaVersion)
|
||||
@@ -121,7 +121,8 @@ namespace gaseous_server.Classes
|
||||
}
|
||||
}
|
||||
|
||||
public static void MySql_1002_MigrateMetadataVersion() {
|
||||
public static void MySql_1002_MigrateMetadataVersion()
|
||||
{
|
||||
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
|
||||
string sql = "";
|
||||
Dictionary<string, object> dbDict = new Dictionary<string, object>();
|
||||
|
@@ -31,7 +31,7 @@ namespace gaseous_server.Classes
|
||||
if (!Directory.Exists(ExtractPath)) { Directory.CreateDirectory(ExtractPath); }
|
||||
try
|
||||
{
|
||||
switch(ImportedFileExtension)
|
||||
switch (ImportedFileExtension)
|
||||
{
|
||||
case ".zip":
|
||||
Logging.Log(Logging.LogType.Information, "Get Signature", "Decompressing using zip");
|
||||
@@ -121,7 +121,8 @@ namespace gaseous_server.Classes
|
||||
|
||||
if (zfi != null)
|
||||
{
|
||||
ArchiveData archiveData = new ArchiveData{
|
||||
ArchiveData archiveData = new ArchiveData
|
||||
{
|
||||
FileName = Path.GetFileName(file),
|
||||
FilePath = zfi.Directory.FullName.Replace(ExtractPath, ""),
|
||||
Size = zfi.Length,
|
||||
@@ -264,7 +265,12 @@ namespace gaseous_server.Classes
|
||||
if (Config.MetadataConfiguration.SignatureSource == HasheousClient.Models.MetadataModel.SignatureSources.Hasheous)
|
||||
{
|
||||
HasheousClient.Hasheous hasheous = new HasheousClient.Hasheous();
|
||||
SignatureLookupItem? HasheousResult = hasheous.RetrieveFromHasheousAsync(new HashLookupModel{
|
||||
SignatureLookupItem? HasheousResult = null;
|
||||
|
||||
try
|
||||
{
|
||||
HasheousResult = hasheous.RetrieveFromHasheousAsync(new HashLookupModel
|
||||
{
|
||||
MD5 = hash.md5hash,
|
||||
SHA1 = hash.sha1hash
|
||||
});
|
||||
@@ -296,6 +302,11 @@ namespace gaseous_server.Classes
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.Log(Logging.LogType.Warning, "Get Signature", "Error retrieving signature from Hasheous", ex);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
@@ -28,7 +28,8 @@ namespace gaseous_server.Classes
|
||||
|
||||
// import files first
|
||||
int importCount = 1;
|
||||
foreach (string importContent in importContents) {
|
||||
foreach (string importContent in importContents)
|
||||
{
|
||||
SetStatus(importCount, importContents.Length, "Importing file: " + importContent);
|
||||
|
||||
ImportGameFile(importContent, null);
|
||||
@@ -197,8 +198,10 @@ namespace gaseous_server.Classes
|
||||
Logging.Log(Logging.LogType.Information, "Import Game", " " + games.Length + " search results found");
|
||||
|
||||
// quite likely we've found sequels and alternate types
|
||||
foreach (Game game in games) {
|
||||
if (game.Name == SearchCandidate) {
|
||||
foreach (Game game in games)
|
||||
{
|
||||
if (game.Name == SearchCandidate)
|
||||
{
|
||||
// found game title matches the search candidate
|
||||
determinedGame = Metadata.Games.GetGame((long)games[0].Id, false, false, false);
|
||||
Logging.Log(Logging.LogType.Information, "Import Game", "Found exact match!");
|
||||
@@ -273,7 +276,8 @@ namespace gaseous_server.Classes
|
||||
|
||||
// assumption: no games have () in their titles so we'll remove them
|
||||
int idx = GameName.IndexOf('(');
|
||||
if (idx >= 0) {
|
||||
if (idx >= 0)
|
||||
{
|
||||
GameName = GameName.Substring(0, idx);
|
||||
}
|
||||
|
||||
@@ -305,7 +309,8 @@ namespace gaseous_server.Classes
|
||||
if (UpdateId == 0)
|
||||
{
|
||||
sql = "INSERT INTO Games_Roms (PlatformId, GameId, Name, Size, CRC, MD5, SHA1, DevelopmentStatus, Attributes, RomType, RomTypeMedia, MediaLabel, Path, MetadataSource, MetadataGameName, MetadataVersion, LibraryId) VALUES (@platformid, @gameid, @name, @size, @crc, @md5, @sha1, @developmentstatus, @Attributes, @romtype, @romtypemedia, @medialabel, @path, @metadatasource, @metadatagamename, @metadataversion, @libraryid); SELECT CAST(LAST_INSERT_ID() AS SIGNED);";
|
||||
} else
|
||||
}
|
||||
else
|
||||
{
|
||||
sql = "UPDATE Games_Roms SET PlatformId=@platformid, GameId=@gameid, Name=@name, Size=@size, DevelopmentStatus=@developmentstatus, Attributes=@Attributes, RomType=@romtype, RomTypeMedia=@romtypemedia, MediaLabel=@medialabel, MetadataSource=@metadatasource, MetadataGameName=@metadatagamename, MetadataVersion=@metadataversion WHERE Id=@id;";
|
||||
dbDict.Add("id", UpdateId);
|
||||
@@ -348,7 +353,8 @@ namespace gaseous_server.Classes
|
||||
if (UpdateId == 0)
|
||||
{
|
||||
romId = (long)romInsert.Rows[0][0];
|
||||
} else
|
||||
}
|
||||
else
|
||||
{
|
||||
romId = UpdateId;
|
||||
}
|
||||
@@ -645,7 +651,7 @@ namespace gaseous_server.Classes
|
||||
long PlatformId;
|
||||
IGDB.Models.Platform determinedPlatform;
|
||||
|
||||
if (sig.Flags.IGDBPlatformId == null || sig.Flags.IGDBPlatformId == 0 )
|
||||
if (sig.Flags.IGDBPlatformId == null || sig.Flags.IGDBPlatformId == 0)
|
||||
{
|
||||
// no platform discovered in the signature
|
||||
PlatformId = library.DefaultPlatformId;
|
||||
@@ -771,7 +777,7 @@ namespace gaseous_server.Classes
|
||||
long PlatformId;
|
||||
IGDB.Models.Platform determinedPlatform;
|
||||
|
||||
if (sig.Flags.IGDBPlatformId == null || sig.Flags.IGDBPlatformId == 0 )
|
||||
if (sig.Flags.IGDBPlatformId == null || sig.Flags.IGDBPlatformId == 0)
|
||||
{
|
||||
// no platform discovered in the signature
|
||||
PlatformId = library.DefaultPlatformId;
|
||||
|
@@ -16,6 +16,7 @@ namespace gaseous_server.Classes
|
||||
Dictionary<string, object> dbDict = new Dictionary<string, object>();
|
||||
|
||||
// remove any entries from the library that have an invalid id
|
||||
Logging.Log(Logging.LogType.Information, "Maintenance", "Removing any entries from the library that have an invalid id");
|
||||
string LibraryWhereClause = "";
|
||||
foreach (GameLibrary.LibraryItem library in GameLibrary.GetLibraries)
|
||||
{
|
||||
@@ -33,9 +34,27 @@ namespace gaseous_server.Classes
|
||||
}
|
||||
|
||||
// delete old logs
|
||||
sql = "DELETE FROM ServerLogs WHERE EventTime < @EventRetentionDate;";
|
||||
Logging.Log(Logging.LogType.Information, "Maintenance", "Removing logs older than " + Config.LoggingConfiguration.LogRetention + " days");
|
||||
long deletedCount = 1;
|
||||
long deletedEventCount = 0;
|
||||
long maxLoops = 1000;
|
||||
while (deletedCount > 0)
|
||||
{
|
||||
sql = "DELETE FROM ServerLogs WHERE EventTime < @EventRetentionDate LIMIT 1000; SELECT ROW_COUNT() AS Count;";
|
||||
dbDict.Add("EventRetentionDate", DateTime.UtcNow.AddDays(Config.LoggingConfiguration.LogRetention * -1));
|
||||
db.ExecuteCMD(sql, dbDict);
|
||||
DataTable deletedCountTable = db.ExecuteCMD(sql, dbDict);
|
||||
deletedCount = (long)deletedCountTable.Rows[0][0];
|
||||
deletedEventCount += deletedCount;
|
||||
|
||||
// check if we've hit the limit
|
||||
maxLoops -= 1;
|
||||
if (maxLoops <= 0)
|
||||
{
|
||||
Logging.Log(Logging.LogType.Warning, "Maintenance", "Hit the maximum number of loops for deleting logs. Stopping.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
Logging.Log(Logging.LogType.Information, "Maintenance", "Deleted " + deletedEventCount + " log entries");
|
||||
|
||||
// delete files and directories older than 7 days in PathsToClean
|
||||
List<string> PathsToClean = new List<string>();
|
||||
@@ -87,7 +106,7 @@ namespace gaseous_server.Classes
|
||||
SetStatus(StatusCounter, tables.Rows.Count, "Optimising table " + row[0].ToString());
|
||||
|
||||
sql = "OPTIMIZE TABLE " + row[0].ToString();
|
||||
DataTable response = db.ExecuteCMD(sql);
|
||||
DataTable response = db.ExecuteCMD(sql, new Dictionary<string, object>(), 240);
|
||||
foreach (DataRow responseRow in response.Rows)
|
||||
{
|
||||
string retVal = "";
|
||||
|
@@ -17,7 +17,7 @@ namespace gaseous_server.Classes.Metadata
|
||||
public class InvalidGameId : Exception
|
||||
{
|
||||
public InvalidGameId(long Id) : base("Unable to find Game by id " + Id)
|
||||
{}
|
||||
{ }
|
||||
}
|
||||
|
||||
public static Game? GetGame(long Id, bool getAllMetadata, bool followSubGames, bool forceRefresh)
|
||||
@@ -78,15 +78,17 @@ namespace gaseous_server.Classes.Metadata
|
||||
if (cacheStatus == Storage.CacheStatus.Current) { cacheStatus = Storage.CacheStatus.Expired; }
|
||||
}
|
||||
|
||||
// set up where clause
|
||||
string WhereClause = "";
|
||||
string searchField = "";
|
||||
switch (searchUsing)
|
||||
{
|
||||
case SearchUsing.id:
|
||||
WhereClause = "where id = " + searchValue;
|
||||
searchField = "id";
|
||||
break;
|
||||
case SearchUsing.slug:
|
||||
WhereClause = "where slug = " + searchValue;
|
||||
WhereClause = "where slug = \"" + searchValue + "\"";
|
||||
searchField = "slug";
|
||||
break;
|
||||
default:
|
||||
throw new Exception("Invalid search type");
|
||||
@@ -110,11 +112,11 @@ namespace gaseous_server.Classes.Metadata
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.Log(Logging.LogType.Warning, "Metadata: " + returnValue.GetType().Name, "An error occurred while connecting to IGDB. WhereClause: " + WhereClause, ex);
|
||||
returnValue = Storage.GetCacheValue<Game>(returnValue, "id", (long)searchValue);
|
||||
returnValue = Storage.GetCacheValue<Game>(returnValue, searchField, searchValue);
|
||||
}
|
||||
return returnValue;
|
||||
case Storage.CacheStatus.Current:
|
||||
returnValue = Storage.GetCacheValue<Game>(returnValue, "id", (long)searchValue);
|
||||
returnValue = Storage.GetCacheValue<Game>(returnValue, searchField, searchValue);
|
||||
UpdateSubClasses(returnValue, false, false, false);
|
||||
return returnValue;
|
||||
default:
|
||||
@@ -125,17 +127,17 @@ namespace gaseous_server.Classes.Metadata
|
||||
private static void UpdateSubClasses(Game Game, bool getAllMetadata, bool followSubGames, bool forceRefresh)
|
||||
{
|
||||
// required metadata
|
||||
if (Game.Cover != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
Cover GameCover = Covers.GetCover(Game.Cover.Id, Config.LibraryConfiguration.LibraryMetadataDirectory_Game(Game), forceRefresh);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.Log(Logging.LogType.Critical, "Game Metadata", "Unable to fetch cover artwork.", ex);
|
||||
}
|
||||
}
|
||||
// if (Game.Cover != null)
|
||||
// {
|
||||
// try
|
||||
// {
|
||||
// Cover GameCover = Covers.GetCover(Game.Cover.Id, Config.LibraryConfiguration.LibraryMetadataDirectory_Game(Game), forceRefresh);
|
||||
// }
|
||||
// catch (Exception ex)
|
||||
// {
|
||||
// Logging.Log(Logging.LogType.Critical, "Game Metadata", "Unable to fetch cover artwork.", ex);
|
||||
// }
|
||||
// }
|
||||
|
||||
if (Game.Genres != null)
|
||||
{
|
||||
@@ -439,7 +441,8 @@ namespace gaseous_server.Classes.Metadata
|
||||
DataTable data = db.ExecuteCMD(sql, dbDict);
|
||||
foreach (DataRow row in data.Rows)
|
||||
{
|
||||
Game game = new Game{
|
||||
Game game = new Game
|
||||
{
|
||||
Id = (long)row["Id"],
|
||||
Name = (string)Common.ReturnValueIfNull(row["Name"], ""),
|
||||
Slug = (string)Common.ReturnValueIfNull(row["Slug"], ""),
|
||||
|
@@ -13,7 +13,7 @@ namespace gaseous_server.Classes.Metadata
|
||||
{
|
||||
}
|
||||
|
||||
public static PlatformVersion? GetPlatformVersion(long Id, Platform ParentPlatform)
|
||||
public static PlatformVersion? GetPlatformVersion(long Id, Platform ParentPlatform, bool GetImages = false)
|
||||
{
|
||||
if (Id == 0)
|
||||
{
|
||||
@@ -21,18 +21,18 @@ namespace gaseous_server.Classes.Metadata
|
||||
}
|
||||
else
|
||||
{
|
||||
Task<PlatformVersion> RetVal = _GetPlatformVersion(SearchUsing.id, Id, ParentPlatform);
|
||||
Task<PlatformVersion> RetVal = _GetPlatformVersion(SearchUsing.id, Id, ParentPlatform, GetImages);
|
||||
return RetVal.Result;
|
||||
}
|
||||
}
|
||||
|
||||
public static PlatformVersion GetPlatformVersion(string Slug, Platform ParentPlatform)
|
||||
public static PlatformVersion GetPlatformVersion(string Slug, Platform ParentPlatform, bool GetImages)
|
||||
{
|
||||
Task<PlatformVersion> RetVal = _GetPlatformVersion(SearchUsing.slug, Slug, ParentPlatform);
|
||||
Task<PlatformVersion> RetVal = _GetPlatformVersion(SearchUsing.slug, Slug, ParentPlatform, GetImages);
|
||||
return RetVal.Result;
|
||||
}
|
||||
|
||||
private static async Task<PlatformVersion> _GetPlatformVersion(SearchUsing searchUsing, object searchValue, Platform ParentPlatform)
|
||||
private static async Task<PlatformVersion> _GetPlatformVersion(SearchUsing searchUsing, object searchValue, Platform ParentPlatform, bool GetImages)
|
||||
{
|
||||
// check database first
|
||||
Storage.CacheStatus? cacheStatus = new Storage.CacheStatus();
|
||||
@@ -67,7 +67,7 @@ namespace gaseous_server.Classes.Metadata
|
||||
if (returnValue != null)
|
||||
{
|
||||
Storage.NewCacheValue(returnValue);
|
||||
UpdateSubClasses(ParentPlatform, returnValue);
|
||||
UpdateSubClasses(ParentPlatform, returnValue, GetImages);
|
||||
}
|
||||
return returnValue;
|
||||
case Storage.CacheStatus.Expired:
|
||||
@@ -75,7 +75,7 @@ namespace gaseous_server.Classes.Metadata
|
||||
{
|
||||
returnValue = await GetObjectFromServer(WhereClause);
|
||||
Storage.NewCacheValue(returnValue, true);
|
||||
UpdateSubClasses(ParentPlatform, returnValue);
|
||||
UpdateSubClasses(ParentPlatform, returnValue, GetImages);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -90,7 +90,9 @@ namespace gaseous_server.Classes.Metadata
|
||||
}
|
||||
}
|
||||
|
||||
private static void UpdateSubClasses(Platform ParentPlatform, PlatformVersion platformVersion)
|
||||
private static void UpdateSubClasses(Platform ParentPlatform, PlatformVersion platformVersion, bool GetImages)
|
||||
{
|
||||
if (GetImages == true)
|
||||
{
|
||||
if (platformVersion.PlatformLogo != null)
|
||||
{
|
||||
@@ -104,6 +106,7 @@ namespace gaseous_server.Classes.Metadata
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private enum SearchUsing
|
||||
{
|
||||
|
@@ -15,7 +15,7 @@ namespace gaseous_server.Classes.Metadata
|
||||
|
||||
}
|
||||
|
||||
public static Platform? GetPlatform(long Id, bool forceRefresh = false)
|
||||
public static Platform? GetPlatform(long Id, bool forceRefresh = false, bool GetImages = false)
|
||||
{
|
||||
if (Id == 0)
|
||||
{
|
||||
@@ -41,10 +41,10 @@ namespace gaseous_server.Classes.Metadata
|
||||
{
|
||||
try
|
||||
{
|
||||
Task<Platform> RetVal = _GetPlatform(SearchUsing.id, Id, forceRefresh);
|
||||
Task<Platform> RetVal = _GetPlatform(SearchUsing.id, Id, forceRefresh, GetImages);
|
||||
return RetVal.Result;
|
||||
}
|
||||
catch(Exception ex)
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.Log(Logging.LogType.Warning, "Metadata", "An error occurred fetching Platform Id " + Id, ex);
|
||||
return null;
|
||||
@@ -52,13 +52,13 @@ namespace gaseous_server.Classes.Metadata
|
||||
}
|
||||
}
|
||||
|
||||
public static Platform GetPlatform(string Slug, bool forceRefresh = false)
|
||||
public static Platform GetPlatform(string Slug, bool forceRefresh = false, bool GetImages = false)
|
||||
{
|
||||
Task<Platform> RetVal = _GetPlatform(SearchUsing.slug, Slug, forceRefresh);
|
||||
Task<Platform> RetVal = _GetPlatform(SearchUsing.slug, Slug, forceRefresh, GetImages);
|
||||
return RetVal.Result;
|
||||
}
|
||||
|
||||
private static async Task<Platform> _GetPlatform(SearchUsing searchUsing, object searchValue, bool forceRefresh)
|
||||
private static async Task<Platform> _GetPlatform(SearchUsing searchUsing, object searchValue, bool forceRefresh, bool GetImages)
|
||||
{
|
||||
// check database first
|
||||
Storage.CacheStatus? cacheStatus = new Storage.CacheStatus();
|
||||
@@ -78,13 +78,16 @@ namespace gaseous_server.Classes.Metadata
|
||||
|
||||
// set up where clause
|
||||
string WhereClause = "";
|
||||
string searchField = "";
|
||||
switch (searchUsing)
|
||||
{
|
||||
case SearchUsing.id:
|
||||
WhereClause = "where id = " + searchValue;
|
||||
searchField = "id";
|
||||
break;
|
||||
case SearchUsing.slug:
|
||||
WhereClause = "where slug = " + searchValue;
|
||||
WhereClause = "where slug = \"" + searchValue + "\"";
|
||||
searchField = "slug";
|
||||
break;
|
||||
default:
|
||||
throw new Exception("Invalid search type");
|
||||
@@ -96,7 +99,7 @@ namespace gaseous_server.Classes.Metadata
|
||||
case Storage.CacheStatus.NotPresent:
|
||||
returnValue = await GetObjectFromServer(WhereClause);
|
||||
Storage.NewCacheValue(returnValue);
|
||||
UpdateSubClasses(returnValue);
|
||||
UpdateSubClasses(returnValue, GetImages);
|
||||
AddPlatformMapping(returnValue);
|
||||
return returnValue;
|
||||
case Storage.CacheStatus.Expired:
|
||||
@@ -104,23 +107,23 @@ namespace gaseous_server.Classes.Metadata
|
||||
{
|
||||
returnValue = await GetObjectFromServer(WhereClause);
|
||||
Storage.NewCacheValue(returnValue, true);
|
||||
UpdateSubClasses(returnValue);
|
||||
UpdateSubClasses(returnValue, GetImages);
|
||||
AddPlatformMapping(returnValue);
|
||||
return returnValue;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.Log(Logging.LogType.Warning, "Metadata: " + returnValue.GetType().Name, "An error occurred while connecting to IGDB. WhereClause: " + WhereClause, ex);
|
||||
return Storage.GetCacheValue<Platform>(returnValue, "id", (long)searchValue);
|
||||
return Storage.GetCacheValue<Platform>(returnValue, searchField, searchValue);
|
||||
}
|
||||
case Storage.CacheStatus.Current:
|
||||
return Storage.GetCacheValue<Platform>(returnValue, "id", (long)searchValue);
|
||||
return Storage.GetCacheValue<Platform>(returnValue, searchField, searchValue);
|
||||
default:
|
||||
throw new Exception("How did you get here?");
|
||||
}
|
||||
}
|
||||
|
||||
private static void UpdateSubClasses(Platform platform)
|
||||
private static void UpdateSubClasses(Platform platform, bool GetImages)
|
||||
{
|
||||
if (platform.Versions != null)
|
||||
{
|
||||
@@ -130,6 +133,8 @@ namespace gaseous_server.Classes.Metadata
|
||||
}
|
||||
}
|
||||
|
||||
if (GetImages == true)
|
||||
{
|
||||
if (platform.PlatformLogo != null)
|
||||
{
|
||||
try
|
||||
@@ -142,6 +147,7 @@ namespace gaseous_server.Classes.Metadata
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void AddPlatformMapping(Platform platform)
|
||||
{
|
||||
@@ -158,11 +164,12 @@ namespace gaseous_server.Classes.Metadata
|
||||
{
|
||||
Logging.Log(Logging.LogType.Information, "Platform Map", "Importing " + platform.Name + " from predefined data.");
|
||||
// doesn't exist - add it
|
||||
item = new Models.PlatformMapping.PlatformMapItem{
|
||||
item = new Models.PlatformMapping.PlatformMapItem
|
||||
{
|
||||
IGDBId = (long)platform.Id,
|
||||
IGDBName = platform.Name,
|
||||
IGDBSlug = platform.Slug,
|
||||
AlternateNames = new List<string>{ platform.AlternativeName }
|
||||
AlternateNames = new List<string> { platform.AlternativeName }
|
||||
};
|
||||
Models.PlatformMapping.WritePlatformMap(item, false, true);
|
||||
}
|
||||
|
@@ -428,6 +428,30 @@ namespace gaseous_server.Classes.Metadata
|
||||
}
|
||||
}
|
||||
|
||||
public static void CreateRelationsTables<T>()
|
||||
{
|
||||
string PrimaryTable = typeof(T).Name;
|
||||
foreach (PropertyInfo property in typeof(T).GetProperties())
|
||||
{
|
||||
string SecondaryTable = property.Name;
|
||||
|
||||
if (property.PropertyType.Name == "IdentitiesOrValues`1")
|
||||
{
|
||||
|
||||
string TableName = "Relation_" + PrimaryTable + "_" + SecondaryTable;
|
||||
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
|
||||
string sql = "SELECT * FROM information_schema.tables WHERE table_schema = '" + Config.DatabaseConfiguration.DatabaseName + "' AND table_name = '" + TableName + "';";
|
||||
DataTable data = db.ExecuteCMD(sql);
|
||||
if (data.Rows.Count == 0)
|
||||
{
|
||||
// table doesn't exist, create it
|
||||
sql = "CREATE TABLE `" + Config.DatabaseConfiguration.DatabaseName + "`.`" + TableName + "` (`" + PrimaryTable + "Id` BIGINT NOT NULL, `" + SecondaryTable + "Id` BIGINT NOT NULL, PRIMARY KEY (`" + PrimaryTable + "Id`, `" + SecondaryTable + "Id`), INDEX `idx_PrimaryColumn` (`" + PrimaryTable + "Id` ASC) VISIBLE);";
|
||||
db.ExecuteCMD(sql);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class MemoryCacheObject
|
||||
{
|
||||
public object Object { get; set; }
|
||||
|
@@ -12,7 +12,13 @@ namespace gaseous_server.Classes
|
||||
public class InvalidRomId : Exception
|
||||
{
|
||||
public InvalidRomId(long Id) : base("Unable to find ROM by id " + Id)
|
||||
{}
|
||||
{ }
|
||||
}
|
||||
|
||||
public class InvalidRomHash : Exception
|
||||
{
|
||||
public InvalidRomHash(String Hash) : base("Unable to find ROM by hash " + Hash)
|
||||
{ }
|
||||
}
|
||||
|
||||
public static GameRomObject GetRoms(long GameId, long PlatformId = -1, string NameSearch = "", int pageNumber = 0, int pageSize = 0, string userid = "")
|
||||
@@ -37,13 +43,16 @@ namespace gaseous_server.Classes
|
||||
// platform query
|
||||
sqlPlatform = "SELECT DISTINCT Games_Roms.PlatformId, Platform.`Name` FROM Games_Roms LEFT JOIN Platform ON Games_Roms.PlatformId = Platform.Id WHERE GameId = @id ORDER BY Platform.`Name`;";
|
||||
|
||||
if (PlatformId == -1) {
|
||||
if (PlatformId == -1)
|
||||
{
|
||||
// data query
|
||||
sql = "SELECT DISTINCT Games_Roms.*, Platform.`Name` AS platformname, GameState.RomId AS SavedStateRomId FROM Games_Roms LEFT JOIN Platform ON Games_Roms.PlatformId = Platform.Id LEFT JOIN GameState ON (Games_Roms.Id = GameState.RomId AND GameState.UserId = @userid AND GameState.IsMediaGroup = 0) WHERE Games_Roms.GameId = @id" + NameSearchWhere + " ORDER BY Platform.`Name`, Games_Roms.`Name` LIMIT 1000;";
|
||||
|
||||
// count query
|
||||
sqlCount = "SELECT COUNT(Games_Roms.Id) AS RomCount FROM Games_Roms WHERE Games_Roms.GameId = @id" + NameSearchWhere + ";";
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
// data query
|
||||
sql = "SELECT DISTINCT Games_Roms.*, Platform.`Name` AS platformname, GameState.RomId AS SavedStateRomId FROM Games_Roms LEFT JOIN Platform ON Games_Roms.PlatformId = Platform.Id LEFT JOIN GameState ON (Games_Roms.Id = GameState.RomId AND GameState.UserId = @userid AND GameState.IsMediaGroup = 0) WHERE Games_Roms.GameId = @id AND Games_Roms.PlatformId = @platformid" + NameSearchWhere + " ORDER BY Platform.`Name`, Games_Roms.`Name` LIMIT 1000;";
|
||||
|
||||
@@ -100,6 +109,26 @@ namespace gaseous_server.Classes
|
||||
}
|
||||
}
|
||||
|
||||
public static GameRomItem GetRom(string MD5)
|
||||
{
|
||||
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
|
||||
string sql = "SELECT Games_Roms.*, Platform.`Name` AS platformname FROM Games_Roms LEFT JOIN Platform ON Games_Roms.PlatformId = Platform.Id WHERE Games_Roms.MD5 = @id";
|
||||
Dictionary<string, object> dbDict = new Dictionary<string, object>();
|
||||
dbDict.Add("id", MD5);
|
||||
DataTable romDT = db.ExecuteCMD(sql, dbDict);
|
||||
|
||||
if (romDT.Rows.Count > 0)
|
||||
{
|
||||
DataRow romDR = romDT.Rows[0];
|
||||
GameRomItem romItem = BuildRom(romDR);
|
||||
return romItem;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidRomHash(MD5);
|
||||
}
|
||||
}
|
||||
|
||||
public static GameRomItem UpdateRom(long RomId, long PlatformId, long GameId)
|
||||
{
|
||||
// ensure metadata for platformid is present
|
||||
@@ -201,10 +230,9 @@ namespace gaseous_server.Classes
|
||||
public Models.PlatformMapping.PlatformMapItem.WebEmulatorItem? Emulator { get; set; }
|
||||
public long GameId { get; set; }
|
||||
public string? Path { get; set; }
|
||||
public string? SignatureSourceGameTitle { get; set;}
|
||||
public string? SignatureSourceGameTitle { get; set; }
|
||||
public bool HasSaveStates { get; set; } = false;
|
||||
public GameLibrary.LibraryItem Library { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -10,7 +10,8 @@ namespace gaseous_server.Classes
|
||||
if (md5.Length > 0)
|
||||
{
|
||||
return _GetSignature("Signatures_Roms.md5 = @searchstring", md5);
|
||||
} else
|
||||
}
|
||||
else
|
||||
{
|
||||
return _GetSignature("Signatures_Roms.sha1 = @searchstring", sha1);
|
||||
}
|
||||
@@ -21,7 +22,8 @@ namespace gaseous_server.Classes
|
||||
if (TosecName.Length > 0)
|
||||
{
|
||||
return _GetSignature("Signatures_Roms.name = @searchstring", TosecName);
|
||||
} else
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
@@ -73,7 +73,8 @@ namespace gaseous_server.Controllers
|
||||
private static async Task<List<GaseousGame>> _SearchForGame(long PlatformId, string SearchString)
|
||||
{
|
||||
string searchBody = "";
|
||||
string searchFields = "fields cover,first_release_date,name,platforms,slug; ";
|
||||
// string searchFields = "fields cover,first_release_date,name,platforms,slug; ";
|
||||
string searchFields = "fields *; ";
|
||||
searchBody += "search \"" + SearchString + "\";";
|
||||
searchBody += "where platforms = (" + PlatformId + ");";
|
||||
searchBody += "limit 100;";
|
||||
@@ -91,7 +92,7 @@ namespace gaseous_server.Controllers
|
||||
foreach (Game game in results.ToList())
|
||||
{
|
||||
Storage.CacheStatus cacheStatus = Storage.GetCacheStatus("Game", (long)game.Id);
|
||||
switch(cacheStatus)
|
||||
switch (cacheStatus)
|
||||
{
|
||||
case Storage.CacheStatus.NotPresent:
|
||||
Storage.NewCacheValue(game, false);
|
||||
|
@@ -70,7 +70,8 @@ namespace gaseous_server.Controllers
|
||||
[HttpGet]
|
||||
[Route("Version")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
public Version GetSystemVersion() {
|
||||
public Version GetSystemVersion()
|
||||
{
|
||||
return Assembly.GetExecutingAssembly().GetName().Version;
|
||||
}
|
||||
|
||||
@@ -80,32 +81,36 @@ namespace gaseous_server.Controllers
|
||||
[Route("VersionFile")]
|
||||
[AllowAnonymous]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
public FileContentResult GetSystemVersionAsFile() {
|
||||
public FileContentResult GetSystemVersionAsFile()
|
||||
{
|
||||
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
|
||||
|
||||
// get age ratings dictionary
|
||||
Dictionary<int, string> ClassificationBoardsStrings = new Dictionary<int, string>();
|
||||
foreach(IGDB.Models.AgeRatingCategory ageRatingCategory in Enum.GetValues(typeof(IGDB.Models.AgeRatingCategory)) )
|
||||
foreach (IGDB.Models.AgeRatingCategory ageRatingCategory in Enum.GetValues(typeof(IGDB.Models.AgeRatingCategory)))
|
||||
{
|
||||
ClassificationBoardsStrings.Add((int)ageRatingCategory, ageRatingCategory.ToString());
|
||||
}
|
||||
|
||||
Dictionary<int, string> AgeRatingsStrings = new Dictionary<int, string>();
|
||||
foreach(IGDB.Models.AgeRatingTitle ageRatingTitle in Enum.GetValues(typeof(IGDB.Models.AgeRatingTitle)) )
|
||||
foreach (IGDB.Models.AgeRatingTitle ageRatingTitle in Enum.GetValues(typeof(IGDB.Models.AgeRatingTitle)))
|
||||
{
|
||||
AgeRatingsStrings.Add((int)ageRatingTitle, ageRatingTitle.ToString());
|
||||
}
|
||||
|
||||
string ver = "var AppVersion = \"" + Assembly.GetExecutingAssembly().GetName().Version.ToString() + "\";" + Environment.NewLine +
|
||||
"var DBSchemaVersion = \"" + db.GetDatabaseSchemaVersion() + "\";" + Environment.NewLine +
|
||||
"var FirstRunStatus = " + Config.ReadSetting<string>("FirstRunStatus", "0") + ";" + Environment.NewLine +
|
||||
"var AgeRatingBoardsStrings = " + JsonSerializer.Serialize(ClassificationBoardsStrings, new JsonSerializerOptions{
|
||||
"var FirstRunStatus = \"" + Config.ReadSetting<string>("FirstRunStatus", "0") + "\";" + Environment.NewLine +
|
||||
"var AgeRatingBoardsStrings = " + JsonSerializer.Serialize(ClassificationBoardsStrings, new JsonSerializerOptions
|
||||
{
|
||||
WriteIndented = true
|
||||
}) + ";" + Environment.NewLine +
|
||||
"var AgeRatingStrings = " + JsonSerializer.Serialize(AgeRatingsStrings, new JsonSerializerOptions{
|
||||
"var AgeRatingStrings = " + JsonSerializer.Serialize(AgeRatingsStrings, new JsonSerializerOptions
|
||||
{
|
||||
WriteIndented = true
|
||||
}) + ";" + Environment.NewLine +
|
||||
"var AgeRatingGroups = " + JsonSerializer.Serialize(AgeGroups.AgeGroupingsFlat, new JsonSerializerOptions{
|
||||
"var AgeRatingGroups = " + JsonSerializer.Serialize(AgeGroups.AgeGroupingsFlat, new JsonSerializerOptions
|
||||
{
|
||||
WriteIndented = true
|
||||
}) + ";" + Environment.NewLine +
|
||||
"var emulatorDebugMode = " + Config.ReadSetting<string>("emulatorDebugMode", false.ToString()).ToLower() + ";";
|
||||
@@ -251,7 +256,8 @@ namespace gaseous_server.Controllers
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
public ActionResult GetSystemSettings()
|
||||
{
|
||||
SystemSettingsModel systemSettingsModel = new SystemSettingsModel{
|
||||
SystemSettingsModel systemSettingsModel = new SystemSettingsModel
|
||||
{
|
||||
AlwaysLogToDisk = Config.LoggingConfiguration.AlwaysLogToDisk,
|
||||
MinimumLogRetentionPeriod = Config.LoggingConfiguration.LogRetention,
|
||||
EmulatorDebugMode = Boolean.Parse(Config.ReadSetting<string>("emulatorDebugMode", false.ToString()))
|
||||
@@ -281,7 +287,8 @@ namespace gaseous_server.Controllers
|
||||
|
||||
private SystemInfo.PathItem GetDisk(string Path)
|
||||
{
|
||||
SystemInfo.PathItem pathItem = new SystemInfo.PathItem {
|
||||
SystemInfo.PathItem pathItem = new SystemInfo.PathItem
|
||||
{
|
||||
LibraryPath = Path,
|
||||
SpaceUsed = Common.DirSize(new DirectoryInfo(Path)),
|
||||
SpaceAvailable = new DriveInfo(Path).AvailableFreeSpace,
|
||||
@@ -293,7 +300,8 @@ namespace gaseous_server.Controllers
|
||||
|
||||
public class SystemInfo
|
||||
{
|
||||
public Version ApplicationVersion {
|
||||
public Version ApplicationVersion
|
||||
{
|
||||
get
|
||||
{
|
||||
return Assembly.GetExecutingAssembly().GetName().Version;
|
||||
@@ -589,7 +597,8 @@ namespace gaseous_server.Controllers
|
||||
}
|
||||
private bool _UserManageable;
|
||||
public bool UserManageable => _UserManageable;
|
||||
public int Interval {
|
||||
public int Interval
|
||||
{
|
||||
get
|
||||
{
|
||||
return int.Parse(Config.ReadSetting<string>("Interval_" + Task, DefaultInterval.ToString()));
|
||||
|
@@ -6,6 +6,7 @@ using Authentication;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using System.Data;
|
||||
using Asp.Versioning;
|
||||
using System.IO.Compression;
|
||||
|
||||
namespace gaseous_server.Controllers.v1_1
|
||||
{
|
||||
@@ -13,7 +14,7 @@ namespace gaseous_server.Controllers.v1_1
|
||||
[ApiVersion("1.0")]
|
||||
[ApiVersion("1.1")]
|
||||
[ApiController]
|
||||
public class StateManagerController: ControllerBase
|
||||
public class StateManagerController : ControllerBase
|
||||
{
|
||||
private readonly UserManager<ApplicationUser> _userManager;
|
||||
private readonly SignInManager<ApplicationUser> _signInManager;
|
||||
@@ -233,11 +234,11 @@ namespace gaseous_server.Controllers.v1_1
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[Route("{RomId}/{StateId}/State/")]
|
||||
[Route("{RomId}/{StateId}/State/savestate.state")]
|
||||
public async Task<ActionResult> GetStateDataAsync(long RomId, long StateId, bool IsMediaGroup = false)
|
||||
public async Task<ActionResult> GetStateDataAsync(long RomId, long StateId, bool IsMediaGroup = false, bool StateOnly = false)
|
||||
{
|
||||
var user = await _userManager.GetUserAsync(User);
|
||||
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
|
||||
string sql = "SELECT Zipped, State FROM GameState WHERE Id = @id AND RomId = @romid AND IsMediaGroup = @ismediagroup AND UserId = @userid;";
|
||||
string sql = "SELECT * FROM GameState WHERE Id = @id AND RomId = @romid AND IsMediaGroup = @ismediagroup AND UserId = @userid;";
|
||||
Dictionary<string, object> dbDict = new Dictionary<string, object>
|
||||
{
|
||||
{ "id", StateId },
|
||||
@@ -254,7 +255,9 @@ namespace gaseous_server.Controllers.v1_1
|
||||
}
|
||||
else
|
||||
{
|
||||
string filename = "savestate.state";
|
||||
// get rom data
|
||||
Roms.GameRomItem romItem = Roms.GetRom(RomId);
|
||||
|
||||
byte[] bytes;
|
||||
if ((bool)data.Rows[0]["Zipped"] == false)
|
||||
{
|
||||
@@ -264,7 +267,86 @@ namespace gaseous_server.Controllers.v1_1
|
||||
{
|
||||
bytes = Common.Decompress((byte[])data.Rows[0]["State"]);
|
||||
}
|
||||
string contentType = "application/octet-stream";
|
||||
|
||||
string contentType = "";
|
||||
string filename = ((DateTime)data.Rows[0]["StateDateTime"]).ToString("yyyy-MM-ddTHH-mm-ss") + "-" + Path.GetFileNameWithoutExtension(romItem.Name);
|
||||
|
||||
|
||||
if (StateOnly == true)
|
||||
{
|
||||
contentType = "application/octet-stream";
|
||||
filename = filename + ".state";
|
||||
}
|
||||
else
|
||||
{
|
||||
contentType = "application/zip";
|
||||
filename = filename + ".zip";
|
||||
|
||||
Dictionary<string, object> RomInfo = new Dictionary<string, object>
|
||||
{
|
||||
{ "Name", romItem.Name },
|
||||
{ "StateDateTime", data.Rows[0]["StateDateTime"] },
|
||||
{ "StateName", data.Rows[0]["Name"] }
|
||||
};
|
||||
if ((int)data.Rows[0]["IsMediaGroup"] == 0)
|
||||
{
|
||||
RomInfo.Add("MD5", romItem.Md5);
|
||||
RomInfo.Add("SHA1", romItem.Sha1);
|
||||
RomInfo.Add("Type", "ROM");
|
||||
}
|
||||
else
|
||||
{
|
||||
RomInfo.Add("Type", "Media Group");
|
||||
RomInfo.Add("MediaGroupId", (long)data.Rows[0]["RomId"]);
|
||||
}
|
||||
string RomInfoString = Newtonsoft.Json.JsonConvert.SerializeObject(RomInfo, Newtonsoft.Json.Formatting.Indented, new Newtonsoft.Json.JsonSerializerSettings { NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore });
|
||||
|
||||
// compile zip file
|
||||
using (var compressedFileStream = new MemoryStream())
|
||||
{
|
||||
List<Dictionary<string, object>> Attachments = new List<Dictionary<string, object>>();
|
||||
Attachments.Add(new Dictionary<string, object>
|
||||
{
|
||||
{ "Name", "savestate.state" },
|
||||
{ "Body", bytes }
|
||||
});
|
||||
// check if value is dbnull
|
||||
if (data.Rows[0]["Screenshot"] != DBNull.Value)
|
||||
{
|
||||
Attachments.Add(new Dictionary<string, object>
|
||||
{
|
||||
{ "Name", "screenshot.jpg" },
|
||||
{ "Body", (byte[])data.Rows[0]["Screenshot"] }
|
||||
});
|
||||
}
|
||||
Attachments.Add(new Dictionary<string, object>
|
||||
{
|
||||
{ "Name", "rominfo.json" },
|
||||
{ "Body", System.Text.Encoding.UTF8.GetBytes(RomInfoString) }
|
||||
});
|
||||
|
||||
//Create an archive and store the stream in memory.
|
||||
using (var zipArchive = new ZipArchive(compressedFileStream, ZipArchiveMode.Create, false))
|
||||
{
|
||||
foreach (var Attachment in Attachments)
|
||||
{
|
||||
//Create a zip entry for each attachment
|
||||
var zipEntry = zipArchive.CreateEntry(Attachment["Name"].ToString());
|
||||
|
||||
//Get the stream of the attachment
|
||||
using (var originalFileStream = new MemoryStream((byte[])Attachment["Body"]))
|
||||
using (var zipEntryStream = zipEntry.Open())
|
||||
{
|
||||
//Copy the attachment stream to the zip entry stream
|
||||
originalFileStream.CopyTo(zipEntryStream);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//return new FileContentResult(compressedFileStream.ToArray(), "application/zip") { FileDownloadName = filename };
|
||||
bytes = compressedFileStream.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
var cd = new System.Net.Mime.ContentDisposition
|
||||
{
|
||||
@@ -279,6 +361,156 @@ namespace gaseous_server.Controllers.v1_1
|
||||
}
|
||||
}
|
||||
|
||||
[MapToApiVersion("1.0")]
|
||||
[MapToApiVersion("1.1")]
|
||||
[HttpPost]
|
||||
[Authorize]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[RequestSizeLimit(long.MaxValue)]
|
||||
[Consumes("multipart/form-data")]
|
||||
[DisableRequestSizeLimit, RequestFormLimits(MultipartBodyLengthLimit = long.MaxValue, ValueLengthLimit = int.MaxValue)]
|
||||
[Route("Upload")]
|
||||
public async Task<ActionResult> UploadStateDataAsync(IFormFile file, long RomId = 0, bool IsMediaGroup = false)
|
||||
{
|
||||
// get user
|
||||
var user = await _userManager.GetUserAsync(User);
|
||||
|
||||
if (file.Length > 0)
|
||||
{
|
||||
MemoryStream fileContent = new MemoryStream();
|
||||
file.CopyTo(fileContent);
|
||||
|
||||
// test if file is a zip file
|
||||
try
|
||||
{
|
||||
using (var zipArchive = new ZipArchive(fileContent, ZipArchiveMode.Read, false))
|
||||
{
|
||||
foreach (var entry in zipArchive.Entries)
|
||||
{
|
||||
if (entry.FullName == "rominfo.json")
|
||||
{
|
||||
using (var stream = entry.Open())
|
||||
using (var reader = new StreamReader(stream))
|
||||
{
|
||||
string RomInfoString = reader.ReadToEnd();
|
||||
Dictionary<string, object> RomInfo = Newtonsoft.Json.JsonConvert.DeserializeObject<Dictionary<string, object>>(RomInfoString);
|
||||
|
||||
// get rom data
|
||||
Roms.GameRomItem romItem;
|
||||
|
||||
try
|
||||
{
|
||||
romItem = Roms.GetRom((string)RomInfo["MD5"]);
|
||||
}
|
||||
catch (Roms.InvalidRomHash)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
// get state data
|
||||
byte[] StateData = null;
|
||||
byte[] ScreenshotData = null;
|
||||
string StateName = RomInfo["StateName"].ToString();
|
||||
DateTime StateDateTime = DateTime.Parse(RomInfo["StateDateTime"].ToString());
|
||||
IsMediaGroup = RomInfo["Type"].ToString() == "Media Group" ? true : false;
|
||||
|
||||
if (zipArchive.GetEntry("savestate.state") != null)
|
||||
{
|
||||
using (var stateStream = zipArchive.GetEntry("savestate.state").Open())
|
||||
using (var stateReader = new MemoryStream())
|
||||
{
|
||||
stateStream.CopyTo(stateReader);
|
||||
StateData = stateReader.ToArray();
|
||||
}
|
||||
}
|
||||
if (zipArchive.GetEntry("screenshot.jpg") != null)
|
||||
{
|
||||
using (var screenshotStream = zipArchive.GetEntry("screenshot.jpg").Open())
|
||||
using (var screenshotReader = new MemoryStream())
|
||||
{
|
||||
screenshotStream.CopyTo(screenshotReader);
|
||||
ScreenshotData = screenshotReader.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
// save state
|
||||
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
|
||||
string sql = "INSERT INTO GameState (UserId, RomId, IsMediaGroup, StateDateTime, Name, Screenshot, State, Zipped) VALUES (@userid, @romid, @ismediagroup, @statedatetime, @name, @screenshot, @state, @zipped); SELECT LAST_INSERT_ID();";
|
||||
Dictionary<string, object> dbDict = new Dictionary<string, object>
|
||||
{
|
||||
{ "userid", user.Id },
|
||||
{ "romid", romItem.Id },
|
||||
{ "ismediagroup", IsMediaGroup },
|
||||
{ "statedatetime", StateDateTime },
|
||||
{ "name", StateName },
|
||||
{ "screenshot", ScreenshotData },
|
||||
{ "state", Common.Compress(StateData) },
|
||||
{ "zipped", true }
|
||||
};
|
||||
DataTable data = db.ExecuteCMD(sql, dbDict);
|
||||
|
||||
RomInfo.Add("RomId", romItem.Id);
|
||||
RomInfo.Add("Management", "Managed");
|
||||
return Ok(RomInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return BadRequest("File is not a valid Gaseous state file.");
|
||||
}
|
||||
catch
|
||||
{
|
||||
// not a zip file
|
||||
if (RomId != 0)
|
||||
{
|
||||
// get rom data
|
||||
Roms.GameRomItem romItem;
|
||||
|
||||
try
|
||||
{
|
||||
romItem = Roms.GetRom(RomId);
|
||||
}
|
||||
catch (Roms.InvalidRomHash)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
// save state
|
||||
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
|
||||
string sql = "INSERT INTO GameState (UserId, RomId, IsMediaGroup, StateDateTime, Name, Screenshot, State, Zipped) VALUES (@userid, @romid, @ismediagroup, @statedatetime, @name, @screenshot, @state, @zipped); SELECT LAST_INSERT_ID();";
|
||||
Dictionary<string, object> dbDict = new Dictionary<string, object>
|
||||
{
|
||||
{ "userid", user.Id },
|
||||
{ "romid", RomId },
|
||||
{ "ismediagroup", IsMediaGroup },
|
||||
{ "statedatetime", DateTime.UtcNow },
|
||||
{ "name", "" },
|
||||
{ "screenshot", null },
|
||||
{ "state", Common.Compress(fileContent.ToArray()) },
|
||||
{ "zipped", true }
|
||||
};
|
||||
DataTable data = db.ExecuteCMD(sql, dbDict);
|
||||
|
||||
return Ok(new Dictionary<string, object>
|
||||
{
|
||||
{ "RomId", RomId },
|
||||
{ "Management", "Unmanaged" }
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
return BadRequest("No rom id provided.");
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return BadRequest("File is empty.");
|
||||
}
|
||||
}
|
||||
|
||||
private Models.GameStateItem BuildGameStateItem(DataRow dr)
|
||||
{
|
||||
bool HasScreenshot = true;
|
||||
|
@@ -16,7 +16,7 @@ namespace gaseous_server.Models
|
||||
{
|
||||
var targetType = this.GetType();
|
||||
var sourceType = game.GetType();
|
||||
foreach(var prop in targetType.GetProperties(BindingFlags.Instance | BindingFlags.Public| BindingFlags.SetProperty))
|
||||
foreach (var prop in targetType.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.SetProperty))
|
||||
{
|
||||
// check whether source object has the the property
|
||||
var sp = sourceType.GetProperty(prop.Name);
|
||||
@@ -39,7 +39,11 @@ namespace gaseous_server.Models
|
||||
{
|
||||
if (this.Cover.Id != null)
|
||||
{
|
||||
IGDB.Models.Cover cover = Covers.GetCover(Cover.Id, Config.LibraryConfiguration.LibraryMetadataDirectory_Game(this), false);
|
||||
// IGDB.Models.Cover cover = Covers.GetCover(Cover.Id, Config.LibraryConfiguration.LibraryMetadataDirectory_Game(this), false);
|
||||
IGDB.Models.Cover cover = new IGDB.Models.Cover()
|
||||
{
|
||||
Id = this.Cover.Id
|
||||
};
|
||||
|
||||
return cover;
|
||||
}
|
||||
|
@@ -27,7 +27,8 @@ namespace gaseous_server.Models
|
||||
{
|
||||
string rawJson = reader.ReadToEnd();
|
||||
List<PlatformMapItem> platforms = new List<PlatformMapItem>();
|
||||
Newtonsoft.Json.JsonSerializerSettings jsonSerializerSettings = new JsonSerializerSettings{
|
||||
Newtonsoft.Json.JsonSerializerSettings jsonSerializerSettings = new JsonSerializerSettings
|
||||
{
|
||||
MaxDepth = 64
|
||||
};
|
||||
platforms = Newtonsoft.Json.JsonConvert.DeserializeObject<List<PlatformMapItem>>(rawJson, jsonSerializerSettings);
|
||||
@@ -74,7 +75,7 @@ namespace gaseous_server.Models
|
||||
foreach (PlatformMapItem mapItem in platforms)
|
||||
{
|
||||
// get the IGDB platform data
|
||||
Platform platform = Platforms.GetPlatform(mapItem.IGDBId);
|
||||
Platform platform = Platforms.GetPlatform(mapItem.IGDBId, false);
|
||||
|
||||
try
|
||||
{
|
||||
@@ -254,7 +255,7 @@ namespace gaseous_server.Models
|
||||
}
|
||||
}
|
||||
|
||||
public static void WriteAvailableEmulators (PlatformMapItem item)
|
||||
public static void WriteAvailableEmulators(PlatformMapItem item)
|
||||
{
|
||||
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
|
||||
string sql = "";
|
||||
@@ -286,7 +287,7 @@ namespace gaseous_server.Models
|
||||
string sql = "";
|
||||
|
||||
// get platform data
|
||||
IGDB.Models.Platform? platform = Platforms.GetPlatform(IGDBId);
|
||||
IGDB.Models.Platform? platform = Platforms.GetPlatform(IGDBId, false);
|
||||
|
||||
if (platform != null)
|
||||
{
|
||||
@@ -369,12 +370,14 @@ namespace gaseous_server.Models
|
||||
mapItem.IGDBName = platform.Name;
|
||||
mapItem.IGDBSlug = platform.Slug;
|
||||
mapItem.AlternateNames = alternateNames;
|
||||
mapItem.Extensions = new PlatformMapItem.FileExtensions{
|
||||
mapItem.Extensions = new PlatformMapItem.FileExtensions
|
||||
{
|
||||
SupportedFileExtensions = knownExtensions,
|
||||
UniqueFileExtensions = uniqueExtensions
|
||||
};
|
||||
mapItem.RetroPieDirectoryName = (string)Common.ReturnValueIfNull(row["RetroPieDirectoryName"], "");
|
||||
mapItem.WebEmulator = new PlatformMapItem.WebEmulatorItem{
|
||||
mapItem.WebEmulator = new PlatformMapItem.WebEmulatorItem
|
||||
{
|
||||
Type = (string)Common.ReturnValueIfNull(row["WebEmulator_Type"], ""),
|
||||
Core = (string)Common.ReturnValueIfNull(row["WebEmulator_Core"], ""),
|
||||
AvailableWebEmulators = Newtonsoft.Json.JsonConvert.DeserializeObject<List<PlatformMapItem.WebEmulatorItem.AvailableWebEmulatorItem>>((string)Common.ReturnValueIfNull(row["AvailableWebEmulators"], "[]"))
|
||||
|
@@ -20,4 +20,3 @@ namespace gaseous_server.Models
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -350,7 +350,8 @@ namespace gaseous_server
|
||||
|
||||
case QueueItemType.DailyMaintainer:
|
||||
Logging.Log(Logging.LogType.Debug, "Timered Event", "Starting Daily Maintenance");
|
||||
Classes.Maintenance maintenance = new Maintenance{
|
||||
Classes.Maintenance maintenance = new Maintenance
|
||||
{
|
||||
CallingQueueItem = this
|
||||
};
|
||||
maintenance.RunDailyMaintenance();
|
||||
@@ -361,7 +362,8 @@ namespace gaseous_server
|
||||
|
||||
case QueueItemType.WeeklyMaintainer:
|
||||
Logging.Log(Logging.LogType.Debug, "Timered Event", "Starting Weekly Maintenance");
|
||||
Classes.Maintenance weeklyMaintenance = new Maintenance{
|
||||
Classes.Maintenance weeklyMaintenance = new Maintenance
|
||||
{
|
||||
CallingQueueItem = this
|
||||
};
|
||||
weeklyMaintenance.RunWeeklyMaintenance();
|
||||
|
@@ -36,12 +36,19 @@ db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.Conn
|
||||
|
||||
// set up db
|
||||
db.InitDB();
|
||||
// create relation tables if they don't exist
|
||||
Storage.CreateRelationsTables<IGDB.Models.Game>();
|
||||
Storage.CreateRelationsTables<IGDB.Models.Platform>();
|
||||
|
||||
// populate db with static data for lookups
|
||||
AgeRatings.PopulateAgeMap();
|
||||
|
||||
// load app settings
|
||||
Config.InitSettings();
|
||||
|
||||
// disable hasheous
|
||||
Config.MetadataConfiguration.SignatureSource = HasheousClient.Models.MetadataModel.SignatureSources.LocalOnly;
|
||||
|
||||
// write updated settings back to the config file
|
||||
Config.UpdateConfig();
|
||||
|
||||
@@ -307,7 +314,7 @@ app.Use(async (context, next) =>
|
||||
string userIdentity;
|
||||
try
|
||||
{
|
||||
userIdentity = context.User.Claims.Where(x=>x.Type==System.Security.Claims.ClaimTypes.NameIdentifier).FirstOrDefault().Value;
|
||||
userIdentity = context.User.Claims.Where(x => x.Type == System.Security.Claims.ClaimTypes.NameIdentifier).FirstOrDefault().Value;
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -20,7 +20,7 @@
|
||||
<PackageReference Include="Asp.Versioning.Mvc.ApiExplorer" Version="8.0.0" />
|
||||
<PackageReference Include="gaseous-signature-parser" Version="2.1.0" />
|
||||
<PackageReference Include="gaseous.IGDB" Version="1.0.2" />
|
||||
<PackageReference Include="hasheous-client" Version="0.2.0" />
|
||||
<PackageReference Include="hasheous-client" Version="0.1.0" />
|
||||
<PackageReference Include="Magick.NET-Q8-AnyCPU" Version="13.5.0" />
|
||||
<PackageReference Include="sharpcompress" Version="0.36.0" />
|
||||
<PackageReference Include="Squid-Box.SevenZipSharp" Version="1.6.1.23" />
|
||||
|
@@ -1,5 +1,6 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<script src="/api/v1.1/System/VersionFile"></script>
|
||||
@@ -44,6 +45,7 @@
|
||||
var userProfile;
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<!-- Notifications -->
|
||||
<div id="notifications_target"></div>
|
||||
@@ -54,7 +56,9 @@
|
||||
<div id="banner_header">
|
||||
<div id="bannerButtons">
|
||||
<div id="banner_user" onclick="showMenu();" class="banner_button dropdown dropbtn">
|
||||
<img src="/images/user.svg" alt="Account" title="Account" id="banner_user_image" class="banner_button_image" style="position: relative; top: 10px; right: 0px; pointer-events: none;" onclick="showMenu();" />
|
||||
<img src="/images/user.svg" alt="Account" title="Account" id="banner_user_image"
|
||||
class="banner_button_image" style="position: relative; top: 10px; right: 0px; pointer-events: none;"
|
||||
onclick="showMenu();" />
|
||||
<div id="myDropdown" class="dropdown-content">
|
||||
<div id="banner_user_roles"></div>
|
||||
<a href="#" onclick="showDialog('userprofile');">Profile</a>
|
||||
@@ -63,22 +67,27 @@
|
||||
</div>
|
||||
|
||||
<div id="banner_cog" onclick="window.location.href = '/index.html?page=settings';" class="banner_button">
|
||||
<img src="/images/settings.svg" alt="Settings" title="Settings" id="banner_system_image" class="banner_button_image" />
|
||||
<img src="/images/settings.svg" alt="Settings" title="Settings" id="banner_system_image"
|
||||
class="banner_button_image" />
|
||||
<span id="banner_system_label">Settings</span>
|
||||
</div>
|
||||
|
||||
<div id="banner_upload" onclick="showDialog('upload');" class="banner_button">
|
||||
<img src="/images/upload.svg" alt="Upload" title="Upload" id="banner_upload_image" class="banner_button_image" />
|
||||
<img src="/images/upload.svg" alt="Upload" title="Upload" id="banner_upload_image"
|
||||
class="banner_button_image" />
|
||||
<span id="banner_upload_label">Upload</span>
|
||||
</div>
|
||||
|
||||
<div id="banner_collections" onclick="window.location.href = '/index.html?page=collections';" class="banner_button">
|
||||
<img src="/images/collections.svg" alt="Collections" title="Collections" id="banner_collections_image" class="banner_button_image" />
|
||||
<div id="banner_collections" onclick="window.location.href = '/index.html?page=collections';"
|
||||
class="banner_button">
|
||||
<img src="/images/collections.svg" alt="Collections" title="Collections" id="banner_collections_image"
|
||||
class="banner_button_image" />
|
||||
<span id="banner_collections_label">Collections</span>
|
||||
</div>
|
||||
|
||||
<div id="banner_library" onclick="window.location.href = '/index.html';" class="banner_button">
|
||||
<img src="/images/library.svg" alt="Library" title="Library" id="banner_library_image" class="banner_button_image" />
|
||||
<img src="/images/library.svg" alt="Library" title="Library" id="banner_library_image"
|
||||
class="banner_button_image" />
|
||||
<span id="banner_library_label">Library</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -97,7 +106,9 @@
|
||||
<!-- Modal content -->
|
||||
<div class="modal-content">
|
||||
<span class="close">×</span>
|
||||
<div><h1 id="modal-heading">Modal heading</h1></div>
|
||||
<div>
|
||||
<h1 id="modal-heading">Modal heading</h1>
|
||||
</div>
|
||||
<div id="modal-content">Some text in the Modal..</div>
|
||||
</div>
|
||||
|
||||
@@ -118,7 +129,7 @@
|
||||
var modalVariables = null;
|
||||
|
||||
// redirect if first run status = 0
|
||||
if (FirstRunStatus == 0) {
|
||||
if (FirstRunStatus == 0 || FirstRunStatus == "0") {
|
||||
window.location.replace("/pages/first.html");
|
||||
}
|
||||
|
||||
@@ -126,7 +137,7 @@
|
||||
ajaxCall(
|
||||
'/api/v1.1/Account/Profile/Basic',
|
||||
'GET',
|
||||
function(result) {
|
||||
function (result) {
|
||||
console.log("User is logged in");
|
||||
userProfile = result;
|
||||
|
||||
@@ -147,7 +158,7 @@
|
||||
|
||||
$('#content').load('/pages/' + myParam + '.html?v=' + AppVersion);
|
||||
},
|
||||
function(error) {
|
||||
function (error) {
|
||||
window.location.replace("/pages/login.html");
|
||||
}
|
||||
);
|
||||
@@ -159,7 +170,7 @@
|
||||
}
|
||||
|
||||
// Close the dropdown menu if the user clicks outside of it
|
||||
window.onclick = function(event) {
|
||||
window.onclick = function (event) {
|
||||
if (!event.target.matches('.dropbtn')) {
|
||||
var dropdowns = document.getElementsByClassName("dropdown-content");
|
||||
var i;
|
||||
@@ -186,4 +197,5 @@
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
@@ -1,6 +1,9 @@
|
||||
<div id="saved_states">
|
||||
|
||||
</div>
|
||||
<div>
|
||||
<span>Upload state file: </span><input type="file" id="stateFile" />
|
||||
</div>
|
||||
|
||||
<script text="text/javascript">
|
||||
document.getElementById('modal-heading').innerHTML = "Load saved state";
|
||||
@@ -13,9 +16,10 @@
|
||||
ajaxCall(
|
||||
statesUrl,
|
||||
'GET',
|
||||
function(result) {
|
||||
function (result) {
|
||||
var statesBox = document.getElementById('saved_states');
|
||||
statesBox.innerHTML = '';
|
||||
document.getElementById('stateFile').value = '';
|
||||
|
||||
for (var i = 0; i < result.length; i++) {
|
||||
var stateBox = document.createElement('div');
|
||||
@@ -62,7 +66,7 @@
|
||||
stateControls.id = 'stateControls_' + result[i].id;
|
||||
stateControls.className = 'saved_state_controls';
|
||||
|
||||
var stateControlsLaunch= document.createElement('span');
|
||||
var stateControlsLaunch = document.createElement('span');
|
||||
stateControlsLaunch.id = 'stateControlsLaunch_' + result[i].id;
|
||||
stateControlsLaunch.className = 'romstart';
|
||||
var emulatorTarget = '/index.html?page=emulator&engine=@engine&core=@core&platformid=@platformid&gameid=@gameid&romid=@romid&mediagroup=@mediagroup&rompath=@rompath&stateid=' + result[i].id;
|
||||
@@ -128,7 +132,7 @@
|
||||
ajaxCall(
|
||||
'/api/v1.1/StateManager/' + modalVariables.romId + '/' + StateId + '?IsMediaGroup=' + IsMediaGroup,
|
||||
'DELETE',
|
||||
function(success) {
|
||||
function (success) {
|
||||
LoadStates();
|
||||
},
|
||||
function (error) {
|
||||
@@ -147,7 +151,7 @@
|
||||
ajaxCall(
|
||||
'/api/v1.1/StateManager/' + modalVariables.romId + '/' + StateId + '?IsMediaGroup=' + IsMediaGroup,
|
||||
'PUT',
|
||||
function(success) {
|
||||
function (success) {
|
||||
LoadStates();
|
||||
},
|
||||
function (error) {
|
||||
@@ -156,4 +160,36 @@
|
||||
JSON.stringify(model)
|
||||
);
|
||||
}
|
||||
|
||||
document.getElementById('stateFile').addEventListener('change', function () {
|
||||
let file = document.getElementById('stateFile').files[0];
|
||||
let formData = new FormData();
|
||||
formData.append('file', file);
|
||||
|
||||
console.log("Uploading state file");
|
||||
|
||||
fetch('/api/v1.1/StateManager/Upload?RomId=' + modalVariables.romId + '&IsMediaGroup=' + modalVariables.IsMediaGroup, {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
console.log('Success:', data);
|
||||
UploadAlert(data);
|
||||
LoadStates();
|
||||
})
|
||||
.catch(error => {
|
||||
console.error("Error:", error);
|
||||
UploadAlert(error);
|
||||
LoadStates();
|
||||
});
|
||||
});
|
||||
|
||||
function UploadAlert(data) {
|
||||
if (data.Management == "Managed") {
|
||||
alert("State uploaded successfully.");
|
||||
} else {
|
||||
alert("State uploaded successfully, but it might not function correctly for this platform and ROM.");
|
||||
}
|
||||
}
|
||||
</script>
|
@@ -13,7 +13,7 @@
|
||||
if (IsMediaGroupInt == 1) { IsMediaGroup = true; }
|
||||
var StateUrl = undefined;
|
||||
if (getQueryString('stateid', 'int')) {
|
||||
StateUrl = '/api/v1.1/StateManager/' + romId + '/' + getQueryString('stateid', 'int') + '/State/savestate.state?IsMediaGroup=' + IsMediaGroup;
|
||||
StateUrl = '/api/v1.1/StateManager/' + romId + '/' + getQueryString('stateid', 'int') + '/State/savestate.state?StateOnly=true&IsMediaGroup=' + IsMediaGroup;
|
||||
}
|
||||
var gameData;
|
||||
var artworks = null;
|
||||
|
@@ -1,5 +1,6 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<script src="/api/v1.1/System/VersionFile"></script>
|
||||
@@ -40,8 +41,10 @@
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="bgImage" style="background-image: url('/images/LoginWallpaper.jpg'); background-position: center; background-repeat: no-repeat; background-size: cover; filter: blur(10px); -webkit-filter: blur(10px);">
|
||||
<div id="bgImage"
|
||||
style="background-image: url('/images/LoginWallpaper.jpg'); background-position: center; background-repeat: no-repeat; background-size: cover; filter: blur(10px); -webkit-filter: blur(10px);">
|
||||
<div id="bgImage_Opacity"></div>
|
||||
</div>
|
||||
|
||||
@@ -52,7 +55,9 @@
|
||||
|
||||
<div id="loginwindow_header_label" style="display: block; text-align: center;">Gaseous Games</div>
|
||||
|
||||
<button type="button" value="Get Started" onclick="document.getElementById('first_welcome').style.display = 'none'; document.getElementById('first_newadmin').style.display = '';" class="bigbutton">Get Started</button>
|
||||
<button type="button" value="Get Started"
|
||||
onclick="document.getElementById('first_welcome').style.display = 'none'; document.getElementById('first_newadmin').style.display = '';"
|
||||
class="bigbutton">Get Started</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="loginwindow" id="first_newadmin" style="display: none;">
|
||||
@@ -67,15 +72,18 @@
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Email</th>
|
||||
<td><input type="email" id="login_email" style="width: 95%;" onkeyup="checkPasswordsMatch();" /></td>
|
||||
<td><input type="email" id="login_email" style="width: 95%;" onkeyup="checkPasswordsMatch();" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>New Password</th>
|
||||
<td><input type="password" id="login_password" style="width: 95%;" onkeyup="checkPasswordsMatch();" /></td>
|
||||
<td><input type="password" id="login_password" style="width: 95%;"
|
||||
onkeyup="checkPasswordsMatch();" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Confirm Password</th>
|
||||
<td><input type="password" id="login_confirmpassword" style="width: 95%;" onkeyup="checkPasswordsMatch();" /></td>
|
||||
<td><input type="password" id="login_confirmpassword" style="width: 95%;"
|
||||
onkeyup="checkPasswordsMatch();" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2" id="login_passwordnotice"> </td>
|
||||
@@ -85,7 +93,9 @@
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2" style="padding-top: 20px;">
|
||||
<button id="login_createaccount" type="button" value="Create Account" onclick="registerAccount();" disabled="disabled" class="bigbutton">Create Account</button>
|
||||
<button id="login_createaccount" type="button" value="Create Account"
|
||||
onclick="registerAccount();" disabled="disabled" class="bigbutton">Create
|
||||
Account</button>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
@@ -94,12 +104,14 @@
|
||||
</div>
|
||||
|
||||
<div id="settings_photocredit">
|
||||
Wallpaper by <a href="https://unsplash.com/@spideyjoey" class="romlink">Joey Kwok</a> / <a href="https://unsplash.com/photos/a-room-filled-with-arcade-machines-and-neon-lights-jbIsTd7rdd8" class="romlink">Unsplash</a>
|
||||
Wallpaper by <a href="https://unsplash.com/@spideyjoey" class="romlink">Joey Kwok</a> / <a
|
||||
href="https://unsplash.com/photos/a-room-filled-with-arcade-machines-and-neon-lights-jbIsTd7rdd8"
|
||||
class="romlink">Unsplash</a>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
// redirect if first run status != 0 as 0 indicates that first run needs to be run
|
||||
if (FirstRunStatus != 0) {
|
||||
if (FirstRunStatus != 0 && FirstRunStatus != "0") {
|
||||
window.location.replace("/");
|
||||
}
|
||||
|
||||
@@ -146,10 +158,10 @@
|
||||
ajaxCall(
|
||||
'/api/v1.1/FirstSetup/0',
|
||||
'POST',
|
||||
function(result){
|
||||
function (result) {
|
||||
loginCallback(result);
|
||||
},
|
||||
function(error){
|
||||
function (error) {
|
||||
loginCallback(error);
|
||||
},
|
||||
JSON.stringify(model)
|
||||
|
@@ -63,9 +63,9 @@ function getQueryString(stringName, type) {
|
||||
|
||||
function setCookie(cname, cvalue, exdays) {
|
||||
const d = new Date();
|
||||
d.setTime(d.getTime() + (exdays*24*60*60*1000));
|
||||
d.setTime(d.getTime() + (exdays * 24 * 60 * 60 * 1000));
|
||||
if (exdays) {
|
||||
let expires = "expires="+ d.toUTCString();
|
||||
let expires = "expires=" + d.toUTCString();
|
||||
document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/";
|
||||
} else {
|
||||
document.cookie = cname + "=" + cvalue + ";path=/";
|
||||
@@ -76,7 +76,7 @@ function getCookie(cname) {
|
||||
let name = cname + "=";
|
||||
let decodedCookie = decodeURIComponent(document.cookie);
|
||||
let ca = decodedCookie.split(';');
|
||||
for(let i = 0; i <ca.length; i++) {
|
||||
for (let i = 0; i < ca.length; i++) {
|
||||
let c = ca[i];
|
||||
while (c.charAt(0) == ' ') {
|
||||
c = c.substring(1);
|
||||
@@ -207,7 +207,7 @@ function createTableRow(isHeader, row, rowClass, cellClass) {
|
||||
}
|
||||
|
||||
var newCell = document.createElement(cellType);
|
||||
if (typeof(row[i]) != "object") {
|
||||
if (typeof (row[i]) != "object") {
|
||||
newCell.innerHTML = row[i];
|
||||
newCell.className = cellClass;
|
||||
} else {
|
||||
@@ -258,7 +258,7 @@ function DropDownRenderGameOption(state) {
|
||||
|
||||
if (state.cover) {
|
||||
response = $(
|
||||
'<table class="dropdown-div"><tr><td class="dropdown-cover"><img src="/api/v1.1/Games/' + state.id + '/cover/image/cover_small/' + state.cover.imageId + '.jpg" /></td><td class="dropdown-label"><span class="dropdown-title">' + state.text + '</span><span class="dropdown-releasedate">' + releaseDate + '</span></td></tr></table>'
|
||||
'<table class="dropdown-div"><tr><td class="dropdown-cover"><img src="/api/v1.1/Games/' + state.id + '/cover/image/cover_small/' + state.id + '.jpg" class="game_tile_small_search" /></td><td class="dropdown-label"><span class="dropdown-title">' + state.text + '</span><span class="dropdown-releasedate">' + releaseDate + '</span></td></tr></table>'
|
||||
);
|
||||
} else {
|
||||
response = $(
|
||||
@@ -318,7 +318,7 @@ function CreateEditableTable(TableName, Headers) {
|
||||
addButton.value = 'Add Row';
|
||||
addButton.innerHTML = 'Add Row';
|
||||
|
||||
$(addButton).click(function() {
|
||||
$(addButton).click(function () {
|
||||
eTable.appendChild(AddEditableTableRow(Headers));
|
||||
});
|
||||
|
||||
@@ -463,10 +463,10 @@ function SetPreference(Setting, Value) {
|
||||
ajaxCall(
|
||||
'/api/v1.1/Account/Preferences',
|
||||
'POST',
|
||||
function(result) {
|
||||
function (result) {
|
||||
SetPreference_Local(Setting, Value);
|
||||
},
|
||||
function(error) {
|
||||
function (error) {
|
||||
SetPreference_Local(Setting, Value);
|
||||
},
|
||||
JSON.stringify(model)
|
||||
@@ -478,12 +478,12 @@ function SetPreference_Batch(model) {
|
||||
ajaxCall(
|
||||
'/api/v1.1/Account/Preferences',
|
||||
'POST',
|
||||
function(result) {
|
||||
function (result) {
|
||||
for (var i = 0; i < model.length; i++) {
|
||||
SetPreference_Local(model[i].setting, model[i].value.toString());
|
||||
}
|
||||
},
|
||||
function(error) {
|
||||
function (error) {
|
||||
for (var i = 0; i < model.length; i++) {
|
||||
SetPreference_Local(model[i].setting, model[i].value.toString());
|
||||
}
|
||||
@@ -509,11 +509,11 @@ function SetPreference_Local(Setting, Value) {
|
||||
}
|
||||
}
|
||||
|
||||
function Uint8ToString(u8a){
|
||||
function Uint8ToString(u8a) {
|
||||
var CHUNK_SZ = 0x8000;
|
||||
var c = [];
|
||||
for (var i=0; i < u8a.length; i+=CHUNK_SZ) {
|
||||
c.push(String.fromCharCode.apply(null, u8a.subarray(i, i+CHUNK_SZ)));
|
||||
for (var i = 0; i < u8a.length; i += CHUNK_SZ) {
|
||||
c.push(String.fromCharCode.apply(null, u8a.subarray(i, i + CHUNK_SZ)));
|
||||
}
|
||||
return c.join("");
|
||||
}
|
||||
|
@@ -23,21 +23,29 @@ h3 {
|
||||
border-bottom-width: 1px;
|
||||
/*border-image: linear-gradient(to right, blue 25%, yellow 25%, yellow 50%,red 50%, red 75%, teal 75%) 5;*/
|
||||
|
||||
border-image: linear-gradient(to right, rgba(255,0,0,1) 0%, rgba(251,255,0,1) 16%, rgba(0,255,250,1) 30%, rgba(0,16,255,1) 46%, rgba(250,0,255,1) 62%, rgba(255,0,0,1) 78%, rgba(255,237,0,1) 90%, rgba(20,255,0,1) 100%) 5;
|
||||
border-image: linear-gradient(to right, rgba(255, 0, 0, 1) 0%, rgba(251, 255, 0, 1) 16%, rgba(0, 255, 250, 1) 30%, rgba(0, 16, 255, 1) 46%, rgba(250, 0, 255, 1) 62%, rgba(255, 0, 0, 1) 78%, rgba(255, 237, 0, 1) 90%, rgba(20, 255, 0, 1) 100%) 5;
|
||||
}
|
||||
|
||||
/* The Modal (background) */
|
||||
.modal {
|
||||
display: none; /* Hidden by default */
|
||||
position: fixed; /* Stay in place */
|
||||
z-index: 100; /* Sit on top */
|
||||
display: none;
|
||||
/* Hidden by default */
|
||||
position: fixed;
|
||||
/* Stay in place */
|
||||
z-index: 100;
|
||||
/* Sit on top */
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%; /* Full width */
|
||||
height: 100%; /* Full height */
|
||||
overflow: none; /* Enable scroll if needed */
|
||||
background-color: rgb(0,0,0); /* Fallback color */
|
||||
background-color: rgba(0,0,0,0.4); /* Black w/ opacity */
|
||||
width: 100%;
|
||||
/* Full width */
|
||||
height: 100%;
|
||||
/* Full height */
|
||||
overflow: none;
|
||||
/* Enable scroll if needed */
|
||||
background-color: rgb(0, 0, 0);
|
||||
/* Fallback color */
|
||||
background-color: rgba(0, 0, 0, 0.4);
|
||||
/* Black w/ opacity */
|
||||
backdrop-filter: blur(8px);
|
||||
-webkit-backdrop-filter: blur(8px);
|
||||
filter: drop-shadow(5px 5px 10px #000);
|
||||
@@ -47,22 +55,28 @@ h3 {
|
||||
/* Modal Content/Box */
|
||||
.modal-content {
|
||||
background-color: #383838;
|
||||
margin: 10% auto; /* 15% from the top and centered */
|
||||
margin: 10% auto;
|
||||
/* 15% from the top and centered */
|
||||
padding: 10px;
|
||||
border: 1px solid #888;
|
||||
border-radius: 10px;
|
||||
width: 700px; /* Could be more or less, depending on screen size */
|
||||
width: 700px;
|
||||
/* Could be more or less, depending on screen size */
|
||||
min-height: 358px;
|
||||
}
|
||||
|
||||
.modal-content-sub {
|
||||
background-color: #383838;
|
||||
margin: 20% auto; /* 20% from the top and centered */
|
||||
margin: 20% auto;
|
||||
/* 20% from the top and centered */
|
||||
padding: 10px;
|
||||
border: 1px solid #888;
|
||||
border-radius: 10px;
|
||||
width: 300px; /* Could be more or less, depending on screen size */
|
||||
width: 300px;
|
||||
/* Could be more or less, depending on screen size */
|
||||
min-height: 110px;
|
||||
}
|
||||
|
||||
#modal-heading {
|
||||
margin-block: 5px;
|
||||
border-bottom-style: solid;
|
||||
@@ -70,8 +84,9 @@ h3 {
|
||||
border-bottom-width: 3px;
|
||||
/*border-image: linear-gradient(to right, blue 25%, yellow 25%, yellow 50%,red 50%, red 75%, teal 75%) 5;*/
|
||||
|
||||
border-image: linear-gradient(to right, rgba(255,0,0,1) 0%, rgba(251,255,0,1) 16%, rgba(0,255,250,1) 30%, rgba(0,16,255,1) 46%, rgba(250,0,255,1) 62%, rgba(255,0,0,1) 78%, rgba(255,237,0,1) 90%, rgba(20,255,0,1) 100%) 5;
|
||||
border-image: linear-gradient(to right, rgba(255, 0, 0, 1) 0%, rgba(251, 255, 0, 1) 16%, rgba(0, 255, 250, 1) 30%, rgba(0, 16, 255, 1) 46%, rgba(250, 0, 255, 1) 62%, rgba(255, 0, 0, 1) 78%, rgba(255, 237, 0, 1) 90%, rgba(20, 255, 0, 1) 100%) 5;
|
||||
}
|
||||
|
||||
#modal-content {
|
||||
height: 100%;
|
||||
}
|
||||
@@ -296,7 +311,11 @@ h3 {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
input[type='text'], input[type='number'], input[type="email"], input[type="password"], input[type="datetime-local"] {
|
||||
input[type='text'],
|
||||
input[type='number'],
|
||||
input[type="email"],
|
||||
input[type="password"],
|
||||
input[type="datetime-local"] {
|
||||
background-color: #2b2b2b;
|
||||
color: white;
|
||||
padding: 4px;
|
||||
@@ -313,7 +332,11 @@ input[type='text'], input[type='number'], input[type="email"], input[type="passw
|
||||
height: 21px;
|
||||
}
|
||||
|
||||
input[type='text']:hover, input[type='number']:hover, input[type="email"]:hover, input[type="password"]:hover, input[type="datetime-local"]:hover {
|
||||
input[type='text']:hover,
|
||||
input[type='number']:hover,
|
||||
input[type="email"]:hover,
|
||||
input[type="password"]:hover,
|
||||
input[type="datetime-local"]:hover {
|
||||
border-color: #939393;
|
||||
}
|
||||
|
||||
@@ -351,9 +374,7 @@ input[name='filter_panel_range_max'] {
|
||||
background: #555;
|
||||
}
|
||||
|
||||
.text_link {
|
||||
|
||||
}
|
||||
.text_link {}
|
||||
|
||||
.text_link:hover {
|
||||
cursor: pointer;
|
||||
@@ -390,9 +411,9 @@ input[name='filter_panel_range_max'] {
|
||||
background-color: rgba(0, 22, 56, 0.8);
|
||||
backdrop-filter: blur(8px);
|
||||
-webkit-backdrop-filter: blur(8px);
|
||||
box-shadow: 5px 5px 19px 0px rgba(0,0,0,0.44);
|
||||
-webkit-box-shadow: 5px 5px 19px 0px rgba(0,0,0,0.44);
|
||||
-moz-box-shadow: 5px 5px 19px 0px rgba(0,0,0,0.44);
|
||||
box-shadow: 5px 5px 19px 0px rgba(0, 0, 0, 0.44);
|
||||
-webkit-box-shadow: 5px 5px 19px 0px rgba(0, 0, 0, 0.44);
|
||||
-moz-box-shadow: 5px 5px 19px 0px rgba(0, 0, 0, 0.44);
|
||||
}
|
||||
|
||||
.games_pager_number {
|
||||
@@ -604,6 +625,12 @@ input[name='filter_panel_range_max'] {
|
||||
border: 1px solid #2b2b2b;
|
||||
}
|
||||
|
||||
.game_tile_small_search {
|
||||
min-height: 50px;
|
||||
min-width: 50px;
|
||||
width: 80px;
|
||||
}
|
||||
|
||||
.game_tile_row {
|
||||
padding: 5px;
|
||||
display: block;
|
||||
@@ -706,9 +733,9 @@ input[name='filter_panel_range_max'] {
|
||||
}
|
||||
|
||||
.game_tile_image_shadow {
|
||||
box-shadow: 5px 5px 19px 0px rgba(0,0,0,0.44);
|
||||
-webkit-box-shadow: 5px 5px 19px 0px rgba(0,0,0,0.44);
|
||||
-moz-box-shadow: 5px 5px 19px 0px rgba(0,0,0,0.44);
|
||||
box-shadow: 5px 5px 19px 0px rgba(0, 0, 0, 0.44);
|
||||
-webkit-box-shadow: 5px 5px 19px 0px rgba(0, 0, 0, 0.44);
|
||||
-moz-box-shadow: 5px 5px 19px 0px rgba(0, 0, 0, 0.44);
|
||||
}
|
||||
|
||||
.game_tile_image_row {
|
||||
@@ -720,11 +747,13 @@ input[name='filter_panel_range_max'] {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.game_tile_image, .unknown {
|
||||
.game_tile_image,
|
||||
.unknown {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.game_tile_image_row, .unknown {
|
||||
.game_tile_image_row,
|
||||
.unknown {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
@@ -790,9 +819,9 @@ input[name='filter_panel_range_max'] {
|
||||
max-width: 250px;
|
||||
max-height: 350px;
|
||||
width: 100%;
|
||||
box-shadow: 5px 5px 19px 0px rgba(0,0,0,0.44);
|
||||
-webkit-box-shadow: 5px 5px 19px 0px rgba(0,0,0,0.44);
|
||||
-moz-box-shadow: 5px 5px 19px 0px rgba(0,0,0,0.44);
|
||||
box-shadow: 5px 5px 19px 0px rgba(0, 0, 0, 0.44);
|
||||
-webkit-box-shadow: 5px 5px 19px 0px rgba(0, 0, 0, 0.44);
|
||||
-moz-box-shadow: 5px 5px 19px 0px rgba(0, 0, 0, 0.44);
|
||||
}
|
||||
|
||||
.gamegenrelabel {
|
||||
@@ -842,9 +871,9 @@ input[name='filter_panel_range_max'] {
|
||||
padding: 10px;
|
||||
/*height: 350px;*/
|
||||
margin-bottom: 20px;
|
||||
box-shadow: 5px 5px 19px 0px rgba(0,0,0,0.44);
|
||||
-webkit-box-shadow: 5px 5px 19px 0px rgba(0,0,0,0.44);
|
||||
-moz-box-shadow: 5px 5px 19px 0px rgba(0,0,0,0.44);
|
||||
box-shadow: 5px 5px 19px 0px rgba(0, 0, 0, 0.44);
|
||||
-webkit-box-shadow: 5px 5px 19px 0px rgba(0, 0, 0, 0.44);
|
||||
-moz-box-shadow: 5px 5px 19px 0px rgba(0, 0, 0, 0.44);
|
||||
}
|
||||
|
||||
#gamescreenshots_main {
|
||||
@@ -903,11 +932,16 @@ iframe {
|
||||
background-color: #383838;
|
||||
color: black;
|
||||
text-align: center;
|
||||
user-select: none; /* standard syntax */
|
||||
-webkit-user-select: none; /* webkit (safari, chrome) browsers */
|
||||
-moz-user-select: none; /* mozilla browsers */
|
||||
-khtml-user-select: none; /* webkit (konqueror) browsers */
|
||||
-ms-user-select: none; /* IE10+ */
|
||||
user-select: none;
|
||||
/* standard syntax */
|
||||
-webkit-user-select: none;
|
||||
/* webkit (safari, chrome) browsers */
|
||||
-moz-user-select: none;
|
||||
/* mozilla browsers */
|
||||
-khtml-user-select: none;
|
||||
/* webkit (konqueror) browsers */
|
||||
-ms-user-select: none;
|
||||
/* IE10+ */
|
||||
}
|
||||
|
||||
.gamescreenshots_arrows:hover {
|
||||
@@ -981,9 +1015,7 @@ iframe {
|
||||
background-color: rgba(56, 56, 56, 0.9);
|
||||
}
|
||||
|
||||
#gamesummarytext_label {
|
||||
|
||||
}
|
||||
#gamesummarytext_label {}
|
||||
|
||||
.line-clamp-4 {
|
||||
overflow: hidden;
|
||||
@@ -1057,9 +1089,9 @@ th {
|
||||
-webkit-border-radius: 5px 5px 5px 5px;
|
||||
-moz-border-radius: 5px 5px 5px 5px;
|
||||
border: 1px solid #19d348;
|
||||
box-shadow: 5px 5px 19px 0px rgba(0,0,0,0.44);
|
||||
-webkit-box-shadow: 5px 5px 19px 0px rgba(0,0,0,0.44);
|
||||
-moz-box-shadow: 5px 5px 19px 0px rgba(0,0,0,0.44);
|
||||
box-shadow: 5px 5px 19px 0px rgba(0, 0, 0, 0.44);
|
||||
-webkit-box-shadow: 5px 5px 19px 0px rgba(0, 0, 0, 0.44);
|
||||
-moz-box-shadow: 5px 5px 19px 0px rgba(0, 0, 0, 0.44);
|
||||
}
|
||||
|
||||
.romstart:hover {
|
||||
@@ -1084,9 +1116,9 @@ th {
|
||||
border-color: white;
|
||||
background-color: blue;
|
||||
outline-color: blue;
|
||||
box-shadow: 5px 5px 19px 0px rgba(0,0,0,0.44);
|
||||
-webkit-box-shadow: 5px 5px 19px 0px rgba(0,0,0,0.44);
|
||||
-moz-box-shadow: 5px 5px 19px 0px rgba(0,0,0,0.44);
|
||||
box-shadow: 5px 5px 19px 0px rgba(0, 0, 0, 0.44);
|
||||
-webkit-box-shadow: 5px 5px 19px 0px rgba(0, 0, 0, 0.44);
|
||||
-moz-box-shadow: 5px 5px 19px 0px rgba(0, 0, 0, 0.44);
|
||||
}
|
||||
|
||||
.properties_button:hover {
|
||||
@@ -1115,14 +1147,18 @@ th {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
div[name="properties_toc_item"],div[name="properties_user_toc_item"],div[name="properties_profile_toc_item"] {
|
||||
div[name="properties_toc_item"],
|
||||
div[name="properties_user_toc_item"],
|
||||
div[name="properties_profile_toc_item"] {
|
||||
padding: 10px;
|
||||
border-bottom-width: 1px;
|
||||
border-bottom-style: solid;
|
||||
border-bottom-color: #2b2b2b;
|
||||
}
|
||||
|
||||
div[name="properties_toc_item"]:hover,div[name="properties_user_toc_item"]:hover,div[name="properties_profile_toc_item"]:hover {
|
||||
div[name="properties_toc_item"]:hover,
|
||||
div[name="properties_user_toc_item"]:hover,
|
||||
div[name="properties_profile_toc_item"]:hover {
|
||||
background-color: #2b2b2b;
|
||||
cursor: pointer;
|
||||
}
|
||||
@@ -1150,7 +1186,8 @@ div[name="properties_toc_item"]:hover,div[name="properties_user_toc_item"]:hover
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.select2-container--default:hover, .select2-selection--multiple:hover {
|
||||
.select2-container--default:hover,
|
||||
.select2-selection--multiple:hover {
|
||||
border-color: #939393;
|
||||
}
|
||||
|
||||
@@ -1197,7 +1234,8 @@ div[name="properties_toc_item"]:hover,div[name="properties_user_toc_item"]:hover
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.select2-selection--single:hover, .select2-selection__rendered:hover {
|
||||
.select2-selection--single:hover,
|
||||
.select2-selection__rendered:hover {
|
||||
border-color: #939393;
|
||||
}
|
||||
|
||||
@@ -1302,9 +1340,7 @@ button:not(.select2-selection__choice__remove):not(.ejs_menu_button):disabled {
|
||||
background-color: #555;
|
||||
}
|
||||
|
||||
#emulator {
|
||||
|
||||
}
|
||||
#emulator {}
|
||||
|
||||
.emulator_partscreen {
|
||||
margin: 0 auto;
|
||||
@@ -1377,15 +1413,14 @@ button:not(.select2-selection__choice__remove):not(.ejs_menu_button):disabled {
|
||||
margin-left: 15px;
|
||||
}
|
||||
|
||||
.rom_checkbox_box {
|
||||
|
||||
}
|
||||
.rom_checkbox_box {}
|
||||
|
||||
.rom_checkbox_box_hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#rom_edit, #rom_edit_delete {
|
||||
#rom_edit,
|
||||
#rom_edit_delete {
|
||||
float: right;
|
||||
}
|
||||
|
||||
@@ -1398,9 +1433,9 @@ button:not(.select2-selection__choice__remove):not(.ejs_menu_button):disabled {
|
||||
}
|
||||
|
||||
#game {
|
||||
box-shadow: 5px 5px 19px 0px rgba(0,0,0,0.44);
|
||||
-webkit-box-shadow: 5px 5px 19px 0px rgba(0,0,0,0.44);
|
||||
-moz-box-shadow: 5px 5px 19px 0px rgba(0,0,0,0.44);
|
||||
box-shadow: 5px 5px 19px 0px rgba(0, 0, 0, 0.44);
|
||||
-webkit-box-shadow: 5px 5px 19px 0px rgba(0, 0, 0, 0.44);
|
||||
-moz-box-shadow: 5px 5px 19px 0px rgba(0, 0, 0, 0.44);
|
||||
}
|
||||
|
||||
#gametitle_criticrating {
|
||||
@@ -1440,7 +1475,7 @@ button:not(.select2-selection__choice__remove):not(.ejs_menu_button):disabled {
|
||||
width: 1000px;
|
||||
height: 90%;
|
||||
margin: 25px auto;
|
||||
/* overflow-x: scroll;*/
|
||||
/* overflow-x: scroll;*/
|
||||
position: relative;
|
||||
}
|
||||
|
||||
@@ -1468,7 +1503,8 @@ button:not(.select2-selection__choice__remove):not(.ejs_menu_button):disabled {
|
||||
}
|
||||
|
||||
.bgalt1 {
|
||||
background-color: transparent;;
|
||||
background-color: transparent;
|
||||
;
|
||||
}
|
||||
|
||||
.logs_table_cell_150px {
|
||||
@@ -1520,13 +1556,33 @@ button:not(.select2-selection__choice__remove):not(.ejs_menu_button):disabled {
|
||||
background-color: #383838;
|
||||
}
|
||||
|
||||
.string { color: lightblue; }
|
||||
.number { color: lightblue; }
|
||||
.boolean { color: lightblue; }
|
||||
.null { color: magenta; }
|
||||
.key { color: greenyellow; }
|
||||
.brace { color: #888; }
|
||||
.square { color: #fff000; }
|
||||
.string {
|
||||
color: lightblue;
|
||||
}
|
||||
|
||||
.number {
|
||||
color: lightblue;
|
||||
}
|
||||
|
||||
.boolean {
|
||||
color: lightblue;
|
||||
}
|
||||
|
||||
.null {
|
||||
color: magenta;
|
||||
}
|
||||
|
||||
.key {
|
||||
color: greenyellow;
|
||||
}
|
||||
|
||||
.brace {
|
||||
color: #888;
|
||||
}
|
||||
|
||||
.square {
|
||||
color: #fff000;
|
||||
}
|
||||
|
||||
.tagBox {
|
||||
position: absolute;
|
||||
@@ -1557,12 +1613,16 @@ button:not(.select2-selection__choice__remove):not(.ejs_menu_button):disabled {
|
||||
}
|
||||
|
||||
.loginwindow {
|
||||
position: fixed; /* Stay in place */
|
||||
position: fixed;
|
||||
/* Stay in place */
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%; /* Full width */
|
||||
height: 100%; /* Full height */
|
||||
overflow: auto; /* Enable scroll if needed */
|
||||
width: 100%;
|
||||
/* Full width */
|
||||
height: 100%;
|
||||
/* Full height */
|
||||
overflow: auto;
|
||||
/* Enable scroll if needed */
|
||||
/*background-color: rgb(0,0,0); /* Fallback color */
|
||||
/*background-color: rgba(0,0,0,0.4); /* Black w/ opacity */
|
||||
/*backdrop-filter: blur(8px);
|
||||
@@ -1575,11 +1635,13 @@ button:not(.select2-selection__choice__remove):not(.ejs_menu_button):disabled {
|
||||
.loginwindow-content {
|
||||
position: relative;
|
||||
background-color: #383838;
|
||||
margin: 15% auto; /* 15% from the top and centered */
|
||||
margin: 15% auto;
|
||||
/* 15% from the top and centered */
|
||||
padding: 10px;
|
||||
border: 1px solid #888;
|
||||
border-radius: 10px;
|
||||
width: 350px; /* Could be more or less, depending on screen size */
|
||||
width: 350px;
|
||||
/* Could be more or less, depending on screen size */
|
||||
min-height: 250px;
|
||||
}
|
||||
|
||||
@@ -1615,7 +1677,8 @@ button:not(.select2-selection__choice__remove):not(.ejs_menu_button):disabled {
|
||||
}
|
||||
|
||||
/* Links inside the dropdown */
|
||||
.dropdown-content a, .dropdown-content span {
|
||||
.dropdown-content a,
|
||||
.dropdown-content span {
|
||||
color: black;
|
||||
padding: 12px 16px;
|
||||
text-decoration: none;
|
||||
@@ -1627,10 +1690,14 @@ button:not(.select2-selection__choice__remove):not(.ejs_menu_button):disabled {
|
||||
}
|
||||
|
||||
/* Change color of dropdown links on hover */
|
||||
.dropdown-content a:hover {background-color: #ddd;}
|
||||
.dropdown-content a:hover {
|
||||
background-color: #ddd;
|
||||
}
|
||||
|
||||
/* Show the dropdown menu (use JS to add this class to the .dropdown-content container when the user clicks on the dropdown button) */
|
||||
.show {display:block;}
|
||||
.show {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.dropdownroleitem {
|
||||
text-transform: capitalize;
|
||||
|
Reference in New Issue
Block a user