Compare commits
1 Commits
devtest-20
...
playtest-2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c271ce4e1d |
@@ -1,106 +1,16 @@
|
||||
; Top-most http://editorconfig.org/ file
|
||||
root = true
|
||||
charset=utf-8
|
||||
|
||||
; Unix-style newlines
|
||||
[*]
|
||||
end_of_line = LF
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
; 4-column tab indentation and .NET coding conventions
|
||||
; 4-column tab indentation
|
||||
[*.cs]
|
||||
indent_style = tab
|
||||
indent_size = 4
|
||||
|
||||
dotnet_separate_import_directive_groups = false
|
||||
dotnet_sort_system_directives_first = true
|
||||
|
||||
csharp_style_var_elsewhere = true:suggestion
|
||||
csharp_style_var_for_built_in_types = true:suggestion
|
||||
csharp_style_var_when_type_is_apparent = true:suggestion
|
||||
|
||||
csharp_prefer_braces = when_multiline:suggestion
|
||||
csharp_using_directive_placement = outside_namespace:suggestion
|
||||
csharp_new_line_before_open_brace = all
|
||||
csharp_space_around_binary_operators = before_and_after
|
||||
|
||||
#### Naming styles ####
|
||||
|
||||
dotnet_naming_style.camel_case.capitalization = camel_case
|
||||
|
||||
dotnet_naming_style.pascal_case.capitalization = pascal_case
|
||||
|
||||
# Symbol specifications
|
||||
|
||||
dotnet_naming_symbols.interface.applicable_kinds = interface
|
||||
dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal
|
||||
|
||||
dotnet_naming_symbols.const_private_field.applicable_kinds = field
|
||||
dotnet_naming_symbols.const_private_field.required_modifiers = const
|
||||
dotnet_naming_symbols.const_private_field.applicable_accessibilities = private
|
||||
|
||||
dotnet_naming_symbols.internal_field.applicable_kinds = field
|
||||
dotnet_naming_symbols.internal_field.applicable_accessibilities = internal
|
||||
|
||||
dotnet_naming_symbols.static_private_or_internal_field.required_modifiers = static
|
||||
dotnet_naming_symbols.static_private_or_internal_field.applicable_accessibilities = internal, private
|
||||
|
||||
dotnet_naming_symbols.private_or_internal_field.applicable_kinds = field
|
||||
dotnet_naming_symbols.private_or_internal_field.applicable_accessibilities = internal, private
|
||||
|
||||
dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
|
||||
dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal
|
||||
|
||||
dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
|
||||
dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal
|
||||
|
||||
# Naming rules
|
||||
|
||||
dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = warning
|
||||
dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
|
||||
dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
|
||||
|
||||
|
||||
dotnet_naming_rule.static_private_or_internal_field_should_be_pascal_case.severity = none
|
||||
dotnet_naming_rule.static_private_or_internal_field_should_be_pascal_case.symbols = static_private_or_internal_field
|
||||
dotnet_naming_rule.static_private_or_internal_field_should_be_pascal_case.style = pascal_case
|
||||
|
||||
dotnet_naming_rule.const_private_field_should_be_pascal_case.severity = warning
|
||||
dotnet_naming_rule.const_private_field_should_be_pascal_case.symbols = const_private_field
|
||||
dotnet_naming_rule.const_private_field_should_be_pascal_case.style = pascal_case
|
||||
|
||||
dotnet_naming_rule.const_private_or_internal_field_should_be_pascal_case.severity = warning
|
||||
dotnet_naming_rule.const_private_or_internal_field_should_be_pascal_case.symbols = internal_field
|
||||
dotnet_naming_rule.const_private_or_internal_field_should_be_pascal_case.style = pascal_case
|
||||
|
||||
dotnet_naming_rule.private_or_internal_field_should_be_camel_case.severity = warning
|
||||
dotnet_naming_rule.private_or_internal_field_should_be_camel_case.symbols = private_or_internal_field
|
||||
dotnet_naming_rule.private_or_internal_field_should_be_camel_case.style = camel_case
|
||||
|
||||
# Naming rules
|
||||
|
||||
#require a space before the colon for bases or interfaces in a type declaration
|
||||
csharp_space_after_colon_in_inheritance_clause = true
|
||||
#require a space after a keyword in a control flow statement such as a for loop
|
||||
csharp_space_after_keywords_in_control_flow_statements = true
|
||||
#require a space before the colon for bases or interfaces in a type declaration
|
||||
csharp_space_before_colon_in_inheritance_clause = true
|
||||
|
||||
#Formatting - wrapping options
|
||||
|
||||
#leave code block on single line
|
||||
csharp_preserve_single_line_blocks = true
|
||||
#leave statements and member declarations on the same line
|
||||
csharp_preserve_single_line_statements = true
|
||||
|
||||
#prefer the language keyword for member access expressions, instead of the type name, for types that have a keyword to represent them
|
||||
dotnet_style_predefined_type_for_member_access = true:suggestion
|
||||
|
||||
#prefer the language keyword for local variables, method parameters, and class members, instead of the type name, for types that have a keyword to represent them
|
||||
dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
|
||||
|
||||
; 4-column tab indentation
|
||||
[*.yaml]
|
||||
indent_style = tab
|
||||
indent_size = 4
|
||||
indent_size = 4
|
||||
8
.gitattributes
vendored
8
.gitattributes
vendored
@@ -1,10 +1,12 @@
|
||||
# Enforce LF normalization on Windows
|
||||
*.yaml eol=lf
|
||||
*.lua eol=lf
|
||||
*.cs eol=lf
|
||||
*.csproj eol=lf
|
||||
*.sln eol=lf
|
||||
* text=lf
|
||||
|
||||
# Custom for Visual Studio
|
||||
*.cs diff=csharp
|
||||
*.sln merge=union
|
||||
*.csproj merge=union
|
||||
*.vbproj merge=union
|
||||
*.fsproj merge=union
|
||||
*.dbproj merge=union
|
||||
33
.github/ISSUE_TEMPLATE/bug-report.md
vendored
33
.github/ISSUE_TEMPLATE/bug-report.md
vendored
@@ -1,33 +0,0 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Report unexpected behavior or any issues you experienced in OpenRA.
|
||||
title: ''
|
||||
labels: Bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!-- This is a guideline that shall help you to include information we need to understand and fix the issue you experienced. Please follow the instructions and replace any placeholders that are written in capital letters. Instructions like this comment will not be visible in your report. -->
|
||||
|
||||
<!-- Important: Help us to avoid duplicates and use the search function to find existing reports for your issue. Please do not submit duplicate reports! Try to help others to find your report by using a precise title. -->
|
||||
|
||||
## Issue Summary
|
||||
<!-- Please provide a a clear and concise description of what the issue is below. -->
|
||||
|
||||
DESCRIPTION
|
||||
|
||||
|
||||
## Reproduction
|
||||
<!-- Please provide information about how the issue can be reproduced below. -->
|
||||
|
||||
STEPS TO REPRODUCE THE ISSUE
|
||||
|
||||
|
||||
## Expected behavior
|
||||
<!-- Please explain what you expected to happen below. -->
|
||||
|
||||
EXPECTED BEHAVIOR
|
||||
|
||||
|
||||
## Screenshots / Screen recordings / Replays
|
||||
<!-- If applicable, attach screenshots, screen recordings or replays to help explain your problem. -->
|
||||
14
.github/ISSUE_TEMPLATE/config.yml
vendored
14
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,14 +0,0 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Frequently Asked Questions
|
||||
url: https://github.com/OpenRA/OpenRA/wiki/FAQ#frequently-asked-questions
|
||||
about: Explanations for common problems and questions.
|
||||
- name: OpenRA Forum
|
||||
url: https://forum.openra.net/
|
||||
about: Please ask questions about modding here.
|
||||
- name: OpenRA Discord server
|
||||
url: https://discord.openra.net/
|
||||
about: Join the community Discord server for community discussion and support.
|
||||
- name: OpenRA IRC Channel
|
||||
url: https://webchat.freenode.net/#openra
|
||||
about: Join our development IRC channel on freenode for discussion of development topics.
|
||||
35
.github/ISSUE_TEMPLATE/crash-report.md
vendored
35
.github/ISSUE_TEMPLATE/crash-report.md
vendored
@@ -1,35 +0,0 @@
|
||||
---
|
||||
name: Crash report
|
||||
about: Report a game crash. Check the FAQ first https://github.com/OpenRA/OpenRA/wiki/FAQ#common-issues
|
||||
title: My game crashed
|
||||
labels: Crash
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!-- This is a guideline that shall help you to include all the required information we depend on to investigate and fix game-breaking bugs. Please follow the instructions and replace any placeholders that are written in capital letters. Instructions like this comment will not be visible in your report. -->
|
||||
|
||||
## System Information
|
||||
<!-- Information about the operating system, engine version, game mod and package source are mandatory for investigating and fixing crashes. -->
|
||||
|
||||
- Operating System: OPERATING SYSTEM
|
||||
- OpenRA Version: ENGINE VERSION
|
||||
- OpenRA Mod: GAME MOD
|
||||
- Source: Official download package OR self-compiled OR third-party package
|
||||
- For self-compiled or third-party packages: Mono version
|
||||
|
||||
|
||||
## Exception log
|
||||
<!-- Please replace the placeholder below with the content of the exception.log file. The three backticks before and after the placeholder are used for formatting, so don't remove them. If you don't find the log folder consult https://github.com/OpenRA/OpenRA/wiki/FAQ#my-game-just-crashed. -->
|
||||
|
||||
```
|
||||
PASTE LOG HERE
|
||||
```
|
||||
|
||||
|
||||
## Replay
|
||||
<!-- If you have a replay file for the game that crashed, and it crashes again when you play it back, it will be a great help for us to fix the issue. Please compress the replay into a zip file and drag it here to include it in the report. -->
|
||||
|
||||
|
||||
## Additional information
|
||||
<!-- Please tell us below everything that you think is important for us to know about the crash. Specifically, what you were doing in the moment before the crash or ideally steps to reproduce it are very valuable information. -->
|
||||
@@ -1,35 +0,0 @@
|
||||
---
|
||||
name: Feature / Enhancement request
|
||||
about: Describe what you think is missing or could be improved in OpenRA.
|
||||
title: ''
|
||||
labels: Idea/Wishlist
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!-- This is a guideline that shall help you to describe your idea for a missing feature or enhancement. Please follow the instructions and replace any placeholders that are written in capital letters. Instructions like this comment will not be visible in your report. -->
|
||||
|
||||
<!-- Important: Help us to avoid duplicates and use the search function to find existing requests. Please do not submit duplicate requests! Try to help others to find your request by using a precise title. -->
|
||||
|
||||
## Motivation
|
||||
<!-- Please provide a clear and concise description of the motivation behind the request. If your request is related to a problem or limitation, describe it below. -->
|
||||
|
||||
REQUEST MOTIVATION
|
||||
|
||||
|
||||
## Proposed solution
|
||||
<!-- Please describe your idea and how it is intended to address the motivation of your request. Provide a clear and concise description of the proposed changes. -->
|
||||
|
||||
PROPOSED SOLUTION
|
||||
|
||||
|
||||
## Side effects
|
||||
<!-- Changes often have side effects or unintended consequences. If you expect that your solution has any side effects, please describe them below. -->
|
||||
|
||||
EXPECTED SIDE EFFECTS
|
||||
|
||||
|
||||
## Alternatives
|
||||
<!-- Please outline any alternative solutions you have considered. -->
|
||||
|
||||
ALTERNATIVES
|
||||
16
.github/PULL_REQUEST_TEMPLATE.md
vendored
16
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -1,16 +0,0 @@
|
||||
Thank you for your contribution to OpenRA!
|
||||
|
||||
Please be aware that we do not have enough project maintainers to match the rate of contributions, so it may take several days before somebody is able to respond to your Pull Request.
|
||||
|
||||
You can help speed up the review process by following a few steps:
|
||||
|
||||
* Make sure that you have read and understand the OpenRA Coding Standard (see https://github.com/OpenRA/OpenRA/wiki/Coding-Standard).
|
||||
* Write quality commit messages (see https://chris.beams.io/posts/git-commit/).
|
||||
* Only commit changes that directly relate to your Pull Request. Use your Git interface to unstage any unrelated changes to project files, line endings, whitespace, or other files.
|
||||
* Review the code diff view below to double check that your changes satisfy the above three points.
|
||||
* Use the `make test` and `make check` commands to check for (and fix!) any issues that are reported by our automated tests.
|
||||
* If you are changing shared mod or engine code, make sure that you have tested your changes in all four default mods.
|
||||
* Respond to review comments as soon as you reasonably can. Reviewers will usually prioritize Pull Requests that are still fresh in their minds. Make sure to leave a comment when you push new changes, otherwise GitHub does not automatically notify reviewers!
|
||||
* Leave a polite comment asking for reviews if a week or more has passed without feedback.
|
||||
|
||||
If you need any help you can ask in the #openra IRC channel on freenode (most active during European evenings).
|
||||
49
.github/workflows/ci.yaml
vendored
49
.github/workflows/ci.yaml
vendored
@@ -1,49 +0,0 @@
|
||||
name: Continuous Integration
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
branches: [ bleed ]
|
||||
|
||||
jobs:
|
||||
linux-mono:
|
||||
name: Linux (mono)
|
||||
runs-on: ubuntu-20.04
|
||||
|
||||
steps:
|
||||
- name: Clone Repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Check Code
|
||||
run: |
|
||||
mono --version
|
||||
make check
|
||||
mono ~/.nuget/packages/nunit.consolerunner/3.11.1/tools/nunit3-console.exe --noresult bin/OpenRA.Test.dll
|
||||
|
||||
- name: Check Mods
|
||||
run: |
|
||||
sudo apt-get install lua5.1
|
||||
make check-scripts
|
||||
make test
|
||||
|
||||
windows:
|
||||
name: Windows (Framework 4.7)
|
||||
runs-on: windows-2019
|
||||
|
||||
steps:
|
||||
- name: Clone Repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Check Code
|
||||
shell: powershell
|
||||
run: |
|
||||
dotnet nuget locals all --clear
|
||||
.\make.ps1 check
|
||||
Invoke-Expression "$home\.nuget\packages\nunit.consolerunner\3.11.1\tools\nunit3-console.exe --noresult bin/OpenRA.Test.dll"
|
||||
|
||||
- name: Check Mods
|
||||
run: |
|
||||
chocolatey install lua --version 5.1.5.52
|
||||
$ENV:Path = $ENV:Path + ";C:\Program Files (x86)\Lua\5.1\"
|
||||
.\make.ps1 check-scripts
|
||||
.\make.ps1 test
|
||||
107
.github/workflows/documentation.yml
vendored
107
.github/workflows/documentation.yml
vendored
@@ -1,107 +0,0 @@
|
||||
name: Deploy Documentation
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: 'Git Tag'
|
||||
required: true
|
||||
default: 'release-xxxxxxxx'
|
||||
|
||||
jobs:
|
||||
wiki:
|
||||
name: Update Wiki
|
||||
if: github.repository == 'openra/openra'
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- name: Clone Repository
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
ref: ${{ github.event.inputs.tag }}
|
||||
|
||||
- name: Prepare Environment
|
||||
run: |
|
||||
make all
|
||||
|
||||
- name: Clone Wiki
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: openra/openra.wiki
|
||||
token: ${{ secrets.DOCS_TOKEN }}
|
||||
path: wiki
|
||||
|
||||
- name: Update Wiki (Playtest)
|
||||
if: startsWith(github.event.inputs.tag, 'playtest-')
|
||||
env:
|
||||
GIT_TAG: ${{ github.event.inputs.tag }}
|
||||
run: |
|
||||
./utility.sh all --settings-docs "${GIT_TAG}" > "wiki/Settings (playtest).md"
|
||||
|
||||
- name: Update Wiki (Release)
|
||||
if: startsWith(github.event.inputs.tag, 'release-')
|
||||
env:
|
||||
GIT_TAG: ${{ github.event.inputs.tag }}
|
||||
run: |
|
||||
./utility.sh all --settings-docs "${GIT_TAG}" > "wiki/Settings.md"
|
||||
|
||||
- name: Push Wiki
|
||||
env:
|
||||
GIT_TAG: ${{ github.event.inputs.tag }}
|
||||
run: |
|
||||
cd wiki
|
||||
git config --local user.email "actions@github.com"
|
||||
git config --local user.name "GitHub Actions"
|
||||
git add --all
|
||||
git commit -m "Update auto-generated documentation for ${GIT_TAG}"
|
||||
git push origin master
|
||||
|
||||
docs:
|
||||
name: Update docs.openra.net
|
||||
if: github.repository == 'openra/openra'
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- name: Clone Repository
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
ref: ${{ github.event.inputs.tag }}
|
||||
|
||||
- name: Prepare Environment
|
||||
run: |
|
||||
make all
|
||||
|
||||
- name: Clone docs.openra.net
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: openra/docs
|
||||
token: ${{ secrets.DOCS_TOKEN }}
|
||||
path: docs
|
||||
|
||||
- name: Update docs.openra.net (Playtest)
|
||||
if: startsWith(github.event.inputs.tag, 'playtest-')
|
||||
env:
|
||||
GIT_TAG: ${{ github.event.inputs.tag }}
|
||||
run: |
|
||||
./utility.sh all --docs "${GIT_TAG}" > "docs/api/playtest/traits.md"
|
||||
./utility.sh all --weapon-docs "${GIT_TAG}" > "docs/api/playtest/weapons.md"
|
||||
./utility.sh all --lua-docs "${GIT_TAG}" > "docs/api/playtest/lua.md"
|
||||
|
||||
- name: Update docs.openra.net (Release)
|
||||
if: startsWith(github.event.inputs.tag, 'release-')
|
||||
env:
|
||||
GIT_TAG: ${{ github.event.inputs.tag }}
|
||||
run: |
|
||||
./utility.sh all --docs "${GIT_TAG}" > "docs/api/release/traits.md"
|
||||
./utility.sh all --weapon-docs "${GIT_TAG}" > "docs/api/release/weapons.md"
|
||||
./utility.sh all --lua-docs "${GIT_TAG}" > "docs/api/release/lua.md"
|
||||
|
||||
- name: Push docs.openra.net
|
||||
env:
|
||||
GIT_TAG: ${{ github.event.inputs.tag }}
|
||||
run: |
|
||||
cd docs
|
||||
git config --local user.email "actions@github.com"
|
||||
git config --local user.name "GitHub Actions"
|
||||
git add --all
|
||||
git commit -m "Update auto-generated documentation for ${GIT_TAG}"
|
||||
git push origin master
|
||||
|
||||
86
.github/workflows/itch.yml
vendored
86
.github/workflows/itch.yml
vendored
@@ -1,86 +0,0 @@
|
||||
name: Deploy itch.io Packages
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: 'Git Tag'
|
||||
required: true
|
||||
default: 'release-xxxxxxxx'
|
||||
|
||||
jobs:
|
||||
itch:
|
||||
name: Deploy to itch.io
|
||||
runs-on: ubuntu-20.04
|
||||
if: github.repository == 'openra/openra'
|
||||
steps:
|
||||
- name: Download Packages
|
||||
run: |
|
||||
wget -q "https://github.com/${{ github.repository }}/releases/download/${{ github.event.inputs.tag }}/OpenRA-${{ github.event.inputs.tag }}-x64.exe"
|
||||
wget -q "https://github.com/${{ github.repository }}/releases/download/${{ github.event.inputs.tag }}/OpenRA-${{ github.event.inputs.tag }}-x64-winportable.zip" -O "OpenRA-${{ github.event.inputs.tag }}-x64-win-itch.zip"
|
||||
wget -q "https://github.com/${{ github.repository }}/releases/download/${{ github.event.inputs.tag }}/OpenRA-${{ github.event.inputs.tag }}.dmg"
|
||||
wget -q "https://github.com/${{ github.repository }}/releases/download/${{ github.event.inputs.tag }}/OpenRA-Dune-2000-x86_64.AppImage"
|
||||
wget -q "https://github.com/${{ github.repository }}/releases/download/${{ github.event.inputs.tag }}/OpenRA-Red-Alert-x86_64.AppImage"
|
||||
wget -q "https://github.com/${{ github.repository }}/releases/download/${{ github.event.inputs.tag }}/OpenRA-Tiberian-Dawn-x86_64.AppImage"
|
||||
wget -q "https://raw.githubusercontent.com/${{ github.repository }}/${{ github.event.inputs.tag }}/packaging/.itch.toml"
|
||||
zip -u "OpenRA-${{ github.event.inputs.tag }}-x64-win-itch.zip" .itch.toml
|
||||
|
||||
- name: Publish Windows Installer
|
||||
uses: josephbmanley/butler-publish-itchio-action@master
|
||||
env:
|
||||
BUTLER_CREDENTIALS: ${{ secrets.BUTLER_CREDENTIALS }}
|
||||
CHANNEL: win
|
||||
ITCH_GAME: openra
|
||||
ITCH_USER: openra-developers
|
||||
VERSION: ${{ github.event.inputs.tag }}
|
||||
PACKAGE: OpenRA-${{ github.event.inputs.tag }}}-x64.exe
|
||||
|
||||
- name: Publish Windows Itch Bundle
|
||||
uses: josephbmanley/butler-publish-itchio-action@master
|
||||
env:
|
||||
BUTLER_CREDENTIALS: ${{ secrets.BUTLER_CREDENTIALS }}
|
||||
CHANNEL: itch
|
||||
ITCH_GAME: openra
|
||||
ITCH_USER: openra-developers
|
||||
VERSION: ${{ github.event.inputs.tag }}
|
||||
PACKAGE: OpenRA-${{ github.event.inputs.tag }}-x64-win-itch.zip
|
||||
|
||||
- name: Publish macOS Package
|
||||
uses: josephbmanley/butler-publish-itchio-action@master
|
||||
env:
|
||||
BUTLER_CREDENTIALS: ${{ secrets.BUTLER_CREDENTIALS }}
|
||||
CHANNEL: macos
|
||||
ITCH_GAME: openra
|
||||
ITCH_USER: openra-developers
|
||||
VERSION: ${{ github.event.inputs.tag }}
|
||||
PACKAGE: OpenRA-${{ github.event.inputs.tag }}}.dmg
|
||||
|
||||
- name: Publish RA AppImage
|
||||
uses: josephbmanley/butler-publish-itchio-action@master
|
||||
env:
|
||||
BUTLER_CREDENTIALS: ${{ secrets.BUTLER_CREDENTIALS }}
|
||||
CHANNEL: linux-ra
|
||||
ITCH_GAME: openra
|
||||
ITCH_USER: openra-developers
|
||||
VERSION: ${{ github.event.inputs.tag }}
|
||||
PACKAGE: OpenRA-Red-Alert-x86_64.AppImage
|
||||
|
||||
- name: Publish TD AppImage
|
||||
uses: josephbmanley/butler-publish-itchio-action@master
|
||||
env:
|
||||
BUTLER_CREDENTIALS: ${{ secrets.BUTLER_CREDENTIALS }}
|
||||
CHANNEL: linux-cnc
|
||||
ITCH_GAME: openra
|
||||
ITCH_USER: openra-developers
|
||||
VERSION: ${{ github.event.inputs.tag }}
|
||||
PACKAGE: OpenRA-Tiberian-Dawn-x86_64.AppImage
|
||||
|
||||
- name: Publish D2k AppImage
|
||||
uses: josephbmanley/butler-publish-itchio-action@master
|
||||
env:
|
||||
BUTLER_CREDENTIALS: ${{ secrets.BUTLER_CREDENTIALS }}
|
||||
CHANNEL: linux-d2k
|
||||
ITCH_GAME: openra
|
||||
ITCH_USER: openra-developers
|
||||
VERSION: ${{ github.event.inputs.tag }}
|
||||
PACKAGE: OpenRA-Dune-2000-x86_64.AppImage
|
||||
114
.github/workflows/packaging.yml
vendored
114
.github/workflows/packaging.yml
vendored
@@ -1,114 +0,0 @@
|
||||
name: Release Packaging
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'release-*'
|
||||
- 'playtest-*'
|
||||
- 'devtest-*'
|
||||
|
||||
jobs:
|
||||
source:
|
||||
name: Source Tarball
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- name: Clone Repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Prepare Environment
|
||||
run: echo "GIT_TAG=${GITHUB_REF#refs/tags/}" >> ${GITHUB_ENV}
|
||||
|
||||
- name: Package Source
|
||||
run: |
|
||||
mkdir -p build/source
|
||||
./packaging/source/buildpackage.sh "${GIT_TAG}" "${PWD}/build/source"
|
||||
|
||||
- name: Upload Packages
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
tag: ${{ github.ref }}
|
||||
overwrite: true
|
||||
file_glob: true
|
||||
file: build/source/*
|
||||
|
||||
linux:
|
||||
name: Linux AppImages
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- name: Clone Repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Prepare Environment
|
||||
run: echo "GIT_TAG=${GITHUB_REF#refs/tags/}" >> ${GITHUB_ENV}
|
||||
|
||||
- name: Package AppImages
|
||||
run: |
|
||||
mkdir -p build/linux
|
||||
./packaging/linux/buildpackage.sh "${GIT_TAG}" "${PWD}/build/linux"
|
||||
|
||||
- name: Upload Packages
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
tag: ${{ github.ref }}
|
||||
overwrite: true
|
||||
file_glob: true
|
||||
file: build/linux/*
|
||||
|
||||
macos:
|
||||
name: macOS Disk Images
|
||||
runs-on: macos-10.15
|
||||
steps:
|
||||
- name: Clone Repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Prepare Environment
|
||||
run: echo "GIT_TAG=${GITHUB_REF#refs/tags/}" >> ${GITHUB_ENV}
|
||||
|
||||
- name: Package Disk Images
|
||||
env:
|
||||
MACOS_DEVELOPER_IDENTITY: ${{ secrets.MACOS_DEVELOPER_IDENTITY }}
|
||||
MACOS_DEVELOPER_CERTIFICATE_BASE64: ${{ secrets.MACOS_DEVELOPER_CERTIFICATE_BASE64 }}
|
||||
MACOS_DEVELOPER_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_DEVELOPER_CERTIFICATE_PASSWORD }}
|
||||
MACOS_DEVELOPER_USERNAME: ${{ secrets.MACOS_DEVELOPER_USERNAME }}
|
||||
MACOS_DEVELOPER_PASSWORD: ${{ secrets.MACOS_DEVELOPER_PASSWORD }}
|
||||
run: |
|
||||
mkdir -p build/macos
|
||||
./packaging/macos/buildpackage.sh "${GIT_TAG}" "${PWD}/build/macos"
|
||||
|
||||
- name: Upload Packages
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
tag: ${{ github.ref }}
|
||||
overwrite: true
|
||||
file_glob: true
|
||||
file: build/macos/*
|
||||
|
||||
windows:
|
||||
name: Windows Installers
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- name: Clone Repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Prepare Environment
|
||||
run: |
|
||||
echo "GIT_TAG=${GITHUB_REF#refs/tags/}" >> ${GITHUB_ENV}
|
||||
sudo apt-get update
|
||||
sudo apt-get install nsis
|
||||
|
||||
- name: Package Installers
|
||||
run: |
|
||||
mkdir -p build/windows
|
||||
./packaging/windows/buildpackage.sh "${GIT_TAG}" "${PWD}/build/windows"
|
||||
|
||||
- name: Upload Packages
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
tag: ${{ github.ref }}
|
||||
overwrite: true
|
||||
file_glob: true
|
||||
file: build/windows/*
|
||||
149
.gitignore
vendored
149
.gitignore
vendored
@@ -1,69 +1,80 @@
|
||||
# Visual Studio
|
||||
Release
|
||||
bin
|
||||
obj
|
||||
*.ncb
|
||||
*.vcproj*
|
||||
*.suo
|
||||
*.user
|
||||
*.sln.cache
|
||||
*.manifest
|
||||
*.CodeAnalysisLog.xml
|
||||
*.lastcodeanalysissucceeded
|
||||
_ReSharper.*/
|
||||
/.vs
|
||||
|
||||
# binaries
|
||||
mods/*/*.dll
|
||||
mods/*/*.mdb
|
||||
mods/*/*.pdb
|
||||
/*.dll
|
||||
/*.dll.config
|
||||
/*.so
|
||||
/*.dylib
|
||||
/*.pdb
|
||||
/*.mdb
|
||||
/*.exe
|
||||
/*.exe.config
|
||||
thirdparty/download/*
|
||||
IP2LOCATION-LITE-DB1.IPV6.BIN.ZIP
|
||||
|
||||
# backup files by various editors
|
||||
*~
|
||||
*.orig
|
||||
\#*
|
||||
.*.sw?
|
||||
|
||||
# Monodevelop
|
||||
*.pidb
|
||||
*.userprefs
|
||||
|
||||
# Mac OS X
|
||||
.DS_Store
|
||||
|
||||
# auto-generated documentation
|
||||
DOCUMENTATION.md
|
||||
WEAPONS.md
|
||||
Lua-API.md
|
||||
Settings.md
|
||||
*.html
|
||||
openra.6
|
||||
update.log
|
||||
|
||||
# StyleCop
|
||||
*.Cache
|
||||
StyleCopViolations.xml
|
||||
|
||||
# SublimeText
|
||||
*.sublime-project
|
||||
*.sublime-workspace
|
||||
|
||||
# NUnit
|
||||
/TestResult.xml
|
||||
/lib/
|
||||
|
||||
# Support directory
|
||||
/Support
|
||||
|
||||
# IntelliJ files
|
||||
.idea
|
||||
# Visual Studio
|
||||
Release
|
||||
bin
|
||||
obj
|
||||
*.ncb
|
||||
*.vcproj*
|
||||
*.suo
|
||||
*.user
|
||||
*.sln.cache
|
||||
*.manifest
|
||||
*.CodeAnalysisLog.xml
|
||||
*.lastcodeanalysissucceeded
|
||||
_ReSharper.*/
|
||||
|
||||
# movies
|
||||
*.vqa
|
||||
|
||||
# archives
|
||||
*.mix
|
||||
|
||||
# binaries
|
||||
mods/*/*.dll
|
||||
mods/*/*.mdb
|
||||
/*.dll
|
||||
/*.dll.config
|
||||
/*.so
|
||||
/*.dylib
|
||||
/*.pdb
|
||||
/*.mdb
|
||||
/*.exe
|
||||
thirdparty/download/*
|
||||
*.mmdb.gz
|
||||
|
||||
# backup files by various editors
|
||||
*~
|
||||
*.orig
|
||||
\#*
|
||||
.*.sw?
|
||||
|
||||
# Monodevelop
|
||||
*.pidb
|
||||
*.userprefs
|
||||
|
||||
# Mac OS X
|
||||
.DS_Store
|
||||
|
||||
# XCode
|
||||
packaging/osx/launcher/build/
|
||||
packaging/osx/launcher/OpenRA.xcodeproj/*.pbxuser
|
||||
packaging/osx/launcher/OpenRA.xcodeproj/*.perspectivev3
|
||||
packaging/osx/launcher/OpenRA.xcodeproj/*.mode1v3
|
||||
temp.c
|
||||
temp.o
|
||||
temp.s
|
||||
OpenRA.Launcher.Mac/build/
|
||||
OpenRA.Launcher.Mac/OpenRA.xcodeproj/*.pbxuser
|
||||
OpenRA.Launcher.Mac/OpenRA.xcodeproj/*.perspectivev3
|
||||
OpenRA.Launcher.Mac/OpenRA.xcodeproj/*.mode1v3
|
||||
*.resources
|
||||
|
||||
# auto-generated documentation
|
||||
DOCUMENTATION.md
|
||||
Lua-API.md
|
||||
*.html
|
||||
openra.6
|
||||
|
||||
# StyleCop
|
||||
*.Cache
|
||||
StyleCopViolations.xml
|
||||
|
||||
# SublimeText
|
||||
*.sublime-project
|
||||
*.sublime-workspace
|
||||
|
||||
# NUnit
|
||||
/TestResult.xml
|
||||
/lib/
|
||||
|
||||
# Support directory
|
||||
/Support
|
||||
|
||||
81
.travis.yml
Normal file
81
.travis.yml
Normal file
@@ -0,0 +1,81 @@
|
||||
# Travis-CI Build for OpenRA
|
||||
# see travis-ci.org for details
|
||||
|
||||
language: csharp
|
||||
mono: 4.2.1
|
||||
|
||||
# http://docs.travis-ci.com/user/migrating-from-legacy
|
||||
sudo: false
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- thirdparty/download
|
||||
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- lua5.1
|
||||
- nsis
|
||||
- nsis-common
|
||||
- dpkg
|
||||
- markdown
|
||||
|
||||
# Environment variables
|
||||
env:
|
||||
secure: "C0+Hlfa0YGErxUuWV00Tj6p45otC/D3YwYFuLpi2mj1rDFn/4dgh5WRngjvdDBVbXJ3duaZ78jPHWm1jr7vn2jqj9yETsCIK9psWd38ep/FEBM0SDr6MUD89OuXk/YyvxJAE+UXF6bXg7giey09g/CwBigjMW7ynET3wNAWPHPs="
|
||||
|
||||
# Fetch dependencies
|
||||
# Run the build script
|
||||
# Check source code with StyleCop
|
||||
# call OpenRA to check for YAML errors
|
||||
# Run the NUnit tests
|
||||
script:
|
||||
- travis_retry make all-dependencies
|
||||
- make all SDK="-sdk:4.0"
|
||||
- make check
|
||||
- make check-scripts
|
||||
- make test
|
||||
- make nunit
|
||||
|
||||
# Automatically update the trait documentation and Lua API
|
||||
after_success:
|
||||
- test $TRAVIS_PULL_REQUEST == "false" && make docs && cd packaging && ./update-wiki.sh $TRAVIS_BRANCH; cd ..
|
||||
|
||||
# Only watch the development branch and tagged release.
|
||||
branches:
|
||||
only:
|
||||
- /^release-.*$/
|
||||
- /^playtest-.*$/
|
||||
- /^pkgtest-.*$/
|
||||
- /^prep-.*$/
|
||||
- bleed
|
||||
|
||||
# Notify developers when build passed/failed.
|
||||
notifications:
|
||||
irc:
|
||||
template:
|
||||
- "%{repository}#%{build_number} %{commit} %{author}: %{message} %{build_url}"
|
||||
channels:
|
||||
- "irc.freenode.net#openra"
|
||||
use_notice: true
|
||||
skip_join: true
|
||||
|
||||
before_deploy:
|
||||
- export PATH=$PATH:$HOME/usr/bin
|
||||
- DOTVERSION=`echo ${TRAVIS_TAG} | sed "s/-/\\./g"`
|
||||
- cd packaging
|
||||
- mkdir build
|
||||
- ./package-all.sh ${TRAVIS_TAG} ${PWD}/build/
|
||||
deploy:
|
||||
provider: releases
|
||||
api_key:
|
||||
secure: "g/LU11f+mjqv+lj0sR1UliHwogXL4ofJUwoG5Dbqlvdf5UTLWytw/OWSCv8RGyuh10miyWeaoqHh1cn2C1IFhUEqN1sSeKKKOWOTvJ2FR5mzi9uH3d/MOBzG5icQ7Qh0fZ1YPz5RaJJhYu6bmfvA/1gD49GoaX2kxQL4J5cEBgg="
|
||||
file:
|
||||
- build/OpenRA-${TRAVIS_TAG}.exe
|
||||
- build/OpenRA-${TRAVIS_TAG}.zip
|
||||
- build/openra_${DOTVERSION}_all.deb
|
||||
skip_cleanup: true
|
||||
on:
|
||||
all_branches: true
|
||||
tags: true
|
||||
repo: OpenRA/OpenRA
|
||||
7
.vscode/extensions.json
vendored
7
.vscode/extensions.json
vendored
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"ms-dotnettools.csharp",
|
||||
"EditorConfig.EditorConfig",
|
||||
"ms-vscode.mono-debug"
|
||||
]
|
||||
}
|
||||
61
.vscode/launch.json
vendored
61
.vscode/launch.json
vendored
@@ -1,61 +0,0 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Launch (TD)",
|
||||
"type": "clr",
|
||||
"linux": {
|
||||
"type": "mono"
|
||||
},
|
||||
"osx": {
|
||||
"type": "mono"
|
||||
},
|
||||
"request": "launch",
|
||||
"program": "${workspaceRoot}/bin/OpenRA.exe",
|
||||
"args": ["Game.Mod=cnc", "Engine.EngineDir=.."],
|
||||
"preLaunchTask": "build",
|
||||
},
|
||||
{
|
||||
"name": "Launch (RA)",
|
||||
"type": "clr",
|
||||
"linux": {
|
||||
"type": "mono"
|
||||
},
|
||||
"osx": {
|
||||
"type": "mono"
|
||||
},
|
||||
"request": "launch",
|
||||
"program": "${workspaceRoot}/bin/OpenRA.exe",
|
||||
"args": ["Game.Mod=ra", "Engine.EngineDir=.."],
|
||||
"preLaunchTask": "build",
|
||||
},
|
||||
{
|
||||
"name": "Launch (D2k)",
|
||||
"type": "clr",
|
||||
"linux": {
|
||||
"type": "mono"
|
||||
},
|
||||
"osx": {
|
||||
"type": "mono"
|
||||
},
|
||||
"request": "launch",
|
||||
"program": "${workspaceRoot}/bin/OpenRA.exe",
|
||||
"args": ["Game.Mod=d2k", "Engine.EngineDir=.."],
|
||||
"preLaunchTask": "build",
|
||||
},
|
||||
{
|
||||
"name": "Launch (TS)",
|
||||
"type": "clr",
|
||||
"linux": {
|
||||
"type": "mono"
|
||||
},
|
||||
"osx": {
|
||||
"type": "mono"
|
||||
},
|
||||
"request": "launch",
|
||||
"program": "${workspaceRoot}/bin/OpenRA.exe",
|
||||
"args": ["Game.Mod=ts", "Engine.EngineDir=.."],
|
||||
"preLaunchTask": "build",
|
||||
},
|
||||
]
|
||||
}
|
||||
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"omnisharp.enableRoslynAnalyzers": true
|
||||
}
|
||||
13
.vscode/tasks.json
vendored
13
.vscode/tasks.json
vendored
@@ -1,13 +0,0 @@
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "build",
|
||||
"command": "make",
|
||||
"args": ["all"],
|
||||
"windows": {
|
||||
"command": "make.cmd"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
72
AUTHORS
72
AUTHORS
@@ -3,31 +3,29 @@ hard work of many contributors.
|
||||
|
||||
The OpenRA developers are:
|
||||
* Chris Forbes (chrisf)
|
||||
* Igor Popov (ihptru)
|
||||
* Lukas Franke (abcdefg30)
|
||||
* Matthias Mailänder (Mailaender)
|
||||
* Oliver Brakmann (obrakmann)
|
||||
* Paul Chote (pchote)
|
||||
* Pavel Penev (penev92)
|
||||
* Reaperrr
|
||||
* Tom Roostan (RoosterDragon)
|
||||
|
||||
Previous developers included:
|
||||
* Alli Witheford (alzeih)
|
||||
* Caleb Anderson (RobotCaleb)
|
||||
* Curtis Shmyr (hamb)
|
||||
* Daniel Hernandez (Mancano)
|
||||
* Igor Popov (ihptru)
|
||||
* Matthias Mailänder (Mailaender)
|
||||
* Megan Bowra-Dean (beedee)
|
||||
* Mike Bundy (kehaar)
|
||||
* Oliver Brakmann (obrakmann)
|
||||
* Pavel Penev (penev92)
|
||||
* Robert Pepperell (ytinasni)
|
||||
* ScottNZ
|
||||
* Tom Roostan (RoosterDragon)
|
||||
|
||||
Also thanks to:
|
||||
* abmyii
|
||||
* Adam Valy (Tschokky)
|
||||
* Akseli Virtanen (RAGEQUIT)
|
||||
* Alexander Fast (mizipzor)
|
||||
* Alexis Hunt (alercah)
|
||||
* Allen262
|
||||
* Andrew Aldridge (i80and)
|
||||
* Andrew Perkins
|
||||
@@ -39,16 +37,12 @@ Also thanks to:
|
||||
* Bellator
|
||||
* Biofreak
|
||||
* Braxton Williams (Buddytex)
|
||||
* Brendan Gluth (Mechanical_Man)
|
||||
* Brent Gardner (bggardner)
|
||||
* Bryan Wilbur
|
||||
* Bugra Cuhadaroglu (BugraC)
|
||||
* Chris Cameron (Vesuvian)
|
||||
* Chris Grant (Unit158)
|
||||
* Christer Ulfsparre (Holloweye)
|
||||
* Chris Grant (Unit158)
|
||||
* clem
|
||||
* Cody Brittain (Generalcamo)
|
||||
* Constantin Helmig (CH4Code)
|
||||
* D2k Sardaukar
|
||||
* D'Arcy Rush (r34ch)
|
||||
* Daniel Derejvanik (Harisson)
|
||||
@@ -57,7 +51,6 @@ Also thanks to:
|
||||
* David Russell (DavidARussell)
|
||||
* DeadlySurprise
|
||||
* Dmitri Suvorov (suvjunmd)
|
||||
* dtluna
|
||||
* Erasmus Schroder (rasco)
|
||||
* Eric Bajumpaa (SteelPhase)
|
||||
* Evgeniy Sergeev (evgeniysergeev)
|
||||
@@ -65,26 +58,24 @@ Also thanks to:
|
||||
* Florian Wiesbauer (FiveAces)
|
||||
* Frank Razenberg (zzattack)
|
||||
* Gareth Needham (Ripley`)
|
||||
* Glen Anderson (glen7)
|
||||
* Glen Anderson (GlenAnderson)
|
||||
* Glenn Martin Jensen (Baxxster)
|
||||
* Gordon Martin (Happy0)
|
||||
* Guido Lipke (LipkeGu)
|
||||
* Gyula Zimmermann (Graion Dilach)
|
||||
* Hervé Matysiak (Herve-M)
|
||||
* Huw Pascoe
|
||||
* Ian T. Jacobsen (Smilex)
|
||||
* Imago
|
||||
* Iran
|
||||
* Ishan Bhargava (ishantheperson)
|
||||
* Jacob Dufault (jacobdufault)
|
||||
* James Dunne (jsd)
|
||||
* James Gilbert (DSUK)
|
||||
* Jan-Willem Buurlage (jwbuurlage)
|
||||
* Jason (atlimit8)
|
||||
* Jeff Harris (jeff_1amstudios)
|
||||
* Jefri Sevkin (Arular)
|
||||
* Jes
|
||||
* Joakim Lindberg (booom3)
|
||||
* Joe Alam (joealam)
|
||||
* John Turner (whinis)
|
||||
* Jonas A. Lind (SoScared)
|
||||
* Joppy Furr
|
||||
@@ -92,29 +83,21 @@ Also thanks to:
|
||||
* Kenny Hoxworth (hoxworth)
|
||||
* Kevin Azzam (ChaoticMind)
|
||||
* Krishnakanth Mallik
|
||||
* Kyle Smith (Smitty)
|
||||
* Kyrre Soerensen (zypres)
|
||||
* Lawrence Wang
|
||||
* Lesueur Benjamin (Valkirie)
|
||||
* Maarten Meuris (Nyerguds)
|
||||
* Manuel Geiger (Ectras)
|
||||
* Mark Olson (markolson)
|
||||
* Markus Hartung (hartmark)
|
||||
* Matija Hustic (matija-hustic)
|
||||
* Matthew Gatland (mgatland)
|
||||
* Matthew Uzzell (MUzzell)
|
||||
* Matthijs Benschop (Nerdie)
|
||||
* Max621
|
||||
* Max Ugrumov (katzsmile)
|
||||
* Mazar Farran (mazarf)
|
||||
* Michael Rätzel
|
||||
* Michael Silber (frühstück)
|
||||
* Michael Sztolcman (s1w_)
|
||||
* Mike Gagné (AngryBirdz)
|
||||
* Muh
|
||||
* Mustafa Alperen Seki (MustaphaTR)
|
||||
* Neil Shivkar (havok13888)
|
||||
* Nikolay Fomin (netnazgul)
|
||||
* Nooze
|
||||
* Nukem
|
||||
* Okunev Yu Dmitry (xaionaro)
|
||||
@@ -122,12 +105,9 @@ Also thanks to:
|
||||
* Paolo Chiodi (paolochiodi)
|
||||
* Paul Dovydaitis (pdovy)
|
||||
* Pavlos Touboulidis (pav)
|
||||
* Pedro Ferreira Ramos (bateramos)
|
||||
* Peter Amrehn (jongleur1983)
|
||||
* Pizzaoverhead
|
||||
* Pi Delport (pjdelport)
|
||||
* Piët Delport (pjdelport)
|
||||
* Psydev
|
||||
* Raphael Vogt (TheRaffy, Yellow)
|
||||
* Raymond Bedrossian (Squiggles211)
|
||||
* Raymond Martineau (mart0258)
|
||||
* Riderr3
|
||||
@@ -138,19 +118,13 @@ Also thanks to:
|
||||
* Sebastien Kerguen (xanax)
|
||||
* Shawn Collins (UberWaffe)
|
||||
* Simon Verbeke (Saticmotion)
|
||||
* Stuart McHattie (SDJMcHattie)
|
||||
* Taryn Hill (Phrohdoh)
|
||||
* Teemu Nieminen (Temeez)
|
||||
* Tim Mylemans (gecko)
|
||||
* Tirili
|
||||
* Tomas Einarsson (Mesacer)
|
||||
* Tom van Leth (tovl)
|
||||
* Trevor Nichols (ocdi)
|
||||
* Tristan Keating (Kilkakon)
|
||||
* Tristan Mühlbacher (MicroBit)
|
||||
* UnknownProgrammer
|
||||
* Vladimir Komarov (VrKomarov)
|
||||
* Wojciech Walaszek (Voidwalker)
|
||||
* Wuschel
|
||||
|
||||
Using GNU FreeFont distributed under the GNU GPL
|
||||
@@ -164,6 +138,15 @@ FreeType License.
|
||||
|
||||
Using OpenAL Soft distributed under the GNU LGPL.
|
||||
|
||||
Using MaxMind GeoIP2 .NET API distributed under
|
||||
the Apache 2.0 license.
|
||||
|
||||
Using GeoLite2 data created by MaxMind and
|
||||
distributed under the CC BY-SA 3.0 license.
|
||||
|
||||
Using SharpFont created by Robert Rouhani and
|
||||
distributed under the MIT license.
|
||||
|
||||
Using SDL2-CS and OpenAL-CS created by Ethan
|
||||
Lee and released under the zlib license.
|
||||
|
||||
@@ -173,26 +156,15 @@ under the MIT license.
|
||||
Using FuzzyLogicLibrary (fuzzynet) by Dmitry
|
||||
Kaluzhny and released under the GNU GPL terms.
|
||||
|
||||
Using Open.Nat by Lucas Ontivero, based on the work
|
||||
of Alan McGovern and Ben Motmans and distributed
|
||||
under the MIT license.
|
||||
Using Mono.Nat by Alan McGovern and Ben
|
||||
Motmans and distributed under the MIT license.
|
||||
|
||||
Using ICSharpCode.SharpZipLib initially by Mike
|
||||
Krueger and distributed under the GNU GPL terms.
|
||||
|
||||
Using rix0rrr.BeaconLib developed by Rico Huijbers
|
||||
distributed under MIT License.
|
||||
Using SmartIrc4Net developed by Mirco Bauer
|
||||
distributed under the LGPL version 2.1 or later.
|
||||
|
||||
Using DiscordRichPresence developed by Lachee
|
||||
distributed under MIT License.
|
||||
|
||||
Using Json.NET developed by James Newton-King
|
||||
distributed under MIT License.
|
||||
|
||||
Using ANGLE distributed under the BS3 3-Clause license.
|
||||
|
||||
This site or product includes IP2Location LITE data
|
||||
available from http://www.ip2location.com.
|
||||
|
||||
Finally, special thanks goes to the original teams
|
||||
at Westwood Studios and EA for creating the classic
|
||||
|
||||
@@ -1,76 +0,0 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and maintainers pledge to making participation in our project and
|
||||
our community a harassment-free experience for everyone, regardless of age, body
|
||||
size, disability, ethnicity, gender identity and expression, level of experience,
|
||||
nationality, personal appearance, race, religion, or sexual identity and
|
||||
orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||
advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable
|
||||
behavior and are expected to take appropriate and fair corrective action in
|
||||
response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or
|
||||
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||
permanently any contributor for other behaviors that they deem inappropriate,
|
||||
threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces
|
||||
when an individual is representing the project or its community. Examples of
|
||||
representing a project or community include using an official project e-mail
|
||||
address, posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event. Representation of a project may be
|
||||
further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by private-messaging a project team member (users with a + in front
|
||||
of their name) via our IRC channel (#openra on freenode –
|
||||
[webchat](http://webchat.freenode.net/?channels=openra)). All
|
||||
complaints will be reviewed and investigated and will result in a response that
|
||||
is deemed necessary and appropriate to the circumstances. The project team is
|
||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||
faith may face temporary or permanent repercussions as determined by other
|
||||
members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||
available at [http://contributor-covenant.org/version/1/4][version]
|
||||
|
||||
[homepage]: http://contributor-covenant.org
|
||||
[version]: http://contributor-covenant.org/version/1/4/
|
||||
@@ -1,8 +1,5 @@
|
||||
# OpenRA Contributing Guidelines
|
||||
|
||||
## Participating
|
||||
Help us keep OpenRA open and inclusive. Please read and follow our [Code of Conduct](https://github.com/OpenRA/OpenRA/blob/bleed/CODE_OF_CONDUCT.md).
|
||||
|
||||
## Bug reports
|
||||
|
||||
* Have you read the [FAQ](https://github.com/OpenRA/OpenRA/wiki/FAQ)?
|
||||
@@ -20,7 +17,7 @@ Help us keep OpenRA open and inclusive. Please read and follow our [Code of Cond
|
||||
|
||||
Please `git rebase` to the latest revision of the bleed branch.
|
||||
|
||||
Don't forget to add yourself to [AUTHORS](https://github.com/OpenRA/OpenRA/blob/bleed/AUTHORS).
|
||||
Don't forget to add youself to [AUTHORS](https://github.com/OpenRA/OpenRA/blob/bleed/AUTHORS).
|
||||
|
||||
Please propose a [CHANGELOG](https://github.com/OpenRA/OpenRA/wiki/CHANGELOG) entry in the pull-request comments.
|
||||
|
||||
|
||||
124
INSTALL.md
124
INSTALL.md
@@ -6,106 +6,66 @@ The following lists per-platform dependencies required to build from source.
|
||||
Windows
|
||||
=======
|
||||
|
||||
Compiling OpenRA requires the following dependencies:
|
||||
* [Windows PowerShell >= 4.0](http://microsoft.com/powershell) (included by default in recent Windows 10 versions)
|
||||
* [.NET Framework 4.7.2 (Developer Pack)](https://dotnet.microsoft.com/download/dotnet-framework/net472) (or via Visual Studio 2017)
|
||||
* [.NET Core 2.2 SDK](https://dotnet.microsoft.com/download/dotnet-core/2.2) (or via Visual Studio 2017)
|
||||
* [Windows PowerShell >= 4.0](http://microsoft.com/powershell)
|
||||
* [.NET Framework >= 4.0 (Client Profile)](http://www.microsoft.com/en-us/download/details.aspx?id=17113)
|
||||
* [SDL 2](http://www.libsdl.org/download-2.0.php) (included)
|
||||
* [FreeType](http://gnuwin32.sourceforge.net/packages/freetype.htm) (included)
|
||||
* [zlib](http://gnuwin32.sourceforge.net/packages/zlib.htm) (included)
|
||||
* [OpenAL](http://kcat.strangesoft.net/openal.html) (included)
|
||||
* [liblua 5.1](http://luabinaries.sourceforge.net/download.html) (included)
|
||||
|
||||
You need to fetch the thirdparty dependencies using [NuGet](http://www.nuget.org) and place them at the appropriate places by typing `make dependencies` in a command terminal.
|
||||
|
||||
To compile OpenRA, open the `OpenRA.sln` solution in the main folder, build it from the command-line with MSBuild or use the Makefile analogue command `make all` scripted in PowerShell syntax.
|
||||
|
||||
Run the game with `launch-game.cmd`. It can be handed arguments that specify the exact mod one wishes to run, for example, run `launch-game.cmd Game.Mod=ra` to launch Red Alert, `launch-game.cmd Game.Mod=cnc` to start Tiberian dawn or `launch-game.cmd Game.Mod=d2k` to launch Dune 2000.
|
||||
Run the game with `OpenRA.Game.exe Game.Mod=ra` for Red Alert or `OpenRA.Game.exe Game.Mod=cnc` for Tiberian Dawn.
|
||||
|
||||
Linux
|
||||
=====
|
||||
|
||||
Mono, version 5.18 or later, is required to compile OpenRA. You can add the [upstream mono repository](https://www.mono-project.com/download/stable/#download-lin) for your distro to obtain the latest version if your system packages are not sufficient.
|
||||
Use `make dependencies` to map the native libraries to your system, fetch the remaining CLI dependencies using [NuGet](http://www.nuget.org) and place them at the appropriate places.
|
||||
|
||||
To compile OpenRA, run `make` from the command line. After this one can run the game with `./launch-game.sh`. It is also possible to specify the mod you wish to run from the command line, e.g. with `./launch-game.sh Game.Mod=ts` if you wish to try the experimental Tiberian Sun mod.
|
||||
To compile OpenRA, run `make all` from the command line.
|
||||
|
||||
The default behaviour on the x86_64 architecture is to download several pre-compiled native libraries using the Nuget packaging manager. If you prefer to use system libraries, compile instead using `make TARGETPLATFORM=unix-generic`.
|
||||
Run with either `launch-game.sh` or `mono --debug OpenRA.Game.exe`.
|
||||
|
||||
If you choose to use system libraries, or your system is not x86_64, you will need to install the following using your system package manager:
|
||||
* [SDL 2](http://www.libsdl.org/download-2.0.php)
|
||||
* [FreeType](http://gnuwin32.sourceforge.net/packages/freetype.htm)
|
||||
* [OpenAL](http://kcat.strangesoft.net/openal.html)
|
||||
* [liblua 5.1](http://luabinaries.sourceforge.net/download.html)
|
||||
|
||||
Type `sudo make install` for system-wide installation. Run `sudo make install-linux-shortcuts` to get startup scripts, icons and desktop files. You can then run the Red Alert by executing the `openra-ra` command, the Dune 2000 mod by running the `openra-d2k` command and Tiberian Dawn by the `openra-cnc` command. Alternatively, you can also run these mods by clicking on their desktop shortcuts if you ran `sudo make install-linux-shortcuts`.
|
||||
|
||||
Arch Linux
|
||||
----------
|
||||
|
||||
It is important to note there is an unofficial [`openra-git`](https://aur.archlinux.org/packages/openra-git) package in the Arch User Repository (AUR) of Arch Linux. If manually compiling is the way you wish to go the build and runtime dependencies can be installed with:
|
||||
|
||||
```
|
||||
sudo pacman -S mono openal libgl freetype2 sdl2 lua51 xdg-utils zenity
|
||||
```
|
||||
Type `sudo make install-all` for system wide installation. Run `make install-linux-shortcuts` to get startup scripts, icons and desktop files. You can then run from the `openra` shortcut.
|
||||
|
||||
Debian/Ubuntu
|
||||
-------------
|
||||
|
||||
:warning: The `mono` packages in the Ubuntu < 19.04 and Debian < 10 repositories are too old to support OpenRA. :warning:
|
||||
|
||||
See the instructions under the *Linux* section above to upgrade `mono` using the upstream releases if needed.
|
||||
|
||||
```
|
||||
sudo apt install mono-devel libfreetype6 libopenal1 liblua5.1-0 libsdl2-2.0-0 xdg-utils zenity wget
|
||||
```
|
||||
|
||||
Fedora
|
||||
------
|
||||
|
||||
:warning: The `mono` packages in the Fedora repositories are too old to support OpenRA. :warning:
|
||||
|
||||
See the instructions under the *Linux* section above to upgrade `mono` using the upstream releases.
|
||||
|
||||
|
||||
```
|
||||
sudo dnf install "pkgconfig(mono)" SDL2 freetype "lua = 5.1" openal-soft xdg-utils zenity
|
||||
```
|
||||
|
||||
Gentoo
|
||||
------
|
||||
|
||||
```
|
||||
sudo emerge -av dev-lang/mono dev-dotnet/libgdiplus media-libs/freetype:2 media-libs/libsdl2 media-libs/openal virtual/jpeg virtual/opengl '=dev-lang/lua-5.1.5*' x11-misc/xdg-utils gnome-extra/zenity
|
||||
```
|
||||
|
||||
Mageia
|
||||
------
|
||||
|
||||
```
|
||||
sudo dnf install "pkgconfig(mono)" SDL2 freetype "lib*lua5.1" "lib*freetype2" "lib*sdl2.0_0" openal-soft xdg-utils zenity
|
||||
```
|
||||
* nuget
|
||||
* mono-devel
|
||||
* libfreetype6
|
||||
* libopenal1
|
||||
* liblua5.1-0
|
||||
* libsdl2-2.0-0
|
||||
* xdg-utils
|
||||
* zenity
|
||||
|
||||
openSUSE
|
||||
--------
|
||||
|
||||
```
|
||||
sudo zypper in mono-devel openal-soft freetype2 SDL2 lua51 xdg-utils zenity
|
||||
```
|
||||
* mono-devel
|
||||
* nuget
|
||||
* openal
|
||||
* freetype2
|
||||
* SDL2
|
||||
* lua51
|
||||
* xdg-utils
|
||||
* zenity
|
||||
|
||||
Red Hat Enterprise Linux (and rebuilds, e.g. CentOS)
|
||||
----------------------------------------------------
|
||||
|
||||
The EPEL repository is required in order for the following command to run properly.
|
||||
|
||||
```
|
||||
sudo yum install "pkgconfig(mono)" SDL2 freetype "lua = 5.1" openal-soft xdg-utils zenity
|
||||
```
|
||||
|
||||
macOS
|
||||
=====
|
||||
|
||||
Before compiling OpenRA you must install the following dependencies:
|
||||
* [Mono >= 5.18](https://www.mono-project.com/download/stable/#download-mac)
|
||||
|
||||
To compile OpenRA, run `make` from the command line. Run with `./launch-game.sh`.
|
||||
|
||||
The default behaviour is to download several pre-compiled native libraries using the Nuget packaging manager. If you prefer to use system libraries, compile instead using `make TARGETPLATFORM=unix-generic`. If you choose to use system libraries you will need to install:
|
||||
* [SDL 2](http://www.libsdl.org/download-2.0.php) (`brew install sdl2`)
|
||||
* [FreeType](http://gnuwin32.sourceforge.net/packages/freetype.htm) (`brew install freetype`)
|
||||
* [OpenAL](http://kcat.strangesoft.net/openal.html) (`brew install openal-soft`)
|
||||
* [liblua 5.1](http://luabinaries.sourceforge.net/download.html) (`brew install lua@5.1`)
|
||||
Gentoo
|
||||
------
|
||||
|
||||
* dev-lang/mono
|
||||
* dev-dotnet/libgdiplus
|
||||
* dev-dotnet/nuget
|
||||
* media-libs/freetype:2
|
||||
* media-libs/libsdl2
|
||||
* media-libs/openal
|
||||
* virtual/jpeg
|
||||
* virtual/opengl
|
||||
* dev-lang/lua-5.1.5
|
||||
* x11-misc/xdg-utils
|
||||
* gnome-extra/zenity
|
||||
|
||||
550
Makefile
550
Makefile
@@ -1,49 +1,68 @@
|
||||
############################# INSTRUCTIONS #############################
|
||||
#
|
||||
# to compile, run:
|
||||
# make [DEBUG=true]
|
||||
# make [DEBUG=false]
|
||||
#
|
||||
# to compile using system libraries for native dependencies, run:
|
||||
# make [DEBUG=true] TARGETPLATFORM=unix-generic
|
||||
# to compile with development tools, run:
|
||||
# make all [DEBUG=false]
|
||||
#
|
||||
# to check unit tests (requires NUnit version >= 2.6), run:
|
||||
# make nunit [NUNIT_CONSOLE=<path-to/nunit[2]-console>] [NUNIT_LIBS_PATH=<path-to-libs-dir>] [NUNIT_LIBS=<nunit-libs>]
|
||||
# Use NUNIT_CONSOLE if nunit[3|2]-console was not downloaded by `make dependencies` nor is it in bin search paths
|
||||
# Use NUNIT_LIBS_PATH if NUnit libs are not in search paths. Include trailing /
|
||||
# Use NUNIT_LIBS if NUnit libs have different names (such as including a prefix or suffix)
|
||||
# to check the official mods for erroneous yaml files, run:
|
||||
# make test
|
||||
#
|
||||
# to check the engine and official mod dlls for code style violations, run:
|
||||
# to check the official mod dlls for StyleCop violations, run:
|
||||
# make check
|
||||
#
|
||||
# to compile and install Red Alert, Tiberian Dawn, and Dune 2000, run:
|
||||
# to generate documentation aimed at modders, run:
|
||||
# make docs
|
||||
#
|
||||
# to install, run:
|
||||
# make [prefix=/foo] [bindir=/bar/bin] install
|
||||
#
|
||||
# to compile and install Red Alert, Tiberian Dawn, and Dune 2000
|
||||
# using system libraries for native dependencies, run:
|
||||
# make [prefix=/foo] [bindir=/bar/bin] TARGETPLATFORM=unix-generic install
|
||||
# to install with development tools, run:
|
||||
# make [prefix=/foo] [bindir=/bar/bin] install-all
|
||||
#
|
||||
# to install Linux startup scripts, desktop files, icons, and MIME metadata
|
||||
# make install-linux-shortcuts
|
||||
# to install Linux startup scripts, desktop files and icons:
|
||||
# make install-linux-shortcuts [DEBUG=false]
|
||||
#
|
||||
# to install Linux AppStream metadata
|
||||
# make install-linux-appdata
|
||||
# to uninstall, run:
|
||||
# make uninstall
|
||||
#
|
||||
# for help, run:
|
||||
# make help
|
||||
#
|
||||
# to start the game, run:
|
||||
# openra
|
||||
|
||||
|
||||
|
||||
############################## TOOLCHAIN ###############################
|
||||
#
|
||||
# List of .NET assemblies that we can guarantee exist
|
||||
WHITELISTED_OPENRA_ASSEMBLIES = OpenRA.exe OpenRA.Utility.exe OpenRA.Server.exe OpenRA.Platforms.Default.dll OpenRA.Game.dll OpenRA.Mods.Common.dll OpenRA.Mods.Cnc.dll OpenRA.Mods.D2k.dll
|
||||
SDK ?=
|
||||
CSC = mcs $(SDK)
|
||||
CSFLAGS = -nologo -warn:4 -codepage:utf8 -unsafe -warnaserror
|
||||
DEFINE = TRACE
|
||||
COMMON_LIBS = System.dll System.Core.dll System.Data.dll System.Data.DataSetExtensions.dll System.Drawing.dll System.Xml.dll thirdparty/download/ICSharpCode.SharpZipLib.dll thirdparty/download/FuzzyLogicLibrary.dll thirdparty/download/Mono.Nat.dll thirdparty/download/MaxMind.Db.dll thirdparty/download/MaxMind.GeoIP2.dll thirdparty/download/Eluant.dll thirdparty/download/SmarIrc4net.dll
|
||||
NUNIT_LIBS_PATH :=
|
||||
NUNIT_LIBS := $(NUNIT_LIBS_PATH)nunit.framework.dll
|
||||
|
||||
DEBUG = true
|
||||
ifeq ($(DEBUG), $(filter $(DEBUG),false no n off 0))
|
||||
CSFLAGS += -debug:pdbonly -optimize+
|
||||
else
|
||||
CSFLAGS += -debug:full -optimize-
|
||||
DEFINE := DEBUG;$(DEFINE)
|
||||
endif
|
||||
|
||||
# These are explicitly shipped alongside our core files by the packaging script
|
||||
WHITELISTED_THIRDPARTY_ASSEMBLIES = ICSharpCode.SharpZipLib.dll FuzzyLogicLibrary.dll Eluant.dll BeaconLib.dll Open.Nat.dll SDL2-CS.dll OpenAL-CS.Core.dll DiscordRPC.dll Newtonsoft.Json.dll
|
||||
|
||||
# These are shipped in our custom minimal mono runtime and also available in the full system-installed .NET/mono stack
|
||||
# This list *must* be kept in sync with the files packaged by the AppImageSupport and OpenRALauncherOSX repositories
|
||||
WHITELISTED_CORE_ASSEMBLIES = mscorlib.dll System.dll System.Configuration.dll System.Core.dll System.Numerics.dll System.Security.dll System.Xml.dll Mono.Security.dll netstandard.dll
|
||||
|
||||
######################### UTILITIES/SETTINGS ###########################
|
||||
#
|
||||
# Install locations for local installs and downstream packaging
|
||||
# install locations
|
||||
prefix ?= /usr/local
|
||||
datarootdir ?= $(prefix)/share
|
||||
datadir ?= $(datarootdir)
|
||||
@@ -52,137 +71,470 @@ bindir ?= $(prefix)/bin
|
||||
libdir ?= $(prefix)/lib
|
||||
gameinstalldir ?= $(libdir)/openra
|
||||
|
||||
# Toolchain
|
||||
CWD = $(shell pwd)
|
||||
MSBUILD = msbuild -verbosity:m -nologo
|
||||
MONO = mono
|
||||
BIN_INSTALL_DIR = $(DESTDIR)$(bindir)
|
||||
DATA_INSTALL_DIR = $(DESTDIR)$(gameinstalldir)
|
||||
|
||||
# install tools
|
||||
RM = rm
|
||||
RM_R = $(RM) -r
|
||||
RM_F = $(RM) -f
|
||||
RM_RF = $(RM) -rf
|
||||
CP = cp
|
||||
CP_R = $(CP) -r
|
||||
INSTALL = install
|
||||
INSTALL_DIR = $(INSTALL) -d
|
||||
INSTALL_PROGRAM = $(INSTALL) -m755
|
||||
INSTALL_DATA = $(INSTALL) -m644
|
||||
|
||||
# Only for use in target version:
|
||||
VERSION := $(shell git name-rev --name-only --tags --no-undefined HEAD 2>/dev/null || (c=$$(git rev-parse --short HEAD 2>/dev/null) && echo git-$$c))
|
||||
# program targets
|
||||
CORE = pdefault game utility server
|
||||
TOOLS = gamemonitor
|
||||
VERSION = $(shell git name-rev --name-only --tags --no-undefined HEAD 2>/dev/null || echo git-`git rev-parse --short HEAD`)
|
||||
|
||||
# Detect target platform for dependencies if not given by the user
|
||||
ifndef TARGETPLATFORM
|
||||
# dependencies
|
||||
UNAME_S := $(shell uname -s)
|
||||
UNAME_M := $(shell uname -m)
|
||||
ifeq ($(UNAME_S),Darwin)
|
||||
TARGETPLATFORM = osx-x64
|
||||
os-dependencies = osx-dependencies
|
||||
else
|
||||
ifeq ($(UNAME_M),x86_64)
|
||||
TARGETPLATFORM = linux-x64
|
||||
else
|
||||
TARGETPLATFORM = unix-generic
|
||||
endif
|
||||
endif
|
||||
os-dependencies = linux-dependencies
|
||||
endif
|
||||
|
||||
OPENRA_UTILITY = ENGINE_DIR=".." $(MONO) --debug bin/OpenRA.Utility.exe
|
||||
|
||||
##################### DEVELOPMENT BUILDS AND TESTS #####################
|
||||
|
||||
######################## PROGRAM TARGET RULES ##########################
|
||||
#
|
||||
all:
|
||||
@command -v $(firstword $(MSBUILD)) >/dev/null || (echo "OpenRA requires the '$(MSBUILD)' tool provided by Mono >= 5.18."; exit 1)
|
||||
@$(MSBUILD) -t:Build -restore -p:Configuration=Release -p:TargetPlatform=$(TARGETPLATFORM)
|
||||
ifeq ($(TARGETPLATFORM), unix-generic)
|
||||
@./configure-system-libraries.sh
|
||||
endif
|
||||
@./fetch-geoip.sh
|
||||
# Core binaries
|
||||
|
||||
clean:
|
||||
@-$(RM_RF) ./bin ./*/bin ./*/obj
|
||||
@$(MSBUILD) -t:Clean
|
||||
@-$(RM_F) IP2LOCATION-LITE-DB1.IPV6.BIN.ZIP
|
||||
game_SRCS := $(shell find OpenRA.Game/ -iname '*.cs')
|
||||
game_TARGET = OpenRA.Game.exe
|
||||
game_KIND = winexe
|
||||
game_LIBS = $(COMMON_LIBS) $(game_DEPS) thirdparty/download/SharpFont.dll
|
||||
game_FLAGS = -win32icon:OpenRA.Game/OpenRA.ico
|
||||
PROGRAMS += game
|
||||
game: $(game_TARGET)
|
||||
|
||||
check:
|
||||
@echo
|
||||
@echo "Compiling in debug mode..."
|
||||
@$(MSBUILD) -t:build -restore -p:Configuration=Debug -p:TargetPlatform=$(TARGETPLATFORM)
|
||||
ifeq ($(TARGETPLATFORM), unix-generic)
|
||||
@./configure-system-libraries.sh
|
||||
endif
|
||||
@echo
|
||||
@echo "Checking runtime assemblies..."
|
||||
@$(OPENRA_UTILITY) all --check-runtime-assemblies $(WHITELISTED_OPENRA_ASSEMBLIES) $(WHITELISTED_THIRDPARTY_ASSEMBLIES) $(WHITELISTED_CORE_ASSEMBLIES)
|
||||
@echo
|
||||
@echo "Checking for explicit interface violations..."
|
||||
@$(OPENRA_UTILITY) all --check-explicit-interfaces
|
||||
@echo
|
||||
@echo "Checking for incorrect conditional trait interface overrides..."
|
||||
@$(OPENRA_UTILITY) all --check-conditional-trait-interface-overrides
|
||||
# Platform dlls
|
||||
pdefault_SRCS := $(shell find OpenRA.Platforms.Default/ -iname '*.cs')
|
||||
pdefault_TARGET = OpenRA.Platforms.Default.dll
|
||||
pdefault_KIND = library
|
||||
pdefault_DEPS = $(game_TARGET)
|
||||
pdefault_LIBS = $(COMMON_LIBS) thirdparty/download/SDL2-CS.dll thirdparty/download/OpenAL-CS.dll $(pdefault_DEPS)
|
||||
PROGRAMS += pdefault
|
||||
platforms: $(pdefault_TARGET)
|
||||
|
||||
# Mods Common
|
||||
mod_common_SRCS := $(shell find OpenRA.Mods.Common/ -iname '*.cs')
|
||||
mod_common_TARGET = mods/common/OpenRA.Mods.Common.dll
|
||||
mod_common_KIND = library
|
||||
mod_common_DEPS = $(game_TARGET)
|
||||
mod_common_LIBS = $(COMMON_LIBS) $(STD_MOD_LIBS) thirdparty/download/StyleCop.dll thirdparty/download/StyleCop.CSharp.dll thirdparty/download/StyleCop.CSharp.Rules.dll
|
||||
PROGRAMS += mod_common
|
||||
mod_common: $(mod_common_TARGET)
|
||||
|
||||
# NUnit testing
|
||||
test_dll_SRCS := $(shell find OpenRA.Test/ -iname '*.cs')
|
||||
test_dll_TARGET = OpenRA.Test.dll
|
||||
test_dll_KIND = library
|
||||
test_dll_DEPS = $(game_TARGET) $(mod_common_TARGET)
|
||||
test_dll_FLAGS = -warn:1
|
||||
test_dll_LIBS = $(COMMON_LIBS) $(game_TARGET) $(mod_common_TARGET) $(NUNIT_LIBS)
|
||||
PROGRAMS += test_dll
|
||||
test_dll: $(test_dll_TARGET)
|
||||
|
||||
##### Official Mods #####
|
||||
|
||||
STD_MOD_LIBS = $(game_TARGET)
|
||||
STD_MOD_DEPS = $(STD_MOD_LIBS)
|
||||
|
||||
# Red Alert
|
||||
mod_ra_SRCS := $(shell find OpenRA.Mods.RA/ -iname '*.cs')
|
||||
mod_ra_TARGET = mods/ra/OpenRA.Mods.RA.dll
|
||||
mod_ra_KIND = library
|
||||
mod_ra_DEPS = $(STD_MOD_DEPS) $(mod_common_TARGET)
|
||||
mod_ra_LIBS = $(COMMON_LIBS) $(STD_MOD_LIBS) $(mod_common_TARGET)
|
||||
PROGRAMS += mod_ra
|
||||
mod_ra: $(mod_ra_TARGET)
|
||||
|
||||
# Command and Conquer
|
||||
mod_cnc_SRCS := $(shell find OpenRA.Mods.Cnc/ -iname '*.cs')
|
||||
mod_cnc_TARGET = mods/cnc/OpenRA.Mods.Cnc.dll
|
||||
mod_cnc_KIND = library
|
||||
mod_cnc_DEPS = $(STD_MOD_DEPS) $(mod_common_TARGET)
|
||||
mod_cnc_LIBS = $(COMMON_LIBS) $(STD_MOD_LIBS) $(mod_common_TARGET)
|
||||
PROGRAMS += mod_cnc
|
||||
mod_cnc: $(mod_cnc_TARGET)
|
||||
|
||||
# Dune 2000
|
||||
mod_d2k_SRCS := $(shell find OpenRA.Mods.D2k/ -iname '*.cs')
|
||||
mod_d2k_TARGET = mods/d2k/OpenRA.Mods.D2k.dll
|
||||
mod_d2k_KIND = library
|
||||
mod_d2k_DEPS = $(STD_MOD_DEPS) $(mod_common_TARGET)
|
||||
mod_d2k_LIBS = $(COMMON_LIBS) $(STD_MOD_LIBS) $(mod_common_TARGET)
|
||||
PROGRAMS += mod_d2k
|
||||
mod_d2k: $(mod_d2k_TARGET)
|
||||
|
||||
# Tiberian Sun
|
||||
mod_ts_SRCS := $(shell find OpenRA.Mods.TS/ -iname '*.cs')
|
||||
mod_ts_TARGET = mods/ts/OpenRA.Mods.TS.dll
|
||||
mod_ts_KIND = library
|
||||
mod_ts_DEPS = $(STD_MOD_DEPS) $(mod_common_TARGET)
|
||||
mod_ts_LIBS = $(COMMON_LIBS) $(STD_MOD_LIBS) $(mod_common_TARGET)
|
||||
PROGRAMS += mod_ts
|
||||
mod_ts: $(mod_ts_TARGET)
|
||||
|
||||
check-scripts:
|
||||
@echo
|
||||
@echo "Checking for Lua syntax errors..."
|
||||
@luac -p $(shell find mods/*/maps/* -iname '*.lua')
|
||||
@luac -p $(shell find lua/* -iname '*.lua')
|
||||
@luac -p $(shell find mods/*/bits/scripts/* -iname '*.lua')
|
||||
|
||||
test: all
|
||||
check: utility mods
|
||||
@echo
|
||||
@echo "Checking for code style violations in OpenRA.Game..."
|
||||
@mono --debug OpenRA.Utility.exe ra --check-code-style OpenRA.Game
|
||||
@echo
|
||||
@echo "Checking for code style violations in OpenRA.Platforms.Default..."
|
||||
@mono --debug OpenRA.Utility.exe ra --check-code-style OpenRA.Platforms.Default
|
||||
@echo
|
||||
@echo "Checking for code style violations in OpenRA.GameMonitor..."
|
||||
@mono --debug OpenRA.Utility.exe ra --check-code-style OpenRA.GameMonitor
|
||||
@echo
|
||||
@echo "Checking for code style violations in OpenRA.Mods.Common..."
|
||||
@mono --debug OpenRA.Utility.exe ra --check-code-style OpenRA.Mods.Common
|
||||
@echo
|
||||
@echo "Checking for code style violations in OpenRA.Mods.RA..."
|
||||
@mono --debug OpenRA.Utility.exe ra --check-code-style OpenRA.Mods.RA
|
||||
@echo
|
||||
@echo "Checking for code style violations in OpenRA.Mods.Cnc..."
|
||||
@mono --debug OpenRA.Utility.exe ra --check-code-style OpenRA.Mods.Cnc
|
||||
@echo
|
||||
@echo "Checking for code style violations in OpenRA.Mods.D2k..."
|
||||
@mono --debug OpenRA.Utility.exe ra --check-code-style OpenRA.Mods.D2k
|
||||
@echo
|
||||
@echo "Checking for code style violations in OpenRA.Mods.TS..."
|
||||
@mono --debug OpenRA.Utility.exe ra --check-code-style OpenRA.Mods.TS
|
||||
@echo
|
||||
@echo "Checking for code style violations in OpenRA.Utility..."
|
||||
@mono --debug OpenRA.Utility.exe ra --check-code-style OpenRA.Utility
|
||||
@echo
|
||||
@echo "Checking for code style violations in OpenRA.Test..."
|
||||
@mono --debug OpenRA.Utility.exe ra --check-code-style OpenRA.Test
|
||||
@echo
|
||||
@echo "Checking for explicit interface violations..."
|
||||
@mono --debug OpenRA.Utility.exe all --check-explicit-interfaces
|
||||
@echo
|
||||
@echo "Checking for code style violations in OpenRA.Server..."
|
||||
@mono --debug OpenRA.Utility.exe ra --check-code-style OpenRA.Server
|
||||
|
||||
NUNIT_CONSOLE := $(shell test -f thirdparty/download/nunit3-console.exe && echo mono thirdparty/download/nunit3-console.exe || \
|
||||
which nunit3-console 2>/dev/null || which nunit2-console 2>/dev/null || which nunit-console 2>/dev/null)
|
||||
nunit: test_dll
|
||||
@echo
|
||||
@echo "Checking unit tests..."
|
||||
@if [ "$(NUNIT_CONSOLE)" = "" ] ; then \
|
||||
echo 'nunit[3|2]-console not found!'; \
|
||||
echo 'Was "make dependencies" called or is NUnit installed?'>&2; \
|
||||
echo 'See "make help".'; \
|
||||
exit 1; \
|
||||
fi
|
||||
@if $(NUNIT_CONSOLE) --help | head -n 1 | grep -E "NUnit version (1|2\.[0-5])";then \
|
||||
echo 'NUnit version >= 2.6 required'>&2; \
|
||||
echo 'Try "make dependencies" first to use NUnit from NuGet.'>&2; \
|
||||
echo 'See "make help".'; \
|
||||
exit 1; \
|
||||
fi
|
||||
@$(NUNIT_CONSOLE) --noresult OpenRA.Test.nunit
|
||||
|
||||
test: utility mods
|
||||
@echo
|
||||
@echo "Testing Tiberian Sun mod MiniYAML..."
|
||||
@$(OPENRA_UTILITY) ts --check-yaml
|
||||
@mono --debug OpenRA.Utility.exe ts --check-yaml
|
||||
@echo
|
||||
@echo "Testing Dune 2000 mod MiniYAML..."
|
||||
@$(OPENRA_UTILITY) d2k --check-yaml
|
||||
@mono --debug OpenRA.Utility.exe d2k --check-yaml
|
||||
@echo
|
||||
@echo "Testing Tiberian Dawn mod MiniYAML..."
|
||||
@$(OPENRA_UTILITY) cnc --check-yaml
|
||||
@mono --debug OpenRA.Utility.exe cnc --check-yaml
|
||||
@echo
|
||||
@echo "Testing Red Alert mod MiniYAML..."
|
||||
@$(OPENRA_UTILITY) ra --check-yaml
|
||||
@mono --debug OpenRA.Utility.exe ra --check-yaml
|
||||
|
||||
############# LOCAL INSTALLATION AND DOWNSTREAM PACKAGING ##############
|
||||
|
||||
##### Launchers / Utilities #####
|
||||
|
||||
gamemonitor_SRCS := $(shell find OpenRA.GameMonitor/ -iname '*.cs')
|
||||
gamemonitor_TARGET = OpenRA.exe
|
||||
gamemonitor_KIND = winexe
|
||||
gamemonitor_DEPS = $(game_TARGET)
|
||||
gamemonitor_LIBS = $(COMMON_LIBS) $(gamemonitor_DEPS) System.Windows.Forms.dll
|
||||
gamemonitor_FLAGS = -win32icon:OpenRA.Game/OpenRA.ico
|
||||
PROGRAMS += gamemonitor
|
||||
gamemonitor: $(gamemonitor_TARGET)
|
||||
|
||||
# Backend for the launcher apps - queries game/mod info and applies actions to an install
|
||||
utility_SRCS := $(shell find OpenRA.Utility/ -iname '*.cs')
|
||||
utility_TARGET = OpenRA.Utility.exe
|
||||
utility_KIND = exe
|
||||
utility_DEPS = $(game_TARGET)
|
||||
utility_LIBS = $(COMMON_LIBS) $(utility_DEPS) thirdparty/download/ICSharpCode.SharpZipLib.dll
|
||||
PROGRAMS += utility
|
||||
utility: $(utility_TARGET)
|
||||
|
||||
# Dedicated server
|
||||
server_SRCS := $(shell find OpenRA.Server/ -iname '*.cs')
|
||||
server_TARGET = OpenRA.Server.exe
|
||||
server_KIND = exe
|
||||
server_DEPS = $(game_TARGET)
|
||||
server_LIBS = $(COMMON_LIBS) $(server_DEPS)
|
||||
PROGRAMS += server
|
||||
server: $(server_TARGET)
|
||||
|
||||
# Patches binary headers to work around a mono bug
|
||||
fixheader.exe: packaging/fixheader.cs
|
||||
@echo CSC fixheader.exe
|
||||
@$(CSC) packaging/fixheader.cs $(CSFLAGS) -out:fixheader.exe -t:exe $(COMMON_LIBS:%=-r:%)
|
||||
|
||||
# Generate build rules for each target defined above in PROGRAMS
|
||||
define BUILD_ASSEMBLY
|
||||
|
||||
$$($(1)_TARGET): $$($(1)_SRCS) Makefile $$($(1)_DEPS) fixheader.exe
|
||||
@echo CSC $$(@)
|
||||
@$(CSC) $$($(1)_LIBS:%=-r:%) \
|
||||
-out:$$(@) $(CSFLAGS) $$($(1)_FLAGS) \
|
||||
-define:"$(DEFINE)" \
|
||||
-t:"$$($(1)_KIND)" \
|
||||
$$($(1)_EXTRA) \
|
||||
$$($(1)_SRCS)
|
||||
@mono fixheader.exe $$(@) > /dev/null
|
||||
@test `echo $$(@) | sed 's/^.*\.//'` = "dll" && chmod a-x $$(@) || ``
|
||||
@$$($(1)_EXTRA_CMDS)
|
||||
endef
|
||||
|
||||
$(foreach prog,$(PROGRAMS),$(eval $(call BUILD_ASSEMBLY,$(prog))))
|
||||
|
||||
|
||||
|
||||
########################## MAKE/INSTALL RULES ##########################
|
||||
#
|
||||
version: VERSION mods/ra/mod.yaml mods/cnc/mod.yaml mods/d2k/mod.yaml mods/ts/mod.yaml mods/modcontent/mod.yaml mods/all/mod.yaml
|
||||
ifeq ($(VERSION),)
|
||||
$(error Unable to determine new version (requires git or override of variable VERSION))
|
||||
default: core
|
||||
|
||||
core: game platforms mods utility server
|
||||
|
||||
tools: gamemonitor
|
||||
|
||||
package: all-dependencies core tools docs version
|
||||
|
||||
mods: mod_common mod_ra mod_cnc mod_d2k mod_ts
|
||||
|
||||
all: dependencies core tools
|
||||
|
||||
clean:
|
||||
@-$(RM_F) *.exe *.dll *.dylib *.dll.config ./OpenRA*/*.dll ./OpenRA*/*.mdb *.mdb mods/**/*.dll mods/**/*.mdb *.resources
|
||||
@-$(RM_RF) ./*/bin ./*/obj
|
||||
@-$(RM_RF) ./thirdparty/download
|
||||
|
||||
distclean: clean
|
||||
|
||||
cli-dependencies:
|
||||
@./thirdparty/fetch-thirdparty-deps.sh
|
||||
@ $(CP_R) thirdparty/download/*.dll .
|
||||
@ $(CP_R) thirdparty/download/*.dll.config .
|
||||
|
||||
linux-dependencies: cli-dependencies linux-native-dependencies
|
||||
|
||||
linux-native-dependencies:
|
||||
@./thirdparty/configure-native-deps.sh
|
||||
|
||||
windows-dependencies:
|
||||
@./thirdparty/fetch-thirdparty-deps-windows.sh
|
||||
|
||||
osx-dependencies: cli-dependencies
|
||||
@./thirdparty/fetch-thirdparty-deps-osx.sh
|
||||
@ $(CP_R) thirdparty/download/osx/*.dylib .
|
||||
@ $(CP_R) thirdparty/download/osx/*.dll.config .
|
||||
|
||||
dependencies: $(os-dependencies)
|
||||
@./thirdparty/fetch-geoip-db.sh
|
||||
@ $(CP) thirdparty/download/GeoLite2-Country.mmdb.gz .
|
||||
|
||||
all-dependencies: cli-dependencies windows-dependencies osx-dependencies
|
||||
|
||||
version: mods/ra/mod.yaml mods/cnc/mod.yaml mods/d2k/mod.yaml mods/ts/mod.yaml mods/modchooser/mod.yaml mods/all/mod.yaml
|
||||
@for i in $? ; do \
|
||||
awk '{sub("Version:.*$$","Version: $(VERSION)"); print $0}' $${i} > $${i}.tmp && \
|
||||
awk '{sub("\tmodchooser:.*$$","\tmodchooser: $(VERSION)"); print $0}' $${i}.tmp > $${i}.tmp2 && \
|
||||
awk '{sub("/[^/]*: User$$", "/$(VERSION): User"); print $0}' $${i}.tmp2 > $${i} && \
|
||||
rm $${i}.tmp $${i}.tmp2; \
|
||||
done
|
||||
|
||||
docs: utility mods version
|
||||
@mono --debug OpenRA.Utility.exe all --docs > DOCUMENTATION.md
|
||||
@mono --debug OpenRA.Utility.exe all --lua-docs > Lua-API.md
|
||||
|
||||
man-page: utility mods
|
||||
@mono --debug OpenRA.Utility.exe all --man-page > openra.6
|
||||
|
||||
install: install-core
|
||||
|
||||
install-all: install-core install-tools
|
||||
|
||||
install-linux-shortcuts: install-linux-scripts install-linux-icons install-linux-desktop
|
||||
|
||||
install-core: default
|
||||
@-echo "Installing OpenRA to $(DATA_INSTALL_DIR)"
|
||||
@$(INSTALL_DIR) "$(DATA_INSTALL_DIR)"
|
||||
@$(INSTALL_PROGRAM) $(foreach prog,$(CORE),$($(prog)_TARGET)) "$(DATA_INSTALL_DIR)"
|
||||
@$(INSTALL_DIR) "$(DATA_INSTALL_DIR)/mods"
|
||||
@$(CP_R) mods/common "$(DATA_INSTALL_DIR)/mods/"
|
||||
@$(INSTALL_PROGRAM) $(mod_common_TARGET) "$(DATA_INSTALL_DIR)/mods/common"
|
||||
@$(CP_R) mods/cnc "$(DATA_INSTALL_DIR)/mods/"
|
||||
@$(INSTALL_PROGRAM) $(mod_cnc_TARGET) "$(DATA_INSTALL_DIR)/mods/cnc"
|
||||
@$(CP_R) mods/ra "$(DATA_INSTALL_DIR)/mods/"
|
||||
@$(INSTALL_PROGRAM) $(mod_ra_TARGET) "$(DATA_INSTALL_DIR)/mods/ra"
|
||||
@$(CP_R) mods/d2k "$(DATA_INSTALL_DIR)/mods/"
|
||||
@$(INSTALL_PROGRAM) $(mod_d2k_TARGET) "$(DATA_INSTALL_DIR)/mods/d2k"
|
||||
@$(CP_R) mods/modchooser "$(DATA_INSTALL_DIR)/mods/"
|
||||
|
||||
@$(INSTALL_DATA) "global mix database.dat" "$(DATA_INSTALL_DIR)/global mix database.dat"
|
||||
@$(INSTALL_DATA) "GeoLite2-Country.mmdb.gz" "$(DATA_INSTALL_DIR)/GeoLite2-Country.mmdb.gz"
|
||||
@$(INSTALL_DATA) AUTHORS "$(DATA_INSTALL_DIR)/AUTHORS"
|
||||
@$(INSTALL_DATA) COPYING "$(DATA_INSTALL_DIR)/COPYING"
|
||||
|
||||
@$(CP_R) glsl "$(DATA_INSTALL_DIR)"
|
||||
@$(CP_R) lua "$(DATA_INSTALL_DIR)"
|
||||
@$(CP) SDL2-CS* "$(DATA_INSTALL_DIR)"
|
||||
@$(CP) OpenAL-CS* "$(DATA_INSTALL_DIR)"
|
||||
@$(CP) Eluant* "$(DATA_INSTALL_DIR)"
|
||||
@$(INSTALL_PROGRAM) ICSharpCode.SharpZipLib.dll "$(DATA_INSTALL_DIR)"
|
||||
@$(INSTALL_PROGRAM) FuzzyLogicLibrary.dll "$(DATA_INSTALL_DIR)"
|
||||
@$(INSTALL_PROGRAM) SharpFont.dll "$(DATA_INSTALL_DIR)"
|
||||
@$(CP) SharpFont.dll.config "$(DATA_INSTALL_DIR)"
|
||||
@$(INSTALL_PROGRAM) Mono.Nat.dll "$(DATA_INSTALL_DIR)"
|
||||
@$(INSTALL_PROGRAM) MaxMind.Db.dll "$(DATA_INSTALL_DIR)"
|
||||
@$(INSTALL_PROGRAM) MaxMind.GeoIP2.dll "$(DATA_INSTALL_DIR)"
|
||||
@$(INSTALL_PROGRAM) Newtonsoft.Json.dll "$(DATA_INSTALL_DIR)"
|
||||
@$(INSTALL_PROGRAM) RestSharp.dll "$(DATA_INSTALL_DIR)"
|
||||
@$(INSTALL_PROGRAM) SmarIrc4net.dll "$(DATA_INSTALL_DIR)"
|
||||
|
||||
ifneq ($(UNAME_S),Darwin)
|
||||
@$(CP) *.sh "$(DATA_INSTALL_DIR)"
|
||||
endif
|
||||
@sh -c '. ./packaging/functions.sh; set_engine_version "$(VERSION)" .'
|
||||
@sh -c '. ./packaging/functions.sh; set_mod_version "$(VERSION)" mods/ra/mod.yaml mods/cnc/mod.yaml mods/d2k/mod.yaml mods/ts/mod.yaml mods/modcontent/mod.yaml mods/all/mod.yaml'
|
||||
|
||||
install:
|
||||
@sh -c '. ./packaging/functions.sh; install_assemblies_mono $(CWD) $(DESTDIR)$(gameinstalldir) $(TARGETPLATFORM) True True True'
|
||||
@sh -c '. ./packaging/functions.sh; install_data $(CWD) $(DESTDIR)$(gameinstalldir) cnc d2k ra'
|
||||
install-tools: tools
|
||||
@-echo "Installing OpenRA tools to $(DATA_INSTALL_DIR)"
|
||||
@$(INSTALL_DIR) "$(DATA_INSTALL_DIR)"
|
||||
@$(INSTALL_PROGRAM) $(foreach prog,$(TOOLS),$($(prog)_TARGET)) "$(DATA_INSTALL_DIR)"
|
||||
|
||||
install-linux-shortcuts:
|
||||
@sh -c '. ./packaging/functions.sh; install_linux_shortcuts $(CWD) "$(DESTDIR)" "$(gameinstalldir)" "$(bindir)" "$(datadir)" "$(shell head -n1 VERSION)" cnc d2k ra'
|
||||
install-linux-icons:
|
||||
@$(INSTALL_DIR) "$(DESTDIR)$(datadir)/icons/"
|
||||
@$(CP_R) packaging/linux/hicolor "$(DESTDIR)$(datadir)/icons/"
|
||||
|
||||
install-linux-desktop:
|
||||
@$(INSTALL_DIR) "$(DESTDIR)$(datadir)/applications"
|
||||
@$(INSTALL_DATA) packaging/linux/openra.desktop "$(DESTDIR)$(datadir)/applications"
|
||||
|
||||
install-linux-mime:
|
||||
@$(INSTALL_DIR) "$(DESTDIR)$(datadir)/mime/packages/"
|
||||
@$(INSTALL_DATA) packaging/linux/openra-mimeinfo.xml "$(DESTDIR)$(datadir)/mime/packages/openra.xml"
|
||||
|
||||
@$(INSTALL_DIR) "$(DESTDIR)$(datadir)/applications"
|
||||
@$(INSTALL_DATA) packaging/linux/openra-join-servers.desktop "$(DESTDIR)$(datadir)/applications"
|
||||
@$(INSTALL_DATA) packaging/linux/openra-replays.desktop "$(DESTDIR)$(datadir)/applications"
|
||||
|
||||
install-linux-appdata:
|
||||
@sh -c '. ./packaging/functions.sh; install_linux_appdata $(CWD) "$(DESTDIR)" "$(datadir)" cnc d2k ra'
|
||||
@$(INSTALL_DIR) "$(DESTDIR)$(datadir)/appdata/"
|
||||
@$(INSTALL_DATA) packaging/linux/openra.appdata.xml "$(DESTDIR)$(datadir)/appdata/"
|
||||
|
||||
install-man-page: man-page
|
||||
@$(INSTALL_DIR) "$(DESTDIR)$(mandir)/man6/"
|
||||
@$(INSTALL_DATA) openra.6 "$(DESTDIR)$(mandir)/man6/"
|
||||
|
||||
install-linux-scripts:
|
||||
@echo "#!/bin/sh" > openra
|
||||
@echo 'cd "$(gameinstalldir)"' >> openra
|
||||
ifeq ($(DEBUG), $(filter $(DEBUG),false no n off 0))
|
||||
@echo 'mono OpenRA.Game.exe "$$@"' >> openra
|
||||
else
|
||||
@echo 'mono --debug OpenRA.Game.exe "$$@"' >> openra
|
||||
endif
|
||||
@echo 'if [ $$? != 0 -a $$? != 1 ]' >> openra
|
||||
@echo 'then' >> openra
|
||||
@echo 'ZENITY=`which zenity` || echo "OpenRA needs zenity installed to display a graphical error dialog. See ~/.openra. for log files."' >> openra
|
||||
@echo '$$ZENITY --question --title "OpenRA" --text "OpenRA has encountered a fatal error.\nLog Files are available in ~/.openra." --ok-label "Quit" --cancel-label "View FAQ" || xdg-open https://github.com/OpenRA/OpenRA/wiki/FAQ' >> openra
|
||||
@echo 'exit 1' >> openra
|
||||
@echo 'fi' >> openra
|
||||
|
||||
@$(INSTALL_DIR) "$(BIN_INSTALL_DIR)"
|
||||
@$(INSTALL_PROGRAM) -m +rx openra "$(BIN_INSTALL_DIR)"
|
||||
@-$(RM) openra
|
||||
|
||||
@echo "#!/bin/sh" > openra-server
|
||||
@echo 'cd "$(gameinstalldir)"' >> openra-server
|
||||
ifeq ($(DEBUG), $(filter $(DEBUG),false no n off 0))
|
||||
@echo 'mono OpenRA.Server.exe "$$@"' >> openra-server
|
||||
else
|
||||
@echo 'mono --debug OpenRA.Server.exe "$$@"' >> openra-server
|
||||
endif
|
||||
|
||||
@$(INSTALL_DIR) "$(BIN_INSTALL_DIR)"
|
||||
@$(INSTALL_PROGRAM) -m +rx openra-server "$(BIN_INSTALL_DIR)"
|
||||
@-$(RM) openra-server
|
||||
|
||||
uninstall:
|
||||
@-$(RM_R) "$(DATA_INSTALL_DIR)"
|
||||
@-$(RM_F) "$(BIN_INSTALL_DIR)/openra"
|
||||
@-$(RM_F) "$(BIN_INSTALL_DIR)/openra-server"
|
||||
@-$(RM_F) "$(DESTDIR)$(datadir)/applications/openra.desktop"
|
||||
@-$(RM_F) "$(DESTDIR)$(datadir)/icons/hicolor/16x16/apps/openra.png"
|
||||
@-$(RM_F) "$(DESTDIR)$(datadir)/icons/hicolor/32x32/apps/openra.png"
|
||||
@-$(RM_F) "$(DESTDIR)$(datadir)/icons/hicolor/48x48/apps/openra.png"
|
||||
@-$(RM_F) "$(DESTDIR)$(datadir)/icons/hicolor/64x64/apps/openra.png"
|
||||
@-$(RM_F) "$(DESTDIR)$(datadir)/icons/hicolor/128x128/apps/openra.png"
|
||||
@-$(RM_F) "$(DESTDIR)$(datadir)/mime/packages/openra.xml"
|
||||
@-$(RM_F) "$(DESTDIR)$(datadir)/appdata/openra.appdata.xml"
|
||||
@-$(RM_F) "$(DESTDIR)$(mandir)/man6/openra.6"
|
||||
|
||||
help:
|
||||
@echo 'to compile, run:'
|
||||
@echo ' make [DEBUG=true]'
|
||||
@echo ' make [DEBUG=false]'
|
||||
@echo
|
||||
@echo 'to compile using system libraries for native dependencies, run:'
|
||||
@echo ' make [DEBUG=true] TARGETPLATFORM=unix-generic'
|
||||
@echo 'to compile with development tools, run:'
|
||||
@echo ' make all [DEBUG=false]'
|
||||
@echo
|
||||
@echo 'to check unit tests (requires NUnit version >= 2.6), run:'
|
||||
@echo ' make nunit [NUNIT_CONSOLE=<path-to/nunit[3|2]-console>] [NUNIT_LIBS_PATH=<path-to-libs-dir>] [NUNIT_LIBS=<nunit-libs>]'
|
||||
@echo ' Use NUNIT_CONSOLE if nunit[3|2]-console was not downloaded by `make dependencies` nor is it in bin search paths'
|
||||
@echo ' Use NUNIT_LIBS_PATH if NUnit libs are not in search paths. Include trailing /'
|
||||
@echo ' Use NUNIT_LIBS if NUnit libs have different names (such as including a prefix or suffix)'
|
||||
@echo
|
||||
@echo 'to check the official mods for erroneous yaml files, run:'
|
||||
@echo ' make test'
|
||||
@echo
|
||||
@echo 'to check the engine and official mod dlls for code style violations, run:'
|
||||
@echo ' make test'
|
||||
@echo 'to generate documentation aimed at modders, run:'
|
||||
@echo ' make docs'
|
||||
@echo
|
||||
@echo 'to compile and install Red Alert, Tiberian Dawn, and Dune 2000 run:'
|
||||
@echo ' make [prefix=/foo] [TARGETPLATFORM=unix-generic] install'
|
||||
@echo 'to install, run:'
|
||||
@echo ' make [prefix=/foo] [bindir=/bar/bin] install'
|
||||
@echo
|
||||
@echo 'to compile and install Red Alert, Tiberian Dawn, and Dune 2000'
|
||||
@echo 'using system libraries for native dependencies, run:'
|
||||
@echo ' make [prefix=/foo] [bindir=/bar/bin] TARGETPLATFORM=unix-generic install'
|
||||
@echo 'to install with development tools, run:'
|
||||
@echo ' make [prefix=/foo] [bindir=/bar/bin] install-all'
|
||||
@echo
|
||||
@echo 'to install Linux startup scripts, desktop files, icons, and MIME metadata'
|
||||
@echo ' make install-linux-shortcuts'
|
||||
@echo 'to install Linux startup scripts, desktop files and icons'
|
||||
@echo ' make install-linux-shortcuts [DEBUG=false]'
|
||||
@echo
|
||||
@echo 'to install Linux AppStream metadata'
|
||||
@echo ' make install-linux-appdata'
|
||||
@echo 'to uninstall, run:'
|
||||
@echo ' make uninstall'
|
||||
@echo
|
||||
@echo 'to start the game, run:'
|
||||
@echo ' openra'
|
||||
|
||||
|
||||
|
||||
|
||||
########################### MAKEFILE SETTINGS ##########################
|
||||
#
|
||||
.DEFAULT_GOAL := all
|
||||
.DEFAULT_GOAL := default
|
||||
|
||||
.SUFFIXES:
|
||||
|
||||
.PHONY: all clean check check-scripts test version install install-linux-shortcuts install-linux-appdata help
|
||||
.PHONY: core tools package all mods clean distclean dependencies version $(PROGRAMS) nunit
|
||||
|
||||
6
OpenRA.Game.exe.config
Normal file
6
OpenRA.Game.exe.config
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<configuration>
|
||||
<runtime>
|
||||
<loadFromRemoteSources enabled="true" />
|
||||
</runtime>
|
||||
</configuration>
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
@@ -9,202 +9,26 @@
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Primitives;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Activities
|
||||
{
|
||||
public enum ActivityState { Queued, Active, Canceling, Done }
|
||||
|
||||
public class TargetLineNode
|
||||
public abstract class Activity
|
||||
{
|
||||
public readonly Target Target;
|
||||
public readonly Color Color;
|
||||
public readonly Sprite Tile;
|
||||
public Activity NextActivity { get; set; }
|
||||
protected bool IsCanceled { get; private set; }
|
||||
|
||||
public TargetLineNode(in Target target, Color color, Sprite tile = null)
|
||||
public abstract Activity Tick(Actor self);
|
||||
|
||||
public virtual void Cancel(Actor self)
|
||||
{
|
||||
// Note: Not all activities are drawable. In that case, pass Target.Invalid as target,
|
||||
// if "yield break" in TargetLineNode(Actor self) is not feasible.
|
||||
Target = target;
|
||||
Color = color;
|
||||
Tile = tile;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Things to be aware of when writing activities:
|
||||
*
|
||||
* - Use "return true" at least once somewhere in the tick method.
|
||||
* - Do not "reuse" activity objects (by queuing them as next or child, for example) that have already started running.
|
||||
* Queue a new instance instead.
|
||||
* - Avoid calling actor.CancelActivity(). It is almost always a bug. Call activity.Cancel() instead.
|
||||
* - Do not evaluate dynamic state (an actor's location, health, conditions, etc.) in the activity's constructor,
|
||||
* as that might change before the activity gets to tick for the first time. Use the OnFirstRun() method instead.
|
||||
*/
|
||||
public abstract class Activity : IActivityInterface
|
||||
{
|
||||
public ActivityState State { get; private set; }
|
||||
|
||||
Activity childActivity;
|
||||
protected Activity ChildActivity
|
||||
{
|
||||
get { return SkipDoneActivities(childActivity); }
|
||||
private set { childActivity = value; }
|
||||
IsCanceled = true;
|
||||
NextActivity = null;
|
||||
}
|
||||
|
||||
Activity nextActivity;
|
||||
public Activity NextActivity
|
||||
{
|
||||
get { return SkipDoneActivities(nextActivity); }
|
||||
private set { nextActivity = value; }
|
||||
}
|
||||
|
||||
internal static Activity SkipDoneActivities(Activity first)
|
||||
{
|
||||
// If first.Cancel() was called while it was queued (i.e. before it first ticked), its state will be Done
|
||||
// rather than Queued (the activity system guarantees that it cannot be Active or Canceling).
|
||||
// An unknown number of ticks may have elapsed between the Cancel() call and now,
|
||||
// so we cannot make any assumptions on the value of first.NextActivity.
|
||||
// We must not return first (ticking it would be bogus), but returning null would potentially
|
||||
// drop valid activities queued after it. Walk the queue until we find a valid activity or
|
||||
// (more likely) run out of activities.
|
||||
while (first != null && first.State == ActivityState.Done)
|
||||
first = first.NextActivity;
|
||||
|
||||
return first;
|
||||
}
|
||||
|
||||
public bool IsInterruptible { get; protected set; }
|
||||
public bool ChildHasPriority { get; protected set; }
|
||||
public bool IsCanceling { get { return State == ActivityState.Canceling; } }
|
||||
bool finishing;
|
||||
bool firstRunCompleted;
|
||||
bool lastRun;
|
||||
|
||||
public Activity()
|
||||
{
|
||||
IsInterruptible = true;
|
||||
ChildHasPriority = true;
|
||||
}
|
||||
|
||||
public Activity TickOuter(Actor self)
|
||||
{
|
||||
if (State == ActivityState.Done)
|
||||
throw new InvalidOperationException("Actor {0} attempted to tick activity {1} after it had already completed.".F(self, GetType()));
|
||||
|
||||
if (State == ActivityState.Queued)
|
||||
{
|
||||
OnFirstRun(self);
|
||||
firstRunCompleted = true;
|
||||
State = ActivityState.Active;
|
||||
}
|
||||
|
||||
if (!firstRunCompleted)
|
||||
throw new InvalidOperationException("Actor {0} attempted to tick activity {1} before running its OnFirstRun method.".F(self, GetType()));
|
||||
|
||||
// Only run the parent tick when the child is done.
|
||||
// We must always let the child finish on its own before continuing.
|
||||
if (ChildHasPriority)
|
||||
{
|
||||
lastRun = TickChild(self) && (finishing || Tick(self));
|
||||
finishing |= lastRun;
|
||||
}
|
||||
|
||||
// The parent determines whether the child gets a chance at ticking.
|
||||
else
|
||||
lastRun = Tick(self);
|
||||
|
||||
// Avoid a single tick delay if the childactivity was just queued.
|
||||
if (ChildActivity != null && ChildActivity.State == ActivityState.Queued)
|
||||
{
|
||||
if (ChildHasPriority)
|
||||
lastRun = TickChild(self) && finishing;
|
||||
else
|
||||
TickChild(self);
|
||||
}
|
||||
|
||||
if (lastRun)
|
||||
{
|
||||
State = ActivityState.Done;
|
||||
OnLastRun(self);
|
||||
return NextActivity;
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
protected bool TickChild(Actor self)
|
||||
{
|
||||
ChildActivity = ActivityUtils.RunActivity(self, ChildActivity);
|
||||
return ChildActivity == null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called every tick to run activity logic. Returns false if the activity should
|
||||
/// remain active, or true if it is complete. Cancelled activities must ensure they
|
||||
/// return the actor to a consistent state before returning true.
|
||||
///
|
||||
/// Child activities can be queued using QueueChild, and these will be ticked
|
||||
/// instead of the parent while they are active. Activities that need to run logic
|
||||
/// in parallel with child activities should set ChildHasPriority to false and
|
||||
/// manually call TickChildren.
|
||||
///
|
||||
/// Queuing one or more child activities and returning true is valid, and causes
|
||||
/// the activity to be completed immediately (without ticking again) once the
|
||||
/// children have completed.
|
||||
/// </summary>
|
||||
public virtual bool Tick(Actor self)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runs once immediately before the first Tick() execution.
|
||||
/// </summary>
|
||||
protected virtual void OnFirstRun(Actor self) { }
|
||||
|
||||
/// <summary>
|
||||
/// Runs once immediately after the last Tick() execution.
|
||||
/// </summary>
|
||||
protected virtual void OnLastRun(Actor self) { }
|
||||
|
||||
/// <summary>
|
||||
/// Runs once on Actor.Dispose() (through OnActorDisposeOuter) and can be used to perform activity clean-up on actor death/disposal,
|
||||
/// for example by force-triggering OnLastRun (which would otherwise be skipped).
|
||||
/// </summary>
|
||||
protected virtual void OnActorDispose(Actor self) { }
|
||||
|
||||
/// <summary>
|
||||
/// Runs once on Actor.Dispose().
|
||||
/// Main purpose is to ensure ChildActivity.OnActorDispose runs as well (which isn't otherwise accessible due to protection level).
|
||||
/// </summary>
|
||||
internal void OnActorDisposeOuter(Actor self)
|
||||
{
|
||||
ChildActivity?.OnActorDisposeOuter(self);
|
||||
|
||||
OnActorDispose(self);
|
||||
}
|
||||
|
||||
public virtual void Cancel(Actor self, bool keepQueue = false)
|
||||
{
|
||||
if (!keepQueue)
|
||||
NextActivity = null;
|
||||
|
||||
if (!IsInterruptible)
|
||||
return;
|
||||
|
||||
ChildActivity?.Cancel(self);
|
||||
|
||||
// Directly mark activities that are queued and therefore didn't run yet as done
|
||||
State = State == ActivityState.Queued ? ActivityState.Done : ActivityState.Canceling;
|
||||
}
|
||||
|
||||
public void Queue(Activity activity)
|
||||
public virtual void Queue(Activity activity)
|
||||
{
|
||||
if (NextActivity != null)
|
||||
NextActivity.Queue(activity);
|
||||
@@ -212,73 +36,20 @@ namespace OpenRA.Activities
|
||||
NextActivity = activity;
|
||||
}
|
||||
|
||||
public void QueueChild(Activity activity)
|
||||
{
|
||||
if (ChildActivity != null)
|
||||
ChildActivity.Queue(activity);
|
||||
else
|
||||
ChildActivity = activity;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prints the activity tree, starting from the top or optionally from a given origin.
|
||||
///
|
||||
/// Call this method from any place that's called during a tick, such as the Tick() method itself or
|
||||
/// the Before(First|Last)Run() methods. The origin activity will be marked in the output.
|
||||
/// </summary>
|
||||
/// <param name="self">The actor performing this activity.</param>
|
||||
/// <param name="origin">Activity from which to start traversing, and which to mark. If null, mark the calling activity, and start traversal from the top.</param>
|
||||
/// <param name="level">Initial level of indentation.</param>
|
||||
protected void PrintActivityTree(Actor self, Activity origin = null, int level = 0)
|
||||
{
|
||||
if (origin == null)
|
||||
self.CurrentActivity.PrintActivityTree(self, this);
|
||||
else
|
||||
{
|
||||
Console.Write(new string(' ', level * 2));
|
||||
if (origin == this)
|
||||
Console.Write("*");
|
||||
|
||||
Console.WriteLine(GetType().ToString().Split('.').Last());
|
||||
|
||||
ChildActivity?.PrintActivityTree(self, origin, level + 1);
|
||||
|
||||
NextActivity?.PrintActivityTree(self, origin, level);
|
||||
}
|
||||
}
|
||||
|
||||
public virtual IEnumerable<Target> GetTargets(Actor self)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual IEnumerable<TargetLineNode> TargetLineNodes(Actor self)
|
||||
public static class ActivityExts
|
||||
{
|
||||
public static IEnumerable<Target> GetTargetQueue(this Actor self)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
public IEnumerable<string> DebugLabelComponents()
|
||||
{
|
||||
var act = this;
|
||||
while (act != null)
|
||||
{
|
||||
yield return act.GetType().Name;
|
||||
act = act.ChildActivity;
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<T> ActivitiesImplementing<T>(bool includeChildren = true) where T : IActivityInterface
|
||||
{
|
||||
if (includeChildren && ChildActivity != null)
|
||||
foreach (var a in ChildActivity.ActivitiesImplementing<T>())
|
||||
yield return a;
|
||||
|
||||
if (this is T)
|
||||
yield return (T)(object)this;
|
||||
|
||||
if (NextActivity != null)
|
||||
foreach (var a in NextActivity.ActivitiesImplementing<T>())
|
||||
yield return a;
|
||||
return self.GetCurrentActivity()
|
||||
.Iterate(u => u.NextActivity)
|
||||
.TakeWhile(u => u != null)
|
||||
.SelectMany(u => u.GetTargets(self));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
@@ -16,18 +16,25 @@ namespace OpenRA.Activities
|
||||
public class CallFunc : Activity
|
||||
{
|
||||
public CallFunc(Action a) { this.a = a; }
|
||||
public CallFunc(Action a, bool interruptible)
|
||||
public CallFunc(Action a, bool interruptable)
|
||||
{
|
||||
this.a = a;
|
||||
IsInterruptible = interruptible;
|
||||
this.interruptable = interruptable;
|
||||
}
|
||||
|
||||
Action a;
|
||||
bool interruptable;
|
||||
|
||||
public override bool Tick(Actor self)
|
||||
public override Activity Tick(Actor self)
|
||||
{
|
||||
a?.Invoke();
|
||||
return true;
|
||||
if (a != null) a();
|
||||
return NextActivity;
|
||||
}
|
||||
|
||||
public override void Cancel(Actor self)
|
||||
{
|
||||
if (interruptable)
|
||||
base.Cancel(self);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using Eluant;
|
||||
using Eluant.ObjectBinding;
|
||||
@@ -28,9 +28,8 @@ namespace OpenRA
|
||||
internal struct SyncHash
|
||||
{
|
||||
public readonly ISync Trait;
|
||||
readonly Func<object, int> hashFunction;
|
||||
public SyncHash(ISync trait) { Trait = trait; hashFunction = Sync.GetHashFunction(trait); }
|
||||
public int Hash() { return hashFunction(Trait); }
|
||||
public readonly int Hash;
|
||||
public SyncHash(ISync trait, int hash) { Trait = trait; Hash = hash; }
|
||||
}
|
||||
|
||||
public readonly ActorInfo Info;
|
||||
@@ -39,27 +38,23 @@ namespace OpenRA
|
||||
|
||||
public readonly uint ActorID;
|
||||
|
||||
public Player Owner { get; internal set; }
|
||||
public Player Owner { get; set; }
|
||||
|
||||
public bool IsInWorld { get; internal set; }
|
||||
public bool WillDispose { get; private set; }
|
||||
public bool Disposed { get; private set; }
|
||||
|
||||
Activity currentActivity;
|
||||
public Activity CurrentActivity
|
||||
{
|
||||
get { return Activity.SkipDoneActivities(currentActivity); }
|
||||
private set { currentActivity = value; }
|
||||
}
|
||||
|
||||
public Group Group;
|
||||
public int Generation;
|
||||
public Actor ReplacedByActor;
|
||||
|
||||
public Rectangle Bounds { get; private set; }
|
||||
public Rectangle VisualBounds { get; private set; }
|
||||
public IEffectiveOwner EffectiveOwner { get; private set; }
|
||||
public IOccupySpace OccupiesSpace { get; private set; }
|
||||
public ITargetable[] Targetables { get; private set; }
|
||||
|
||||
public bool IsIdle { get { return CurrentActivity == null; } }
|
||||
public bool IsIdle { get { return currentActivity == null; } }
|
||||
public bool IsDead { get { return Disposed || (health != null && health.IsDead); } }
|
||||
|
||||
public CPos Location { get { return OccupiesSpace.TopLeft; } }
|
||||
@@ -69,69 +64,30 @@ namespace OpenRA
|
||||
{
|
||||
get
|
||||
{
|
||||
return facing != null ? facing.Orientation : WRot.None;
|
||||
// TODO: Support non-zero pitch/roll in IFacing (IOrientation?)
|
||||
var facingValue = facing != null ? facing.Facing : 0;
|
||||
return new WRot(WAngle.Zero, WAngle.Zero, WAngle.FromFacing(facingValue));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Value used to represent an invalid token.</summary>
|
||||
public static readonly int InvalidConditionToken = -1;
|
||||
|
||||
class ConditionState
|
||||
{
|
||||
/// <summary>Delegates that have registered to be notified when this condition changes.</summary>
|
||||
public readonly List<VariableObserverNotifier> Notifiers = new List<VariableObserverNotifier>();
|
||||
|
||||
/// <summary>Unique integers identifying granted instances of the condition.</summary>
|
||||
public readonly HashSet<int> Tokens = new HashSet<int>();
|
||||
}
|
||||
|
||||
readonly Dictionary<string, ConditionState> conditionStates = new Dictionary<string, ConditionState>();
|
||||
|
||||
/// <summary>Each granted condition receives a unique token that is used when revoking.</summary>
|
||||
readonly Dictionary<int, string> conditionTokens = new Dictionary<int, string>();
|
||||
|
||||
int nextConditionToken = 1;
|
||||
|
||||
/// <summary>Cache of condition -> enabled state for quick evaluation of token counter conditions.</summary>
|
||||
readonly Dictionary<string, int> conditionCache = new Dictionary<string, int>();
|
||||
|
||||
/// <summary>Read-only version of conditionCache that is passed to IConditionConsumers.</summary>
|
||||
readonly IReadOnlyDictionary<string, int> readOnlyConditionCache;
|
||||
|
||||
internal SyncHash[] SyncHashes { get; private set; }
|
||||
internal IEnumerable<SyncHash> SyncHashes { get; private set; }
|
||||
|
||||
readonly IFacing facing;
|
||||
readonly IHealth health;
|
||||
readonly IResolveOrder[] resolveOrders;
|
||||
readonly IRenderModifier[] renderModifiers;
|
||||
readonly IRender[] renders;
|
||||
readonly IMouseBounds[] mouseBounds;
|
||||
readonly IDisable[] disables;
|
||||
readonly IVisibilityModifier[] visibilityModifiers;
|
||||
readonly IDefaultVisibility defaultVisibility;
|
||||
readonly INotifyBecomingIdle[] becomingIdles;
|
||||
readonly INotifyIdle[] tickIdles;
|
||||
readonly IEnumerable<ITargetablePositions> enabledTargetablePositions;
|
||||
WPos[] staticTargetablePositions;
|
||||
bool created;
|
||||
bool setStaticTargetablePositions;
|
||||
|
||||
internal Actor(World world, string name, TypeDictionary initDict)
|
||||
{
|
||||
var duplicateInit = initDict.WithInterface<ISingleInstanceInit>().GroupBy(i => i.GetType())
|
||||
.FirstOrDefault(i => i.Count() > 1);
|
||||
|
||||
if (duplicateInit != null)
|
||||
throw new InvalidDataException("Duplicate initializer '{0}'".F(duplicateInit.Key.Name));
|
||||
|
||||
var init = new ActorInitializer(this, initDict);
|
||||
|
||||
readOnlyConditionCache = new ReadOnlyDictionary<string, int>(conditionCache);
|
||||
|
||||
World = world;
|
||||
ActorID = world.NextAID();
|
||||
var ownerInit = init.GetOrDefault<OwnerInit>();
|
||||
if (ownerInit != null)
|
||||
Owner = ownerInit.Value(world);
|
||||
if (initDict.Contains<OwnerInit>())
|
||||
Owner = init.Get<OwnerInit, Player>();
|
||||
|
||||
if (name != null)
|
||||
{
|
||||
@@ -141,145 +97,75 @@ namespace OpenRA
|
||||
throw new NotImplementedException("No rules definition for unit " + name);
|
||||
|
||||
Info = world.Map.Rules.Actors[name];
|
||||
|
||||
IPositionable positionable = null;
|
||||
var resolveOrdersList = new List<IResolveOrder>();
|
||||
var renderModifiersList = new List<IRenderModifier>();
|
||||
var rendersList = new List<IRender>();
|
||||
var mouseBoundsList = new List<IMouseBounds>();
|
||||
var visibilityModifiersList = new List<IVisibilityModifier>();
|
||||
var becomingIdlesList = new List<INotifyBecomingIdle>();
|
||||
var tickIdlesList = new List<INotifyIdle>();
|
||||
var targetablesList = new List<ITargetable>();
|
||||
var targetablePositionsList = new List<ITargetablePositions>();
|
||||
var syncHashesList = new List<SyncHash>();
|
||||
|
||||
foreach (var traitInfo in Info.TraitsInConstructOrder())
|
||||
foreach (var trait in Info.TraitsInConstructOrder())
|
||||
{
|
||||
var trait = traitInfo.Create(init);
|
||||
AddTrait(trait);
|
||||
AddTrait(trait.Create(init));
|
||||
|
||||
// PERF: Cache all these traits as soon as the actor is created. This is a fairly cheap one-off cost per
|
||||
// actor that allows us to provide some fast implementations of commonly used methods that are relied on by
|
||||
// performance-sensitive parts of the core game engine, such as pathfinding, visibility and rendering.
|
||||
// Note: The blocks are required to limit the scope of the t's, so we make an exception to our normal style
|
||||
// rules for spacing in order to keep these assignments compact and readable.
|
||||
{ if (trait is IPositionable t) positionable = t; }
|
||||
{ if (trait is IOccupySpace t) OccupiesSpace = t; }
|
||||
{ if (trait is IEffectiveOwner t) EffectiveOwner = t; }
|
||||
{ if (trait is IFacing t) facing = t; }
|
||||
{ if (trait is IHealth t) health = t; }
|
||||
{ if (trait is IResolveOrder t) resolveOrdersList.Add(t); }
|
||||
{ if (trait is IRenderModifier t) renderModifiersList.Add(t); }
|
||||
{ if (trait is IRender t) rendersList.Add(t); }
|
||||
{ if (trait is IMouseBounds t) mouseBoundsList.Add(t); }
|
||||
{ if (trait is IVisibilityModifier t) visibilityModifiersList.Add(t); }
|
||||
{ if (trait is IDefaultVisibility t) defaultVisibility = t; }
|
||||
{ if (trait is INotifyBecomingIdle t) becomingIdlesList.Add(t); }
|
||||
{ if (trait is INotifyIdle t) tickIdlesList.Add(t); }
|
||||
{ if (trait is ITargetable t) targetablesList.Add(t); }
|
||||
{ if (trait is ITargetablePositions t) targetablePositionsList.Add(t); }
|
||||
{ if (trait is ISync t) syncHashesList.Add(new SyncHash(t)); }
|
||||
// Some traits rely on properties provided by IOccupySpace in their initialization,
|
||||
// so we must ready it now, we cannot wait until all traits have finished construction.
|
||||
if (trait is IOccupySpaceInfo)
|
||||
OccupiesSpace = Trait<IOccupySpace>();
|
||||
}
|
||||
|
||||
resolveOrders = resolveOrdersList.ToArray();
|
||||
renderModifiers = renderModifiersList.ToArray();
|
||||
renders = rendersList.ToArray();
|
||||
mouseBounds = mouseBoundsList.ToArray();
|
||||
visibilityModifiers = visibilityModifiersList.ToArray();
|
||||
becomingIdles = becomingIdlesList.ToArray();
|
||||
tickIdles = tickIdlesList.ToArray();
|
||||
Targetables = targetablesList.ToArray();
|
||||
var targetablePositions = targetablePositionsList.ToArray();
|
||||
enabledTargetablePositions = targetablePositions.Where(Exts.IsTraitEnabled);
|
||||
SyncHashes = syncHashesList.ToArray();
|
||||
|
||||
setStaticTargetablePositions = positionable == null && targetablePositions.Any() && targetablePositions.All(tp => tp.AlwaysEnabled);
|
||||
}
|
||||
|
||||
// PERF: Cache all these traits as soon as the actor is created. This is a fairly cheap one-off cost per
|
||||
// actor that allows us to provide some fast implementations of commonly used methods that are relied on by
|
||||
// performance-sensitive parts of the core game engine, such as pathfinding, visibility and rendering.
|
||||
Bounds = DetermineBounds();
|
||||
VisualBounds = DetermineVisualBounds();
|
||||
EffectiveOwner = TraitOrDefault<IEffectiveOwner>();
|
||||
facing = TraitOrDefault<IFacing>();
|
||||
health = TraitOrDefault<IHealth>();
|
||||
renderModifiers = TraitsImplementing<IRenderModifier>().ToArray();
|
||||
renders = TraitsImplementing<IRender>().ToArray();
|
||||
disables = TraitsImplementing<IDisable>().ToArray();
|
||||
visibilityModifiers = TraitsImplementing<IVisibilityModifier>().ToArray();
|
||||
defaultVisibility = Trait<IDefaultVisibility>();
|
||||
Targetables = TraitsImplementing<ITargetable>().ToArray();
|
||||
|
||||
SyncHashes =
|
||||
TraitsImplementing<ISync>()
|
||||
.Select(sync => Pair.New(sync, Sync.GetHashFunction(sync)))
|
||||
.ToArray()
|
||||
.Select(pair => new SyncHash(pair.First, pair.Second(pair.First)));
|
||||
}
|
||||
|
||||
internal void Initialize(bool addToWorld = true)
|
||||
Rectangle DetermineBounds()
|
||||
{
|
||||
created = true;
|
||||
var si = Info.TraitInfoOrDefault<SelectableInfo>();
|
||||
var size = (si != null && si.Bounds != null) ? new int2(si.Bounds[0], si.Bounds[1]) :
|
||||
TraitsImplementing<IAutoSelectionSize>().Select(x => x.SelectionSize(this)).FirstOrDefault();
|
||||
|
||||
// Make sure traits are usable for condition notifiers
|
||||
foreach (var t in TraitsImplementing<INotifyCreated>())
|
||||
t.Created(this);
|
||||
var offset = -size / 2;
|
||||
if (si != null && si.Bounds != null && si.Bounds.Length > 2)
|
||||
offset += new int2(si.Bounds[2], si.Bounds[3]);
|
||||
|
||||
var allObserverNotifiers = new HashSet<VariableObserverNotifier>();
|
||||
foreach (var provider in TraitsImplementing<IObservesVariables>())
|
||||
{
|
||||
foreach (var variableUser in provider.GetVariableObservers())
|
||||
{
|
||||
allObserverNotifiers.Add(variableUser.Notifier);
|
||||
foreach (var variable in variableUser.Variables)
|
||||
{
|
||||
var cs = conditionStates.GetOrAdd(variable);
|
||||
cs.Notifiers.Add(variableUser.Notifier);
|
||||
return new Rectangle(offset.X, offset.Y, size.X, size.Y);
|
||||
}
|
||||
|
||||
// Initialize conditions that have not yet been granted to 0
|
||||
// NOTE: Some conditions may have already been granted by INotifyCreated calling GrantCondition,
|
||||
// and we choose to assign the token count to safely cover both cases instead of adding an if branch.
|
||||
conditionCache[variable] = cs.Tokens.Count;
|
||||
}
|
||||
}
|
||||
}
|
||||
Rectangle DetermineVisualBounds()
|
||||
{
|
||||
var sd = Info.TraitInfoOrDefault<ISelectionDecorationsInfo>();
|
||||
if (sd == null || sd.SelectionBoxBounds == null)
|
||||
return Bounds;
|
||||
|
||||
// Update all traits with their initial condition state
|
||||
foreach (var notify in allObserverNotifiers)
|
||||
notify(this, readOnlyConditionCache);
|
||||
var size = new int2(sd.SelectionBoxBounds[0], sd.SelectionBoxBounds[1]);
|
||||
|
||||
// All actors that can move or teleport should have IPositionable, if not it's pretty safe to assume the actor is completely immobile and
|
||||
// all targetable positions can be cached if all ITargetablePositions have no conditional requirements.
|
||||
if (setStaticTargetablePositions)
|
||||
staticTargetablePositions = enabledTargetablePositions.SelectMany(tp => tp.TargetablePositions(this)).ToArray();
|
||||
var offset = -size / 2;
|
||||
if (sd.SelectionBoxBounds.Length > 2)
|
||||
offset += new int2(sd.SelectionBoxBounds[2], sd.SelectionBoxBounds[3]);
|
||||
|
||||
// TODO: Other traits may need initialization after being notified of initial condition state.
|
||||
|
||||
// TODO: A post condition initialization notification phase may allow queueing activities instead.
|
||||
// The initial activity should run before any activities queued by INotifyCreated.Created
|
||||
// However, we need to know which traits are enabled (via conditions), so wait for after the calls and insert the activity as the first
|
||||
ICreationActivity creationActivity = null;
|
||||
foreach (var ica in TraitsImplementing<ICreationActivity>())
|
||||
{
|
||||
if (!ica.IsTraitEnabled())
|
||||
continue;
|
||||
|
||||
if (creationActivity != null)
|
||||
throw new InvalidOperationException("More than one enabled ICreationActivity trait: {0} and {1}".F(creationActivity.GetType().Name, ica.GetType().Name));
|
||||
|
||||
var activity = ica.GetCreationActivity();
|
||||
if (activity == null)
|
||||
continue;
|
||||
|
||||
creationActivity = ica;
|
||||
|
||||
activity.Queue(CurrentActivity);
|
||||
CurrentActivity = activity;
|
||||
}
|
||||
|
||||
if (addToWorld)
|
||||
World.Add(this);
|
||||
return new Rectangle(offset.X, offset.Y, size.X, size.Y);
|
||||
}
|
||||
|
||||
public void Tick()
|
||||
{
|
||||
var wasIdle = IsIdle;
|
||||
CurrentActivity = ActivityUtils.RunActivity(this, CurrentActivity);
|
||||
currentActivity = ActivityUtils.RunActivity(this, currentActivity);
|
||||
|
||||
if (!wasIdle && IsIdle)
|
||||
{
|
||||
foreach (var n in becomingIdles)
|
||||
foreach (var n in TraitsImplementing<INotifyBecomingIdle>())
|
||||
n.OnBecomingIdle(this);
|
||||
|
||||
// If IsIdle is true, it means the last CurrentActivity.Tick returned null.
|
||||
// If a next activity has been queued via OnBecomingIdle, we need to start running it now,
|
||||
// to avoid an 'empty' null tick where the actor will (visibly, if moving) do nothing.
|
||||
CurrentActivity = ActivityUtils.RunActivity(this, CurrentActivity);
|
||||
}
|
||||
else if (wasIdle)
|
||||
foreach (var tickIdle in tickIdles)
|
||||
tickIdle.TickIdle(this);
|
||||
}
|
||||
|
||||
public IEnumerable<IRenderable> Render(WorldRenderer wr)
|
||||
@@ -305,57 +191,30 @@ namespace OpenRA
|
||||
yield return renderable;
|
||||
}
|
||||
|
||||
public IEnumerable<Rectangle> ScreenBounds(WorldRenderer wr)
|
||||
{
|
||||
var bounds = Bounds(wr);
|
||||
foreach (var modifier in renderModifiers)
|
||||
bounds = modifier.ModifyScreenBounds(this, wr, bounds);
|
||||
return bounds;
|
||||
}
|
||||
|
||||
IEnumerable<Rectangle> Bounds(WorldRenderer wr)
|
||||
{
|
||||
// PERF: Avoid LINQ. See comments for Renderables
|
||||
foreach (var render in renders)
|
||||
foreach (var r in render.ScreenBounds(this, wr))
|
||||
if (!r.IsEmpty)
|
||||
yield return r;
|
||||
}
|
||||
|
||||
public Polygon MouseBounds(WorldRenderer wr)
|
||||
{
|
||||
foreach (var mb in mouseBounds)
|
||||
{
|
||||
var bounds = mb.MouseoverBounds(this, wr);
|
||||
if (!bounds.IsEmpty)
|
||||
return bounds;
|
||||
}
|
||||
|
||||
return Polygon.Empty;
|
||||
}
|
||||
|
||||
public void QueueActivity(bool queued, Activity nextActivity)
|
||||
{
|
||||
if (!queued)
|
||||
CancelActivity();
|
||||
|
||||
QueueActivity(nextActivity);
|
||||
}
|
||||
|
||||
public void QueueActivity(Activity nextActivity)
|
||||
{
|
||||
if (!created)
|
||||
throw new InvalidOperationException("An activity was queued before the actor was created. Queue it inside the INotifyCreated.Created callback instead.");
|
||||
|
||||
if (CurrentActivity == null)
|
||||
CurrentActivity = nextActivity;
|
||||
if (currentActivity == null)
|
||||
currentActivity = nextActivity;
|
||||
else
|
||||
CurrentActivity.Queue(nextActivity);
|
||||
currentActivity.Queue(nextActivity);
|
||||
}
|
||||
|
||||
public void CancelActivity()
|
||||
{
|
||||
CurrentActivity?.Cancel(this);
|
||||
if (currentActivity != null)
|
||||
currentActivity.Cancel(this);
|
||||
}
|
||||
|
||||
public Activity GetCurrentActivity()
|
||||
{
|
||||
return currentActivity;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
@@ -405,13 +264,6 @@ namespace OpenRA
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// If CurrentActivity isn't null, run OnActorDisposeOuter in case some cleanups are needed.
|
||||
// This should be done before the FrameEndTask to avoid dependency issues.
|
||||
CurrentActivity?.OnActorDisposeOuter(this);
|
||||
|
||||
// Allow traits/activities to prevent a race condition when they depend on disposing the actor (e.g. Transforms)
|
||||
WillDispose = true;
|
||||
|
||||
World.AddFrameEndTask(w =>
|
||||
{
|
||||
if (Disposed)
|
||||
@@ -426,49 +278,35 @@ namespace OpenRA
|
||||
World.TraitDict.RemoveActor(this);
|
||||
Disposed = true;
|
||||
|
||||
luaInterface?.Value.OnActorDestroyed();
|
||||
if (luaInterface != null)
|
||||
luaInterface.Value.OnActorDestroyed();
|
||||
});
|
||||
}
|
||||
|
||||
public void ResolveOrder(Order order)
|
||||
{
|
||||
foreach (var r in resolveOrders)
|
||||
r.ResolveOrder(this, order);
|
||||
}
|
||||
|
||||
// TODO: move elsewhere.
|
||||
public void ChangeOwner(Player newOwner)
|
||||
{
|
||||
World.AddFrameEndTask(_ => ChangeOwnerSync(newOwner));
|
||||
}
|
||||
World.AddFrameEndTask(w =>
|
||||
{
|
||||
if (Disposed)
|
||||
return;
|
||||
|
||||
/// <summary>
|
||||
/// Change the actors owner without queuing a FrameEndTask.
|
||||
/// This must only be called from inside an existing FrameEndTask.
|
||||
/// </summary>
|
||||
public void ChangeOwnerSync(Player newOwner)
|
||||
{
|
||||
if (Disposed)
|
||||
return;
|
||||
var oldOwner = Owner;
|
||||
var wasInWorld = IsInWorld;
|
||||
|
||||
var oldOwner = Owner;
|
||||
var wasInWorld = IsInWorld;
|
||||
// momentarily remove from world so the ownership queries don't get confused
|
||||
if (wasInWorld)
|
||||
w.Remove(this);
|
||||
|
||||
// momentarily remove from world so the ownership queries don't get confused
|
||||
if (wasInWorld)
|
||||
World.Remove(this);
|
||||
Owner = newOwner;
|
||||
Generation++;
|
||||
|
||||
Owner = newOwner;
|
||||
Generation++;
|
||||
if (wasInWorld)
|
||||
w.Add(this);
|
||||
|
||||
foreach (var t in TraitsImplementing<INotifyOwnerChanged>())
|
||||
t.OnOwnerChanged(this, oldOwner, newOwner);
|
||||
|
||||
foreach (var t in World.WorldActor.TraitsImplementing<INotifyOwnerChanged>())
|
||||
t.OnOwnerChanged(this, oldOwner, newOwner);
|
||||
|
||||
if (wasInWorld)
|
||||
World.Add(this);
|
||||
foreach (var t in TraitsImplementing<INotifyOwnerChanged>())
|
||||
t.OnOwnerChanged(this, oldOwner, newOwner);
|
||||
});
|
||||
}
|
||||
|
||||
public DamageState GetDamageState()
|
||||
@@ -479,20 +317,29 @@ namespace OpenRA
|
||||
return (health == null) ? DamageState.Undamaged : health.DamageState;
|
||||
}
|
||||
|
||||
public void InflictDamage(Actor attacker, Damage damage)
|
||||
public void InflictDamage(Actor attacker, int damage, IWarhead warhead)
|
||||
{
|
||||
if (Disposed || health == null)
|
||||
return;
|
||||
|
||||
health.InflictDamage(this, attacker, damage, false);
|
||||
health.InflictDamage(this, attacker, damage, warhead, false);
|
||||
}
|
||||
|
||||
public void Kill(Actor attacker, BitSet<DamageType> damageTypes = default(BitSet<DamageType>))
|
||||
public void Kill(Actor attacker)
|
||||
{
|
||||
if (Disposed || health == null)
|
||||
return;
|
||||
|
||||
health.Kill(this, attacker, damageTypes);
|
||||
health.Kill(this, attacker);
|
||||
}
|
||||
|
||||
public bool IsDisabled()
|
||||
{
|
||||
// PERF: Avoid LINQ.
|
||||
foreach (var disable in disables)
|
||||
if (disable.Disabled)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool CanBeViewedByPlayer(Player player)
|
||||
@@ -505,23 +352,21 @@ namespace OpenRA
|
||||
return defaultVisibility.IsVisible(this, player);
|
||||
}
|
||||
|
||||
public BitSet<TargetableType> GetAllTargetTypes()
|
||||
public IEnumerable<string> GetAllTargetTypes()
|
||||
{
|
||||
// PERF: Avoid LINQ.
|
||||
var targetTypes = default(BitSet<TargetableType>);
|
||||
foreach (var targetable in Targetables)
|
||||
targetTypes = targetTypes.Union(targetable.TargetTypes);
|
||||
return targetTypes;
|
||||
foreach (var targetType in targetable.TargetTypes)
|
||||
yield return targetType;
|
||||
}
|
||||
|
||||
public BitSet<TargetableType> GetEnabledTargetTypes()
|
||||
public IEnumerable<string> GetEnabledTargetTypes()
|
||||
{
|
||||
// PERF: Avoid LINQ.
|
||||
var targetTypes = default(BitSet<TargetableType>);
|
||||
foreach (var targetable in Targetables)
|
||||
if (targetable.IsTraitEnabled())
|
||||
targetTypes = targetTypes.Union(targetable.TargetTypes);
|
||||
return targetTypes;
|
||||
foreach (var targetType in targetable.TargetTypes)
|
||||
yield return targetType;
|
||||
}
|
||||
|
||||
public bool IsTargetableBy(Actor byActor)
|
||||
@@ -534,76 +379,6 @@ namespace OpenRA
|
||||
return false;
|
||||
}
|
||||
|
||||
public IEnumerable<WPos> GetTargetablePositions()
|
||||
{
|
||||
if (staticTargetablePositions != null)
|
||||
return staticTargetablePositions;
|
||||
|
||||
if (enabledTargetablePositions.Any())
|
||||
return enabledTargetablePositions.SelectMany(tp => tp.TargetablePositions(this));
|
||||
|
||||
return new[] { CenterPosition };
|
||||
}
|
||||
|
||||
#region Conditions
|
||||
|
||||
void UpdateConditionState(string condition, int token, bool isRevoke)
|
||||
{
|
||||
ConditionState conditionState = conditionStates.GetOrAdd(condition);
|
||||
|
||||
if (isRevoke)
|
||||
conditionState.Tokens.Remove(token);
|
||||
else
|
||||
conditionState.Tokens.Add(token);
|
||||
|
||||
conditionCache[condition] = conditionState.Tokens.Count;
|
||||
|
||||
// Conditions may be granted or revoked before the state is initialized.
|
||||
// These notifications will be processed after INotifyCreated.Created.
|
||||
if (created)
|
||||
foreach (var notify in conditionState.Notifiers)
|
||||
notify(this, readOnlyConditionCache);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Grants a specified condition if it is valid.
|
||||
/// Otherwise, just returns InvalidConditionToken.
|
||||
/// </summary>
|
||||
/// <returns>The token that is used to revoke this condition.</returns>
|
||||
public int GrantCondition(string condition)
|
||||
{
|
||||
if (string.IsNullOrEmpty(condition))
|
||||
return InvalidConditionToken;
|
||||
|
||||
var token = nextConditionToken++;
|
||||
conditionTokens.Add(token, condition);
|
||||
UpdateConditionState(condition, token, false);
|
||||
return token;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Revokes a previously granted condition.
|
||||
/// </summary>
|
||||
/// <param name="token">The token ID returned by GrantCondition.</param>
|
||||
/// <returns>The invalid token ID.</returns>
|
||||
public int RevokeCondition(int token)
|
||||
{
|
||||
if (!conditionTokens.TryGetValue(token, out var condition))
|
||||
throw new InvalidOperationException("Attempting to revoke condition with invalid token {0} for {1}.".F(token, this));
|
||||
|
||||
conditionTokens.Remove(token);
|
||||
UpdateConditionState(condition, token, true);
|
||||
return InvalidConditionToken;
|
||||
}
|
||||
|
||||
/// <summary>Returns whether the specified token is valid for RevokeCondition</summary>
|
||||
public bool TokenValid(int token)
|
||||
{
|
||||
return conditionTokens.ContainsKey(token);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Scripting interface
|
||||
|
||||
Lazy<ScriptActorInterface> luaInterface;
|
||||
@@ -621,7 +396,8 @@ namespace OpenRA
|
||||
|
||||
public LuaValue Equals(LuaRuntime runtime, LuaValue left, LuaValue right)
|
||||
{
|
||||
if (!left.TryGetClrValue(out Actor a) || !right.TryGetClrValue(out Actor b))
|
||||
Actor a, b;
|
||||
if (!left.TryGetClrValue(out a) || !right.TryGetClrValue(out b))
|
||||
return false;
|
||||
|
||||
return a == b;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
@@ -18,44 +18,27 @@ namespace OpenRA
|
||||
{
|
||||
public struct CPos : IScriptBindable, ILuaAdditionBinding, ILuaSubtractionBinding, ILuaEqualityBinding, ILuaTableBinding, IEquatable<CPos>
|
||||
{
|
||||
// Coordinates are packed in a 32 bit signed int
|
||||
// X and Y are 12 bits (signed): -2048...2047
|
||||
// Layer is an unsigned byte
|
||||
// Packing is XXXX XXXX XXXX YYYY YYYY YYYY LLLL LLLL
|
||||
public readonly int Bits;
|
||||
public readonly int X, Y;
|
||||
|
||||
// X is padded to MSB, so bit shift does the correct sign extension
|
||||
public int X { get { return Bits >> 20; } }
|
||||
|
||||
// Align Y with a short, cast, then shift the rest of the way
|
||||
// The signed short bit shift does the correct sign extension
|
||||
public int Y { get { return ((short)(Bits >> 4)) >> 4; } }
|
||||
|
||||
public byte Layer { get { return (byte)Bits; } }
|
||||
|
||||
public CPos(int bits) { Bits = bits; }
|
||||
public CPos(int x, int y)
|
||||
: this(x, y, 0) { }
|
||||
public CPos(int x, int y, byte layer)
|
||||
{
|
||||
Bits = (x & 0xFFF) << 20 | (y & 0xFFF) << 8 | layer;
|
||||
}
|
||||
|
||||
public static readonly CPos Zero = new CPos(0, 0, 0);
|
||||
public CPos(int x, int y) { X = x; Y = y; }
|
||||
public static readonly CPos Zero = new CPos(0, 0);
|
||||
|
||||
public static explicit operator CPos(int2 a) { return new CPos(a.X, a.Y); }
|
||||
|
||||
public static CPos operator +(CVec a, CPos b) { return new CPos(a.X + b.X, a.Y + b.Y, b.Layer); }
|
||||
public static CPos operator +(CPos a, CVec b) { return new CPos(a.X + b.X, a.Y + b.Y, a.Layer); }
|
||||
public static CPos operator -(CPos a, CVec b) { return new CPos(a.X - b.X, a.Y - b.Y, a.Layer); }
|
||||
public static CPos operator +(CVec a, CPos b) { return new CPos(a.X + b.X, a.Y + b.Y); }
|
||||
public static CPos operator +(CPos a, CVec b) { return new CPos(a.X + b.X, a.Y + b.Y); }
|
||||
public static CPos operator -(CPos a, CVec b) { return new CPos(a.X - b.X, a.Y - b.Y); }
|
||||
public static CVec operator -(CPos a, CPos b) { return new CVec(a.X - b.X, a.Y - b.Y); }
|
||||
|
||||
public static bool operator ==(CPos me, CPos other) { return me.Bits == other.Bits; }
|
||||
public static bool operator ==(CPos me, CPos other) { return me.X == other.X && me.Y == other.Y; }
|
||||
public static bool operator !=(CPos me, CPos other) { return !(me == other); }
|
||||
|
||||
public override int GetHashCode() { return Bits.GetHashCode(); }
|
||||
public static CPos Max(CPos a, CPos b) { return new CPos(Math.Max(a.X, b.X), Math.Max(a.Y, b.Y)); }
|
||||
public static CPos Min(CPos a, CPos b) { return new CPos(Math.Min(a.X, b.X), Math.Min(a.Y, b.Y)); }
|
||||
|
||||
public bool Equals(CPos other) { return Bits == other.Bits; }
|
||||
public override int GetHashCode() { return X.GetHashCode() ^ Y.GetHashCode(); }
|
||||
|
||||
public bool Equals(CPos other) { return X == other.X && Y == other.Y; }
|
||||
public override bool Equals(object obj) { return obj is CPos && Equals((CPos)obj); }
|
||||
|
||||
public override string ToString() { return X + "," + Y; }
|
||||
@@ -89,7 +72,9 @@ namespace OpenRA
|
||||
|
||||
public LuaValue Add(LuaRuntime runtime, LuaValue left, LuaValue right)
|
||||
{
|
||||
if (!left.TryGetClrValue(out CPos a) || !right.TryGetClrValue(out CVec b))
|
||||
CPos a;
|
||||
CVec b;
|
||||
if (!left.TryGetClrValue(out a) || !right.TryGetClrValue(out b))
|
||||
throw new LuaException("Attempted to call CPos.Add(CPos, CVec) with invalid arguments ({0}, {1})".F(left.WrappedClrType().Name, right.WrappedClrType().Name));
|
||||
|
||||
return new LuaCustomClrObject(a + b);
|
||||
@@ -97,27 +82,18 @@ namespace OpenRA
|
||||
|
||||
public LuaValue Subtract(LuaRuntime runtime, LuaValue left, LuaValue right)
|
||||
{
|
||||
var rightType = right.WrappedClrType();
|
||||
if (!left.TryGetClrValue(out CPos a))
|
||||
throw new LuaException("Attempted to call CPos.Subtract(CPos, (CPos|CVec)) with invalid arguments ({0}, {1})".F(left.WrappedClrType().Name, rightType.Name));
|
||||
CPos a;
|
||||
CVec b;
|
||||
if (!left.TryGetClrValue(out a) || !right.TryGetClrValue(out b))
|
||||
throw new LuaException("Attempted to call CPos.Subtract(CPos, CVec) with invalid arguments ({0}, {1})".F(left.WrappedClrType().Name, right.WrappedClrType().Name));
|
||||
|
||||
if (rightType == typeof(CPos))
|
||||
{
|
||||
right.TryGetClrValue(out CPos b);
|
||||
return new LuaCustomClrObject(a - b);
|
||||
}
|
||||
else if (rightType == typeof(CVec))
|
||||
{
|
||||
right.TryGetClrValue(out CVec b);
|
||||
return new LuaCustomClrObject(a - b);
|
||||
}
|
||||
|
||||
throw new LuaException("Attempted to call CPos.Subtract(CPos, (CPos|CVec)) with invalid arguments ({0}, {1})".F(left.WrappedClrType().Name, rightType.Name));
|
||||
return new LuaCustomClrObject(a - b);
|
||||
}
|
||||
|
||||
public LuaValue Equals(LuaRuntime runtime, LuaValue left, LuaValue right)
|
||||
{
|
||||
if (!left.TryGetClrValue(out CPos a) || !right.TryGetClrValue(out CPos b))
|
||||
CPos a, b;
|
||||
if (!left.TryGetClrValue(out a) || !right.TryGetClrValue(out b))
|
||||
return false;
|
||||
|
||||
return a == b;
|
||||
@@ -131,7 +107,6 @@ namespace OpenRA
|
||||
{
|
||||
case "X": return X;
|
||||
case "Y": return Y;
|
||||
case "Layer": return Layer;
|
||||
default: throw new LuaException("CPos does not define a member '{0}'".F(key));
|
||||
}
|
||||
}
|
||||
@@ -144,4 +119,4 @@ namespace OpenRA
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
@@ -10,9 +10,9 @@
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using Eluant;
|
||||
using Eluant.ObjectBinding;
|
||||
using OpenRA.Primitives;
|
||||
using OpenRA.Scripting;
|
||||
|
||||
namespace OpenRA
|
||||
@@ -75,7 +75,8 @@ namespace OpenRA
|
||||
|
||||
public LuaValue Add(LuaRuntime runtime, LuaValue left, LuaValue right)
|
||||
{
|
||||
if (!left.TryGetClrValue(out CVec a) || !right.TryGetClrValue(out CVec b))
|
||||
CVec a, b;
|
||||
if (!left.TryGetClrValue(out a) || !right.TryGetClrValue(out b))
|
||||
throw new LuaException("Attempted to call CVec.Add(CVec, CVec) with invalid arguments ({0}, {1})".F(left.WrappedClrType().Name, right.WrappedClrType().Name));
|
||||
|
||||
return new LuaCustomClrObject(a + b);
|
||||
@@ -83,7 +84,8 @@ namespace OpenRA
|
||||
|
||||
public LuaValue Subtract(LuaRuntime runtime, LuaValue left, LuaValue right)
|
||||
{
|
||||
if (!left.TryGetClrValue(out CVec a) || !right.TryGetClrValue(out CVec b))
|
||||
CVec a, b;
|
||||
if (!left.TryGetClrValue(out a) || !right.TryGetClrValue(out b))
|
||||
throw new LuaException("Attempted to call CVec.Subtract(CVec, CVec) with invalid arguments ({0}, {1})".F(left.WrappedClrType().Name, right.WrappedClrType().Name));
|
||||
|
||||
return new LuaCustomClrObject(a - b);
|
||||
@@ -96,7 +98,8 @@ namespace OpenRA
|
||||
|
||||
public LuaValue Equals(LuaRuntime runtime, LuaValue left, LuaValue right)
|
||||
{
|
||||
if (!left.TryGetClrValue(out CVec a) || !right.TryGetClrValue(out CVec b))
|
||||
CVec a, b;
|
||||
if (!left.TryGetClrValue(out a) || !right.TryGetClrValue(out b))
|
||||
return false;
|
||||
|
||||
return a == b;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
@@ -17,4 +17,4 @@ namespace OpenRA
|
||||
void Store(string key, T data);
|
||||
T Retrieve(string key);
|
||||
}
|
||||
}
|
||||
}
|
||||
41
OpenRA.Game/ContentInstaller.cs
Normal file
41
OpenRA.Game/ContentInstaller.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace OpenRA
|
||||
{
|
||||
// Referenced from ModMetadata, so needs to be in OpenRA.Game :(
|
||||
public class ContentInstaller : IGlobalModData
|
||||
{
|
||||
public enum FilenameCase { Input, ForceLower, ForceUpper }
|
||||
|
||||
public readonly string[] TestFiles = { };
|
||||
public readonly string[] DiskTestFiles = { };
|
||||
public readonly string PackageToExtractFromCD = null;
|
||||
public readonly bool OverwriteFiles = true;
|
||||
|
||||
public readonly FilenameCase OutputFilenameCase = FilenameCase.ForceLower;
|
||||
public readonly Dictionary<string, string[]> CopyFilesFromCD = new Dictionary<string, string[]>();
|
||||
public readonly Dictionary<string, string[]> ExtractFilesFromCD = new Dictionary<string, string[]>();
|
||||
|
||||
public readonly string PackageMirrorList = null;
|
||||
|
||||
public readonly string MusicPackageMirrorList = null;
|
||||
public readonly int ShippedSoundtracks = 0;
|
||||
|
||||
/// <summary> InstallShield .CAB file IDs, used to extract Mod-specific files. </summary>
|
||||
public readonly HashSet<int> InstallShieldCABFileIds = new HashSet<int>();
|
||||
|
||||
/// <summary> InstallShield .CAB file IDs, used to extract Mod-specific archives and extract contents of ExtractFilesFromCD. </summary>
|
||||
public readonly HashSet<string> InstallShieldCABFilePackageIds = new HashSet<string>();
|
||||
}
|
||||
}
|
||||
@@ -1,261 +0,0 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace OpenRA
|
||||
{
|
||||
public static class CryptoUtil
|
||||
{
|
||||
// Fixed byte pattern for the OID header
|
||||
static readonly byte[] OIDHeader = { 0x30, 0xD, 0x6, 0x9, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0xD, 0x1, 0x1, 0x1, 0x5, 0x0 };
|
||||
|
||||
public static string PublicKeyFingerprint(RSAParameters parameters)
|
||||
{
|
||||
// Public key fingerprint is defined as the SHA1 of the modulus + exponent bytes
|
||||
return SHA1Hash(parameters.Modulus.Append(parameters.Exponent).ToArray());
|
||||
}
|
||||
|
||||
public static string EncodePEMPublicKey(RSAParameters parameters)
|
||||
{
|
||||
var data = Convert.ToBase64String(EncodePublicKey(parameters));
|
||||
var output = new StringBuilder();
|
||||
output.AppendLine("-----BEGIN PUBLIC KEY-----");
|
||||
for (var i = 0; i < data.Length; i += 64)
|
||||
output.AppendLine(data.Substring(i, Math.Min(64, data.Length - i)));
|
||||
output.Append("-----END PUBLIC KEY-----");
|
||||
|
||||
return output.ToString();
|
||||
}
|
||||
|
||||
public static RSAParameters DecodePEMPublicKey(string key)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Reconstruct original key data
|
||||
var lines = key.Split('\n');
|
||||
var data = Convert.FromBase64String(lines.Skip(1).Take(lines.Length - 2).JoinWith(""));
|
||||
|
||||
// Pull the modulus and exponent bytes out of the ASN.1 tree
|
||||
// Expect this to blow up if the key is not correctly formatted
|
||||
using (var s = new MemoryStream(data))
|
||||
{
|
||||
// SEQUENCE
|
||||
s.ReadByte();
|
||||
ReadTLVLength(s);
|
||||
|
||||
// SEQUENCE -> fixed header junk
|
||||
s.ReadByte();
|
||||
var headerLength = ReadTLVLength(s);
|
||||
s.Position += headerLength;
|
||||
|
||||
// SEQUENCE -> BIT_STRING
|
||||
s.ReadByte();
|
||||
ReadTLVLength(s);
|
||||
s.ReadByte();
|
||||
|
||||
// SEQUENCE -> BIT_STRING -> SEQUENCE
|
||||
s.ReadByte();
|
||||
ReadTLVLength(s);
|
||||
|
||||
// SEQUENCE -> BIT_STRING -> SEQUENCE -> INTEGER (modulus)
|
||||
s.ReadByte();
|
||||
var modulusLength = ReadTLVLength(s);
|
||||
s.ReadByte();
|
||||
var modulus = s.ReadBytes(modulusLength - 1);
|
||||
|
||||
// SEQUENCE -> BIT_STRING -> SEQUENCE -> INTEGER (exponent)
|
||||
s.ReadByte();
|
||||
var exponentLength = ReadTLVLength(s);
|
||||
s.ReadByte();
|
||||
var exponent = s.ReadBytes(exponentLength - 1);
|
||||
|
||||
return new RSAParameters
|
||||
{
|
||||
Modulus = modulus,
|
||||
Exponent = exponent
|
||||
};
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new InvalidDataException("Invalid PEM public key", e);
|
||||
}
|
||||
}
|
||||
|
||||
static byte[] EncodePublicKey(RSAParameters parameters)
|
||||
{
|
||||
using (var stream = new MemoryStream())
|
||||
{
|
||||
var writer = new BinaryWriter(stream);
|
||||
|
||||
var modExpLength = TripletFullLength(parameters.Modulus.Length + 1) + TripletFullLength(parameters.Exponent.Length + 1);
|
||||
var bitStringLength = TripletFullLength(modExpLength + 1);
|
||||
var sequenceLength = TripletFullLength(bitStringLength + OIDHeader.Length);
|
||||
|
||||
// SEQUENCE
|
||||
writer.Write((byte)0x30);
|
||||
WriteTLVLength(writer, sequenceLength);
|
||||
|
||||
// SEQUENCE -> fixed header junk
|
||||
writer.Write(OIDHeader);
|
||||
|
||||
// SEQUENCE -> BIT_STRING
|
||||
writer.Write((byte)0x03);
|
||||
WriteTLVLength(writer, bitStringLength);
|
||||
writer.Write((byte)0x00);
|
||||
|
||||
// SEQUENCE -> BIT_STRING -> SEQUENCE
|
||||
writer.Write((byte)0x30);
|
||||
WriteTLVLength(writer, modExpLength);
|
||||
|
||||
// SEQUENCE -> BIT_STRING -> SEQUENCE -> INTEGER
|
||||
// Modulus is padded with a zero to avoid issues with the sign bit
|
||||
writer.Write((byte)0x02);
|
||||
WriteTLVLength(writer, parameters.Modulus.Length + 1);
|
||||
writer.Write((byte)0);
|
||||
writer.Write(parameters.Modulus);
|
||||
|
||||
// SEQUENCE -> BIT_STRING -> SEQUENCE -> INTEGER
|
||||
// Exponent is padded with a zero to avoid issues with the sign bit
|
||||
writer.Write((byte)0x02);
|
||||
WriteTLVLength(writer, parameters.Exponent.Length + 1);
|
||||
writer.Write((byte)0);
|
||||
writer.Write(parameters.Exponent);
|
||||
|
||||
return stream.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
static void WriteTLVLength(BinaryWriter writer, int length)
|
||||
{
|
||||
if (length < 0x80)
|
||||
{
|
||||
// Length < 128 is stored in a single byte
|
||||
writer.Write((byte)length);
|
||||
}
|
||||
else
|
||||
{
|
||||
// If 128 <= length < 256**128 first byte encodes number of bytes required to hold the length
|
||||
// High-bit is set as a flag to use this long-form encoding
|
||||
var lengthBytes = BitConverter.GetBytes(length).Reverse().SkipWhile(b => b == 0).ToArray();
|
||||
writer.Write((byte)(0x80 | lengthBytes.Length));
|
||||
writer.Write(lengthBytes);
|
||||
}
|
||||
}
|
||||
|
||||
static int ReadTLVLength(Stream s)
|
||||
{
|
||||
var length = s.ReadByte();
|
||||
if (length < 0x80)
|
||||
return length;
|
||||
|
||||
var data = new byte[4];
|
||||
s.ReadBytes(data, 0, Math.Min(length & 0x7F, 4));
|
||||
return BitConverter.ToInt32(data.ToArray(), 0);
|
||||
}
|
||||
|
||||
static int TripletFullLength(int dataLength)
|
||||
{
|
||||
if (dataLength < 0x80)
|
||||
return 2 + dataLength;
|
||||
|
||||
return 2 + dataLength + BitConverter.GetBytes(dataLength).Reverse().SkipWhile(b => b == 0).Count();
|
||||
}
|
||||
|
||||
public static string DecryptString(RSAParameters parameters, string data)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var rsa = new RSACryptoServiceProvider())
|
||||
{
|
||||
rsa.ImportParameters(parameters);
|
||||
return Encoding.UTF8.GetString(rsa.Decrypt(Convert.FromBase64String(data), false));
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Write("debug", "Failed to decrypt string with exception: {0}", e);
|
||||
Console.WriteLine("String decryption failed: {0}", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static string Sign(RSAParameters parameters, string data)
|
||||
{
|
||||
return Sign(parameters, Encoding.UTF8.GetBytes(data));
|
||||
}
|
||||
|
||||
public static string Sign(RSAParameters parameters, byte[] data)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var rsa = new RSACryptoServiceProvider())
|
||||
{
|
||||
rsa.ImportParameters(parameters);
|
||||
using (var csp = SHA1.Create())
|
||||
return Convert.ToBase64String(rsa.SignHash(csp.ComputeHash(data), CryptoConfig.MapNameToOID("SHA1")));
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Write("debug", "Failed to sign string with exception: {0}", e);
|
||||
Console.WriteLine("String signing failed: {0}", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool VerifySignature(RSAParameters parameters, string data, string signature)
|
||||
{
|
||||
return VerifySignature(parameters, Encoding.UTF8.GetBytes(data), signature);
|
||||
}
|
||||
|
||||
public static bool VerifySignature(RSAParameters parameters, byte[] data, string signature)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var rsa = new RSACryptoServiceProvider())
|
||||
{
|
||||
rsa.ImportParameters(parameters);
|
||||
using (var csp = SHA1.Create())
|
||||
return rsa.VerifyHash(csp.ComputeHash(data), CryptoConfig.MapNameToOID("SHA1"), Convert.FromBase64String(signature));
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Write("debug", "Failed to verify signature with exception: {0}", e);
|
||||
Console.WriteLine("Signature validation failed: {0}", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static string SHA1Hash(Stream data)
|
||||
{
|
||||
using (var csp = SHA1.Create())
|
||||
return new string(csp.ComputeHash(data).SelectMany(a => a.ToString("x2")).ToArray());
|
||||
}
|
||||
|
||||
public static string SHA1Hash(byte[] data)
|
||||
{
|
||||
using (var csp = SHA1.Create())
|
||||
return new string(csp.ComputeHash(data).SelectMany(a => a.ToString("x2")).ToArray());
|
||||
}
|
||||
|
||||
public static string SHA1Hash(string data)
|
||||
{
|
||||
return SHA1Hash(Encoding.UTF8.GetBytes(data));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA
|
||||
{
|
||||
public class DefaultPlayer : IGlobalModData
|
||||
{
|
||||
public readonly Color Color = Color.FromAhsl(0, 0, 238);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
@@ -17,8 +17,8 @@ namespace OpenRA
|
||||
{
|
||||
public class Download
|
||||
{
|
||||
readonly object syncObject = new object();
|
||||
WebClient wc;
|
||||
bool cancelled;
|
||||
|
||||
public static string FormatErrorMessage(Exception e)
|
||||
{
|
||||
@@ -28,8 +28,6 @@ namespace OpenRA
|
||||
|
||||
switch (ex.Status)
|
||||
{
|
||||
case WebExceptionStatus.RequestCanceled:
|
||||
return "Cancelled";
|
||||
case WebExceptionStatus.NameResolutionFailure:
|
||||
return "DNS lookup failed";
|
||||
case WebExceptionStatus.Timeout:
|
||||
@@ -43,54 +41,40 @@ namespace OpenRA
|
||||
}
|
||||
}
|
||||
|
||||
void EnableTLS12OnWindows()
|
||||
public Download(string url, string path, Action<DownloadProgressChangedEventArgs> onProgress, Action<AsyncCompletedEventArgs, bool> onComplete)
|
||||
{
|
||||
// Enable TLS 1.2 on Windows: .NET 4.7 on Windows 10 only supports obsolete protocols by default
|
||||
// SecurityProtocolType.Tls12 is not defined in the .NET 4.5 reference dlls used by mono,
|
||||
// so we must use the enum's constant value directly
|
||||
if (Platform.CurrentPlatform == PlatformType.Windows)
|
||||
ServicePointManager.SecurityProtocol |= (SecurityProtocolType)3072;
|
||||
wc = new WebClient();
|
||||
wc.Proxy = null;
|
||||
|
||||
wc.DownloadProgressChanged += (_, a) => onProgress(a);
|
||||
wc.DownloadFileCompleted += (_, a) => onComplete(a, cancelled);
|
||||
|
||||
Game.OnQuit += Cancel;
|
||||
wc.DownloadFileCompleted += (_, a) => { Game.OnQuit -= Cancel; };
|
||||
|
||||
wc.DownloadFileAsync(new Uri(url), path);
|
||||
}
|
||||
|
||||
public Download(string url, string path, Action<DownloadProgressChangedEventArgs> onProgress, Action<AsyncCompletedEventArgs> onComplete)
|
||||
public Download(string url, Action<DownloadProgressChangedEventArgs> onProgress, Action<DownloadDataCompletedEventArgs, bool> onComplete)
|
||||
{
|
||||
EnableTLS12OnWindows();
|
||||
wc = new WebClient();
|
||||
wc.Proxy = null;
|
||||
|
||||
lock (syncObject)
|
||||
{
|
||||
wc = new WebClient { Proxy = null };
|
||||
wc.DownloadProgressChanged += (_, a) => onProgress(a);
|
||||
wc.DownloadFileCompleted += (_, a) => { DisposeWebClient(); onComplete(a); };
|
||||
wc.DownloadFileAsync(new Uri(url), path);
|
||||
}
|
||||
wc.DownloadProgressChanged += (_, a) => onProgress(a);
|
||||
wc.DownloadDataCompleted += (_, a) => onComplete(a, cancelled);
|
||||
|
||||
Game.OnQuit += Cancel;
|
||||
wc.DownloadDataCompleted += (_, a) => { Game.OnQuit -= Cancel; };
|
||||
|
||||
wc.DownloadDataAsync(new Uri(url));
|
||||
}
|
||||
|
||||
public Download(string url, Action<DownloadProgressChangedEventArgs> onProgress, Action<DownloadDataCompletedEventArgs> onComplete)
|
||||
public void Cancel()
|
||||
{
|
||||
EnableTLS12OnWindows();
|
||||
|
||||
lock (syncObject)
|
||||
{
|
||||
wc = new WebClient { Proxy = null };
|
||||
wc.DownloadProgressChanged += (_, a) => onProgress(a);
|
||||
wc.DownloadDataCompleted += (_, a) => { DisposeWebClient(); onComplete(a); };
|
||||
wc.DownloadDataAsync(new Uri(url));
|
||||
}
|
||||
}
|
||||
|
||||
void DisposeWebClient()
|
||||
{
|
||||
lock (syncObject)
|
||||
{
|
||||
wc.Dispose();
|
||||
wc = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void CancelAsync()
|
||||
{
|
||||
lock (syncObject)
|
||||
wc?.CancelAsync();
|
||||
Game.OnQuit -= Cancel;
|
||||
wc.CancelAsync();
|
||||
wc.Dispose();
|
||||
cancelled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.Collections.Generic;
|
||||
using OpenRA.GameRules;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Effects
|
||||
{
|
||||
public class DelayedImpact : IEffect
|
||||
{
|
||||
readonly Target target;
|
||||
readonly IWarhead wh;
|
||||
readonly WarheadArgs args;
|
||||
|
||||
int delay;
|
||||
|
||||
public DelayedImpact(int delay, IWarhead wh, Target target, WarheadArgs args)
|
||||
{
|
||||
this.wh = wh;
|
||||
this.delay = delay;
|
||||
this.target = target;
|
||||
this.args = args;
|
||||
}
|
||||
|
||||
public void Tick(World world)
|
||||
{
|
||||
if (--delay <= 0)
|
||||
world.AddFrameEndTask(w => { w.Remove(this); wh.DoImpact(target, args); });
|
||||
}
|
||||
|
||||
public IEnumerable<IRenderable> Render(WorldRenderer wr) { yield break; }
|
||||
}
|
||||
}
|
||||
55
OpenRA.Game/Effects/FlashTarget.cs
Normal file
55
OpenRA.Game/Effects/FlashTarget.cs
Normal file
@@ -0,0 +1,55 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenRA.Graphics;
|
||||
|
||||
namespace OpenRA.Effects
|
||||
{
|
||||
public class FlashTarget : IEffect
|
||||
{
|
||||
Actor target;
|
||||
Player player;
|
||||
int remainingTicks;
|
||||
|
||||
public FlashTarget(Actor target, Player asPlayer = null, int ticks = 4)
|
||||
{
|
||||
this.target = target;
|
||||
player = asPlayer;
|
||||
remainingTicks = ticks;
|
||||
target.World.RemoveAll(effect =>
|
||||
{
|
||||
var flashTarget = effect as FlashTarget;
|
||||
return flashTarget != null && flashTarget.target == target;
|
||||
});
|
||||
}
|
||||
|
||||
public void Tick(World world)
|
||||
{
|
||||
if (--remainingTicks == 0 || !target.IsInWorld)
|
||||
world.AddFrameEndTask(w => w.Remove(this));
|
||||
}
|
||||
|
||||
public IEnumerable<IRenderable> Render(WorldRenderer wr)
|
||||
{
|
||||
if (target.IsInWorld && remainingTicks % 2 == 0)
|
||||
{
|
||||
var palette = wr.Palette(player == null ? "highlight" : "highlight" + player.InternalName);
|
||||
return target.Render(wr)
|
||||
.Where(r => !r.IsDecoration)
|
||||
.Select(r => r.WithPalette(palette));
|
||||
}
|
||||
|
||||
return SpriteRenderable.None;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
@@ -19,10 +19,4 @@ namespace OpenRA.Effects
|
||||
void Tick(World world);
|
||||
IEnumerable<IRenderable> Render(WorldRenderer r);
|
||||
}
|
||||
|
||||
// Identifier interface for effects that are added to ScreenMap
|
||||
public interface ISpatiallyPartitionable { }
|
||||
|
||||
public interface IEffectAboveShroud { IEnumerable<IRenderable> RenderAboveShroud(WorldRenderer wr); }
|
||||
public interface IEffectAnnotation { IEnumerable<IRenderable> RenderAnnotation(WorldRenderer wr); }
|
||||
}
|
||||
|
||||
51
OpenRA.Game/Effects/SpriteEffect.cs
Normal file
51
OpenRA.Game/Effects/SpriteEffect.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.Collections.Generic;
|
||||
using OpenRA.Graphics;
|
||||
|
||||
namespace OpenRA.Effects
|
||||
{
|
||||
public class SpriteEffect : IEffect
|
||||
{
|
||||
readonly World world;
|
||||
readonly string palette;
|
||||
readonly Animation anim;
|
||||
readonly WPos pos;
|
||||
readonly bool visibleThroughFog;
|
||||
readonly bool scaleSizeWithZoom;
|
||||
|
||||
public SpriteEffect(WPos pos, World world, string image, string sequence, string palette, bool visibleThroughFog = false, bool scaleSizeWithZoom = false)
|
||||
{
|
||||
this.world = world;
|
||||
this.pos = pos;
|
||||
this.palette = palette;
|
||||
this.scaleSizeWithZoom = scaleSizeWithZoom;
|
||||
this.visibleThroughFog = visibleThroughFog;
|
||||
anim = new Animation(world, image);
|
||||
anim.PlayThen(sequence, () => world.AddFrameEndTask(w => w.Remove(this)));
|
||||
}
|
||||
|
||||
public void Tick(World world)
|
||||
{
|
||||
anim.Tick();
|
||||
}
|
||||
|
||||
public IEnumerable<IRenderable> Render(WorldRenderer wr)
|
||||
{
|
||||
if (world.FogObscures(pos) && !visibleThroughFog)
|
||||
return SpriteRenderable.None;
|
||||
|
||||
var zoom = scaleSizeWithZoom ? 1f / wr.Viewport.Zoom : 1f;
|
||||
return anim.Render(pos, WVec.Zero, 0, wr.Palette(palette), zoom);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,302 +0,0 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using OpenRA.FileFormats;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA
|
||||
{
|
||||
[Flags]
|
||||
enum ModRegistration { User = 1, System = 2 }
|
||||
|
||||
public class ExternalMod
|
||||
{
|
||||
public readonly string Id;
|
||||
public readonly string Version;
|
||||
public readonly string Title;
|
||||
public readonly string LaunchPath;
|
||||
public readonly string[] LaunchArgs;
|
||||
public Sprite Icon { get; internal set; }
|
||||
public Sprite Icon2x { get; internal set; }
|
||||
public Sprite Icon3x { get; internal set; }
|
||||
|
||||
public static string MakeKey(Manifest mod) { return MakeKey(mod.Id, mod.Metadata.Version); }
|
||||
public static string MakeKey(ExternalMod mod) { return MakeKey(mod.Id, mod.Version); }
|
||||
public static string MakeKey(string modId, string modVersion) { return modId + "-" + modVersion; }
|
||||
}
|
||||
|
||||
public class ExternalMods : IReadOnlyDictionary<string, ExternalMod>
|
||||
{
|
||||
readonly Dictionary<string, ExternalMod> mods = new Dictionary<string, ExternalMod>();
|
||||
readonly SheetBuilder sheetBuilder;
|
||||
|
||||
Sheet CreateSheet()
|
||||
{
|
||||
var sheet = new Sheet(SheetType.BGRA, new Size(512, 512));
|
||||
|
||||
// We must manually force the buffer creation to avoid a crash
|
||||
// that is indirectly triggered by rendering from a Sheet that
|
||||
// has not yet been written to.
|
||||
sheet.CreateBuffer();
|
||||
sheet.GetTexture().ScaleFilter = TextureScaleFilter.Linear;
|
||||
|
||||
return sheet;
|
||||
}
|
||||
|
||||
public ExternalMods()
|
||||
{
|
||||
// Don't try to load mod icons if we don't have a texture to put them in
|
||||
if (Game.Renderer != null)
|
||||
sheetBuilder = new SheetBuilder(SheetType.BGRA, CreateSheet);
|
||||
|
||||
// Several types of support directory types are available, depending on
|
||||
// how the player has installed and launched the game.
|
||||
// Read registration metadata from all of them
|
||||
var sources = Enum.GetValues(typeof(SupportDirType))
|
||||
.Cast<SupportDirType>()
|
||||
.Select(t => Platform.GetSupportDir(t))
|
||||
.Distinct();
|
||||
|
||||
foreach (var source in sources)
|
||||
{
|
||||
var metadataPath = Path.Combine(source, "ModMetadata");
|
||||
if (!Directory.Exists(metadataPath))
|
||||
continue;
|
||||
|
||||
foreach (var path in Directory.GetFiles(metadataPath, "*.yaml"))
|
||||
{
|
||||
try
|
||||
{
|
||||
var yaml = MiniYaml.FromStream(File.OpenRead(path), path).First().Value;
|
||||
LoadMod(yaml, path);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Write("debug", "Failed to parse mod metadata file '{0}'", path);
|
||||
Log.Write("debug", e.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LoadMod(MiniYaml yaml, string path = null, bool forceRegistration = false)
|
||||
{
|
||||
var mod = FieldLoader.Load<ExternalMod>(yaml);
|
||||
|
||||
if (sheetBuilder != null)
|
||||
{
|
||||
var iconNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Icon");
|
||||
if (iconNode != null && !string.IsNullOrEmpty(iconNode.Value.Value))
|
||||
using (var stream = new MemoryStream(Convert.FromBase64String(iconNode.Value.Value)))
|
||||
mod.Icon = sheetBuilder.Add(new Png(stream));
|
||||
|
||||
var icon2xNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Icon2x");
|
||||
if (icon2xNode != null && !string.IsNullOrEmpty(icon2xNode.Value.Value))
|
||||
using (var stream = new MemoryStream(Convert.FromBase64String(icon2xNode.Value.Value)))
|
||||
mod.Icon2x = sheetBuilder.Add(new Png(stream), 1f / 2);
|
||||
|
||||
var icon3xNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Icon3x");
|
||||
if (icon3xNode != null && !string.IsNullOrEmpty(icon3xNode.Value.Value))
|
||||
using (var stream = new MemoryStream(Convert.FromBase64String(icon3xNode.Value.Value)))
|
||||
mod.Icon3x = sheetBuilder.Add(new Png(stream), 1f / 3);
|
||||
}
|
||||
|
||||
// Avoid possibly overwriting a valid mod with an obviously bogus one
|
||||
var key = ExternalMod.MakeKey(mod);
|
||||
if ((forceRegistration || File.Exists(mod.LaunchPath)) && (path == null || Path.GetFileNameWithoutExtension(path) == key))
|
||||
mods[key] = mod;
|
||||
}
|
||||
|
||||
internal void Register(Manifest mod, string launchPath, IEnumerable<string> launchArgs, ModRegistration registration)
|
||||
{
|
||||
if (mod.Metadata.Hidden)
|
||||
return;
|
||||
|
||||
var key = ExternalMod.MakeKey(mod);
|
||||
var yaml = new MiniYamlNode("Registration", new MiniYaml("", new List<MiniYamlNode>()
|
||||
{
|
||||
new MiniYamlNode("Id", mod.Id),
|
||||
new MiniYamlNode("Version", mod.Metadata.Version),
|
||||
new MiniYamlNode("Title", mod.Metadata.Title),
|
||||
new MiniYamlNode("LaunchPath", launchPath),
|
||||
new MiniYamlNode("LaunchArgs", new[] { "Game.Mod=" + mod.Id }.Concat(launchArgs).JoinWith(", "))
|
||||
}));
|
||||
|
||||
using (var stream = mod.Package.GetStream("icon.png"))
|
||||
if (stream != null)
|
||||
yaml.Value.Nodes.Add(new MiniYamlNode("Icon", Convert.ToBase64String(stream.ReadAllBytes())));
|
||||
|
||||
using (var stream = mod.Package.GetStream("icon-2x.png"))
|
||||
if (stream != null)
|
||||
yaml.Value.Nodes.Add(new MiniYamlNode("Icon2x", Convert.ToBase64String(stream.ReadAllBytes())));
|
||||
|
||||
using (var stream = mod.Package.GetStream("icon-3x.png"))
|
||||
if (stream != null)
|
||||
yaml.Value.Nodes.Add(new MiniYamlNode("Icon3x", Convert.ToBase64String(stream.ReadAllBytes())));
|
||||
|
||||
var sources = new List<string>();
|
||||
if (registration.HasFlag(ModRegistration.System))
|
||||
sources.Add(Platform.GetSupportDir(SupportDirType.System));
|
||||
|
||||
if (registration.HasFlag(ModRegistration.User))
|
||||
{
|
||||
sources.Add(Platform.GetSupportDir(SupportDirType.User));
|
||||
|
||||
// If using the modern support dir we must also write the registration
|
||||
// to the legacy support dir for older engine versions, but ONLY if it exists
|
||||
var legacyPath = Platform.GetSupportDir(SupportDirType.LegacyUser);
|
||||
if (Directory.Exists(legacyPath))
|
||||
sources.Add(legacyPath);
|
||||
}
|
||||
|
||||
// Make sure the mod is available for this session, even if saving it fails
|
||||
LoadMod(yaml.Value, forceRegistration: true);
|
||||
|
||||
var lines = new List<MiniYamlNode> { yaml }.ToLines().ToArray();
|
||||
foreach (var source in sources.Distinct())
|
||||
{
|
||||
var metadataPath = Path.Combine(source, "ModMetadata");
|
||||
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(metadataPath);
|
||||
File.WriteAllLines(Path.Combine(metadataPath, key + ".yaml"), lines);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Write("debug", "Failed to register current mod metadata");
|
||||
Log.Write("debug", e.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes invalid mod registrations:
|
||||
/// * LaunchPath no longer exists
|
||||
/// * LaunchPath and mod id matches the active mod, but the version is different
|
||||
/// * Filename doesn't match internal key
|
||||
/// * Fails to parse as a mod registration
|
||||
/// </summary>
|
||||
internal void ClearInvalidRegistrations(ExternalMod activeMod, ModRegistration registration)
|
||||
{
|
||||
var sources = new List<string>();
|
||||
if (registration.HasFlag(ModRegistration.System))
|
||||
sources.Add(Platform.GetSupportDir(SupportDirType.System));
|
||||
|
||||
if (registration.HasFlag(ModRegistration.User))
|
||||
{
|
||||
// User support dir may be using the modern or legacy value, or overridden by the user
|
||||
// Add all the possibilities and let the .Distinct() below ignore the duplicates
|
||||
sources.Add(Platform.GetSupportDir(SupportDirType.User));
|
||||
sources.Add(Platform.GetSupportDir(SupportDirType.ModernUser));
|
||||
sources.Add(Platform.GetSupportDir(SupportDirType.LegacyUser));
|
||||
}
|
||||
|
||||
var activeModKey = ExternalMod.MakeKey(activeMod);
|
||||
foreach (var source in sources.Distinct())
|
||||
{
|
||||
var metadataPath = Path.Combine(source, "ModMetadata");
|
||||
if (!Directory.Exists(metadataPath))
|
||||
continue;
|
||||
|
||||
foreach (var path in Directory.GetFiles(metadataPath, "*.yaml"))
|
||||
{
|
||||
string modKey = null;
|
||||
try
|
||||
{
|
||||
var yaml = MiniYaml.FromStream(File.OpenRead(path), path).First().Value;
|
||||
var m = FieldLoader.Load<ExternalMod>(yaml);
|
||||
modKey = ExternalMod.MakeKey(m);
|
||||
|
||||
// Continue to the next entry if it is the active mod (even if the LaunchPath is bogus)
|
||||
if (modKey == activeModKey)
|
||||
continue;
|
||||
|
||||
// Continue to the next entry if this one is valid
|
||||
if (File.Exists(m.LaunchPath) && Path.GetFileNameWithoutExtension(path) == modKey &&
|
||||
!(activeMod != null && m.LaunchPath == activeMod.LaunchPath && m.Id == activeMod.Id && m.Version != activeMod.Version))
|
||||
continue;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Write("debug", "Failed to parse mod metadata file '{0}'", path);
|
||||
Log.Write("debug", e.ToString());
|
||||
}
|
||||
|
||||
// Remove from the ingame mod switcher
|
||||
if (Path.GetFileNameWithoutExtension(path) == modKey)
|
||||
mods.Remove(modKey);
|
||||
|
||||
// Remove stale or corrupted metadata
|
||||
try
|
||||
{
|
||||
File.Delete(path);
|
||||
Log.Write("debug", "Removed invalid mod metadata file '{0}'", path);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Write("debug", "Failed to remove mod metadata file '{0}'", path);
|
||||
Log.Write("debug", e.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal void Unregister(Manifest mod, ModRegistration registration)
|
||||
{
|
||||
var sources = new List<string>();
|
||||
if (registration.HasFlag(ModRegistration.System))
|
||||
sources.Add(Platform.GetSupportDir(SupportDirType.System));
|
||||
|
||||
if (registration.HasFlag(ModRegistration.User))
|
||||
{
|
||||
// User support dir may be using the modern or legacy value, or overridden by the user
|
||||
// Add all the possibilities and let the .Distinct() below ignore the duplicates
|
||||
sources.Add(Platform.GetSupportDir(SupportDirType.User));
|
||||
sources.Add(Platform.GetSupportDir(SupportDirType.ModernUser));
|
||||
sources.Add(Platform.GetSupportDir(SupportDirType.LegacyUser));
|
||||
}
|
||||
|
||||
var key = ExternalMod.MakeKey(mod);
|
||||
mods.Remove(key);
|
||||
|
||||
foreach (var source in sources.Distinct())
|
||||
{
|
||||
var path = Path.Combine(source, "ModMetadata", key + ".yaml");
|
||||
try
|
||||
{
|
||||
if (File.Exists(path))
|
||||
File.Delete(path);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Write("debug", "Failed to remove mod metadata file '{0}'", path);
|
||||
Log.Write("debug", e.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ExternalMod this[string key] { get { return mods[key]; } }
|
||||
public int Count { get { return mods.Count; } }
|
||||
public ICollection<string> Keys { get { return mods.Keys; } }
|
||||
public ICollection<ExternalMod> Values { get { return mods.Values; } }
|
||||
public bool ContainsKey(string key) { return mods.ContainsKey(key); }
|
||||
public IEnumerator<KeyValuePair<string, ExternalMod>> GetEnumerator() { return mods.GetEnumerator(); }
|
||||
public bool TryGetValue(string key, out ExternalMod value) { return mods.TryGetValue(key, out value); }
|
||||
IEnumerator IEnumerable.GetEnumerator() { return mods.GetEnumerator(); }
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
@@ -11,10 +11,11 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Imaging;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using OpenRA.Primitives;
|
||||
using OpenRA.Support;
|
||||
using OpenRA.Traits;
|
||||
|
||||
@@ -78,9 +79,19 @@ namespace OpenRA
|
||||
return val;
|
||||
}
|
||||
|
||||
public static bool Contains(this Rectangle r, int2 p)
|
||||
{
|
||||
return r.Contains(p.ToPoint());
|
||||
}
|
||||
|
||||
public static bool Contains(this RectangleF r, int2 p)
|
||||
{
|
||||
return r.Contains(p.ToPointF());
|
||||
}
|
||||
|
||||
static int WindingDirectionTest(int2 v0, int2 v1, int2 p)
|
||||
{
|
||||
return Math.Sign((v1.X - v0.X) * (p.Y - v0.Y) - (p.X - v0.X) * (v1.Y - v0.Y));
|
||||
return (v1.X - v0.X) * (p.Y - v0.Y) - (p.X - v0.X) * (v1.Y - v0.Y);
|
||||
}
|
||||
|
||||
public static bool PolygonContains(this int2[] polygon, int2 p)
|
||||
@@ -101,16 +112,6 @@ namespace OpenRA
|
||||
return windingNumber != 0;
|
||||
}
|
||||
|
||||
public static bool LinesIntersect(int2 a, int2 b, int2 c, int2 d)
|
||||
{
|
||||
// If line segments AB and CD intersect:
|
||||
// - the triangles ACD and BCD must have opposite sense (clockwise or anticlockwise)
|
||||
// - the triangles CAB and DAB must have opposite sense
|
||||
// Segments intersect if the orientation (clockwise or anticlockwise) of the two points in each line segment are opposite with respect to the other
|
||||
// Assumes that lines are not colinear
|
||||
return WindingDirectionTest(c, d, a) != WindingDirectionTest(c, d, b) && WindingDirectionTest(a, b, c) != WindingDirectionTest(a, b, d);
|
||||
}
|
||||
|
||||
public static bool HasModifier(this Modifiers k, Modifiers mod)
|
||||
{
|
||||
// PERF: Enum.HasFlag is slower and requires allocations.
|
||||
@@ -120,19 +121,13 @@ namespace OpenRA
|
||||
public static V GetOrAdd<K, V>(this Dictionary<K, V> d, K k)
|
||||
where V : new()
|
||||
{
|
||||
return d.GetOrAdd(k, new V());
|
||||
}
|
||||
|
||||
public static V GetOrAdd<K, V>(this Dictionary<K, V> d, K k, V v)
|
||||
{
|
||||
if (!d.TryGetValue(k, out var ret))
|
||||
d.Add(k, ret = v);
|
||||
return ret;
|
||||
return d.GetOrAdd(k, _ => new V());
|
||||
}
|
||||
|
||||
public static V GetOrAdd<K, V>(this Dictionary<K, V> d, K k, Func<K, V> createFn)
|
||||
{
|
||||
if (!d.TryGetValue(k, out var ret))
|
||||
V ret;
|
||||
if (!d.TryGetValue(k, out ret))
|
||||
d.Add(k, ret = createFn(k));
|
||||
return ret;
|
||||
}
|
||||
@@ -167,26 +162,6 @@ namespace OpenRA
|
||||
return xs.ElementAt(r.Next(xs.Count));
|
||||
}
|
||||
|
||||
public static Rectangle Union(this IEnumerable<Rectangle> rects)
|
||||
{
|
||||
// PERF: Avoid LINQ.
|
||||
var first = true;
|
||||
var result = Rectangle.Empty;
|
||||
foreach (var rect in rects)
|
||||
{
|
||||
if (first)
|
||||
{
|
||||
first = false;
|
||||
result = rect;
|
||||
continue;
|
||||
}
|
||||
|
||||
result = Rectangle.Union(rect, result);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static float Product(this IEnumerable<float> xs)
|
||||
{
|
||||
return xs.Aggregate(1f, (a, x) => a * x);
|
||||
@@ -200,11 +175,7 @@ namespace OpenRA
|
||||
|
||||
public static IEnumerable<T> Iterate<T>(this T t, Func<T, T> f)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
yield return t;
|
||||
t = f(t);
|
||||
}
|
||||
for (;;) { yield return t; t = f(t); }
|
||||
}
|
||||
|
||||
public static T MinBy<T, U>(this IEnumerable<T> ts, Func<T, U> selector)
|
||||
@@ -359,7 +330,8 @@ namespace OpenRA
|
||||
|
||||
public static int IntegerDivisionRoundingAwayFromZero(int dividend, int divisor)
|
||||
{
|
||||
var quotient = Math.DivRem(dividend, divisor, out var remainder);
|
||||
int remainder;
|
||||
var quotient = Math.DivRem(dividend, divisor, out remainder);
|
||||
if (remainder == 0)
|
||||
return quotient;
|
||||
return quotient + (Math.Sign(dividend) == Math.Sign(divisor) ? 1 : -1);
|
||||
@@ -403,14 +375,11 @@ namespace OpenRA
|
||||
var key = keySelector(item);
|
||||
var element = elementSelector(item);
|
||||
|
||||
// Discard elements with null keys
|
||||
if (!typeof(TKey).IsValueType && key == null)
|
||||
continue;
|
||||
|
||||
// Check for a key conflict:
|
||||
if (d.ContainsKey(key))
|
||||
{
|
||||
if (!dupKeys.TryGetValue(key, out var dupKeyMessages))
|
||||
List<string> dupKeyMessages;
|
||||
if (!dupKeys.TryGetValue(key, out dupKeyMessages))
|
||||
{
|
||||
// Log the initial conflicting value already inserted:
|
||||
dupKeyMessages = new List<string>();
|
||||
@@ -475,6 +444,27 @@ namespace OpenRA
|
||||
return result;
|
||||
}
|
||||
|
||||
public static Rectangle Bounds(this Bitmap b) { return new Rectangle(0, 0, b.Width, b.Height); }
|
||||
|
||||
public static Bitmap CloneWith32bbpArgbPixelFormat(this Bitmap original)
|
||||
{
|
||||
// Note: We would use original.Clone(original.Bounds(), PixelFormat.Format32bppArgb)
|
||||
// but this doesn't work on mono.
|
||||
var clone = new Bitmap(original.Width, original.Height, PixelFormat.Format32bppArgb);
|
||||
try
|
||||
{
|
||||
using (var g = System.Drawing.Graphics.FromImage(clone))
|
||||
g.DrawImage(original, original.Bounds());
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
clone.Dispose();
|
||||
throw;
|
||||
}
|
||||
|
||||
return clone;
|
||||
}
|
||||
|
||||
public static int ToBits(this IEnumerable<bool> bits)
|
||||
{
|
||||
var i = 0;
|
||||
@@ -494,45 +484,19 @@ namespace OpenRA
|
||||
return int.Parse(s, NumberStyles.Integer, NumberFormatInfo.InvariantInfo);
|
||||
}
|
||||
|
||||
public static byte ParseByte(string s)
|
||||
{
|
||||
return byte.Parse(s, NumberStyles.Integer, NumberFormatInfo.InvariantInfo);
|
||||
}
|
||||
|
||||
public static bool TryParseIntegerInvariant(string s, out int i)
|
||||
{
|
||||
return int.TryParse(s, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out i);
|
||||
}
|
||||
|
||||
public static bool TryParseInt64Invariant(string s, out long i)
|
||||
public static bool IsTraitEnabled(this object trait)
|
||||
{
|
||||
return long.TryParse(s, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out i);
|
||||
return trait as IDisabledTrait == null || !(trait as IDisabledTrait).IsTraitDisabled;
|
||||
}
|
||||
|
||||
public static bool IsTraitEnabled<T>(this T trait)
|
||||
public static bool IsTraitEnabled<T>(T t)
|
||||
{
|
||||
var disabledTrait = trait as IDisabledTrait;
|
||||
return disabledTrait == null || !disabledTrait.IsTraitDisabled;
|
||||
}
|
||||
|
||||
public static T FirstEnabledTraitOrDefault<T>(this IEnumerable<T> ts)
|
||||
{
|
||||
// PERF: Avoid LINQ.
|
||||
foreach (var t in ts)
|
||||
if (t.IsTraitEnabled())
|
||||
return t;
|
||||
|
||||
return default(T);
|
||||
}
|
||||
|
||||
public static T FirstEnabledTraitOrDefault<T>(this T[] ts)
|
||||
{
|
||||
// PERF: Avoid LINQ.
|
||||
foreach (var t in ts)
|
||||
if (t.IsTraitEnabled())
|
||||
return t;
|
||||
|
||||
return default(T);
|
||||
return IsTraitEnabled(t as object);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
@@ -12,14 +12,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Imaging;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Text.RegularExpressions;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Primitives;
|
||||
using OpenRA.Support;
|
||||
|
||||
namespace OpenRA
|
||||
{
|
||||
@@ -39,8 +40,7 @@ namespace OpenRA
|
||||
}
|
||||
}
|
||||
|
||||
public MissingFieldsException(string[] missing, string header = null, string headerSingle = null)
|
||||
: base(null)
|
||||
public MissingFieldsException(string[] missing, string header = null, string headerSingle = null) : base(null)
|
||||
{
|
||||
Header = missing.Length > 1 ? header : headerSingle ?? header;
|
||||
Missing = missing;
|
||||
@@ -69,11 +69,6 @@ namespace OpenRA
|
||||
static readonly ConcurrentCache<MemberInfo, bool> MemberHasTranslateAttribute =
|
||||
new ConcurrentCache<MemberInfo, bool>(member => member.HasAttribute<TranslateAttribute>());
|
||||
|
||||
static readonly ConcurrentCache<string, BooleanExpression> BooleanExpressionCache =
|
||||
new ConcurrentCache<string, BooleanExpression>(expression => new BooleanExpression(expression));
|
||||
static readonly ConcurrentCache<string, IntegerExpression> IntegerExpressionCache =
|
||||
new ConcurrentCache<string, IntegerExpression>(expression => new IntegerExpression(expression));
|
||||
|
||||
static readonly object TranslationsLock = new object();
|
||||
static Dictionary<string, string> translations;
|
||||
|
||||
@@ -121,7 +116,8 @@ namespace OpenRA
|
||||
{
|
||||
ret = null;
|
||||
|
||||
if (!md.TryGetValue(yamlName, out var yaml))
|
||||
MiniYaml yaml;
|
||||
if (!md.TryGetValue(yamlName, out yaml))
|
||||
return false;
|
||||
|
||||
ret = GetValue(field.Name, field.FieldType, yaml, field);
|
||||
@@ -179,36 +175,42 @@ namespace OpenRA
|
||||
|
||||
public static object GetValue(string fieldName, Type fieldType, MiniYaml yaml, MemberInfo field)
|
||||
{
|
||||
var value = yaml.Value?.Trim();
|
||||
var value = yaml.Value;
|
||||
if (value != null) value = value.Trim();
|
||||
|
||||
if (fieldType == typeof(int))
|
||||
{
|
||||
if (Exts.TryParseIntegerInvariant(value, out var res))
|
||||
int res;
|
||||
if (Exts.TryParseIntegerInvariant(value, out res))
|
||||
return res;
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
else if (fieldType == typeof(ushort))
|
||||
{
|
||||
if (ushort.TryParse(value, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out var res))
|
||||
ushort res;
|
||||
if (ushort.TryParse(value, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out res))
|
||||
return res;
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
|
||||
if (fieldType == typeof(long))
|
||||
{
|
||||
if (long.TryParse(value, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out var res))
|
||||
long res;
|
||||
if (long.TryParse(value, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out res))
|
||||
return res;
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
else if (fieldType == typeof(float))
|
||||
{
|
||||
if (value != null && float.TryParse(value.Replace("%", ""), NumberStyles.Float, NumberFormatInfo.InvariantInfo, out var res))
|
||||
float res;
|
||||
if (value != null && float.TryParse(value.Replace("%", ""), NumberStyles.Float, NumberFormatInfo.InvariantInfo, out res))
|
||||
return res * (value.Contains('%') ? 0.01f : 1f);
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
else if (fieldType == typeof(decimal))
|
||||
{
|
||||
if (value != null && decimal.TryParse(value.Replace("%", ""), NumberStyles.Float, NumberFormatInfo.InvariantInfo, out var res))
|
||||
decimal res;
|
||||
if (value != null && decimal.TryParse(value.Replace("%", ""), NumberStyles.Float, NumberFormatInfo.InvariantInfo, out res))
|
||||
return res * (value.Contains('%') ? 0.01m : 1m);
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
@@ -220,25 +222,59 @@ namespace OpenRA
|
||||
}
|
||||
else if (fieldType == typeof(Color))
|
||||
{
|
||||
if (value != null && Color.TryParse(value, out var color))
|
||||
Color color;
|
||||
if (value != null && HSLColor.TryParseRGB(value, out color))
|
||||
return color;
|
||||
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
else if (fieldType == typeof(Color[]))
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
var parts = value.Split(',');
|
||||
var colors = new Color[parts.Length];
|
||||
|
||||
for (var i = 0; i < colors.Length; i++)
|
||||
if (!HSLColor.TryParseRGB(parts[i], out colors[i]))
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
|
||||
return colors;
|
||||
}
|
||||
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
else if (fieldType == typeof(HSLColor))
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
Color rgb;
|
||||
if (HSLColor.TryParseRGB(value, out rgb))
|
||||
return new HSLColor(rgb);
|
||||
|
||||
// Allow old HSLColor/ColorRamp formats to be parsed as HSLColor
|
||||
var parts = value.Split(',');
|
||||
if (parts.Length == 3 || parts.Length == 4)
|
||||
return new HSLColor(
|
||||
(byte)Exts.ParseIntegerInvariant(parts[0]).Clamp(0, 255),
|
||||
(byte)Exts.ParseIntegerInvariant(parts[1]).Clamp(0, 255),
|
||||
(byte)Exts.ParseIntegerInvariant(parts[2]).Clamp(0, 255));
|
||||
}
|
||||
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
else if (fieldType == typeof(Hotkey))
|
||||
{
|
||||
if (Hotkey.TryParse(value, out var res))
|
||||
Hotkey res;
|
||||
if (Hotkey.TryParse(value, out res))
|
||||
return res;
|
||||
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
else if (fieldType == typeof(HotkeyReference))
|
||||
{
|
||||
return Game.ModData.Hotkeys[value];
|
||||
}
|
||||
else if (fieldType == typeof(WDist))
|
||||
{
|
||||
if (WDist.TryParse(value, out var res))
|
||||
WDist res;
|
||||
if (WDist.TryParse(value, out res))
|
||||
return res;
|
||||
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
@@ -250,7 +286,8 @@ namespace OpenRA
|
||||
var parts = value.Split(',');
|
||||
if (parts.Length == 3)
|
||||
{
|
||||
if (WDist.TryParse(parts[0], out var rx) && WDist.TryParse(parts[1], out var ry) && WDist.TryParse(parts[2], out var rz))
|
||||
WDist rx, ry, rz;
|
||||
if (WDist.TryParse(parts[0], out rx) && WDist.TryParse(parts[1], out ry) && WDist.TryParse(parts[2], out rz))
|
||||
return new WVec(rx, ry, rz);
|
||||
}
|
||||
}
|
||||
@@ -270,7 +307,8 @@ namespace OpenRA
|
||||
|
||||
for (var i = 0; i < vecs.Length; ++i)
|
||||
{
|
||||
if (WDist.TryParse(parts[3 * i], out var rx) && WDist.TryParse(parts[3 * i + 1], out var ry) && WDist.TryParse(parts[3 * i + 2], out var rz))
|
||||
WDist rx, ry, rz;
|
||||
if (WDist.TryParse(parts[3 * i], out rx) && WDist.TryParse(parts[3 * i + 1], out ry) && WDist.TryParse(parts[3 * i + 2], out rz))
|
||||
vecs[i] = new WVec(rx, ry, rz);
|
||||
}
|
||||
|
||||
@@ -286,7 +324,8 @@ namespace OpenRA
|
||||
var parts = value.Split(',');
|
||||
if (parts.Length == 3)
|
||||
{
|
||||
if (WDist.TryParse(parts[0], out var rx) && WDist.TryParse(parts[1], out var ry) && WDist.TryParse(parts[2], out var rz))
|
||||
WDist rx, ry, rz;
|
||||
if (WDist.TryParse(parts[0], out rx) && WDist.TryParse(parts[1], out ry) && WDist.TryParse(parts[2], out rz))
|
||||
return new WPos(rx, ry, rz);
|
||||
}
|
||||
}
|
||||
@@ -295,7 +334,8 @@ namespace OpenRA
|
||||
}
|
||||
else if (fieldType == typeof(WAngle))
|
||||
{
|
||||
if (Exts.TryParseIntegerInvariant(value, out var res))
|
||||
int res;
|
||||
if (Exts.TryParseIntegerInvariant(value, out res))
|
||||
return new WAngle(res);
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
@@ -306,7 +346,8 @@ namespace OpenRA
|
||||
var parts = value.Split(',');
|
||||
if (parts.Length == 3)
|
||||
{
|
||||
if (Exts.TryParseIntegerInvariant(parts[0], out var rr) && Exts.TryParseIntegerInvariant(parts[1], out var rp) && Exts.TryParseIntegerInvariant(parts[2], out var ry))
|
||||
int rr, rp, ry;
|
||||
if (Exts.TryParseIntegerInvariant(value, out rr) && Exts.TryParseIntegerInvariant(value, out rp) && Exts.TryParseIntegerInvariant(value, out ry))
|
||||
return new WRot(new WAngle(rr), new WAngle(rp), new WAngle(ry));
|
||||
}
|
||||
}
|
||||
@@ -345,7 +386,8 @@ namespace OpenRA
|
||||
var vecs = new CVec[parts.Length / 2];
|
||||
for (var i = 0; i < vecs.Length; i++)
|
||||
{
|
||||
if (int.TryParse(parts[2 * i], out var rx) && int.TryParse(parts[2 * i + 1], out var ry))
|
||||
int rx, ry;
|
||||
if (int.TryParse(parts[2 * i], out rx) && int.TryParse(parts[2 * i + 1], out ry))
|
||||
vecs[i] = new CVec(rx, ry);
|
||||
}
|
||||
|
||||
@@ -354,38 +396,6 @@ namespace OpenRA
|
||||
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
else if (fieldType == typeof(BooleanExpression))
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
return BooleanExpressionCache[value];
|
||||
}
|
||||
catch (InvalidDataException e)
|
||||
{
|
||||
throw new YamlException(e.Message);
|
||||
}
|
||||
}
|
||||
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
else if (fieldType == typeof(IntegerExpression))
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
return IntegerExpressionCache[value];
|
||||
}
|
||||
catch (InvalidDataException e)
|
||||
{
|
||||
throw new YamlException(e.Message);
|
||||
}
|
||||
}
|
||||
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
else if (fieldType.IsEnum)
|
||||
{
|
||||
try
|
||||
@@ -397,45 +407,44 @@ namespace OpenRA
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
}
|
||||
else if (fieldType == typeof(bool))
|
||||
{
|
||||
if (bool.TryParse(value.ToLowerInvariant(), out var result))
|
||||
return result;
|
||||
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
else if (fieldType == typeof(int2[]))
|
||||
else if (fieldType == typeof(ImageFormat))
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
var parts = value.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
if (parts.Length % 2 != 0)
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
|
||||
var ints = new int2[parts.Length / 2];
|
||||
for (var i = 0; i < ints.Length; i++)
|
||||
ints[i] = new int2(Exts.ParseIntegerInvariant(parts[2 * i]), Exts.ParseIntegerInvariant(parts[2 * i + 1]));
|
||||
|
||||
return ints;
|
||||
switch (value.ToLowerInvariant())
|
||||
{
|
||||
case "bmp":
|
||||
return ImageFormat.Bmp;
|
||||
case "gif":
|
||||
return ImageFormat.Gif;
|
||||
case "jpg":
|
||||
case "jpeg":
|
||||
return ImageFormat.Jpeg;
|
||||
case "tif":
|
||||
case "tiff":
|
||||
return ImageFormat.Tiff;
|
||||
default:
|
||||
return ImageFormat.Png;
|
||||
}
|
||||
}
|
||||
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
else if (fieldType == typeof(bool))
|
||||
return ParseYesNo(value, fieldType, fieldName);
|
||||
else if (fieldType.IsArray && fieldType.GetArrayRank() == 1)
|
||||
{
|
||||
if (value == null)
|
||||
return Array.CreateInstance(fieldType.GetElementType(), 0);
|
||||
|
||||
var options = field != null && field.HasAttribute<AllowEmptyEntriesAttribute>() ?
|
||||
StringSplitOptions.None : StringSplitOptions.RemoveEmptyEntries;
|
||||
var parts = value.Split(new char[] { ',' }, options);
|
||||
var parts = value.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
var ret = Array.CreateInstance(fieldType.GetElementType(), parts.Length);
|
||||
for (var i = 0; i < parts.Length; i++)
|
||||
ret.SetValue(GetValue(fieldName, fieldType.GetElementType(), parts[i].Trim(), field), i);
|
||||
return ret;
|
||||
}
|
||||
else if (fieldType.IsGenericType && (fieldType.GetGenericTypeDefinition() == typeof(HashSet<>) || fieldType.GetGenericTypeDefinition() == typeof(List<>)))
|
||||
else if (fieldType.IsGenericType && fieldType.GetGenericTypeDefinition() == typeof(HashSet<>))
|
||||
{
|
||||
var set = Activator.CreateInstance(fieldType);
|
||||
if (value == null)
|
||||
@@ -477,9 +486,6 @@ namespace OpenRA
|
||||
if (value != null)
|
||||
{
|
||||
var parts = value.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
if (parts.Length != 2)
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
|
||||
return new int2(Exts.ParseIntegerInvariant(parts[0]), Exts.ParseIntegerInvariant(parts[1]));
|
||||
}
|
||||
|
||||
@@ -492,7 +498,8 @@ namespace OpenRA
|
||||
var parts = value.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
float xx = 0;
|
||||
float yy = 0;
|
||||
if (float.TryParse(parts[0].Replace("%", ""), NumberStyles.Float, NumberFormatInfo.InvariantInfo, out var res))
|
||||
float res;
|
||||
if (float.TryParse(parts[0].Replace("%", ""), NumberStyles.Float, NumberFormatInfo.InvariantInfo, out res))
|
||||
xx = res * (parts[0].Contains('%') ? 0.01f : 1f);
|
||||
if (float.TryParse(parts[1].Replace("%", ""), NumberStyles.Float, NumberFormatInfo.InvariantInfo, out res))
|
||||
yy = res * (parts[1].Contains('%') ? 0.01f : 1f);
|
||||
@@ -501,24 +508,6 @@ namespace OpenRA
|
||||
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
else if (fieldType == typeof(float3))
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
var parts = value.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
float.TryParse(parts[0], NumberStyles.Float, NumberFormatInfo.InvariantInfo, out var x);
|
||||
float.TryParse(parts[1], NumberStyles.Float, NumberFormatInfo.InvariantInfo, out var y);
|
||||
|
||||
// z component is optional for compatibility with older float2 definitions
|
||||
float z = 0;
|
||||
if (parts.Length > 2)
|
||||
float.TryParse(parts[2], NumberStyles.Float, NumberFormatInfo.InvariantInfo, out z);
|
||||
|
||||
return new float3(x, y, z);
|
||||
}
|
||||
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
else if (fieldType == typeof(Rectangle))
|
||||
{
|
||||
if (value != null)
|
||||
@@ -533,29 +522,28 @@ namespace OpenRA
|
||||
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
else if (fieldType.IsGenericType && fieldType.GetGenericTypeDefinition() == typeof(BitSet<>))
|
||||
else if (fieldType.IsGenericType && fieldType.GetGenericTypeDefinition() == typeof(Bits<>))
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
var parts = value.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
var ctor = fieldType.GetConstructor(new[] { typeof(string[]) });
|
||||
return ctor.Invoke(new object[] { parts.Select(p => p.Trim()).ToArray() });
|
||||
var argTypes = new Type[] { typeof(string[]) };
|
||||
var argValues = new object[] { parts };
|
||||
return fieldType.GetConstructor(argTypes).Invoke(argValues);
|
||||
}
|
||||
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
else if (fieldType.IsGenericType && fieldType.GetGenericTypeDefinition() == typeof(Nullable<>))
|
||||
{
|
||||
if (string.IsNullOrEmpty(value))
|
||||
return null;
|
||||
|
||||
var innerType = fieldType.GetGenericArguments().First();
|
||||
var innerValue = GetValue("Nullable<T>", innerType, value, field);
|
||||
return fieldType.GetConstructor(new[] { innerType }).Invoke(new[] { innerValue });
|
||||
}
|
||||
else if (fieldType == typeof(DateTime))
|
||||
{
|
||||
if (DateTime.TryParseExact(value, "yyyy-MM-dd HH-mm-ss", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out var dt))
|
||||
DateTime dt;
|
||||
if (DateTime.TryParseExact(value, "yyyy-MM-dd HH-mm-ss", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out dt))
|
||||
return dt;
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
@@ -579,6 +567,20 @@ namespace OpenRA
|
||||
return null;
|
||||
}
|
||||
|
||||
static object ParseYesNo(string p, Type fieldType, string field)
|
||||
{
|
||||
if (string.IsNullOrEmpty(p))
|
||||
return InvalidValueAction(p, fieldType, field);
|
||||
|
||||
p = p.ToLowerInvariant();
|
||||
if (p == "yes") return true;
|
||||
if (p == "true") return true;
|
||||
if (p == "no") return false;
|
||||
if (p == "false") return false;
|
||||
|
||||
return InvalidValueAction(p, fieldType, field);
|
||||
}
|
||||
|
||||
public sealed class FieldLoadInfo
|
||||
{
|
||||
public readonly FieldInfo Field;
|
||||
@@ -639,13 +641,6 @@ namespace OpenRA
|
||||
: base(true, true) { }
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Field)]
|
||||
public sealed class AllowEmptyEntriesAttribute : SerializeAttribute
|
||||
{
|
||||
public AllowEmptyEntriesAttribute()
|
||||
: base(allowEmptyEntries: true) { }
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Field)]
|
||||
public sealed class LoadUsingAttribute : SerializeAttribute
|
||||
{
|
||||
@@ -669,13 +664,11 @@ namespace OpenRA
|
||||
public bool FromYamlKey;
|
||||
public bool DictionaryFromYamlKey;
|
||||
public bool Required;
|
||||
public bool AllowEmptyEntries;
|
||||
|
||||
public SerializeAttribute(bool serialize = true, bool required = false, bool allowEmptyEntries = false)
|
||||
public SerializeAttribute(bool serialize = true, bool required = false)
|
||||
{
|
||||
Serialize = serialize;
|
||||
Required = required;
|
||||
AllowEmptyEntries = allowEmptyEntries;
|
||||
}
|
||||
|
||||
internal Func<MiniYaml, object> GetLoader(Type type)
|
||||
@@ -705,7 +698,8 @@ namespace OpenRA
|
||||
if (translations == null)
|
||||
return key;
|
||||
|
||||
if (!translations.TryGetValue(key, out var value))
|
||||
string value;
|
||||
if (!translations.TryGetValue(key, out value))
|
||||
return key;
|
||||
|
||||
return value;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
@@ -12,10 +12,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Imaging;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using OpenRA.Primitives;
|
||||
using OpenRA.Graphics;
|
||||
|
||||
namespace OpenRA
|
||||
{
|
||||
@@ -73,9 +75,21 @@ namespace OpenRA
|
||||
|
||||
var t = v.GetType();
|
||||
|
||||
// Color.ToString() does the wrong thing; force it to format as rgb[a] hex
|
||||
if (t == typeof(Color))
|
||||
{
|
||||
return ((Color)v).ToString();
|
||||
return HSLColor.ToHexString((Color)v);
|
||||
}
|
||||
|
||||
// HSLColor.ToString() does the wrong thing; force it to format as rgb[a] hex
|
||||
if (t == typeof(HSLColor))
|
||||
{
|
||||
return ((HSLColor)v).ToHexString();
|
||||
}
|
||||
|
||||
if (t == typeof(ImageFormat))
|
||||
{
|
||||
return ((ImageFormat)v).ToString();
|
||||
}
|
||||
|
||||
if (t == typeof(Rectangle))
|
||||
@@ -84,17 +98,12 @@ namespace OpenRA
|
||||
return "{0},{1},{2},{3}".F(r.X, r.Y, r.Width, r.Height);
|
||||
}
|
||||
|
||||
if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(BitSet<>))
|
||||
{
|
||||
return ((IEnumerable<string>)v).Select(FormatValue).JoinWith(", ");
|
||||
}
|
||||
|
||||
if (t.IsArray && t.GetArrayRank() == 1)
|
||||
{
|
||||
return ((Array)v).Cast<object>().Select(FormatValue).JoinWith(", ");
|
||||
}
|
||||
|
||||
if (t.IsGenericType && (t.GetGenericTypeDefinition() == typeof(HashSet<>) || t.GetGenericTypeDefinition() == typeof(List<>)))
|
||||
if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(HashSet<>))
|
||||
{
|
||||
return ((System.Collections.IEnumerable)v).Cast<object>().Select(FormatValue).JoinWith(", ");
|
||||
}
|
||||
|
||||
241
OpenRA.Game/FileFormats/AudLoader.cs
Normal file
241
OpenRA.Game/FileFormats/AudLoader.cs
Normal file
@@ -0,0 +1,241 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace OpenRA.FileFormats
|
||||
{
|
||||
[Flags]
|
||||
enum SoundFlags
|
||||
{
|
||||
Stereo = 0x1,
|
||||
_16Bit = 0x2,
|
||||
}
|
||||
|
||||
enum SoundFormat
|
||||
{
|
||||
WestwoodCompressed = 1,
|
||||
ImaAdpcm = 99,
|
||||
}
|
||||
|
||||
struct Chunk
|
||||
{
|
||||
public int CompressedSize;
|
||||
public int OutputSize;
|
||||
|
||||
public static Chunk Read(Stream s)
|
||||
{
|
||||
Chunk c;
|
||||
c.CompressedSize = s.ReadUInt16();
|
||||
c.OutputSize = s.ReadUInt16();
|
||||
|
||||
if (s.ReadUInt32() != 0xdeaf)
|
||||
throw new InvalidDataException("Chunk header is bogus");
|
||||
return c;
|
||||
}
|
||||
}
|
||||
|
||||
public class AudLoader : ISoundLoader
|
||||
{
|
||||
bool IsAud(Stream s)
|
||||
{
|
||||
var start = s.Position;
|
||||
s.Position += 10;
|
||||
var readFlag = s.ReadByte();
|
||||
var readFormat = s.ReadByte();
|
||||
s.Position = start;
|
||||
|
||||
if (!Enum.IsDefined(typeof(SoundFlags), readFlag))
|
||||
return false;
|
||||
|
||||
return Enum.IsDefined(typeof(SoundFormat), readFormat);
|
||||
}
|
||||
|
||||
bool ISoundLoader.TryParseSound(Stream stream, out ISoundFormat sound)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (IsAud(stream))
|
||||
{
|
||||
sound = new AudFormat(stream);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Not a supported AUD
|
||||
}
|
||||
|
||||
sound = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public class AudFormat : ISoundFormat
|
||||
{
|
||||
public int Channels { get { return 1; } }
|
||||
public int SampleBits { get { return 16; } }
|
||||
public int SampleRate { get { return sampleRate; } }
|
||||
public float LengthInSeconds { get { return AudReader.SoundLength(stream); } }
|
||||
public Stream GetPCMInputStream() { return new MemoryStream(rawData.Value); }
|
||||
|
||||
int sampleRate;
|
||||
Lazy<byte[]> rawData;
|
||||
|
||||
Stream stream;
|
||||
|
||||
public AudFormat(Stream stream)
|
||||
{
|
||||
this.stream = stream;
|
||||
|
||||
var position = stream.Position;
|
||||
rawData = Exts.Lazy(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
byte[] data;
|
||||
if (!AudReader.LoadSound(stream, out data, out sampleRate))
|
||||
throw new InvalidDataException();
|
||||
return data;
|
||||
}
|
||||
finally
|
||||
{
|
||||
stream.Position = position;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public class AudReader
|
||||
{
|
||||
static readonly int[] IndexAdjust = { -1, -1, -1, -1, 2, 4, 6, 8 };
|
||||
static readonly int[] StepTable =
|
||||
{
|
||||
7, 8, 9, 10, 11, 12, 13, 14, 16,
|
||||
17, 19, 21, 23, 25, 28, 31, 34, 37,
|
||||
41, 45, 50, 55, 60, 66, 73, 80, 88,
|
||||
97, 107, 118, 130, 143, 157, 173, 190, 209,
|
||||
230, 253, 279, 307, 337, 371, 408, 449, 494,
|
||||
544, 598, 658, 724, 796, 876, 963, 1060, 1166,
|
||||
1282, 1411, 1552, 1707, 1878, 2066, 2272, 2499, 2749,
|
||||
3024, 3327, 3660, 4026, 4428, 4871, 5358, 5894, 6484,
|
||||
7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899, 15289,
|
||||
16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767
|
||||
};
|
||||
|
||||
static short DecodeSample(byte b, ref int index, ref int current)
|
||||
{
|
||||
var sb = (b & 8) != 0;
|
||||
b &= 7;
|
||||
|
||||
var delta = (StepTable[index] * b) / 4 + StepTable[index] / 8;
|
||||
if (sb) delta = -delta;
|
||||
|
||||
current += delta;
|
||||
if (current > short.MaxValue) current = short.MaxValue;
|
||||
if (current < short.MinValue) current = short.MinValue;
|
||||
|
||||
index += IndexAdjust[b];
|
||||
if (index < 0) index = 0;
|
||||
if (index > 88) index = 88;
|
||||
|
||||
return (short)current;
|
||||
}
|
||||
|
||||
public static byte[] LoadSound(byte[] raw, ref int index)
|
||||
{
|
||||
var s = new MemoryStream(raw);
|
||||
var dataSize = raw.Length;
|
||||
var outputSize = raw.Length * 4;
|
||||
|
||||
var output = new byte[outputSize];
|
||||
var offset = 0;
|
||||
var currentSample = 0;
|
||||
|
||||
while (dataSize-- > 0)
|
||||
{
|
||||
var b = s.ReadUInt8();
|
||||
|
||||
var t = DecodeSample(b, ref index, ref currentSample);
|
||||
output[offset++] = (byte)t;
|
||||
output[offset++] = (byte)(t >> 8);
|
||||
|
||||
t = DecodeSample((byte)(b >> 4), ref index, ref currentSample);
|
||||
output[offset++] = (byte)t;
|
||||
output[offset++] = (byte)(t >> 8);
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
public static float SoundLength(Stream s)
|
||||
{
|
||||
var sampleRate = s.ReadUInt16();
|
||||
/*var dataSize = */ s.ReadInt32();
|
||||
var outputSize = s.ReadInt32();
|
||||
var flags = (SoundFlags)s.ReadByte();
|
||||
|
||||
var samples = outputSize;
|
||||
if (0 != (flags & SoundFlags.Stereo)) samples /= 2;
|
||||
if (0 != (flags & SoundFlags._16Bit)) samples /= 2;
|
||||
return samples / sampleRate;
|
||||
}
|
||||
|
||||
public static bool LoadSound(Stream s, out byte[] rawData, out int sampleRate)
|
||||
{
|
||||
rawData = null;
|
||||
|
||||
sampleRate = s.ReadUInt16();
|
||||
var dataSize = s.ReadInt32();
|
||||
var outputSize = s.ReadInt32();
|
||||
|
||||
var readFlag = s.ReadByte();
|
||||
if (!Enum.IsDefined(typeof(SoundFlags), readFlag))
|
||||
return false;
|
||||
|
||||
var readFormat = s.ReadByte();
|
||||
if (!Enum.IsDefined(typeof(SoundFormat), readFormat))
|
||||
return false;
|
||||
|
||||
var output = new byte[outputSize];
|
||||
var offset = 0;
|
||||
var index = 0;
|
||||
var currentSample = 0;
|
||||
|
||||
while (dataSize > 0)
|
||||
{
|
||||
var chunk = Chunk.Read(s);
|
||||
for (var n = 0; n < chunk.CompressedSize; n++)
|
||||
{
|
||||
var b = s.ReadUInt8();
|
||||
|
||||
var t = DecodeSample(b, ref index, ref currentSample);
|
||||
output[offset++] = (byte)t;
|
||||
output[offset++] = (byte)(t >> 8);
|
||||
|
||||
if (offset < outputSize)
|
||||
{
|
||||
/* possible that only half of the final byte is used! */
|
||||
t = DecodeSample((byte)(b >> 4), ref index, ref currentSample);
|
||||
output[offset++] = (byte)t;
|
||||
output[offset++] = (byte)(t >> 8);
|
||||
}
|
||||
}
|
||||
|
||||
dataSize -= 8 + chunk.CompressedSize;
|
||||
}
|
||||
|
||||
rawData = output;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,12 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
#region Additional Copyright & License Information
|
||||
/*
|
||||
*
|
||||
* This file is based on the blast routines (version 1.1 by Mark Adler)
|
||||
* included in zlib/contrib
|
||||
*/
|
||||
@@ -18,15 +15,14 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace OpenRA.Mods.Common.FileFormats
|
||||
namespace OpenRA.FileFormats
|
||||
{
|
||||
public static class Blast
|
||||
{
|
||||
public static readonly int MAXBITS = 13; // maximum code length
|
||||
public static readonly int MAXWIN = 4096; // maximum window size
|
||||
|
||||
static byte[] litlen =
|
||||
{
|
||||
static byte[] litlen = new byte[] {
|
||||
11, 124, 8, 7, 28, 7, 188, 13, 76, 4,
|
||||
10, 8, 12, 10, 12, 10, 8, 23, 8, 9,
|
||||
7, 6, 7, 8, 7, 6, 55, 8, 23, 24,
|
||||
@@ -40,21 +36,19 @@ namespace OpenRA.Mods.Common.FileFormats
|
||||
};
|
||||
|
||||
// bit lengths of length codes 0..15
|
||||
static byte[] lenlen = { 2, 35, 36, 53, 38, 23 };
|
||||
static byte[] lenlen = new byte[] { 2, 35, 36, 53, 38, 23 };
|
||||
|
||||
// bit lengths of distance codes 0..63
|
||||
static byte[] distlen = { 2, 20, 53, 230, 247, 151, 248 };
|
||||
static byte[] distlen = new byte[] { 2, 20, 53, 230, 247, 151, 248 };
|
||||
|
||||
// base for length codes
|
||||
static short[] lengthbase =
|
||||
{
|
||||
static short[] lengthbase = new short[] {
|
||||
3, 2, 4, 5, 6, 7, 8, 9, 10, 12,
|
||||
16, 24, 40, 72, 136, 264
|
||||
};
|
||||
|
||||
// extra bits for length codes
|
||||
static byte[] extra =
|
||||
{
|
||||
static byte[] extra = new byte[] {
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 1, 2,
|
||||
3, 4, 5, 6, 7, 8
|
||||
};
|
||||
@@ -63,13 +57,10 @@ namespace OpenRA.Mods.Common.FileFormats
|
||||
static Huffman lencode = new Huffman(lenlen, lenlen.Length, 16);
|
||||
static Huffman distcode = new Huffman(distlen, distlen.Length, 64);
|
||||
|
||||
/// <summary>PKWare Compression Library stream.</summary>
|
||||
/// <param name="input">Compressed input stream.</param>
|
||||
/// <param name="output">Stream to write the decompressed output.</param>
|
||||
/// <param name="onProgress">Progress callback, invoked with (read bytes, written bytes).</param>
|
||||
public static void Decompress(Stream input, Stream output, Action<long, long> onProgress = null)
|
||||
// Decode PKWare Compression Library stream.
|
||||
public static byte[] Decompress(byte[] src)
|
||||
{
|
||||
var br = new BitReader(input);
|
||||
var br = new BitReader(src);
|
||||
|
||||
// Are literals coded?
|
||||
var coded = br.ReadBits(8);
|
||||
@@ -87,9 +78,7 @@ namespace OpenRA.Mods.Common.FileFormats
|
||||
ushort next = 0; // index of next write location in out[]
|
||||
var first = true; // true to check distances (for first 4K)
|
||||
var outBuffer = new byte[MAXWIN]; // output buffer and sliding window
|
||||
|
||||
var inputStart = input.Position;
|
||||
var outputStart = output.Position;
|
||||
var ms = new MemoryStream();
|
||||
|
||||
// decode literals and length/distance pairs
|
||||
do
|
||||
@@ -105,9 +94,7 @@ namespace OpenRA.Mods.Common.FileFormats
|
||||
if (len == 519)
|
||||
{
|
||||
for (var i = 0; i < next; i++)
|
||||
output.WriteByte(outBuffer[i]);
|
||||
|
||||
onProgress?.Invoke(input.Position - inputStart, output.Position - outputStart);
|
||||
ms.WriteByte(outBuffer[i]);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -150,14 +137,11 @@ namespace OpenRA.Mods.Common.FileFormats
|
||||
if (next == MAXWIN)
|
||||
{
|
||||
for (var i = 0; i < next; i++)
|
||||
output.WriteByte(outBuffer[i]);
|
||||
ms.WriteByte(outBuffer[i]);
|
||||
next = 0;
|
||||
first = false;
|
||||
|
||||
onProgress?.Invoke(input.Position - inputStart, output.Position - outputStart);
|
||||
}
|
||||
}
|
||||
while (len != 0);
|
||||
} while (len != 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -167,15 +151,14 @@ namespace OpenRA.Mods.Common.FileFormats
|
||||
if (next == MAXWIN)
|
||||
{
|
||||
for (var i = 0; i < next; i++)
|
||||
output.WriteByte(outBuffer[i]);
|
||||
ms.WriteByte(outBuffer[i]);
|
||||
next = 0;
|
||||
first = false;
|
||||
|
||||
onProgress?.Invoke(input.Position - inputStart, output.Position - outputStart);
|
||||
}
|
||||
}
|
||||
}
|
||||
while (true);
|
||||
} while (true);
|
||||
|
||||
return ms.ToArray();
|
||||
}
|
||||
|
||||
// Decode a code using Huffman table h.
|
||||
@@ -202,13 +185,14 @@ namespace OpenRA.Mods.Common.FileFormats
|
||||
|
||||
class BitReader
|
||||
{
|
||||
readonly Stream stream;
|
||||
byte bitBuffer = 0;
|
||||
readonly byte[] src;
|
||||
int offset = 0;
|
||||
int bitBuffer = 0;
|
||||
int bitCount = 0;
|
||||
|
||||
public BitReader(Stream stream)
|
||||
public BitReader(byte[] src)
|
||||
{
|
||||
this.stream = stream;
|
||||
this.src = src;
|
||||
}
|
||||
|
||||
public int ReadBits(int count)
|
||||
@@ -219,7 +203,7 @@ namespace OpenRA.Mods.Common.FileFormats
|
||||
{
|
||||
if (bitCount == 0)
|
||||
{
|
||||
bitBuffer = stream.ReadUInt8();
|
||||
bitBuffer = src[offset++];
|
||||
bitCount = 8;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
@@ -9,7 +9,7 @@
|
||||
*/
|
||||
#endregion
|
||||
|
||||
namespace OpenRA.Mods.Cnc.FileFormats
|
||||
namespace OpenRA.FileFormats
|
||||
{
|
||||
class Blowfish
|
||||
{
|
||||
@@ -130,8 +130,7 @@ namespace OpenRA.Mods.Cnc.FileFormats
|
||||
return i;
|
||||
}
|
||||
|
||||
uint[] lookupMfromP =
|
||||
{
|
||||
uint[] lookupMfromP = new uint[] {
|
||||
0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344,
|
||||
0xa4093822, 0x299f31d0, 0x082efa98, 0xec4e6c89,
|
||||
0x452821e6, 0x38d01377, 0xbe5466cf, 0x34e90c6c,
|
||||
@@ -139,8 +138,7 @@ namespace OpenRA.Mods.Cnc.FileFormats
|
||||
0x9216d5d9, 0x8979fb1b
|
||||
};
|
||||
|
||||
uint[,] lookupMfromS =
|
||||
{
|
||||
uint[,] lookupMfromS = new uint[,] {
|
||||
{
|
||||
0xd1310ba6, 0x98dfb5ac, 0x2ffd72db, 0xd01adfb7,
|
||||
0xb8e1afed, 0x6a267e96, 0xba7c9045, 0xf12c7f99,
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
@@ -12,7 +12,7 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace OpenRA.Mods.Cnc.FileFormats
|
||||
namespace OpenRA.FileFormats
|
||||
{
|
||||
/* TODO: Convert this direct C port into readable code. */
|
||||
|
||||
@@ -230,10 +230,10 @@ namespace OpenRA.Mods.Cnc.FileFormats
|
||||
InitBigNum(nTmp, 0, len);
|
||||
InitBigNum(n1, 0, len);
|
||||
nTwoBitLen = (int)BitLenBigNum(n2, len);
|
||||
bit = 1U << (nTwoBitLen % 32);
|
||||
bit = ((uint)1) << (nTwoBitLen % 32);
|
||||
j = ((nTwoBitLen + 32) / 32) - 1;
|
||||
nTwoByteLen = (uint)((nTwoBitLen - 1) / 32) * 4;
|
||||
nTmp[nTwoByteLen / 4] |= 1U << ((nTwoBitLen - 1) & 0x1f);
|
||||
nTmp[nTwoByteLen / 4] |= ((uint)1) << ((nTwoBitLen - 1) & 0x1f);
|
||||
|
||||
while (nTwoBitLen > 0)
|
||||
{
|
||||
@@ -302,7 +302,6 @@ namespace OpenRA.Mods.Cnc.FileFormats
|
||||
pn2++;
|
||||
tmp >>= 16;
|
||||
}
|
||||
|
||||
*pn1 += (ushort)tmp;
|
||||
}
|
||||
}
|
||||
@@ -386,7 +385,7 @@ namespace OpenRA.Mods.Cnc.FileFormats
|
||||
MulBignumWord(esi, globOne, tmp, 2 * len);
|
||||
if ((*edi & 0x8000) == 0)
|
||||
{
|
||||
if (SubBigNum((uint*)esi, (uint*)esi, g1, 0, (int)len) != 0)
|
||||
if (0 != SubBigNum((uint*)esi, (uint*)esi, g1, 0, (int)len))
|
||||
(*edi)--;
|
||||
}
|
||||
}
|
||||
@@ -432,7 +431,7 @@ namespace OpenRA.Mods.Cnc.FileFormats
|
||||
InitTwoDw(n4, n4_len);
|
||||
n3_bitlen = (int)BitLenBigNum(n3, n4_len);
|
||||
n3_len = (uint)((n3_bitlen + 31) / 32);
|
||||
bit_mask = (1U << ((n3_bitlen - 1) % 32)) >> 1;
|
||||
bit_mask = (((uint)1) << ((n3_bitlen - 1) % 32)) >> 1;
|
||||
pn3 += n3_len - 1;
|
||||
n3_bitlen--;
|
||||
MoveBigNum(n1, n2, n4_len);
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
@@ -9,7 +9,7 @@
|
||||
*/
|
||||
#endregion
|
||||
|
||||
namespace OpenRA.Mods.Cnc.FileFormats
|
||||
namespace OpenRA.FileFormats
|
||||
{
|
||||
/// <summary>
|
||||
/// Static class that uses a lookup table to calculates CRC32
|
||||
@@ -132,4 +132,4 @@ namespace OpenRA.Mods.Cnc.FileFormats
|
||||
return Calculate(data, len, 0xFFFFFFFF);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,20 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using OpenRA.Graphics;
|
||||
|
||||
namespace OpenRA.Mods.Cnc.FileFormats
|
||||
namespace OpenRA.FileFormats
|
||||
{
|
||||
public class HvaReader
|
||||
{
|
||||
@@ -48,7 +50,7 @@ namespace OpenRA.Mods.Cnc.FileFormats
|
||||
Transforms[c + ids[k]] = s.ReadFloat();
|
||||
|
||||
Array.Copy(Transforms, 16 * (LimbCount * j + i), testMatrix, 0, 16);
|
||||
if (OpenRA.Graphics.Util.MatrixInverse(testMatrix) == null)
|
||||
if (Util.MatrixInverse(testMatrix) == null)
|
||||
throw new InvalidDataException(
|
||||
"The transformation matrix for HVA file `{0}` section {1} frame {2} is invalid because it is not invertible!"
|
||||
.F(fileName, i, j));
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
@@ -11,8 +11,9 @@
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using OpenRA.FileSystem;
|
||||
|
||||
namespace OpenRA.Mods.Cnc.FileFormats
|
||||
namespace OpenRA.FileFormats
|
||||
{
|
||||
public class IdxReader
|
||||
{
|
||||
@@ -41,4 +42,4 @@ namespace OpenRA.Mods.Cnc.FileFormats
|
||||
Entries.Add(new IdxEntry(s));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
99
OpenRA.Game/FileFormats/ImaAdpcmLoader.cs
Normal file
99
OpenRA.Game/FileFormats/ImaAdpcmLoader.cs
Normal file
@@ -0,0 +1,99 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.IO;
|
||||
|
||||
namespace OpenRA.FileFormats
|
||||
{
|
||||
struct ImaAdpcmChunk
|
||||
{
|
||||
public int CompressedSize;
|
||||
public int OutputSize;
|
||||
|
||||
public static ImaAdpcmChunk Read(Stream s)
|
||||
{
|
||||
ImaAdpcmChunk c;
|
||||
c.CompressedSize = s.ReadUInt16();
|
||||
c.OutputSize = s.ReadUInt16();
|
||||
if (s.ReadUInt32() != 0xdeaf)
|
||||
throw new InvalidDataException("Chunk header is bogus");
|
||||
return c;
|
||||
}
|
||||
}
|
||||
|
||||
public static class ImaAdpcmLoader
|
||||
{
|
||||
static readonly int[] IndexAdjust = { -1, -1, -1, -1, 2, 4, 6, 8 };
|
||||
static readonly int[] StepTable =
|
||||
{
|
||||
7, 8, 9, 10, 11, 12, 13, 14, 16,
|
||||
17, 19, 21, 23, 25, 28, 31, 34, 37,
|
||||
41, 45, 50, 55, 60, 66, 73, 80, 88,
|
||||
97, 107, 118, 130, 143, 157, 173, 190, 209,
|
||||
230, 253, 279, 307, 337, 371, 408, 449, 494,
|
||||
544, 598, 658, 724, 796, 876, 963, 1060, 1166,
|
||||
1282, 1411, 1552, 1707, 1878, 2066, 2272, 2499, 2749,
|
||||
3024, 3327, 3660, 4026, 4428, 4871, 5358, 5894, 6484,
|
||||
7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899, 15289,
|
||||
16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767
|
||||
};
|
||||
|
||||
static short DecodeImaAdpcmSample(byte b, ref int index, ref int current)
|
||||
{
|
||||
var sb = (b & 8) != 0;
|
||||
b &= 7;
|
||||
|
||||
var delta = (StepTable[index] * b) / 4 + StepTable[index] / 8;
|
||||
if (sb) delta = -delta;
|
||||
|
||||
current += delta;
|
||||
if (current > short.MaxValue) current = short.MaxValue;
|
||||
if (current < short.MinValue) current = short.MinValue;
|
||||
|
||||
index += IndexAdjust[b];
|
||||
if (index < 0) index = 0;
|
||||
if (index > 88) index = 88;
|
||||
|
||||
return (short)current;
|
||||
}
|
||||
|
||||
public static byte[] LoadImaAdpcmSound(byte[] raw, ref int index)
|
||||
{
|
||||
var currentSample = 0;
|
||||
return LoadImaAdpcmSound(raw, ref index, ref currentSample);
|
||||
}
|
||||
|
||||
public static byte[] LoadImaAdpcmSound(byte[] raw, ref int index, ref int currentSample)
|
||||
{
|
||||
var s = new MemoryStream(raw);
|
||||
var dataSize = raw.Length;
|
||||
var outputSize = raw.Length * 4;
|
||||
|
||||
var output = new byte[outputSize];
|
||||
var offset = 0;
|
||||
|
||||
while (dataSize-- > 0)
|
||||
{
|
||||
var b = s.ReadUInt8();
|
||||
|
||||
var t = DecodeImaAdpcmSample(b, ref index, ref currentSample);
|
||||
output[offset++] = (byte)t;
|
||||
output[offset++] = (byte)(t >> 8);
|
||||
|
||||
t = DecodeImaAdpcmSample((byte)(b >> 4), ref index, ref currentSample);
|
||||
output[offset++] = (byte)t;
|
||||
output[offset++] = (byte)(t >> 8);
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,355 +0,0 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using ICSharpCode.SharpZipLib.Checksum;
|
||||
using ICSharpCode.SharpZipLib.Zip.Compression.Streams;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.FileFormats
|
||||
{
|
||||
public class Png
|
||||
{
|
||||
static readonly byte[] Signature = { 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a };
|
||||
|
||||
public int Width { get; set; }
|
||||
public int Height { get; set; }
|
||||
public Color[] Palette { get; set; }
|
||||
public byte[] Data { get; set; }
|
||||
public Dictionary<string, string> EmbeddedData = new Dictionary<string, string>();
|
||||
|
||||
public Png(Stream s)
|
||||
{
|
||||
if (!Verify(s))
|
||||
throw new InvalidDataException("PNG Signature is bogus");
|
||||
|
||||
s.Position += 8;
|
||||
var headerParsed = false;
|
||||
var isPaletted = false;
|
||||
var is24Bit = false;
|
||||
var data = new List<byte>();
|
||||
|
||||
while (true)
|
||||
{
|
||||
var length = IPAddress.NetworkToHostOrder(s.ReadInt32());
|
||||
var type = Encoding.UTF8.GetString(s.ReadBytes(4));
|
||||
var content = s.ReadBytes(length);
|
||||
/*var crc = */s.ReadInt32();
|
||||
|
||||
if (!headerParsed && type != "IHDR")
|
||||
throw new InvalidDataException("Invalid PNG file - header does not appear first.");
|
||||
|
||||
using (var ms = new MemoryStream(content))
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case "IHDR":
|
||||
{
|
||||
if (headerParsed)
|
||||
throw new InvalidDataException("Invalid PNG file - duplicate header.");
|
||||
Width = IPAddress.NetworkToHostOrder(ms.ReadInt32());
|
||||
Height = IPAddress.NetworkToHostOrder(ms.ReadInt32());
|
||||
|
||||
var bitDepth = ms.ReadUInt8();
|
||||
var colorType = (PngColorType)ms.ReadByte();
|
||||
isPaletted = IsPaletted(bitDepth, colorType);
|
||||
is24Bit = colorType == PngColorType.Color;
|
||||
|
||||
var dataLength = Width * Height;
|
||||
if (!isPaletted)
|
||||
dataLength *= 4;
|
||||
|
||||
Data = new byte[dataLength];
|
||||
|
||||
var compression = ms.ReadByte();
|
||||
/*var filter = */ms.ReadByte();
|
||||
var interlace = ms.ReadByte();
|
||||
|
||||
if (compression != 0)
|
||||
throw new InvalidDataException("Compression method not supported");
|
||||
|
||||
if (interlace != 0)
|
||||
throw new InvalidDataException("Interlacing not supported");
|
||||
|
||||
headerParsed = true;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case "PLTE":
|
||||
{
|
||||
Palette = new Color[256];
|
||||
for (var i = 0; i < length / 3; i++)
|
||||
{
|
||||
var r = ms.ReadByte(); var g = ms.ReadByte(); var b = ms.ReadByte();
|
||||
Palette[i] = Color.FromArgb(r, g, b);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case "tRNS":
|
||||
{
|
||||
if (Palette == null)
|
||||
throw new InvalidDataException("Non-Palette indexed PNG are not supported.");
|
||||
|
||||
for (var i = 0; i < length; i++)
|
||||
Palette[i] = Color.FromArgb(ms.ReadByte(), Palette[i]);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case "IDAT":
|
||||
{
|
||||
data.AddRange(content);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case "tEXt":
|
||||
{
|
||||
var key = ms.ReadASCIIZ();
|
||||
EmbeddedData.Add(key, ms.ReadASCII(length - key.Length - 1));
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case "IEND":
|
||||
{
|
||||
using (var ns = new MemoryStream(data.ToArray()))
|
||||
{
|
||||
using (var ds = new InflaterInputStream(ns))
|
||||
{
|
||||
var pxStride = isPaletted ? 1 : is24Bit ? 3 : 4;
|
||||
var srcStride = Width * pxStride;
|
||||
var destStride = Width * (isPaletted ? 1 : 4);
|
||||
|
||||
var prevLine = new byte[srcStride];
|
||||
for (var y = 0; y < Height; y++)
|
||||
{
|
||||
var filter = (PngFilter)ds.ReadByte();
|
||||
var line = ds.ReadBytes(srcStride);
|
||||
|
||||
for (var i = 0; i < srcStride; i++)
|
||||
line[i] = i < pxStride
|
||||
? UnapplyFilter(filter, line[i], 0, prevLine[i], 0)
|
||||
: UnapplyFilter(filter, line[i], line[i - pxStride], prevLine[i], prevLine[i - pxStride]);
|
||||
|
||||
if (is24Bit)
|
||||
{
|
||||
// Fold alpha channel into RGB data
|
||||
for (var i = 0; i < line.Length / 3; i++)
|
||||
{
|
||||
Array.Copy(line, 3 * i, Data, y * destStride + 4 * i, 3);
|
||||
Data[y * destStride + 4 * i + 3] = 255;
|
||||
}
|
||||
}
|
||||
else
|
||||
Array.Copy(line, 0, Data, y * destStride, line.Length);
|
||||
|
||||
prevLine = line;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isPaletted && Palette == null)
|
||||
throw new InvalidDataException("Non-Palette indexed PNG are not supported.");
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Png(byte[] data, int width, int height, Color[] palette = null,
|
||||
Dictionary<string, string> embeddedData = null)
|
||||
{
|
||||
var expectLength = width * height;
|
||||
if (palette == null)
|
||||
expectLength *= 4;
|
||||
|
||||
if (data.Length != expectLength)
|
||||
throw new InvalidDataException("Input data does not match expected length");
|
||||
|
||||
Width = width;
|
||||
Height = height;
|
||||
|
||||
Palette = palette;
|
||||
Data = data;
|
||||
|
||||
if (embeddedData != null)
|
||||
EmbeddedData = embeddedData;
|
||||
}
|
||||
|
||||
public static bool Verify(Stream s)
|
||||
{
|
||||
var pos = s.Position;
|
||||
var isPng = Signature.Aggregate(true, (current, t) => current && s.ReadUInt8() == t);
|
||||
s.Position = pos;
|
||||
return isPng;
|
||||
}
|
||||
|
||||
static byte UnapplyFilter(PngFilter f, byte x, byte a, byte b, byte c)
|
||||
{
|
||||
switch (f)
|
||||
{
|
||||
case PngFilter.None: return x;
|
||||
case PngFilter.Sub: return (byte)(x + a);
|
||||
case PngFilter.Up: return (byte)(x + b);
|
||||
case PngFilter.Average: return (byte)(x + (a + b) / 2);
|
||||
case PngFilter.Paeth: return (byte)(x + Paeth(a, b, c));
|
||||
default:
|
||||
throw new InvalidOperationException("Unsupported Filter");
|
||||
}
|
||||
}
|
||||
|
||||
static byte Paeth(byte a, byte b, byte c)
|
||||
{
|
||||
var p = a + b - c;
|
||||
var pa = Math.Abs(p - a);
|
||||
var pb = Math.Abs(p - b);
|
||||
var pc = Math.Abs(p - c);
|
||||
|
||||
return (pa <= pb && pa <= pc) ? a :
|
||||
(pb <= pc) ? b : c;
|
||||
}
|
||||
|
||||
[Flags]
|
||||
enum PngColorType { Indexed = 1, Color = 2, Alpha = 4 }
|
||||
enum PngFilter { None, Sub, Up, Average, Paeth }
|
||||
|
||||
static bool IsPaletted(byte bitDepth, PngColorType colorType)
|
||||
{
|
||||
if (bitDepth == 8 && colorType == (PngColorType.Indexed | PngColorType.Color))
|
||||
return true;
|
||||
|
||||
if (bitDepth == 8 && colorType == (PngColorType.Color | PngColorType.Alpha))
|
||||
return false;
|
||||
|
||||
if (bitDepth == 8 && colorType == PngColorType.Color)
|
||||
return false;
|
||||
|
||||
throw new InvalidDataException("Unknown pixel format");
|
||||
}
|
||||
|
||||
void WritePngChunk(Stream output, string type, Stream input)
|
||||
{
|
||||
input.Position = 0;
|
||||
|
||||
var typeBytes = Encoding.ASCII.GetBytes(type);
|
||||
output.Write(IPAddress.HostToNetworkOrder((int)input.Length));
|
||||
output.WriteArray(typeBytes);
|
||||
|
||||
var data = input.ReadAllBytes();
|
||||
output.WriteArray(data);
|
||||
|
||||
var crc32 = new Crc32();
|
||||
crc32.Update(typeBytes);
|
||||
crc32.Update(data);
|
||||
output.Write(IPAddress.NetworkToHostOrder((int)crc32.Value));
|
||||
}
|
||||
|
||||
public byte[] Save()
|
||||
{
|
||||
using (var output = new MemoryStream())
|
||||
{
|
||||
output.WriteArray(Signature);
|
||||
using (var header = new MemoryStream())
|
||||
{
|
||||
header.Write(IPAddress.HostToNetworkOrder(Width));
|
||||
header.Write(IPAddress.HostToNetworkOrder(Height));
|
||||
header.WriteByte(8); // Bit depth
|
||||
|
||||
var colorType = Palette != null
|
||||
? PngColorType.Indexed | PngColorType.Color
|
||||
: PngColorType.Color | PngColorType.Alpha;
|
||||
header.WriteByte((byte)colorType);
|
||||
|
||||
header.WriteByte(0); // Compression
|
||||
header.WriteByte(0); // Filter
|
||||
header.WriteByte(0); // Interlacing
|
||||
|
||||
WritePngChunk(output, "IHDR", header);
|
||||
}
|
||||
|
||||
bool alphaPalette = false;
|
||||
if (Palette != null)
|
||||
{
|
||||
using (var palette = new MemoryStream())
|
||||
{
|
||||
foreach (var c in Palette)
|
||||
{
|
||||
palette.WriteByte(c.R);
|
||||
palette.WriteByte(c.G);
|
||||
palette.WriteByte(c.B);
|
||||
alphaPalette |= c.A > 0;
|
||||
}
|
||||
|
||||
WritePngChunk(output, "PLTE", palette);
|
||||
}
|
||||
}
|
||||
|
||||
if (alphaPalette)
|
||||
{
|
||||
using (var alpha = new MemoryStream())
|
||||
{
|
||||
foreach (var c in Palette)
|
||||
alpha.WriteByte(c.A);
|
||||
|
||||
WritePngChunk(output, "tRNS", alpha);
|
||||
}
|
||||
}
|
||||
|
||||
using (var data = new MemoryStream())
|
||||
{
|
||||
using (var compressed = new DeflaterOutputStream(data))
|
||||
{
|
||||
var stride = Width * (Palette != null ? 1 : 4);
|
||||
for (var y = 0; y < Height; y++)
|
||||
{
|
||||
// Write uncompressed scanlines for simplicity
|
||||
compressed.WriteByte(0);
|
||||
compressed.Write(Data, y * stride, stride);
|
||||
}
|
||||
|
||||
compressed.Flush();
|
||||
compressed.Finish();
|
||||
|
||||
WritePngChunk(output, "IDAT", data);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var kv in EmbeddedData)
|
||||
{
|
||||
using (var text = new MemoryStream())
|
||||
{
|
||||
text.WriteArray(Encoding.ASCII.GetBytes(kv.Key + (char)0 + kv.Value));
|
||||
WritePngChunk(output, "tEXt", text);
|
||||
}
|
||||
}
|
||||
|
||||
WritePngChunk(output, "IEND", new MemoryStream());
|
||||
return output.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
public void Save(string path)
|
||||
{
|
||||
File.WriteAllBytes(path, Save());
|
||||
}
|
||||
}
|
||||
}
|
||||
206
OpenRA.Game/FileFormats/PngLoader.cs
Normal file
206
OpenRA.Game/FileFormats/PngLoader.cs
Normal file
@@ -0,0 +1,206 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Imaging;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Net;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace OpenRA.FileFormats
|
||||
{
|
||||
public static class PngLoader
|
||||
{
|
||||
public static Bitmap Load(string filename)
|
||||
{
|
||||
using (var s = File.OpenRead(filename))
|
||||
return Load(s);
|
||||
}
|
||||
|
||||
public static Bitmap Load(Stream s)
|
||||
{
|
||||
using (var br = new BinaryReader(s))
|
||||
{
|
||||
var signature = new byte[] { 137, 80, 78, 71, 13, 10, 26, 10 };
|
||||
foreach (var b in signature)
|
||||
if (br.ReadByte() != b)
|
||||
throw new InvalidDataException("PNG Signature is bogus");
|
||||
|
||||
Bitmap bitmap = null;
|
||||
Color[] palette = null;
|
||||
var data = new List<byte>();
|
||||
|
||||
try
|
||||
{
|
||||
for (;;)
|
||||
{
|
||||
var length = IPAddress.NetworkToHostOrder(br.ReadInt32());
|
||||
var type = Encoding.UTF8.GetString(br.ReadBytes(4));
|
||||
var content = br.ReadBytes(length);
|
||||
/*var crc = */br.ReadInt32();
|
||||
|
||||
if (bitmap == null && type != "IHDR")
|
||||
throw new InvalidDataException("Invalid PNG file - header does not appear first.");
|
||||
|
||||
using (var ms = new MemoryStream(content))
|
||||
using (var cr = new BinaryReader(ms))
|
||||
switch (type)
|
||||
{
|
||||
case "IHDR":
|
||||
{
|
||||
if (bitmap != null)
|
||||
throw new InvalidDataException("Invalid PNG file - duplicate header.");
|
||||
|
||||
var width = IPAddress.NetworkToHostOrder(cr.ReadInt32());
|
||||
var height = IPAddress.NetworkToHostOrder(cr.ReadInt32());
|
||||
var bitDepth = cr.ReadByte();
|
||||
var colorType = (PngColorType)cr.ReadByte();
|
||||
var compression = cr.ReadByte();
|
||||
/*var filter = */cr.ReadByte();
|
||||
var interlace = cr.ReadByte();
|
||||
|
||||
if (compression != 0) throw new InvalidDataException("Compression method not supported");
|
||||
if (interlace != 0) throw new InvalidDataException("Interlacing not supported");
|
||||
|
||||
bitmap = new Bitmap(width, height, MakePixelFormat(bitDepth, colorType));
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case "PLTE":
|
||||
{
|
||||
palette = new Color[256];
|
||||
for (var i = 0; i < 256; i++)
|
||||
{
|
||||
var r = cr.ReadByte(); var g = cr.ReadByte(); var b = cr.ReadByte();
|
||||
palette[i] = Color.FromArgb(r, g, b);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case "tRNS":
|
||||
{
|
||||
if (palette == null)
|
||||
throw new InvalidDataException("Non-Palette indexed PNG are not supported.");
|
||||
|
||||
for (var i = 0; i < length; i++)
|
||||
palette[i] = Color.FromArgb(cr.ReadByte(), palette[i]);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case "IDAT":
|
||||
{
|
||||
data.AddRange(content);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case "IEND":
|
||||
{
|
||||
var bits = bitmap.LockBits(bitmap.Bounds(),
|
||||
ImageLockMode.WriteOnly, PixelFormat.Format8bppIndexed);
|
||||
|
||||
using (var ns = new MemoryStream(data.ToArray()))
|
||||
{
|
||||
// 'zlib' flags bytes; confuses the DeflateStream.
|
||||
/*var flags = (byte)*/ns.ReadByte();
|
||||
/*var moreFlags = (byte)*/ns.ReadByte();
|
||||
|
||||
using (var ds = new DeflateStream(ns, CompressionMode.Decompress))
|
||||
using (var dr = new BinaryReader(ds))
|
||||
{
|
||||
var prevLine = new byte[bitmap.Width]; // all zero
|
||||
for (var y = 0; y < bitmap.Height; y++)
|
||||
{
|
||||
var filter = (PngFilter)dr.ReadByte();
|
||||
var line = dr.ReadBytes(bitmap.Width);
|
||||
|
||||
for (var i = 0; i < bitmap.Width; i++)
|
||||
line[i] = i > 0
|
||||
? UnapplyFilter(filter, line[i], line[i - 1], prevLine[i], prevLine[i - 1])
|
||||
: UnapplyFilter(filter, line[i], 0, prevLine[i], 0);
|
||||
|
||||
Marshal.Copy(line, 0, new IntPtr(bits.Scan0.ToInt64() + y * bits.Stride), line.Length);
|
||||
prevLine = line;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bitmap.UnlockBits(bits);
|
||||
|
||||
if (palette == null)
|
||||
throw new InvalidDataException("Non-Palette indexed PNG are not supported.");
|
||||
|
||||
using (var temp = new Bitmap(1, 1, PixelFormat.Format8bppIndexed))
|
||||
{
|
||||
var cp = temp.Palette;
|
||||
for (var i = 0; i < 256; i++)
|
||||
cp.Entries[i] = palette[i]; // finalize the palette.
|
||||
bitmap.Palette = cp;
|
||||
return bitmap;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
if (bitmap != null)
|
||||
bitmap.Dispose();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static byte UnapplyFilter(PngFilter f, byte x, byte a, byte b, byte c)
|
||||
{
|
||||
switch (f)
|
||||
{
|
||||
case PngFilter.None: return x;
|
||||
case PngFilter.Sub: return (byte)(x + a);
|
||||
case PngFilter.Up: return (byte)(x + b);
|
||||
case PngFilter.Average: return (byte)(x + (a + b) / 2);
|
||||
case PngFilter.Paeth: return (byte)(x + Paeth(a, b, c));
|
||||
default:
|
||||
throw new InvalidOperationException("Unsupported Filter");
|
||||
}
|
||||
}
|
||||
|
||||
static byte Paeth(byte a, byte b, byte c)
|
||||
{
|
||||
var p = a + b - c;
|
||||
var pa = Math.Abs(p - a);
|
||||
var pb = Math.Abs(p - b);
|
||||
var pc = Math.Abs(p - c);
|
||||
|
||||
return (pa <= pb && pa <= pc) ? a :
|
||||
(pb <= pc) ? b : c;
|
||||
}
|
||||
|
||||
[Flags]
|
||||
enum PngColorType { Indexed = 1, Color = 2, Alpha = 4 }
|
||||
enum PngFilter { None, Sub, Up, Average, Paeth }
|
||||
|
||||
static PixelFormat MakePixelFormat(byte bitDepth, PngColorType colorType)
|
||||
{
|
||||
if (bitDepth == 8 && colorType == (PngColorType.Indexed | PngColorType.Color))
|
||||
return PixelFormat.Format8bppIndexed;
|
||||
|
||||
throw new InvalidDataException("Unknown pixel format");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
@@ -79,27 +79,45 @@ namespace OpenRA.FileFormats
|
||||
|
||||
public static ReplayMetadata Read(string path)
|
||||
{
|
||||
using (var fs = new FileStream(path, FileMode.Open))
|
||||
return Read(fs, path);
|
||||
}
|
||||
|
||||
static ReplayMetadata Read(FileStream fs, string path)
|
||||
{
|
||||
if (!fs.CanSeek)
|
||||
return null;
|
||||
|
||||
if (fs.Length < 20)
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
using (var fs = new FileStream(path, FileMode.Open))
|
||||
fs.Seek(-(4 + 4), SeekOrigin.End);
|
||||
var dataLength = fs.ReadInt32();
|
||||
if (fs.ReadInt32() == MetaEndMarker)
|
||||
{
|
||||
if (!fs.CanSeek)
|
||||
return null;
|
||||
|
||||
if (fs.Length < 20)
|
||||
return null;
|
||||
|
||||
fs.Seek(-(4 + 4), SeekOrigin.End);
|
||||
var dataLength = fs.ReadInt32();
|
||||
if (fs.ReadInt32() == MetaEndMarker)
|
||||
// go back by (end marker + length storage + data + version + start marker) bytes
|
||||
fs.Seek(-(4 + 4 + dataLength + 4 + 4), SeekOrigin.Current);
|
||||
try
|
||||
{
|
||||
// Go back by (end marker + length storage + data + version + start marker) bytes
|
||||
fs.Seek(-(4 + 4 + dataLength + 4 + 4), SeekOrigin.Current);
|
||||
return new ReplayMetadata(fs, path);
|
||||
}
|
||||
catch (YamlException ex)
|
||||
{
|
||||
Log.Write("debug", ex.ToString());
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
Log.Write("debug", ex.ToString());
|
||||
}
|
||||
catch (NotSupportedException ex)
|
||||
{
|
||||
Log.Write("debug", ex.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
catch (IOException ex)
|
||||
{
|
||||
Log.Write("debug", ex.ToString());
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
@@ -13,9 +13,8 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.Mods.Cnc.AudioLoaders
|
||||
namespace OpenRA.FileFormats
|
||||
{
|
||||
public class VocLoader : ISoundLoader
|
||||
{
|
||||
@@ -36,24 +35,22 @@ namespace OpenRA.Mods.Cnc.AudioLoaders
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class VocFormat : ISoundFormat
|
||||
public class VocFormat : ISoundFormat
|
||||
{
|
||||
public int SampleBits { get { return 8; } }
|
||||
public int Channels { get { return 1; } }
|
||||
public int SampleRate { get; private set; }
|
||||
public float LengthInSeconds { get { return (float)totalSamples / SampleRate; } }
|
||||
public Stream GetPCMInputStream() { return new VocStream(new VocFormat(this)); }
|
||||
public void Dispose() { stream.Dispose(); }
|
||||
public Stream GetPCMInputStream() { return new VocStream(this); }
|
||||
|
||||
readonly byte[] buffer = new byte[4096];
|
||||
readonly Stream stream;
|
||||
readonly VocBlock[] blocks;
|
||||
readonly int totalSamples;
|
||||
int totalSamples = 0;
|
||||
int samplePosition = 0;
|
||||
|
||||
Stream stream;
|
||||
List<VocBlock> blocks = new List<VocBlock>();
|
||||
IEnumerator<VocBlock> currentBlock;
|
||||
bool currentBlockEnded;
|
||||
int samplesLeftInBlock;
|
||||
int samplePosition;
|
||||
int samplesLeftInBlock = 0;
|
||||
byte[] buffer = new byte[4096];
|
||||
|
||||
struct VocFileHeader
|
||||
{
|
||||
@@ -97,22 +94,11 @@ namespace OpenRA.Mods.Cnc.AudioLoaders
|
||||
{
|
||||
this.stream = stream;
|
||||
|
||||
CheckVocHeader(stream);
|
||||
Preload(stream, out blocks, out totalSamples, out var sampleRate);
|
||||
SampleRate = sampleRate;
|
||||
Rewind();
|
||||
CheckVocHeader();
|
||||
Preload();
|
||||
}
|
||||
|
||||
VocFormat(VocFormat cloneFrom)
|
||||
{
|
||||
SampleRate = cloneFrom.SampleRate;
|
||||
stream = SegmentStream.CreateWithoutOwningStream(cloneFrom.stream, 0, (int)cloneFrom.stream.Length);
|
||||
blocks = cloneFrom.blocks;
|
||||
totalSamples = cloneFrom.totalSamples;
|
||||
Rewind();
|
||||
}
|
||||
|
||||
static void CheckVocHeader(Stream stream)
|
||||
void CheckVocHeader()
|
||||
{
|
||||
var vfh = VocFileHeader.Read(stream);
|
||||
|
||||
@@ -120,14 +106,14 @@ namespace OpenRA.Mods.Cnc.AudioLoaders
|
||||
throw new InvalidDataException("Voc header description not recognized");
|
||||
if (vfh.DatablockOffset != 26)
|
||||
throw new InvalidDataException("Voc header offset is wrong");
|
||||
if (vfh.Version < 0x0100 || vfh.Version >= 0x0200)
|
||||
throw new InvalidDataException("Voc header version " + vfh.Version.ToString("X") + " not supported");
|
||||
if (vfh.Version != 0x010A)
|
||||
throw new InvalidDataException("Voc header version not recognized");
|
||||
if (vfh.ID != ~vfh.Version + 0x1234)
|
||||
throw new InvalidDataException("Voc header id is bogus - expected: " +
|
||||
(~vfh.Version + 0x1234).ToString("X") + " but value is : " + vfh.ID.ToString("X"));
|
||||
}
|
||||
|
||||
static int GetSampleRateFromVocRate(int vocSampleRate)
|
||||
int GetSampleRateFromVocRate(int vocSampleRate)
|
||||
{
|
||||
if (vocSampleRate == 256)
|
||||
throw new InvalidDataException("Invalid frequency divisor 256 in voc file");
|
||||
@@ -139,15 +125,11 @@ namespace OpenRA.Mods.Cnc.AudioLoaders
|
||||
return (int)(1000000L / (256L - vocSampleRate));
|
||||
}
|
||||
|
||||
static void Preload(Stream stream, out VocBlock[] blocks, out int totalSamples, out int sampleRate)
|
||||
void Preload()
|
||||
{
|
||||
var blockList = new List<VocBlock>();
|
||||
totalSamples = 0;
|
||||
sampleRate = 0;
|
||||
|
||||
while (true)
|
||||
{
|
||||
var block = default(VocBlock);
|
||||
VocBlock block = new VocBlock();
|
||||
try
|
||||
{
|
||||
block.Code = stream.ReadByte();
|
||||
@@ -184,17 +166,17 @@ namespace OpenRA.Mods.Cnc.AudioLoaders
|
||||
block.SampleBlock.Offset = stream.Position;
|
||||
|
||||
// See if last block contained additional information
|
||||
if (blockList.Count > 0)
|
||||
if (blocks.Count > 0)
|
||||
{
|
||||
var b = blockList.Last();
|
||||
var b = blocks.Last();
|
||||
if (b.Code == 8)
|
||||
{
|
||||
block.SampleBlock.Rate = b.SampleBlock.Rate;
|
||||
blockList.Remove(b);
|
||||
blocks.Remove(b);
|
||||
}
|
||||
}
|
||||
|
||||
sampleRate = Math.Max(sampleRate, block.SampleBlock.Rate);
|
||||
SampleRate = Math.Max(SampleRate, block.SampleBlock.Rate);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -251,28 +233,27 @@ namespace OpenRA.Mods.Cnc.AudioLoaders
|
||||
|
||||
if (skip > 0)
|
||||
stream.Seek(skip, SeekOrigin.Current);
|
||||
blockList.Add(block);
|
||||
blocks.Add(block);
|
||||
}
|
||||
|
||||
// Check validity and calculated total number of samples
|
||||
foreach (var b in blockList)
|
||||
foreach (var b in blocks)
|
||||
{
|
||||
if (b.Code == 8)
|
||||
throw new InvalidDataException("Unused block 8 in voc file");
|
||||
if (b.Code != 1 && b.Code != 9)
|
||||
continue;
|
||||
if (b.SampleBlock.Rate != sampleRate)
|
||||
if (b.SampleBlock.Rate != SampleRate)
|
||||
throw new InvalidDataException("Voc file contains chunks with different sample rate");
|
||||
totalSamples += b.SampleBlock.Samples;
|
||||
}
|
||||
|
||||
blocks = blockList.ToArray();
|
||||
Rewind();
|
||||
}
|
||||
|
||||
void Rewind()
|
||||
{
|
||||
currentBlock = ((IEnumerable<VocBlock>)blocks).GetEnumerator();
|
||||
currentBlockEnded = false;
|
||||
currentBlock = blocks.GetEnumerator();
|
||||
samplesLeftInBlock = 0;
|
||||
samplePosition = 0;
|
||||
|
||||
@@ -285,11 +266,9 @@ namespace OpenRA.Mods.Cnc.AudioLoaders
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
currentBlockEnded = true;
|
||||
}
|
||||
|
||||
bool EndOfData { get { return currentBlockEnded && samplesLeftInBlock == 0; } }
|
||||
bool EndOfData { get { return currentBlock.Current.Equals(blocks.Last()) && samplesLeftInBlock == 0; } }
|
||||
|
||||
int FillBuffer(int maxSamples)
|
||||
{
|
||||
@@ -327,8 +306,6 @@ namespace OpenRA.Mods.Cnc.AudioLoaders
|
||||
samplesLeftInBlock = currentBlock.Current.SampleBlock.Samples;
|
||||
return;
|
||||
}
|
||||
|
||||
currentBlockEnded = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -378,13 +355,6 @@ namespace OpenRA.Mods.Cnc.AudioLoaders
|
||||
public override long Seek(long offset, SeekOrigin origin) { throw new NotImplementedException(); }
|
||||
public override void SetLength(long value) { throw new NotImplementedException(); }
|
||||
public override void Write(byte[] buffer, int offset, int count) { throw new NotImplementedException(); }
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
format.Dispose();
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,19 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
|
||||
#endregion
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace OpenRA.Mods.Cnc.FileFormats
|
||||
namespace OpenRA.FileFormats
|
||||
{
|
||||
public enum NormalType { TiberianSun = 2, RedAlert2 = 4 }
|
||||
public class VxlElement
|
||||
@@ -66,8 +67,7 @@ namespace OpenRA.Mods.Cnc.FileFormats
|
||||
z += count;
|
||||
l.VoxelCount += count;
|
||||
s.Seek(2 * count + 1, SeekOrigin.Current);
|
||||
}
|
||||
while (z < l.Size[2]);
|
||||
} while (z < l.Size[2]);
|
||||
}
|
||||
|
||||
// Read the data
|
||||
@@ -100,8 +100,7 @@ namespace OpenRA.Mods.Cnc.FileFormats
|
||||
|
||||
// Skip duplicate count
|
||||
s.ReadUInt8();
|
||||
}
|
||||
while (z < l.Size[2]);
|
||||
} while (z < l.Size[2]);
|
||||
}
|
||||
}
|
||||
|
||||
248
OpenRA.Game/FileFormats/WavLoader.cs
Normal file
248
OpenRA.Game/FileFormats/WavLoader.cs
Normal file
@@ -0,0 +1,248 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace OpenRA.FileFormats
|
||||
{
|
||||
public class WavLoader : ISoundLoader
|
||||
{
|
||||
bool IsWave(Stream s)
|
||||
{
|
||||
var start = s.Position;
|
||||
var type = s.ReadASCII(4);
|
||||
s.Position += 4;
|
||||
var format = s.ReadASCII(4);
|
||||
s.Position = start;
|
||||
|
||||
return type == "RIFF" && format == "WAVE";
|
||||
}
|
||||
|
||||
bool ISoundLoader.TryParseSound(Stream stream, out ISoundFormat sound)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (IsWave(stream))
|
||||
{
|
||||
sound = new WavFormat(stream);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Not a (supported) WAV
|
||||
}
|
||||
|
||||
sound = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public class WavFormat : ISoundFormat
|
||||
{
|
||||
public int Channels { get { return reader.Value.Channels; } }
|
||||
public int SampleBits { get { return reader.Value.BitsPerSample; } }
|
||||
public int SampleRate { get { return reader.Value.SampleRate; } }
|
||||
public float LengthInSeconds { get { return WavReader.WaveLength(stream); } }
|
||||
public Stream GetPCMInputStream() { return new MemoryStream(reader.Value.RawOutput); }
|
||||
|
||||
Lazy<WavReader> reader;
|
||||
|
||||
readonly Stream stream;
|
||||
|
||||
public WavFormat(Stream stream)
|
||||
{
|
||||
this.stream = stream;
|
||||
|
||||
var position = stream.Position;
|
||||
reader = Exts.Lazy(() =>
|
||||
{
|
||||
var wavReader = new WavReader();
|
||||
try
|
||||
{
|
||||
if (!wavReader.LoadSound(stream))
|
||||
throw new InvalidDataException();
|
||||
}
|
||||
finally
|
||||
{
|
||||
stream.Position = position;
|
||||
}
|
||||
|
||||
return wavReader;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public class WavReader
|
||||
{
|
||||
public int FileSize;
|
||||
public string Format;
|
||||
|
||||
public int FmtChunkSize;
|
||||
public int AudioFormat;
|
||||
public int Channels;
|
||||
public int SampleRate;
|
||||
public int ByteRate;
|
||||
public int BlockAlign;
|
||||
public int BitsPerSample;
|
||||
|
||||
public int UncompressedSize;
|
||||
public int DataSize;
|
||||
public byte[] RawOutput;
|
||||
|
||||
public enum WaveType { Pcm = 0x1, ImaAdpcm = 0x11 }
|
||||
public static WaveType Type { get; private set; }
|
||||
|
||||
public bool LoadSound(Stream s)
|
||||
{
|
||||
var type = s.ReadASCII(4);
|
||||
if (type != "RIFF")
|
||||
return false;
|
||||
|
||||
FileSize = s.ReadInt32();
|
||||
Format = s.ReadASCII(4);
|
||||
if (Format != "WAVE")
|
||||
return false;
|
||||
while (s.Position < s.Length)
|
||||
{
|
||||
if ((s.Position & 1) == 1)
|
||||
s.ReadByte(); // Alignment
|
||||
|
||||
type = s.ReadASCII(4);
|
||||
switch (type)
|
||||
{
|
||||
case "fmt ":
|
||||
FmtChunkSize = s.ReadInt32();
|
||||
AudioFormat = s.ReadInt16();
|
||||
Type = (WaveType)AudioFormat;
|
||||
|
||||
if (!Enum.IsDefined(typeof(WaveType), Type))
|
||||
throw new NotSupportedException("Compression type {0} is not supported.".F(AudioFormat));
|
||||
|
||||
Channels = s.ReadInt16();
|
||||
SampleRate = s.ReadInt32();
|
||||
ByteRate = s.ReadInt32();
|
||||
BlockAlign = s.ReadInt16();
|
||||
BitsPerSample = s.ReadInt16();
|
||||
|
||||
s.ReadBytes(FmtChunkSize - 16);
|
||||
break;
|
||||
case "fact":
|
||||
var chunkSize = s.ReadInt32();
|
||||
UncompressedSize = s.ReadInt32();
|
||||
s.ReadBytes(chunkSize - 4);
|
||||
break;
|
||||
case "data":
|
||||
DataSize = s.ReadInt32();
|
||||
RawOutput = s.ReadBytes(DataSize);
|
||||
break;
|
||||
default:
|
||||
var unknownChunkSize = s.ReadInt32();
|
||||
s.ReadBytes(unknownChunkSize);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (Type == WaveType.ImaAdpcm)
|
||||
{
|
||||
RawOutput = DecodeImaAdpcmData();
|
||||
BitsPerSample = 16;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static float WaveLength(Stream s)
|
||||
{
|
||||
s.Position = 12;
|
||||
var fmt = s.ReadASCII(4);
|
||||
|
||||
if (fmt != "fmt ")
|
||||
return 0;
|
||||
|
||||
s.Position = 22;
|
||||
var channels = s.ReadInt16();
|
||||
var sampleRate = s.ReadInt32();
|
||||
|
||||
s.Position = 34;
|
||||
var bitsPerSample = s.ReadInt16();
|
||||
var length = s.Length * 8;
|
||||
|
||||
return length / (channels * sampleRate * bitsPerSample);
|
||||
}
|
||||
|
||||
public byte[] DecodeImaAdpcmData()
|
||||
{
|
||||
var s = new MemoryStream(RawOutput);
|
||||
|
||||
var numBlocks = DataSize / BlockAlign;
|
||||
var blockDataSize = BlockAlign - (Channels * 4);
|
||||
var outputSize = UncompressedSize * Channels * 2;
|
||||
|
||||
var outOffset = 0;
|
||||
var output = new byte[outputSize];
|
||||
|
||||
var predictor = new int[Channels];
|
||||
var index = new int[Channels];
|
||||
|
||||
// Decode each block of IMA ADPCM data in RawOutput
|
||||
for (var block = 0; block < numBlocks; block++)
|
||||
{
|
||||
// Each block starts with a initial state per-channel
|
||||
for (var c = 0; c < Channels; c++)
|
||||
{
|
||||
predictor[c] = s.ReadInt16();
|
||||
index[c] = s.ReadUInt8();
|
||||
/* unknown/reserved */ s.ReadUInt8();
|
||||
|
||||
// Output first sample from input
|
||||
output[outOffset++] = (byte)predictor[c];
|
||||
output[outOffset++] = (byte)(predictor[c] >> 8);
|
||||
|
||||
if (outOffset >= outputSize)
|
||||
return output;
|
||||
}
|
||||
|
||||
// Decode and output remaining data in this block
|
||||
var blockOffset = 0;
|
||||
while (blockOffset < blockDataSize)
|
||||
{
|
||||
for (var c = 0; c < Channels; c++)
|
||||
{
|
||||
// Decode 4 bytes (to 16 bytes of output) per channel
|
||||
var chunk = s.ReadBytes(4);
|
||||
var decoded = ImaAdpcmLoader.LoadImaAdpcmSound(chunk, ref index[c], ref predictor[c]);
|
||||
|
||||
// Interleave output, one sample per channel
|
||||
var outOffsetChannel = outOffset + (2 * c);
|
||||
for (var i = 0; i < decoded.Length; i += 2)
|
||||
{
|
||||
var outOffsetSample = outOffsetChannel + i;
|
||||
if (outOffsetSample >= outputSize)
|
||||
return output;
|
||||
|
||||
output[outOffsetSample] = decoded[i];
|
||||
output[outOffsetSample + 1] = decoded[i + 1];
|
||||
outOffsetChannel += 2 * (Channels - 1);
|
||||
}
|
||||
|
||||
blockOffset += 4;
|
||||
}
|
||||
|
||||
outOffset += 16 * Channels;
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
}
|
||||
}
|
||||
44
OpenRA.Game/FileFormats/XccGlobalDatabase.cs
Normal file
44
OpenRA.Game/FileFormats/XccGlobalDatabase.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace OpenRA.FileFormats
|
||||
{
|
||||
public class XccGlobalDatabase
|
||||
{
|
||||
public readonly string[] Entries;
|
||||
public XccGlobalDatabase(Stream s)
|
||||
{
|
||||
var entries = new List<string>();
|
||||
while (s.Peek() > -1)
|
||||
{
|
||||
var count = s.ReadInt32();
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var chars = new List<char>();
|
||||
byte c;
|
||||
|
||||
// Read filename
|
||||
while ((c = s.ReadUInt8()) != 0)
|
||||
chars.Add((char)c);
|
||||
entries.Add(new string(chars.ToArray()));
|
||||
|
||||
// Skip comment
|
||||
while ((c = s.ReadUInt8()) != 0) { }
|
||||
}
|
||||
}
|
||||
|
||||
Entries = entries.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
@@ -14,7 +14,7 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace OpenRA.Mods.Cnc.FileFormats
|
||||
namespace OpenRA.FileFormats
|
||||
{
|
||||
public class XccLocalDatabase
|
||||
{
|
||||
@@ -26,15 +26,14 @@ namespace OpenRA.Mods.Cnc.FileFormats
|
||||
var reader = new BinaryReader(s);
|
||||
var count = reader.ReadInt32();
|
||||
Entries = new string[count];
|
||||
var chars = new List<char>();
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var chars = new List<char>();
|
||||
char c;
|
||||
while ((c = reader.ReadChar()) != 0)
|
||||
chars.Add(c);
|
||||
|
||||
Entries[i] = new string(chars.ToArray());
|
||||
chars.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,7 +50,7 @@ namespace OpenRA.Mods.Cnc.FileFormats
|
||||
writer.Write(Encoding.ASCII.GetBytes("XCC by Olaf van der Spek"));
|
||||
writer.Write(new byte[] { 0x1A, 0x04, 0x17, 0x27, 0x10, 0x19, 0x80, 0x00 });
|
||||
|
||||
writer.Write(Entries.Sum(e => e.Length) + Entries.Length + 52); // Size
|
||||
writer.Write(Entries.Aggregate(Entries.Length, (a, b) => a + b.Length) + 52); // Size
|
||||
writer.Write(0); // Type
|
||||
writer.Write(0); // Version
|
||||
writer.Write(0); // Game/Format (0 == TD)
|
||||
127
OpenRA.Game/FileSystem/BagFile.cs
Normal file
127
OpenRA.Game/FileSystem/BagFile.cs
Normal file
@@ -0,0 +1,127 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using OpenRA.FileFormats;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.FileSystem
|
||||
{
|
||||
public sealed class BagFile : IReadOnlyPackage
|
||||
{
|
||||
public string Name { get; private set; }
|
||||
public IEnumerable<string> Contents { get { return index.Keys; } }
|
||||
|
||||
readonly Stream s;
|
||||
readonly Dictionary<string, IdxEntry> index;
|
||||
|
||||
public BagFile(FileSystem context, string filename)
|
||||
{
|
||||
Name = filename;
|
||||
|
||||
// A bag file is always accompanied with an .idx counterpart
|
||||
// For example: audio.bag requires the audio.idx file
|
||||
var indexFilename = Path.ChangeExtension(filename, ".idx");
|
||||
|
||||
// Build the index and dispose the stream, it is no longer needed after this
|
||||
List<IdxEntry> entries;
|
||||
using (var indexStream = context.Open(indexFilename))
|
||||
entries = new IdxReader(indexStream).Entries;
|
||||
|
||||
index = entries.ToDictionaryWithConflictLog(x => x.Filename,
|
||||
"{0} (bag format)".F(filename),
|
||||
null, x => "(offs={0}, len={1})".F(x.Offset, x.Length));
|
||||
|
||||
s = context.Open(filename);
|
||||
}
|
||||
|
||||
public Stream GetStream(string filename)
|
||||
{
|
||||
IdxEntry entry;
|
||||
if (!index.TryGetValue(filename, out entry))
|
||||
return null;
|
||||
|
||||
s.Seek(entry.Offset, SeekOrigin.Begin);
|
||||
|
||||
var waveHeaderMemoryStream = new MemoryStream();
|
||||
|
||||
var channels = (entry.Flags & 1) > 0 ? 2 : 1;
|
||||
|
||||
if ((entry.Flags & 2) > 0)
|
||||
{
|
||||
// PCM
|
||||
waveHeaderMemoryStream.Write(Encoding.ASCII.GetBytes("RIFF"));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes(entry.Length + 36));
|
||||
waveHeaderMemoryStream.Write(Encoding.ASCII.GetBytes("WAVE"));
|
||||
waveHeaderMemoryStream.Write(Encoding.ASCII.GetBytes("fmt "));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes(16));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes((short)WavReader.WaveType.Pcm));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes((short)channels));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes(entry.SampleRate));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes(2 * channels * entry.SampleRate));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes((short)(2 * channels)));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes((short)16));
|
||||
waveHeaderMemoryStream.Write(Encoding.ASCII.GetBytes("data"));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes(entry.Length));
|
||||
}
|
||||
|
||||
if ((entry.Flags & 8) > 0)
|
||||
{
|
||||
// IMA ADPCM
|
||||
var samplesPerChunk = (2 * (entry.ChunkSize - 4)) + 1;
|
||||
var bytesPerSec = (int)Math.Floor(((double)(2 * entry.ChunkSize) / samplesPerChunk) * ((double)entry.SampleRate / 2));
|
||||
var chunkSize = entry.ChunkSize > entry.Length ? entry.Length : entry.ChunkSize;
|
||||
|
||||
waveHeaderMemoryStream.Write(Encoding.ASCII.GetBytes("RIFF"));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes(entry.Length + 52));
|
||||
waveHeaderMemoryStream.Write(Encoding.ASCII.GetBytes("WAVE"));
|
||||
waveHeaderMemoryStream.Write(Encoding.ASCII.GetBytes("fmt "));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes(20));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes((short)WavReader.WaveType.ImaAdpcm));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes((short)channels));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes(entry.SampleRate));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes(bytesPerSec));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes((short)chunkSize));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes((short)4));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes((short)2));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes((short)samplesPerChunk));
|
||||
waveHeaderMemoryStream.Write(Encoding.ASCII.GetBytes("fact"));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes(4));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes(4 * entry.Length));
|
||||
waveHeaderMemoryStream.Write(Encoding.ASCII.GetBytes("data"));
|
||||
waveHeaderMemoryStream.Write(BitConverter.GetBytes(entry.Length));
|
||||
}
|
||||
|
||||
waveHeaderMemoryStream.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
// Construct a merged stream
|
||||
var mergedStream = new MergedStream(waveHeaderMemoryStream, s);
|
||||
mergedStream.SetLength(waveHeaderMemoryStream.Length + entry.Length);
|
||||
|
||||
return mergedStream;
|
||||
}
|
||||
|
||||
public bool Contains(string filename)
|
||||
{
|
||||
return index.ContainsKey(filename);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
s.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
105
OpenRA.Game/FileSystem/BigFile.cs
Normal file
105
OpenRA.Game/FileSystem/BigFile.cs
Normal file
@@ -0,0 +1,105 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace OpenRA.FileSystem
|
||||
{
|
||||
public sealed class BigFile : IReadOnlyPackage
|
||||
{
|
||||
public string Name { get; private set; }
|
||||
public IEnumerable<string> Contents { get { return index.Keys; } }
|
||||
|
||||
readonly Dictionary<string, Entry> index = new Dictionary<string, Entry>();
|
||||
readonly Stream s;
|
||||
|
||||
public BigFile(FileSystem context, string filename)
|
||||
{
|
||||
Name = filename;
|
||||
|
||||
s = context.Open(filename);
|
||||
try
|
||||
{
|
||||
if (s.ReadASCII(4) != "BIGF")
|
||||
throw new InvalidDataException("Header is not BIGF");
|
||||
|
||||
// Total archive size.
|
||||
s.ReadUInt32();
|
||||
|
||||
var entryCount = s.ReadUInt32();
|
||||
if (BitConverter.IsLittleEndian)
|
||||
entryCount = int2.Swap(entryCount);
|
||||
|
||||
// First entry offset? This is apparently bogus for EA's .big files
|
||||
// and we don't have to try seeking there since the entries typically start next in EA's .big files.
|
||||
s.ReadUInt32();
|
||||
|
||||
for (var i = 0; i < entryCount; i++)
|
||||
{
|
||||
var entry = new Entry(s);
|
||||
index.Add(entry.Path, entry);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
Dispose();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
class Entry
|
||||
{
|
||||
readonly Stream s;
|
||||
readonly uint offset;
|
||||
readonly uint size;
|
||||
public readonly string Path;
|
||||
|
||||
public Entry(Stream s)
|
||||
{
|
||||
this.s = s;
|
||||
|
||||
offset = s.ReadUInt32();
|
||||
size = s.ReadUInt32();
|
||||
if (BitConverter.IsLittleEndian)
|
||||
{
|
||||
offset = int2.Swap(offset);
|
||||
size = int2.Swap(size);
|
||||
}
|
||||
|
||||
Path = s.ReadASCIIZ();
|
||||
}
|
||||
|
||||
public Stream GetData()
|
||||
{
|
||||
s.Position = offset;
|
||||
return new MemoryStream(s.ReadBytes((int)size));
|
||||
}
|
||||
}
|
||||
|
||||
public Stream GetStream(string filename)
|
||||
{
|
||||
return index[filename].GetData();
|
||||
}
|
||||
|
||||
public bool Contains(string filename)
|
||||
{
|
||||
return index.ContainsKey(filename);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
s.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
81
OpenRA.Game/FileSystem/D2kSoundResources.cs
Normal file
81
OpenRA.Game/FileSystem/D2kSoundResources.cs
Normal file
@@ -0,0 +1,81 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.FileSystem
|
||||
{
|
||||
public sealed class D2kSoundResources : IReadOnlyPackage
|
||||
{
|
||||
struct Entry
|
||||
{
|
||||
public readonly uint Offset;
|
||||
public readonly uint Length;
|
||||
|
||||
public Entry(uint offset, uint length)
|
||||
{
|
||||
Offset = offset;
|
||||
Length = length;
|
||||
}
|
||||
}
|
||||
|
||||
public string Name { get; private set; }
|
||||
public IEnumerable<string> Contents { get { return index.Keys; } }
|
||||
|
||||
readonly Stream s;
|
||||
readonly Dictionary<string, Entry> index = new Dictionary<string, Entry>();
|
||||
|
||||
public D2kSoundResources(FileSystem context, string filename)
|
||||
{
|
||||
Name = filename;
|
||||
|
||||
s = context.Open(filename);
|
||||
try
|
||||
{
|
||||
var headerLength = s.ReadUInt32();
|
||||
while (s.Position < headerLength + 4)
|
||||
{
|
||||
var name = s.ReadASCIIZ();
|
||||
var offset = s.ReadUInt32();
|
||||
var length = s.ReadUInt32();
|
||||
index.Add(name, new Entry(offset, length));
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
Dispose();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public Stream GetStream(string filename)
|
||||
{
|
||||
Entry e;
|
||||
if (!index.TryGetValue(filename, out e))
|
||||
return null;
|
||||
|
||||
s.Seek(e.Offset, SeekOrigin.Begin);
|
||||
return new MemoryStream(s.ReadBytes((int)e.Length));
|
||||
}
|
||||
|
||||
public bool Contains(string filename)
|
||||
{
|
||||
return index.ContainsKey(filename);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
s.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
@@ -13,6 +13,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.FileSystem
|
||||
@@ -23,7 +24,6 @@ namespace OpenRA.FileSystem
|
||||
bool TryGetPackageContaining(string path, out IReadOnlyPackage package, out string filename);
|
||||
bool TryOpen(string filename, out Stream s);
|
||||
bool Exists(string filename);
|
||||
bool IsExternalModFile(string filename);
|
||||
}
|
||||
|
||||
public class FileSystem : IReadOnlyFileSystem
|
||||
@@ -31,81 +31,105 @@ namespace OpenRA.FileSystem
|
||||
public IEnumerable<IReadOnlyPackage> MountedPackages { get { return mountedPackages.Keys; } }
|
||||
readonly Dictionary<IReadOnlyPackage, int> mountedPackages = new Dictionary<IReadOnlyPackage, int>();
|
||||
readonly Dictionary<string, IReadOnlyPackage> explicitMounts = new Dictionary<string, IReadOnlyPackage>();
|
||||
readonly string modID;
|
||||
|
||||
// Mod packages that should not be disposed
|
||||
readonly List<IReadOnlyPackage> modPackages = new List<IReadOnlyPackage>();
|
||||
readonly IReadOnlyDictionary<string, Manifest> installedMods;
|
||||
readonly IPackageLoader[] packageLoaders;
|
||||
|
||||
Cache<string, List<IReadOnlyPackage>> fileIndex = new Cache<string, List<IReadOnlyPackage>>(_ => new List<IReadOnlyPackage>());
|
||||
|
||||
public FileSystem(string modID, IReadOnlyDictionary<string, Manifest> installedMods, IPackageLoader[] packageLoaders)
|
||||
{
|
||||
this.modID = modID;
|
||||
this.installedMods = installedMods;
|
||||
this.packageLoaders = packageLoaders
|
||||
.Append(new ZipFileLoader())
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
public bool TryParsePackage(Stream stream, string filename, out IReadOnlyPackage package)
|
||||
{
|
||||
package = null;
|
||||
foreach (var packageLoader in packageLoaders)
|
||||
if (packageLoader.TryParsePackage(stream, filename, this, out package))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public IReadOnlyPackage OpenPackage(string filename)
|
||||
{
|
||||
// Raw directories are the easiest and one of the most common cases, so try these first
|
||||
var resolvedPath = Platform.ResolvePath(filename);
|
||||
if (!resolvedPath.Contains("|") && Directory.Exists(resolvedPath))
|
||||
return new Folder(resolvedPath);
|
||||
if (filename.EndsWith(".mix", StringComparison.InvariantCultureIgnoreCase))
|
||||
return new MixFile(this, filename);
|
||||
if (filename.EndsWith(".zip", StringComparison.InvariantCultureIgnoreCase))
|
||||
return new ZipFile(this, filename);
|
||||
if (filename.EndsWith(".oramap", StringComparison.InvariantCultureIgnoreCase))
|
||||
return new ZipFile(this, filename);
|
||||
if (filename.EndsWith(".oramod", StringComparison.InvariantCultureIgnoreCase))
|
||||
return new ZipFile(this, filename);
|
||||
if (filename.EndsWith(".RS", StringComparison.InvariantCultureIgnoreCase))
|
||||
return new D2kSoundResources(this, filename);
|
||||
if (filename.EndsWith(".Z", StringComparison.InvariantCultureIgnoreCase))
|
||||
return new InstallShieldPackage(this, filename);
|
||||
if (filename.EndsWith(".PAK", StringComparison.InvariantCultureIgnoreCase))
|
||||
return new PakFile(this, filename);
|
||||
if (filename.EndsWith(".big", StringComparison.InvariantCultureIgnoreCase))
|
||||
return new BigFile(this, filename);
|
||||
if (filename.EndsWith(".bag", StringComparison.InvariantCultureIgnoreCase))
|
||||
return new BagFile(this, filename);
|
||||
if (filename.EndsWith(".hdr", StringComparison.InvariantCultureIgnoreCase))
|
||||
return new InstallShieldCABExtractor(this, filename);
|
||||
|
||||
// Children of another package require special handling
|
||||
if (TryGetPackageContaining(filename, out var parent, out var subPath))
|
||||
return parent.OpenPackage(subPath, this);
|
||||
IReadOnlyPackage parent;
|
||||
string subPath = null;
|
||||
if (TryGetPackageContaining(filename, out parent, out subPath))
|
||||
return OpenPackage(subPath, parent);
|
||||
|
||||
// Try and open it normally
|
||||
var stream = Open(filename);
|
||||
if (TryParsePackage(stream, filename, out var package))
|
||||
return package;
|
||||
return new Folder(Platform.ResolvePath(filename));
|
||||
}
|
||||
|
||||
// No package loaders took ownership of the stream, so clean it up
|
||||
stream.Dispose();
|
||||
public IReadOnlyPackage OpenPackage(string filename, IReadOnlyPackage parent)
|
||||
{
|
||||
// HACK: limit support to zip and folder until we generalize the PackageLoader support
|
||||
if (parent is Folder)
|
||||
{
|
||||
var path = Path.Combine(parent.Name, filename);
|
||||
|
||||
// HACK: work around SharpZipLib's lack of support for writing to in-memory files
|
||||
if (filename.EndsWith(".zip", StringComparison.InvariantCultureIgnoreCase))
|
||||
return new ZipFile(this, path);
|
||||
if (filename.EndsWith(".oramap", StringComparison.InvariantCultureIgnoreCase))
|
||||
return new ZipFile(this, path);
|
||||
|
||||
var subFolder = Platform.ResolvePath(path);
|
||||
if (Directory.Exists(subFolder))
|
||||
return new Folder(subFolder);
|
||||
}
|
||||
|
||||
if (filename.EndsWith(".zip", StringComparison.InvariantCultureIgnoreCase))
|
||||
return new ZipFile(this, filename, parent.GetStream(filename));
|
||||
if (filename.EndsWith(".oramap", StringComparison.InvariantCultureIgnoreCase))
|
||||
return new ZipFile(this, filename, parent.GetStream(filename));
|
||||
|
||||
if (parent is ZipFile)
|
||||
return new ZipFolder(this, (ZipFile)parent, filename, filename);
|
||||
|
||||
if (parent is ZipFolder)
|
||||
{
|
||||
var folder = (ZipFolder)parent;
|
||||
return new ZipFolder(this, folder.Parent, folder.Name + "/" + filename, filename);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public IReadWritePackage OpenWritablePackage(string filename)
|
||||
{
|
||||
if (filename.EndsWith(".zip", StringComparison.InvariantCultureIgnoreCase))
|
||||
return new ZipFile(this, filename);
|
||||
if (filename.EndsWith(".oramap", StringComparison.InvariantCultureIgnoreCase))
|
||||
return new ZipFile(this, filename);
|
||||
|
||||
return new Folder(filename);
|
||||
}
|
||||
|
||||
public void Mount(string name, string explicitName = null)
|
||||
{
|
||||
var optional = name.StartsWith("~", StringComparison.Ordinal);
|
||||
var optional = name.StartsWith("~");
|
||||
if (optional)
|
||||
name = name.Substring(1);
|
||||
|
||||
try
|
||||
{
|
||||
IReadOnlyPackage package;
|
||||
if (name.StartsWith("$", StringComparison.Ordinal))
|
||||
if (name.StartsWith("$"))
|
||||
{
|
||||
name = name.Substring(1);
|
||||
|
||||
if (!installedMods.TryGetValue(name, out var mod))
|
||||
throw new InvalidOperationException("Could not load mod '{0}'. Available mods: {1}".F(name, installedMods.Keys.JoinWith(", ")));
|
||||
|
||||
package = mod.Package;
|
||||
package = ModMetadata.AllMods[name].Package;
|
||||
modPackages.Add(package);
|
||||
}
|
||||
else
|
||||
{
|
||||
package = OpenPackage(name);
|
||||
if (package == null)
|
||||
throw new InvalidOperationException("Could not open package '{0}', file not found or its format is not supported.".F(name));
|
||||
}
|
||||
|
||||
Mount(package, explicitName);
|
||||
}
|
||||
@@ -118,7 +142,8 @@ namespace OpenRA.FileSystem
|
||||
|
||||
public void Mount(IReadOnlyPackage package, string explicitName = null)
|
||||
{
|
||||
if (mountedPackages.TryGetValue(package, out var mountCount))
|
||||
var mountCount = 0;
|
||||
if (mountedPackages.TryGetValue(package, out mountCount))
|
||||
{
|
||||
// Package is already mounted
|
||||
// Increment the mount count and bump up the file loading priority
|
||||
@@ -144,7 +169,8 @@ namespace OpenRA.FileSystem
|
||||
|
||||
public bool Unmount(IReadOnlyPackage package)
|
||||
{
|
||||
if (!mountedPackages.TryGetValue(package, out var mountCount))
|
||||
var mountCount = 0;
|
||||
if (!mountedPackages.TryGetValue(package, out mountCount))
|
||||
return false;
|
||||
|
||||
if (--mountCount <= 0)
|
||||
@@ -197,12 +223,16 @@ namespace OpenRA.FileSystem
|
||||
var package = fileIndex[filename]
|
||||
.LastOrDefault(x => x.Contains(filename));
|
||||
|
||||
return package?.GetStream(filename);
|
||||
if (package != null)
|
||||
return package.GetStream(filename);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public Stream Open(string filename)
|
||||
{
|
||||
if (!TryOpen(filename, out var s))
|
||||
Stream s;
|
||||
if (!TryOpen(filename, out s))
|
||||
throw new FileNotFoundException("File not found: {0}".F(filename), filename);
|
||||
|
||||
return s;
|
||||
@@ -228,7 +258,8 @@ namespace OpenRA.FileSystem
|
||||
var explicitSplit = filename.IndexOf('|');
|
||||
if (explicitSplit > 0)
|
||||
{
|
||||
if (explicitMounts.TryGetValue(filename.Substring(0, explicitSplit), out var explicitPackage))
|
||||
IReadOnlyPackage explicitPackage;
|
||||
if (explicitMounts.TryGetValue(filename.Substring(0, explicitSplit), out explicitPackage))
|
||||
{
|
||||
s = explicitPackage.GetStream(filename.Substring(explicitSplit + 1));
|
||||
if (s != null)
|
||||
@@ -240,15 +271,9 @@ namespace OpenRA.FileSystem
|
||||
if (s != null)
|
||||
return true;
|
||||
|
||||
// The file should be in an explicit package (but we couldn't find it)
|
||||
// Thus don't try to find it using the filename (which contains the invalid '|' char)
|
||||
// This can be removed once the TODO below is resolved
|
||||
if (explicitSplit > 0)
|
||||
return false;
|
||||
|
||||
// Ask each package individually
|
||||
// TODO: This fallback can be removed once the filesystem cleanups are complete
|
||||
var package = mountedPackages.Keys.LastOrDefault(x => x.Contains(filename));
|
||||
var package = mountedPackages.Keys.LastOrDefault(x => x.Contains(filename));
|
||||
if (package != null)
|
||||
{
|
||||
s = package.GetStream(filename);
|
||||
@@ -263,68 +288,14 @@ namespace OpenRA.FileSystem
|
||||
{
|
||||
var explicitSplit = filename.IndexOf('|');
|
||||
if (explicitSplit > 0)
|
||||
if (explicitMounts.TryGetValue(filename.Substring(0, explicitSplit), out var explicitPackage))
|
||||
{
|
||||
IReadOnlyPackage explicitPackage;
|
||||
if (explicitMounts.TryGetValue(filename.Substring(0, explicitSplit), out explicitPackage))
|
||||
if (explicitPackage.Contains(filename.Substring(explicitSplit + 1)))
|
||||
return true;
|
||||
}
|
||||
|
||||
return fileIndex.ContainsKey(filename);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given filename references an external mod via an explicit mount
|
||||
/// </summary>
|
||||
public bool IsExternalModFile(string filename)
|
||||
{
|
||||
var explicitSplit = filename.IndexOf('|');
|
||||
if (explicitSplit < 0)
|
||||
return false;
|
||||
|
||||
if (!explicitMounts.TryGetValue(filename.Substring(0, explicitSplit), out var explicitPackage))
|
||||
return false;
|
||||
|
||||
if (installedMods[modID].Package == explicitPackage)
|
||||
return false;
|
||||
|
||||
return modPackages.Contains(explicitPackage);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolves a filesystem for an assembly, accounting for explicit and mod mounts.
|
||||
/// Assemblies must exist in the native OS file system (not inside an OpenRA-defined package).
|
||||
/// </summary>
|
||||
public static string ResolveAssemblyPath(string path, Manifest manifest, InstalledMods installedMods)
|
||||
{
|
||||
var explicitSplit = path.IndexOf('|');
|
||||
if (explicitSplit > 0 && !path.StartsWith("^"))
|
||||
{
|
||||
var parent = path.Substring(0, explicitSplit);
|
||||
var filename = path.Substring(explicitSplit + 1);
|
||||
|
||||
var parentPath = manifest.Packages.FirstOrDefault(kv => kv.Value == parent).Key;
|
||||
if (parentPath == null)
|
||||
return null;
|
||||
|
||||
if (parentPath.StartsWith("$", StringComparison.Ordinal))
|
||||
{
|
||||
if (!installedMods.TryGetValue(parentPath.Substring(1), out var mod))
|
||||
return null;
|
||||
|
||||
if (!(mod.Package is Folder))
|
||||
return null;
|
||||
|
||||
path = Path.Combine(mod.Package.Name, filename);
|
||||
}
|
||||
else
|
||||
path = Path.Combine(parentPath, filename);
|
||||
}
|
||||
|
||||
var resolvedPath = Platform.ResolvePath(path);
|
||||
return File.Exists(resolvedPath) ? resolvedPath : null;
|
||||
}
|
||||
|
||||
public string GetPrefix(IReadOnlyPackage package)
|
||||
{
|
||||
return explicitMounts.ContainsValue(package) ? explicitMounts.First(f => f.Value == package).Key : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
@@ -9,10 +9,8 @@
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace OpenRA.FileSystem
|
||||
{
|
||||
@@ -33,11 +31,10 @@ namespace OpenRA.FileSystem
|
||||
{
|
||||
get
|
||||
{
|
||||
// Order may vary on different file systems and it matters for hashing.
|
||||
return Directory.GetFiles(path, "*", SearchOption.TopDirectoryOnly)
|
||||
.Concat(Directory.GetDirectories(path))
|
||||
.Select(Path.GetFileName)
|
||||
.OrderBy(f => f);
|
||||
foreach (var filename in Directory.GetFiles(path, "*", SearchOption.TopDirectoryOnly))
|
||||
yield return Path.GetFileName(filename);
|
||||
foreach (var filename in Directory.GetDirectories(path))
|
||||
yield return Path.GetFileName(filename);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,42 +47,15 @@ namespace OpenRA.FileSystem
|
||||
public bool Contains(string filename)
|
||||
{
|
||||
var combined = Path.Combine(path, filename);
|
||||
return combined.StartsWith(path, StringComparison.Ordinal) && File.Exists(combined);
|
||||
}
|
||||
|
||||
public IReadOnlyPackage OpenPackage(string filename, FileSystem context)
|
||||
{
|
||||
var resolvedPath = Platform.ResolvePath(Path.Combine(Name, filename));
|
||||
if (Directory.Exists(resolvedPath))
|
||||
return new Folder(resolvedPath);
|
||||
|
||||
// Zip files loaded from Folders (and *only* from Folders) can be read-write
|
||||
if (ZipFileLoader.TryParseReadWritePackage(resolvedPath, out var readWritePackage))
|
||||
return readWritePackage;
|
||||
|
||||
// Other package types can be loaded normally
|
||||
var s = GetStream(filename);
|
||||
if (s == null)
|
||||
return null;
|
||||
|
||||
if (context.TryParsePackage(s, filename, out var package))
|
||||
return package;
|
||||
|
||||
s.Dispose();
|
||||
return null;
|
||||
return combined.StartsWith(path) && File.Exists(combined);
|
||||
}
|
||||
|
||||
public void Update(string filename, byte[] contents)
|
||||
{
|
||||
// HACK: ZipFiles can't be loaded as read-write from a stream, so we are
|
||||
// forced to bypass the parent package and load them with their full path
|
||||
// in FileSystem.OpenPackage. Their internal name therefore contains the
|
||||
// full parent path too. We need to be careful to not add a second path
|
||||
// prefix to these hacked packages.
|
||||
var filePath = filename.StartsWith(path) ? filename : Path.Combine(path, filename);
|
||||
if (!Directory.Exists(path))
|
||||
Directory.CreateDirectory(path);
|
||||
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(filePath));
|
||||
using (var s = File.Create(filePath))
|
||||
using (var s = File.Create(Path.Combine(path, filename)))
|
||||
s.Write(contents, 0, contents.Length);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
@@ -15,23 +15,12 @@ using System.IO;
|
||||
|
||||
namespace OpenRA.FileSystem
|
||||
{
|
||||
public interface IPackageLoader
|
||||
{
|
||||
/// <summary>
|
||||
/// Attempt to parse a stream as this type of package.
|
||||
/// If successful, the loader is expected to take ownership of `s` and dispose it once done.
|
||||
/// If unsuccessful, the loader is expected to return the stream position to where it started.
|
||||
/// </summary>
|
||||
bool TryParsePackage(Stream s, string filename, FileSystem context, out IReadOnlyPackage package);
|
||||
}
|
||||
|
||||
public interface IReadOnlyPackage : IDisposable
|
||||
{
|
||||
string Name { get; }
|
||||
IEnumerable<string> Contents { get; }
|
||||
Stream GetStream(string filename);
|
||||
bool Contains(string filename);
|
||||
IReadOnlyPackage OpenPackage(string filename, FileSystem context);
|
||||
}
|
||||
|
||||
public interface IReadWritePackage : IReadOnlyPackage
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
@@ -9,9 +9,10 @@
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace OpenRA.Mods.Cnc.FileFormats
|
||||
namespace OpenRA.FileSystem
|
||||
{
|
||||
public class IdxEntry
|
||||
{
|
||||
508
OpenRA.Game/FileSystem/InstallShieldCABExtractor.cs
Normal file
508
OpenRA.Game/FileSystem/InstallShieldCABExtractor.cs
Normal file
@@ -0,0 +1,508 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using ICSharpCode.SharpZipLib.Zip.Compression;
|
||||
|
||||
namespace OpenRA.FileSystem
|
||||
{
|
||||
public sealed class InstallShieldCABExtractor : IReadOnlyPackage
|
||||
{
|
||||
const uint FileSplit = 0x1;
|
||||
const uint FileObfuscated = 0x2;
|
||||
const uint FileCompressed = 0x4;
|
||||
const uint FileInvalid = 0x8;
|
||||
|
||||
const uint LinkPrev = 0x1;
|
||||
const uint LinkNext = 0x2;
|
||||
const uint MaxFileGroupCount = 71;
|
||||
|
||||
#region Nested Structs
|
||||
|
||||
struct FileGroup
|
||||
{
|
||||
public readonly string Name;
|
||||
public readonly uint FirstFile;
|
||||
public readonly uint LastFile;
|
||||
|
||||
public FileGroup(Stream reader, long offset)
|
||||
{
|
||||
var nameOffset = reader.ReadUInt32();
|
||||
/* unknown */ reader.ReadBytes(18);
|
||||
FirstFile = reader.ReadUInt32();
|
||||
LastFile = reader.ReadUInt32();
|
||||
|
||||
reader.Seek(offset + nameOffset, SeekOrigin.Begin);
|
||||
Name = reader.ReadASCIIZ();
|
||||
}
|
||||
}
|
||||
|
||||
struct VolumeHeader
|
||||
{
|
||||
public readonly uint DataOffset;
|
||||
public readonly uint DataOffsetHigh;
|
||||
public readonly uint FirstFileIndex;
|
||||
public readonly uint LastFileIndex;
|
||||
|
||||
public readonly uint FirstFileOffset;
|
||||
public readonly uint FirstFileOffsetHigh;
|
||||
public readonly uint FirstFileSizeExpanded;
|
||||
public readonly uint FirstFileSizeExpandedHigh;
|
||||
|
||||
public readonly uint FirstFileSizeCompressed;
|
||||
public readonly uint FirstFileSizeCompressedHigh;
|
||||
public readonly uint LastFileOffset;
|
||||
public readonly uint LastFileOffsetHigh;
|
||||
|
||||
public readonly uint LastFileSizeExpanded;
|
||||
public readonly uint LastFileSizeExpandedHigh;
|
||||
public readonly uint LastFileSizeCompressed;
|
||||
public readonly uint LastFileSizeCompressedHigh;
|
||||
|
||||
public VolumeHeader(Stream reader)
|
||||
{
|
||||
DataOffset = reader.ReadUInt32();
|
||||
DataOffsetHigh = reader.ReadUInt32();
|
||||
|
||||
FirstFileIndex = reader.ReadUInt32();
|
||||
LastFileIndex = reader.ReadUInt32();
|
||||
FirstFileOffset = reader.ReadUInt32();
|
||||
FirstFileOffsetHigh = reader.ReadUInt32();
|
||||
|
||||
FirstFileSizeExpanded = reader.ReadUInt32();
|
||||
FirstFileSizeExpandedHigh = reader.ReadUInt32();
|
||||
FirstFileSizeCompressed = reader.ReadUInt32();
|
||||
FirstFileSizeCompressedHigh = reader.ReadUInt32();
|
||||
|
||||
LastFileOffset = reader.ReadUInt32();
|
||||
LastFileOffsetHigh = reader.ReadUInt32();
|
||||
LastFileSizeExpanded = reader.ReadUInt32();
|
||||
LastFileSizeExpandedHigh = reader.ReadUInt32();
|
||||
|
||||
LastFileSizeCompressed = reader.ReadUInt32();
|
||||
LastFileSizeCompressedHigh = reader.ReadUInt32();
|
||||
}
|
||||
}
|
||||
|
||||
struct CommonHeader
|
||||
{
|
||||
public const long Size = 16;
|
||||
public readonly uint Version;
|
||||
public readonly uint VolumeInfo;
|
||||
public readonly long CabDescriptorOffset;
|
||||
public readonly uint CabDescriptorSize;
|
||||
|
||||
public CommonHeader(Stream reader)
|
||||
{
|
||||
Version = reader.ReadUInt32();
|
||||
VolumeInfo = reader.ReadUInt32();
|
||||
CabDescriptorOffset = reader.ReadUInt32();
|
||||
CabDescriptorSize = reader.ReadUInt32();
|
||||
}
|
||||
}
|
||||
|
||||
struct CabDescriptor
|
||||
{
|
||||
public readonly long FileTableOffset;
|
||||
public readonly uint FileTableSize;
|
||||
public readonly uint FileTableSize2;
|
||||
public readonly uint DirectoryCount;
|
||||
|
||||
public readonly uint FileCount;
|
||||
public readonly long FileTableOffset2;
|
||||
|
||||
public CabDescriptor(Stream reader, CommonHeader commonHeader)
|
||||
{
|
||||
reader.Seek(commonHeader.CabDescriptorOffset + 12, SeekOrigin.Begin);
|
||||
FileTableOffset = reader.ReadUInt32();
|
||||
/* unknown */ reader.ReadUInt32();
|
||||
FileTableSize = reader.ReadUInt32();
|
||||
|
||||
FileTableSize2 = reader.ReadUInt32();
|
||||
DirectoryCount = reader.ReadUInt32();
|
||||
/* unknown */ reader.ReadBytes(8);
|
||||
FileCount = reader.ReadUInt32();
|
||||
|
||||
FileTableOffset2 = reader.ReadUInt32();
|
||||
}
|
||||
}
|
||||
|
||||
struct FileDescriptor
|
||||
{
|
||||
public readonly ushort Flags;
|
||||
public readonly uint ExpandedSize;
|
||||
public readonly uint CompressedSize;
|
||||
public readonly uint DataOffset;
|
||||
|
||||
public readonly byte[] MD5;
|
||||
public readonly uint NameOffset;
|
||||
public readonly ushort DirectoryIndex;
|
||||
public readonly uint LinkToPrevious;
|
||||
|
||||
public readonly uint LinkToNext;
|
||||
public readonly byte LinkFlags;
|
||||
public readonly ushort Volume;
|
||||
public readonly string Filename;
|
||||
|
||||
public FileDescriptor(Stream reader, long tableOffset)
|
||||
{
|
||||
Flags = reader.ReadUInt16();
|
||||
ExpandedSize = reader.ReadUInt32();
|
||||
/* unknown */ reader.ReadUInt32();
|
||||
CompressedSize = reader.ReadUInt32();
|
||||
|
||||
/* unknown */ reader.ReadUInt32();
|
||||
DataOffset = reader.ReadUInt32();
|
||||
/* unknown */ reader.ReadUInt32();
|
||||
MD5 = reader.ReadBytes(16);
|
||||
|
||||
/* unknown */ reader.ReadBytes(16);
|
||||
NameOffset = reader.ReadUInt32();
|
||||
DirectoryIndex = reader.ReadUInt16();
|
||||
/* unknown */ reader.ReadBytes(12);
|
||||
LinkToPrevious = reader.ReadUInt32();
|
||||
LinkToNext = reader.ReadUInt32();
|
||||
|
||||
LinkFlags = reader.ReadBytes(1)[0];
|
||||
Volume = reader.ReadUInt16();
|
||||
var posSave = reader.Position;
|
||||
|
||||
reader.Seek(tableOffset + NameOffset, SeekOrigin.Begin);
|
||||
Filename = reader.ReadASCIIZ();
|
||||
reader.Seek(posSave, SeekOrigin.Begin);
|
||||
}
|
||||
}
|
||||
|
||||
class CabReader : IDisposable
|
||||
{
|
||||
readonly FileSystem context;
|
||||
readonly FileDescriptor fileDes;
|
||||
public uint RemainingArchiveStream;
|
||||
public uint RemainingFileStream;
|
||||
readonly uint index;
|
||||
readonly string commonName;
|
||||
ushort volumeNumber;
|
||||
Stream cabFile;
|
||||
|
||||
public CabReader(FileSystem context, FileDescriptor fileDes, uint index, string commonName)
|
||||
{
|
||||
this.fileDes = fileDes;
|
||||
this.index = index;
|
||||
this.commonName = commonName;
|
||||
this.context = context;
|
||||
volumeNumber = (ushort)(fileDes.Volume - 1u);
|
||||
RemainingArchiveStream = 0;
|
||||
if ((fileDes.Flags & FileCompressed) > 0)
|
||||
RemainingFileStream = fileDes.CompressedSize;
|
||||
else
|
||||
RemainingFileStream = fileDes.ExpandedSize;
|
||||
|
||||
cabFile = null;
|
||||
NextFile(context);
|
||||
}
|
||||
|
||||
public void CopyTo(Stream dest)
|
||||
{
|
||||
if ((fileDes.Flags & FileCompressed) != 0)
|
||||
{
|
||||
var inf = new Inflater(true);
|
||||
var buffer = new byte[165535];
|
||||
do
|
||||
{
|
||||
var bytesToExtract = cabFile.ReadUInt16();
|
||||
RemainingArchiveStream -= 2u;
|
||||
RemainingFileStream -= 2u;
|
||||
inf.SetInput(GetBytes(bytesToExtract));
|
||||
RemainingFileStream -= bytesToExtract;
|
||||
while (!inf.IsNeedingInput)
|
||||
{
|
||||
var inflated = inf.Inflate(buffer);
|
||||
dest.Write(buffer, 0, inflated);
|
||||
}
|
||||
|
||||
inf.Reset();
|
||||
}
|
||||
while (RemainingFileStream > 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
do
|
||||
{
|
||||
RemainingFileStream -= RemainingArchiveStream;
|
||||
dest.Write(GetBytes(RemainingArchiveStream), 0, (int)RemainingArchiveStream);
|
||||
}
|
||||
while (RemainingFileStream > 0);
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] GetBytes(uint count)
|
||||
{
|
||||
if (count < RemainingArchiveStream)
|
||||
{
|
||||
RemainingArchiveStream -= count;
|
||||
return cabFile.ReadBytes((int)count);
|
||||
}
|
||||
else
|
||||
{
|
||||
var outArray = new byte[count];
|
||||
var read = cabFile.Read(outArray, 0, (int)RemainingArchiveStream);
|
||||
if (RemainingFileStream > RemainingArchiveStream)
|
||||
{
|
||||
NextFile(context);
|
||||
RemainingArchiveStream -= (uint)cabFile.Read(outArray, read, (int)count - read);
|
||||
}
|
||||
|
||||
return outArray;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
cabFile.Dispose();
|
||||
}
|
||||
|
||||
void NextFile(FileSystem context)
|
||||
{
|
||||
if (cabFile != null)
|
||||
cabFile.Dispose();
|
||||
|
||||
++volumeNumber;
|
||||
cabFile = context.Open("{0}{1}.cab".F(commonName, volumeNumber));
|
||||
if (cabFile.ReadUInt32() != 0x28635349)
|
||||
throw new InvalidDataException("Not an Installshield CAB package");
|
||||
|
||||
uint fileOffset;
|
||||
if ((fileDes.Flags & FileSplit) != 0)
|
||||
{
|
||||
cabFile.Seek(CommonHeader.Size, SeekOrigin.Current);
|
||||
var head = new VolumeHeader(cabFile);
|
||||
if (index == head.LastFileIndex)
|
||||
{
|
||||
if ((fileDes.Flags & FileCompressed) != 0)
|
||||
RemainingArchiveStream = head.LastFileSizeCompressed;
|
||||
else
|
||||
RemainingArchiveStream = head.LastFileSizeExpanded;
|
||||
|
||||
fileOffset = head.LastFileOffset;
|
||||
}
|
||||
else if (index == head.FirstFileIndex)
|
||||
{
|
||||
if ((fileDes.Flags & FileCompressed) != 0)
|
||||
RemainingArchiveStream = head.FirstFileSizeCompressed;
|
||||
else
|
||||
RemainingArchiveStream = head.FirstFileSizeExpanded;
|
||||
|
||||
fileOffset = head.FirstFileOffset;
|
||||
}
|
||||
else
|
||||
throw new Exception("Cannot Resolve Remaining Stream");
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((fileDes.Flags & FileCompressed) != 0)
|
||||
RemainingArchiveStream = fileDes.CompressedSize;
|
||||
else
|
||||
RemainingArchiveStream = fileDes.ExpandedSize;
|
||||
|
||||
fileOffset = fileDes.DataOffset;
|
||||
}
|
||||
|
||||
cabFile.Seek(fileOffset, SeekOrigin.Begin);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
readonly Stream hdrFile;
|
||||
readonly CommonHeader commonHeader;
|
||||
readonly CabDescriptor cabDescriptor;
|
||||
readonly List<uint> directoryTable;
|
||||
readonly Dictionary<uint, string> directoryNames = new Dictionary<uint, string>();
|
||||
readonly Dictionary<uint, FileDescriptor> fileDescriptors = new Dictionary<uint, FileDescriptor>();
|
||||
readonly Dictionary<string, uint> index = new Dictionary<string, uint>();
|
||||
readonly FileSystem context;
|
||||
|
||||
public string Name { get; private set; }
|
||||
public IEnumerable<string> Contents { get { return index.Keys; } }
|
||||
|
||||
public InstallShieldCABExtractor(FileSystem context, string hdrFilename)
|
||||
{
|
||||
var fileGroups = new List<FileGroup>();
|
||||
var fileGroupOffsets = new List<uint>();
|
||||
|
||||
hdrFile = context.Open(hdrFilename);
|
||||
this.context = context;
|
||||
|
||||
// Strips archive number AND file extension
|
||||
Name = Regex.Replace(hdrFilename, @"\d*\.[^\.]*$", "");
|
||||
var signature = hdrFile.ReadUInt32();
|
||||
|
||||
if (signature != 0x28635349)
|
||||
throw new InvalidDataException("Not an Installshield CAB package");
|
||||
|
||||
commonHeader = new CommonHeader(hdrFile);
|
||||
cabDescriptor = new CabDescriptor(hdrFile, commonHeader);
|
||||
/* unknown */ hdrFile.ReadBytes(14);
|
||||
|
||||
for (var i = 0U; i < MaxFileGroupCount; ++i)
|
||||
fileGroupOffsets.Add(hdrFile.ReadUInt32());
|
||||
|
||||
hdrFile.Seek(commonHeader.CabDescriptorOffset + cabDescriptor.FileTableOffset, SeekOrigin.Begin);
|
||||
directoryTable = new List<uint>();
|
||||
|
||||
for (var i = 0U; i < cabDescriptor.DirectoryCount; ++i)
|
||||
directoryTable.Add(hdrFile.ReadUInt32());
|
||||
|
||||
foreach (var offset in fileGroupOffsets)
|
||||
{
|
||||
var nextOffset = offset;
|
||||
while (nextOffset != 0)
|
||||
{
|
||||
hdrFile.Seek((long)nextOffset + 4 + commonHeader.CabDescriptorOffset, SeekOrigin.Begin);
|
||||
var descriptorOffset = hdrFile.ReadUInt32();
|
||||
nextOffset = hdrFile.ReadUInt32();
|
||||
hdrFile.Seek(descriptorOffset + commonHeader.CabDescriptorOffset, SeekOrigin.Begin);
|
||||
|
||||
fileGroups.Add(new FileGroup(hdrFile, commonHeader.CabDescriptorOffset));
|
||||
}
|
||||
}
|
||||
|
||||
hdrFile.Seek(commonHeader.CabDescriptorOffset + cabDescriptor.FileTableOffset + cabDescriptor.FileTableOffset2, SeekOrigin.Begin);
|
||||
foreach (var fileGroup in fileGroups)
|
||||
{
|
||||
for (var i = fileGroup.FirstFile; i <= fileGroup.LastFile; ++i)
|
||||
{
|
||||
AddFileDescriptorToList(i);
|
||||
var fileDescriptor = fileDescriptors[i];
|
||||
var fullFilePath = "{0}\\{1}\\{2}".F(fileGroup.Name, DirectoryName(fileDescriptor.DirectoryIndex), fileDescriptor.Filename);
|
||||
index.Add(fullFilePath, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string DirectoryName(uint index)
|
||||
{
|
||||
if (directoryNames.ContainsKey(index))
|
||||
return directoryNames[index];
|
||||
|
||||
hdrFile.Seek(commonHeader.CabDescriptorOffset +
|
||||
cabDescriptor.FileTableOffset +
|
||||
directoryTable[(int)index],
|
||||
SeekOrigin.Begin);
|
||||
|
||||
var test = hdrFile.ReadASCIIZ();
|
||||
return test;
|
||||
}
|
||||
|
||||
public bool Contains(string filename)
|
||||
{
|
||||
return index.ContainsKey(filename);
|
||||
}
|
||||
|
||||
public uint DirectoryCount()
|
||||
{
|
||||
return cabDescriptor.DirectoryCount;
|
||||
}
|
||||
|
||||
public string FileName(uint index)
|
||||
{
|
||||
if (!fileDescriptors.ContainsKey(index))
|
||||
AddFileDescriptorToList(index);
|
||||
|
||||
return fileDescriptors[index].Filename;
|
||||
}
|
||||
|
||||
void AddFileDescriptorToList(uint index)
|
||||
{
|
||||
hdrFile.Seek(commonHeader.CabDescriptorOffset +
|
||||
cabDescriptor.FileTableOffset +
|
||||
cabDescriptor.FileTableOffset2 +
|
||||
index * 0x57,
|
||||
SeekOrigin.Begin);
|
||||
|
||||
var fd = new FileDescriptor(hdrFile,
|
||||
commonHeader.CabDescriptorOffset + cabDescriptor.FileTableOffset);
|
||||
|
||||
fileDescriptors.Add(index, fd);
|
||||
}
|
||||
|
||||
public uint FileCount()
|
||||
{
|
||||
return cabDescriptor.FileCount;
|
||||
}
|
||||
|
||||
public void ExtractFile(uint index, string fileName)
|
||||
{
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(fileName));
|
||||
using (var destfile = File.Open(fileName, FileMode.Create))
|
||||
GetContentById(index, destfile);
|
||||
}
|
||||
|
||||
public Stream GetContentById(uint index)
|
||||
{
|
||||
var fileDes = fileDescriptors[index];
|
||||
if ((fileDes.Flags & FileInvalid) != 0)
|
||||
throw new Exception("File Invalid");
|
||||
|
||||
if ((fileDes.LinkFlags & LinkPrev) != 0)
|
||||
return GetContentById(fileDes.LinkToPrevious);
|
||||
|
||||
if ((fileDes.Flags & FileObfuscated) != 0)
|
||||
throw new NotImplementedException("Haven't implemented obfuscated files");
|
||||
|
||||
var output = new MemoryStream((int)fileDes.ExpandedSize);
|
||||
|
||||
using (var reader = new CabReader(context, fileDes, index, Name))
|
||||
reader.CopyTo(output);
|
||||
|
||||
if (output.Length != fileDes.ExpandedSize)
|
||||
throw new Exception("Did not fully extract Expected = {0}, Got = {1}".F(fileDes.ExpandedSize, output.Length));
|
||||
|
||||
output.Position = 0;
|
||||
return output;
|
||||
}
|
||||
|
||||
public void GetContentById(uint index, Stream output)
|
||||
{
|
||||
var fileDes = fileDescriptors[index];
|
||||
if ((fileDes.Flags & FileInvalid) != 0)
|
||||
throw new Exception("File Invalid");
|
||||
|
||||
if ((fileDes.LinkFlags & LinkPrev) != 0)
|
||||
{
|
||||
GetContentById(fileDes.LinkToPrevious, output);
|
||||
return;
|
||||
}
|
||||
|
||||
if ((fileDes.Flags & FileObfuscated) != 0)
|
||||
throw new NotImplementedException("Haven't implemented obfuscated files");
|
||||
|
||||
using (var reader = new CabReader(context, fileDes, index, Name))
|
||||
reader.CopyTo(output);
|
||||
|
||||
if (output.Length != fileDes.ExpandedSize)
|
||||
throw new Exception("Did not fully extract Expected = {0}, Got = {1}".F(fileDes.ExpandedSize, output.Length));
|
||||
}
|
||||
|
||||
public Stream GetStream(string fileName)
|
||||
{
|
||||
return GetContentById(index[fileName]);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
hdrFile.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
132
OpenRA.Game/FileSystem/InstallShieldPackage.cs
Normal file
132
OpenRA.Game/FileSystem/InstallShieldPackage.cs
Normal file
@@ -0,0 +1,132 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using OpenRA.FileFormats;
|
||||
|
||||
namespace OpenRA.FileSystem
|
||||
{
|
||||
public sealed class InstallShieldPackage : IReadOnlyPackage
|
||||
{
|
||||
struct Entry
|
||||
{
|
||||
public readonly uint Offset;
|
||||
public readonly uint Length;
|
||||
|
||||
public Entry(uint offset, uint length)
|
||||
{
|
||||
Offset = offset;
|
||||
Length = length;
|
||||
}
|
||||
}
|
||||
|
||||
public string Name { get; private set; }
|
||||
public IEnumerable<string> Contents { get { return index.Keys; } }
|
||||
|
||||
readonly Dictionary<string, Entry> index = new Dictionary<string, Entry>();
|
||||
readonly Stream s;
|
||||
readonly long dataStart = 255;
|
||||
|
||||
public InstallShieldPackage(FileSystem context, string filename)
|
||||
{
|
||||
Name = filename;
|
||||
|
||||
s = context.Open(filename);
|
||||
try
|
||||
{
|
||||
// Parse package header
|
||||
var signature = s.ReadUInt32();
|
||||
if (signature != 0x8C655D13)
|
||||
throw new InvalidDataException("Not an Installshield package");
|
||||
|
||||
s.Position += 8;
|
||||
/*var FileCount = */s.ReadUInt16();
|
||||
s.Position += 4;
|
||||
/*var ArchiveSize = */s.ReadUInt32();
|
||||
s.Position += 19;
|
||||
var tocAddress = s.ReadInt32();
|
||||
s.Position += 4;
|
||||
var dirCount = s.ReadUInt16();
|
||||
|
||||
// Parse the directory list
|
||||
s.Position = tocAddress;
|
||||
|
||||
// Parse directories
|
||||
var directories = new Dictionary<string, uint>();
|
||||
for (var i = 0; i < dirCount; i++)
|
||||
{
|
||||
// Parse directory header
|
||||
var fileCount = s.ReadUInt16();
|
||||
var chunkSize = s.ReadUInt16();
|
||||
var nameLength = s.ReadUInt16();
|
||||
var dirName = s.ReadASCII(nameLength);
|
||||
|
||||
// Skip to the end of the chunk
|
||||
s.ReadBytes(chunkSize - nameLength - 6);
|
||||
directories.Add(dirName, fileCount);
|
||||
}
|
||||
|
||||
// Parse files
|
||||
foreach (var dir in directories)
|
||||
for (var i = 0; i < dir.Value; i++)
|
||||
ParseFile(s, dir.Key);
|
||||
}
|
||||
catch
|
||||
{
|
||||
Dispose();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
uint accumulatedData = 0;
|
||||
void ParseFile(Stream s, string dirName)
|
||||
{
|
||||
s.Position += 7;
|
||||
var compressedSize = s.ReadUInt32();
|
||||
s.Position += 12;
|
||||
var chunkSize = s.ReadUInt16();
|
||||
s.Position += 4;
|
||||
var nameLength = s.ReadByte();
|
||||
var fileName = dirName + "\\" + s.ReadASCII(nameLength);
|
||||
|
||||
// Use index syntax to overwrite any duplicate entries with the last value
|
||||
index[fileName] = new Entry(accumulatedData, compressedSize);
|
||||
accumulatedData += compressedSize;
|
||||
|
||||
// Skip to the end of the chunk
|
||||
s.Position += chunkSize - nameLength - 30;
|
||||
}
|
||||
|
||||
public Stream GetStream(string filename)
|
||||
{
|
||||
Entry e;
|
||||
if (!index.TryGetValue(filename, out e))
|
||||
return null;
|
||||
|
||||
s.Seek(dataStart + e.Offset, SeekOrigin.Begin);
|
||||
var data = s.ReadBytes((int)e.Length);
|
||||
|
||||
return new MemoryStream(Blast.Decompress(data));
|
||||
}
|
||||
|
||||
public bool Contains(string filename)
|
||||
{
|
||||
return index.ContainsKey(filename);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
s.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
216
OpenRA.Game/FileSystem/MixFile.cs
Normal file
216
OpenRA.Game/FileSystem/MixFile.cs
Normal file
@@ -0,0 +1,216 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using OpenRA.FileFormats;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.FileSystem
|
||||
{
|
||||
public sealed class MixFile : IReadOnlyPackage
|
||||
{
|
||||
public string Name { get; private set; }
|
||||
public IEnumerable<string> Contents { get { return index.Keys; } }
|
||||
|
||||
readonly Dictionary<string, PackageEntry> index;
|
||||
readonly long dataStart;
|
||||
readonly Stream s;
|
||||
readonly FileSystem context;
|
||||
|
||||
public MixFile(FileSystem context, string filename)
|
||||
{
|
||||
Name = filename;
|
||||
this.context = context;
|
||||
|
||||
s = context.Open(filename);
|
||||
try
|
||||
{
|
||||
// Detect format type
|
||||
var isCncMix = s.ReadUInt16() != 0;
|
||||
|
||||
// The C&C mix format doesn't contain any flags or encryption
|
||||
var isEncrypted = false;
|
||||
if (!isCncMix)
|
||||
isEncrypted = (s.ReadUInt16() & 0x2) != 0;
|
||||
|
||||
List<PackageEntry> entries;
|
||||
if (isEncrypted)
|
||||
{
|
||||
long unused;
|
||||
entries = ParseHeader(DecryptHeader(s, 4, out dataStart), 0, out unused);
|
||||
}
|
||||
else
|
||||
entries = ParseHeader(s, isCncMix ? 0 : 4, out dataStart);
|
||||
|
||||
index = ParseIndex(entries.ToDictionaryWithConflictLog(x => x.Hash,
|
||||
"{0} ({1} format, Encrypted: {2}, DataStart: {3})".F(filename, isCncMix ? "C&C" : "RA/TS/RA2", isEncrypted, dataStart),
|
||||
null, x => "(offs={0}, len={1})".F(x.Offset, x.Length)));
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
Dispose();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
Dictionary<string, PackageEntry> ParseIndex(Dictionary<uint, PackageEntry> entries)
|
||||
{
|
||||
var classicIndex = new Dictionary<string, PackageEntry>();
|
||||
var crcIndex = new Dictionary<string, PackageEntry>();
|
||||
var allPossibleFilenames = new HashSet<string>();
|
||||
|
||||
// Try and find a local mix database
|
||||
var dbNameClassic = PackageEntry.HashFilename("local mix database.dat", PackageHashType.Classic);
|
||||
var dbNameCRC = PackageEntry.HashFilename("local mix database.dat", PackageHashType.CRC32);
|
||||
foreach (var kv in entries)
|
||||
{
|
||||
if (kv.Key == dbNameClassic || kv.Key == dbNameCRC)
|
||||
{
|
||||
var db = new XccLocalDatabase(GetContent(kv.Value));
|
||||
foreach (var e in db.Entries)
|
||||
allPossibleFilenames.Add(e);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Load the global mix database
|
||||
// TODO: This should be passed to the mix file ctor
|
||||
if (context.Exists("global mix database.dat"))
|
||||
{
|
||||
var db = new XccGlobalDatabase(context.Open("global mix database.dat"));
|
||||
foreach (var e in db.Entries)
|
||||
allPossibleFilenames.Add(e);
|
||||
}
|
||||
|
||||
foreach (var filename in allPossibleFilenames)
|
||||
{
|
||||
var classicHash = PackageEntry.HashFilename(filename, PackageHashType.Classic);
|
||||
var crcHash = PackageEntry.HashFilename(filename, PackageHashType.CRC32);
|
||||
PackageEntry e;
|
||||
|
||||
if (entries.TryGetValue(classicHash, out e))
|
||||
classicIndex.Add(filename, e);
|
||||
|
||||
if (entries.TryGetValue(crcHash, out e))
|
||||
crcIndex.Add(filename, e);
|
||||
}
|
||||
|
||||
var bestIndex = crcIndex.Count > classicIndex.Count ? crcIndex : classicIndex;
|
||||
|
||||
var unknown = entries.Count - bestIndex.Count;
|
||||
if (unknown > 0)
|
||||
Log.Write("debug", "{0}: failed to resolve filenames for {1} unknown hashes".F(Name, unknown));
|
||||
|
||||
return bestIndex;
|
||||
}
|
||||
|
||||
static List<PackageEntry> ParseHeader(Stream s, long offset, out long headerEnd)
|
||||
{
|
||||
s.Seek(offset, SeekOrigin.Begin);
|
||||
var numFiles = s.ReadUInt16();
|
||||
/*uint dataSize = */s.ReadUInt32();
|
||||
|
||||
var items = new List<PackageEntry>();
|
||||
for (var i = 0; i < numFiles; i++)
|
||||
items.Add(new PackageEntry(s));
|
||||
|
||||
headerEnd = offset + 6 + numFiles * PackageEntry.Size;
|
||||
return items;
|
||||
}
|
||||
|
||||
static MemoryStream DecryptHeader(Stream s, long offset, out long headerEnd)
|
||||
{
|
||||
s.Seek(offset, SeekOrigin.Begin);
|
||||
|
||||
// Decrypt blowfish key
|
||||
var keyblock = s.ReadBytes(80);
|
||||
var blowfishKey = new BlowfishKeyProvider().DecryptKey(keyblock);
|
||||
var fish = new Blowfish(blowfishKey);
|
||||
|
||||
// Decrypt first block to work out the header length
|
||||
var ms = Decrypt(ReadBlocks(s, offset + 80, 1), fish);
|
||||
var numFiles = ms.ReadUInt16();
|
||||
|
||||
// Decrypt the full header - round bytes up to a full block
|
||||
var blockCount = (13 + numFiles * PackageEntry.Size) / 8;
|
||||
headerEnd = offset + 80 + blockCount * 8;
|
||||
|
||||
return Decrypt(ReadBlocks(s, offset + 80, blockCount), fish);
|
||||
}
|
||||
|
||||
static MemoryStream Decrypt(uint[] h, Blowfish fish)
|
||||
{
|
||||
var decrypted = fish.Decrypt(h);
|
||||
|
||||
var ms = new MemoryStream();
|
||||
var writer = new BinaryWriter(ms);
|
||||
foreach (var t in decrypted)
|
||||
writer.Write(t);
|
||||
writer.Flush();
|
||||
|
||||
ms.Seek(0, SeekOrigin.Begin);
|
||||
return ms;
|
||||
}
|
||||
|
||||
static uint[] ReadBlocks(Stream s, long offset, int count)
|
||||
{
|
||||
if (offset < 0)
|
||||
throw new ArgumentOutOfRangeException("offset", "Non-negative number required.");
|
||||
|
||||
if (count < 0)
|
||||
throw new ArgumentOutOfRangeException("count", "Non-negative number required.");
|
||||
|
||||
if (offset + (count * 2) > s.Length)
|
||||
throw new ArgumentException("Bytes to read {0} and offset {1} greater than stream length {2}.".F(count * 2, offset, s.Length));
|
||||
|
||||
s.Seek(offset, SeekOrigin.Begin);
|
||||
|
||||
// A block is a single encryption unit (represented as two 32-bit integers)
|
||||
var ret = new uint[2 * count];
|
||||
for (var i = 0; i < ret.Length; i++)
|
||||
ret[i] = s.ReadUInt32();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public Stream GetContent(PackageEntry entry)
|
||||
{
|
||||
Stream parentStream;
|
||||
var offset = dataStart + entry.Offset + SegmentStream.GetOverallNestedOffset(s, out parentStream);
|
||||
var path = ((FileStream)parentStream).Name;
|
||||
return new SegmentStream(File.OpenRead(path), offset, entry.Length);
|
||||
}
|
||||
|
||||
public Stream GetStream(string filename)
|
||||
{
|
||||
PackageEntry e;
|
||||
if (!index.TryGetValue(filename, out e))
|
||||
return null;
|
||||
|
||||
return GetContent(e);
|
||||
}
|
||||
|
||||
public bool Contains(string filename)
|
||||
{
|
||||
return index.ContainsKey(filename);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
s.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
@@ -13,9 +13,9 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using OpenRA.Mods.Cnc.FileFormats;
|
||||
using OpenRA.FileFormats;
|
||||
|
||||
namespace OpenRA.Mods.Cnc.FileSystem
|
||||
namespace OpenRA.FileSystem
|
||||
{
|
||||
public enum PackageHashType { Classic, CRC32 }
|
||||
|
||||
@@ -49,7 +49,8 @@ namespace OpenRA.Mods.Cnc.FileSystem
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
if (names.TryGetValue(Hash, out var filename))
|
||||
string filename;
|
||||
if (names.TryGetValue(Hash, out filename))
|
||||
return "{0} - offset 0x{1:x8} - length 0x{2:x8}".F(filename, Offset, Length);
|
||||
else
|
||||
return "0x{0:x8} - offset 0x{1:x8} - length 0x{2:x8}".F(Hash, Offset, Length);
|
||||
@@ -65,16 +66,16 @@ namespace OpenRA.Mods.Cnc.FileSystem
|
||||
if (name.Length % 4 != 0)
|
||||
name = name.PadRight(name.Length + (4 - name.Length % 4), '\0');
|
||||
|
||||
var result = 0u;
|
||||
var data = Encoding.ASCII.GetBytes(name);
|
||||
var i = 0;
|
||||
while (i < data.Length)
|
||||
using (var ms = new MemoryStream(Encoding.ASCII.GetBytes(name)))
|
||||
{
|
||||
var next = (uint)(data[i++] | data[i++] << 8 | data[i++] << 16 | data[i++] << 24);
|
||||
result = ((result << 1) | (result >> 31)) + next;
|
||||
}
|
||||
var len = name.Length >> 2;
|
||||
uint result = 0;
|
||||
|
||||
return result;
|
||||
while (len-- != 0)
|
||||
result = ((result << 1) | (result >> 31)) + ms.ReadUInt32();
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
case PackageHashType.CRC32:
|
||||
84
OpenRA.Game/FileSystem/Pak.cs
Normal file
84
OpenRA.Game/FileSystem/Pak.cs
Normal file
@@ -0,0 +1,84 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace OpenRA.FileSystem
|
||||
{
|
||||
struct Entry
|
||||
{
|
||||
public uint Offset;
|
||||
public uint Length;
|
||||
public string Filename;
|
||||
}
|
||||
|
||||
public sealed class PakFile : IReadOnlyPackage
|
||||
{
|
||||
public string Name { get; private set; }
|
||||
public IEnumerable<string> Contents { get { return index.Keys; } }
|
||||
|
||||
readonly Dictionary<string, Entry> index;
|
||||
readonly Stream stream;
|
||||
|
||||
public PakFile(FileSystem context, string filename)
|
||||
{
|
||||
Name = filename;
|
||||
index = new Dictionary<string, Entry>();
|
||||
|
||||
stream = context.Open(filename);
|
||||
try
|
||||
{
|
||||
index = new Dictionary<string, Entry>();
|
||||
var offset = stream.ReadUInt32();
|
||||
while (offset != 0)
|
||||
{
|
||||
var file = stream.ReadASCIIZ();
|
||||
var next = stream.ReadUInt32();
|
||||
var length = (next == 0 ? (uint)stream.Length : next) - offset;
|
||||
|
||||
// Ignore duplicate files
|
||||
if (index.ContainsKey(file))
|
||||
continue;
|
||||
|
||||
index.Add(file, new Entry { Offset = offset, Length = length, Filename = file });
|
||||
offset = next;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
Dispose();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public Stream GetStream(string filename)
|
||||
{
|
||||
Entry entry;
|
||||
if (!index.TryGetValue(filename, out entry))
|
||||
return null;
|
||||
|
||||
stream.Seek(entry.Offset, SeekOrigin.Begin);
|
||||
var data = stream.ReadBytes((int)entry.Length);
|
||||
return new MemoryStream(data);
|
||||
}
|
||||
|
||||
public bool Contains(string filename)
|
||||
{
|
||||
return index.ContainsKey(filename);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
stream.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
@@ -9,228 +9,105 @@
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using ICSharpCode.SharpZipLib.Zip;
|
||||
using SZipFile = ICSharpCode.SharpZipLib.Zip.ZipFile;
|
||||
|
||||
namespace OpenRA.FileSystem
|
||||
{
|
||||
public class ZipFileLoader : IPackageLoader
|
||||
public sealed class ZipFile : IReadWritePackage
|
||||
{
|
||||
static readonly string[] Extensions = { ".zip", ".oramap" };
|
||||
public string Name { get; private set; }
|
||||
SZipFile pkg;
|
||||
|
||||
class ReadOnlyZipFile : IReadOnlyPackage
|
||||
static ZipFile()
|
||||
{
|
||||
public string Name { get; protected set; }
|
||||
protected ZipFile pkg;
|
||||
ZipConstants.DefaultCodePage = Encoding.UTF8.CodePage;
|
||||
}
|
||||
|
||||
// Dummy constructor for use with ReadWriteZipFile
|
||||
protected ReadOnlyZipFile() { }
|
||||
public ZipFile(FileSystem context, string filename, Stream stream, bool createOrClearContents = false)
|
||||
{
|
||||
Name = filename;
|
||||
|
||||
public ReadOnlyZipFile(Stream s, string filename)
|
||||
{
|
||||
Name = filename;
|
||||
pkg = new ZipFile(s);
|
||||
}
|
||||
if (createOrClearContents)
|
||||
pkg = SZipFile.Create(stream);
|
||||
else
|
||||
pkg = new SZipFile(stream);
|
||||
}
|
||||
|
||||
public Stream GetStream(string filename)
|
||||
{
|
||||
var entry = pkg.GetEntry(filename);
|
||||
if (entry == null)
|
||||
return null;
|
||||
public ZipFile(IReadOnlyFileSystem context, string filename, bool createOrClearContents = false)
|
||||
{
|
||||
Name = filename;
|
||||
|
||||
using (var z = pkg.GetInputStream(entry))
|
||||
{
|
||||
var ms = new MemoryStream((int)entry.Size);
|
||||
z.CopyTo(ms);
|
||||
ms.Seek(0, SeekOrigin.Begin);
|
||||
return ms;
|
||||
}
|
||||
}
|
||||
if (createOrClearContents)
|
||||
pkg = SZipFile.Create(filename);
|
||||
else
|
||||
pkg = new SZipFile(filename);
|
||||
}
|
||||
|
||||
public IEnumerable<string> Contents
|
||||
{
|
||||
get
|
||||
{
|
||||
foreach (ZipEntry entry in pkg)
|
||||
yield return entry.Name;
|
||||
}
|
||||
}
|
||||
|
||||
public bool Contains(string filename)
|
||||
{
|
||||
return pkg.GetEntry(filename) != null;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
pkg?.Close();
|
||||
}
|
||||
|
||||
public IReadOnlyPackage OpenPackage(string filename, FileSystem context)
|
||||
{
|
||||
// Directories are stored with a trailing "/" in the index
|
||||
var entry = pkg.GetEntry(filename) ?? pkg.GetEntry(filename + "/");
|
||||
if (entry == null)
|
||||
return null;
|
||||
|
||||
if (entry.IsDirectory)
|
||||
return new ZipFolder(this, filename);
|
||||
|
||||
// Other package types can be loaded normally
|
||||
var s = GetStream(filename);
|
||||
if (s == null)
|
||||
return null;
|
||||
|
||||
if (context.TryParsePackage(s, filename, out var package))
|
||||
return package;
|
||||
|
||||
s.Dispose();
|
||||
public Stream GetStream(string filename)
|
||||
{
|
||||
var entry = pkg.GetEntry(filename);
|
||||
if (entry == null)
|
||||
return null;
|
||||
|
||||
using (var z = pkg.GetInputStream(entry))
|
||||
{
|
||||
var ms = new MemoryStream();
|
||||
z.CopyTo(ms);
|
||||
ms.Seek(0, SeekOrigin.Begin);
|
||||
return ms;
|
||||
}
|
||||
}
|
||||
|
||||
sealed class ReadWriteZipFile : ReadOnlyZipFile, IReadWritePackage
|
||||
public IEnumerable<string> Contents
|
||||
{
|
||||
readonly MemoryStream pkgStream = new MemoryStream();
|
||||
|
||||
public ReadWriteZipFile(string filename, bool create = false)
|
||||
get
|
||||
{
|
||||
// SharpZipLib breaks when asked to update archives loaded from outside streams or files
|
||||
// We can work around this by creating a clean in-memory-only file, cutting all outside references
|
||||
if (!create)
|
||||
{
|
||||
using (var copy = new MemoryStream(File.ReadAllBytes(filename)))
|
||||
{
|
||||
pkgStream.Capacity = (int)copy.Length;
|
||||
copy.CopyTo(pkgStream);
|
||||
}
|
||||
}
|
||||
|
||||
pkgStream.Position = 0;
|
||||
pkg = new ZipFile(pkgStream);
|
||||
Name = filename;
|
||||
}
|
||||
|
||||
void Commit()
|
||||
{
|
||||
var pos = pkgStream.Position;
|
||||
pkgStream.Position = 0;
|
||||
File.WriteAllBytes(Name, pkgStream.ReadBytes((int)pkgStream.Length));
|
||||
pkgStream.Position = pos;
|
||||
}
|
||||
|
||||
public void Update(string filename, byte[] contents)
|
||||
{
|
||||
pkg.BeginUpdate();
|
||||
pkg.Add(new StaticStreamDataSource(new MemoryStream(contents)), filename);
|
||||
pkg.CommitUpdate();
|
||||
Commit();
|
||||
}
|
||||
|
||||
public void Delete(string filename)
|
||||
{
|
||||
pkg.BeginUpdate();
|
||||
pkg.Delete(filename);
|
||||
pkg.CommitUpdate();
|
||||
Commit();
|
||||
foreach (ZipEntry entry in pkg)
|
||||
yield return entry.Name;
|
||||
}
|
||||
}
|
||||
|
||||
sealed class ZipFolder : IReadOnlyPackage
|
||||
public bool Contains(string filename)
|
||||
{
|
||||
public string Name { get { return path; } }
|
||||
public ReadOnlyZipFile Parent { get; private set; }
|
||||
readonly string path;
|
||||
|
||||
public ZipFolder(ReadOnlyZipFile parent, string path)
|
||||
{
|
||||
if (path.EndsWith("/", StringComparison.Ordinal))
|
||||
path = path.Substring(0, path.Length - 1);
|
||||
|
||||
Parent = parent;
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
public Stream GetStream(string filename)
|
||||
{
|
||||
// Zip files use '/' as a path separator
|
||||
return Parent.GetStream(path + '/' + filename);
|
||||
}
|
||||
|
||||
public IEnumerable<string> Contents
|
||||
{
|
||||
get
|
||||
{
|
||||
foreach (var entry in Parent.Contents)
|
||||
{
|
||||
if (entry.StartsWith(path, StringComparison.Ordinal) && entry != path)
|
||||
{
|
||||
var filename = entry.Substring(path.Length + 1);
|
||||
var dirLevels = filename.Split('/').Count(c => !string.IsNullOrEmpty(c));
|
||||
if (dirLevels == 1)
|
||||
yield return filename;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool Contains(string filename)
|
||||
{
|
||||
return Parent.Contains(path + '/' + filename);
|
||||
}
|
||||
|
||||
public IReadOnlyPackage OpenPackage(string filename, FileSystem context)
|
||||
{
|
||||
return Parent.OpenPackage(path + '/' + filename, context);
|
||||
}
|
||||
|
||||
public void Dispose() { /* nothing to do */ }
|
||||
return pkg.GetEntry(filename) != null;
|
||||
}
|
||||
|
||||
class StaticStreamDataSource : IStaticDataSource
|
||||
public void Update(string filename, byte[] contents)
|
||||
{
|
||||
readonly Stream s;
|
||||
public StaticStreamDataSource(Stream s)
|
||||
{
|
||||
this.s = s;
|
||||
}
|
||||
|
||||
public Stream GetSource()
|
||||
{
|
||||
return s;
|
||||
}
|
||||
pkg.BeginUpdate();
|
||||
pkg.Add(new StaticMemoryDataSource(contents), filename);
|
||||
pkg.CommitUpdate();
|
||||
}
|
||||
|
||||
public bool TryParsePackage(Stream s, string filename, FileSystem context, out IReadOnlyPackage package)
|
||||
public void Delete(string filename)
|
||||
{
|
||||
if (!Extensions.Any(e => filename.EndsWith(e, StringComparison.InvariantCultureIgnoreCase)))
|
||||
{
|
||||
package = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
package = new ReadOnlyZipFile(s, filename);
|
||||
return true;
|
||||
pkg.BeginUpdate();
|
||||
pkg.Delete(filename);
|
||||
pkg.CommitUpdate();
|
||||
}
|
||||
|
||||
public static bool TryParseReadWritePackage(string filename, out IReadWritePackage package)
|
||||
public void Dispose()
|
||||
{
|
||||
if (!Extensions.Any(e => filename.EndsWith(e, StringComparison.InvariantCultureIgnoreCase)))
|
||||
{
|
||||
package = null;
|
||||
return false;
|
||||
}
|
||||
if (pkg != null)
|
||||
pkg.Close();
|
||||
}
|
||||
}
|
||||
|
||||
package = new ReadWriteZipFile(filename);
|
||||
return true;
|
||||
class StaticMemoryDataSource : IStaticDataSource
|
||||
{
|
||||
byte[] data;
|
||||
public StaticMemoryDataSource(byte[] data)
|
||||
{
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public static IReadWritePackage Create(string filename)
|
||||
public Stream GetSource()
|
||||
{
|
||||
return new ReadWriteZipFile(filename, true);
|
||||
return new MemoryStream(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
75
OpenRA.Game/FileSystem/ZipFolder.cs
Normal file
75
OpenRA.Game/FileSystem/ZipFolder.cs
Normal file
@@ -0,0 +1,75 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2015 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using ICSharpCode.SharpZipLib.Zip;
|
||||
using SZipFile = ICSharpCode.SharpZipLib.Zip.ZipFile;
|
||||
|
||||
namespace OpenRA.FileSystem
|
||||
{
|
||||
public sealed class ZipFolder : IReadOnlyPackage
|
||||
{
|
||||
public string Name { get; private set; }
|
||||
public ZipFile Parent { get; private set; }
|
||||
readonly string path;
|
||||
|
||||
static ZipFolder()
|
||||
{
|
||||
ZipConstants.DefaultCodePage = Encoding.UTF8.CodePage;
|
||||
}
|
||||
|
||||
public ZipFolder(FileSystem context, ZipFile parent, string path, string filename)
|
||||
{
|
||||
if (filename.EndsWith("/"))
|
||||
filename = filename.Substring(0, filename.Length - 1);
|
||||
|
||||
Name = filename;
|
||||
Parent = parent;
|
||||
if (path.EndsWith("/"))
|
||||
path = path.Substring(0, path.Length - 1);
|
||||
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
public Stream GetStream(string filename)
|
||||
{
|
||||
// Zip files use '/' as a path separator
|
||||
return Parent.GetStream(path + '/' + filename);
|
||||
}
|
||||
|
||||
public IEnumerable<string> Contents
|
||||
{
|
||||
get
|
||||
{
|
||||
foreach (var entry in Parent.Contents)
|
||||
{
|
||||
if (entry.StartsWith(path) && entry != path)
|
||||
{
|
||||
var filename = entry.Substring(path.Length + 1);
|
||||
var dirLevels = filename.Split('/').Count(c => !string.IsNullOrEmpty(c));
|
||||
if (dirLevels == 1)
|
||||
yield return filename;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool Contains(string filename)
|
||||
{
|
||||
return Parent.Contains(path + '/' + filename);
|
||||
}
|
||||
|
||||
public void Dispose() { /* nothing to do */ }
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace OpenRA
|
||||
{
|
||||
public class FontData
|
||||
{
|
||||
public readonly string Font;
|
||||
public readonly int Size;
|
||||
public readonly int Ascender;
|
||||
}
|
||||
|
||||
public class Fonts : IGlobalModData
|
||||
{
|
||||
[FieldLoader.LoadUsing("LoadFonts")]
|
||||
public readonly Dictionary<string, FontData> FontList;
|
||||
|
||||
static object LoadFonts(MiniYaml y)
|
||||
{
|
||||
var ret = new Dictionary<string, FontData>();
|
||||
foreach (var node in y.Nodes)
|
||||
ret.Add(node.Key, FieldLoader.Load<FontData>(node.Value));
|
||||
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
@@ -12,18 +12,16 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Imaging;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Reflection;
|
||||
using System.Runtime;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using OpenRA.Chat;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Network;
|
||||
using OpenRA.Primitives;
|
||||
using OpenRA.Server;
|
||||
using OpenRA.Support;
|
||||
using OpenRA.Widgets;
|
||||
|
||||
@@ -35,15 +33,10 @@ namespace OpenRA
|
||||
public const int Timestep = 40;
|
||||
public const int TimestepJankThreshold = 250; // Don't catch up for delays larger than 250ms
|
||||
|
||||
public static InstalledMods Mods { get; private set; }
|
||||
public static ExternalMods ExternalMods { get; private set; }
|
||||
|
||||
public static ModData ModData;
|
||||
public static Settings Settings;
|
||||
public static CursorManager Cursor;
|
||||
public static bool HideCursor;
|
||||
public static ICursor Cursor;
|
||||
static WorldRenderer worldRenderer;
|
||||
static string modLaunchWrapper;
|
||||
|
||||
internal static OrderManager OrderManager;
|
||||
static Server.Server server;
|
||||
@@ -52,36 +45,31 @@ namespace OpenRA
|
||||
|
||||
public static Renderer Renderer;
|
||||
public static Sound Sound;
|
||||
public static bool HasInputFocus = false;
|
||||
|
||||
public static string EngineVersion { get; private set; }
|
||||
public static LocalPlayerProfile LocalPlayerProfile;
|
||||
public static bool BenchmarkMode = false;
|
||||
|
||||
static Task discoverNat;
|
||||
static bool takeScreenshot = false;
|
||||
static Benchmark benchmark = null;
|
||||
public static GlobalChat GlobalChat;
|
||||
|
||||
public static event Action OnShellmapLoaded = () => { };
|
||||
|
||||
public static OrderManager JoinServer(ConnectionTarget endpoint, string password, bool recordReplay = true)
|
||||
public static OrderManager JoinServer(string host, int port, string password, bool recordReplay = true)
|
||||
{
|
||||
var connection = new NetworkConnection(endpoint);
|
||||
var connection = new NetworkConnection(host, port);
|
||||
if (recordReplay)
|
||||
connection.StartRecording(() => { return TimestampedFilename(); });
|
||||
connection.StartRecording(TimestampedFilename);
|
||||
|
||||
var om = new OrderManager(endpoint, password, connection);
|
||||
var om = new OrderManager(host, port, password, connection);
|
||||
JoinInner(om);
|
||||
return om;
|
||||
}
|
||||
|
||||
public static string TimestampedFilename(bool includemilliseconds = false, string extra = "")
|
||||
static string TimestampedFilename()
|
||||
{
|
||||
var format = includemilliseconds ? "yyyy-MM-ddTHHmmssfffZ" : "yyyy-MM-ddTHHmmssZ";
|
||||
return ModData.Manifest.Id + extra + "-" + DateTime.UtcNow.ToString(format, CultureInfo.InvariantCulture);
|
||||
return DateTime.UtcNow.ToString("OpenRA-yyyy-MM-ddTHHmmssZ");
|
||||
}
|
||||
|
||||
static void JoinInner(OrderManager om)
|
||||
{
|
||||
OrderManager?.Dispose();
|
||||
if (OrderManager != null) OrderManager.Dispose();
|
||||
OrderManager = om;
|
||||
lastConnectionState = ConnectionState.PreConnecting;
|
||||
ConnectionStateChanged(OrderManager);
|
||||
@@ -89,30 +77,30 @@ namespace OpenRA
|
||||
|
||||
public static void JoinReplay(string replayFile)
|
||||
{
|
||||
JoinInner(new OrderManager(new ConnectionTarget(), "", new ReplayConnection(replayFile)));
|
||||
JoinInner(new OrderManager("<no server>", -1, "", new ReplayConnection(replayFile)));
|
||||
}
|
||||
|
||||
static void JoinLocal()
|
||||
{
|
||||
JoinInner(new OrderManager(new ConnectionTarget(), "", new EchoConnection()));
|
||||
JoinInner(new OrderManager("<no server>", -1, "", new EchoConnection()));
|
||||
}
|
||||
|
||||
// More accurate replacement for Environment.TickCount
|
||||
static Stopwatch stopwatch = Stopwatch.StartNew();
|
||||
public static long RunTime { get { return stopwatch.ElapsedMilliseconds; } }
|
||||
public static int RunTime { get { return (int)stopwatch.ElapsedMilliseconds; } }
|
||||
|
||||
public static int RenderFrame = 0;
|
||||
public static int NetFrameNumber { get { return OrderManager.NetFrameNumber; } }
|
||||
public static int LocalTick { get { return OrderManager.LocalFrameNumber; } }
|
||||
|
||||
public static event Action<ConnectionTarget> OnRemoteDirectConnect = _ => { };
|
||||
public static event Action<string, int> OnRemoteDirectConnect = (a, b) => { };
|
||||
public static event Action<OrderManager> ConnectionStateChanged = _ => { };
|
||||
static ConnectionState lastConnectionState = ConnectionState.PreConnecting;
|
||||
public static int LocalClientId { get { return OrderManager.Connection.LocalClientId; } }
|
||||
|
||||
public static void RemoteDirectConnect(ConnectionTarget endpoint)
|
||||
public static void RemoteDirectConnect(string host, int port)
|
||||
{
|
||||
OnRemoteDirectConnect(endpoint);
|
||||
OnRemoteDirectConnect(host, port);
|
||||
}
|
||||
|
||||
// Hacky workaround for orderManager visibility
|
||||
@@ -155,7 +143,8 @@ namespace OpenRA
|
||||
internal static void StartGame(string mapUID, WorldType type)
|
||||
{
|
||||
// Dispose of the old world before creating a new one.
|
||||
worldRenderer?.Dispose();
|
||||
if (worldRenderer != null)
|
||||
worldRenderer.Dispose();
|
||||
|
||||
Cursor.SetCursor(null);
|
||||
BeforeGameStart();
|
||||
@@ -165,21 +154,13 @@ namespace OpenRA
|
||||
using (new PerfTimer("PrepareMap"))
|
||||
map = ModData.PrepareMap(mapUID);
|
||||
using (new PerfTimer("NewWorld"))
|
||||
OrderManager.World = new World(ModData, map, OrderManager, type);
|
||||
OrderManager.World = new World(map, OrderManager, type);
|
||||
|
||||
OrderManager.World.GameOver += FinishBenchmark;
|
||||
|
||||
worldRenderer = new WorldRenderer(ModData, OrderManager.World);
|
||||
|
||||
// Proactively collect memory during loading to reduce peak memory.
|
||||
GC.Collect();
|
||||
worldRenderer = new WorldRenderer(OrderManager.World);
|
||||
|
||||
using (new PerfTimer("LoadComplete"))
|
||||
OrderManager.World.LoadComplete(worldRenderer);
|
||||
|
||||
// Proactively collect memory during loading to reduce peak memory.
|
||||
GC.Collect();
|
||||
|
||||
if (OrderManager.GameStarted)
|
||||
return;
|
||||
|
||||
@@ -192,14 +173,6 @@ namespace OpenRA
|
||||
worldRenderer.RefreshPalette();
|
||||
Cursor.SetCursor("default");
|
||||
|
||||
// Now loading is completed, now is the ideal time to run a GC and compact the LOH.
|
||||
// - All the temporary garbage created during loading can be collected.
|
||||
// - Live objects are likely to live for the length of the game or longer,
|
||||
// thus promoting them into a higher generation is not an issue.
|
||||
// - We can remove any fragmentation in the LOH caused by temporary loading garbage.
|
||||
// - A loading screen is visible, so a delay won't matter to the user.
|
||||
// Much better to clean up now then to drop frames during gameplay for GC pauses.
|
||||
GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;
|
||||
GC.Collect();
|
||||
}
|
||||
|
||||
@@ -208,12 +181,7 @@ namespace OpenRA
|
||||
var replay = OrderManager.Connection as ReplayConnection;
|
||||
var replayName = replay != null ? replay.Filename : null;
|
||||
var lobbyInfo = OrderManager.LobbyInfo;
|
||||
|
||||
// Reseed the RNG so this isn't an exact repeat of the last game
|
||||
lobbyInfo.GlobalSettings.RandomSeed = CosmeticRandom.Next();
|
||||
|
||||
var orders = new[]
|
||||
{
|
||||
var orders = new[] {
|
||||
Order.Command("sync_lobby {0}".F(lobbyInfo.Serialize())),
|
||||
Order.Command("startgame")
|
||||
};
|
||||
@@ -229,7 +197,7 @@ namespace OpenRA
|
||||
CreateAndStartLocalServer(lobbyInfo.GlobalSettings.Map, orders);
|
||||
}
|
||||
|
||||
public static void CreateAndStartLocalServer(string mapUID, IEnumerable<Order> setupOrders)
|
||||
public static void CreateAndStartLocalServer(string mapUID, IEnumerable<Order> setupOrders, Action onStart = null)
|
||||
{
|
||||
OrderManager om = null;
|
||||
|
||||
@@ -239,11 +207,13 @@ namespace OpenRA
|
||||
LobbyInfoChanged -= lobbyReady;
|
||||
foreach (var o in setupOrders)
|
||||
om.IssueOrder(o);
|
||||
};
|
||||
|
||||
if (onStart != null)
|
||||
onStart();
|
||||
};
|
||||
LobbyInfoChanged += lobbyReady;
|
||||
|
||||
om = JoinServer(CreateLocalServer(mapUID), "");
|
||||
om = JoinServer(IPAddress.Loopback.ToString(), CreateLocalServer(mapUID), "");
|
||||
}
|
||||
|
||||
public static bool IsHost
|
||||
@@ -262,143 +232,77 @@ namespace OpenRA
|
||||
|
||||
public static void InitializeSettings(Arguments args)
|
||||
{
|
||||
Settings = new Settings(Path.Combine(Platform.SupportDir, "settings.yaml"), args);
|
||||
Settings = new Settings(Platform.ResolvePath(Path.Combine("^", "settings.yaml")), args);
|
||||
}
|
||||
|
||||
public static RunStatus InitializeAndRun(string[] args)
|
||||
internal static void Initialize(Arguments args)
|
||||
{
|
||||
Initialize(new Arguments(args));
|
||||
|
||||
// Proactively collect memory during loading to reduce peak memory.
|
||||
GC.Collect();
|
||||
return Run();
|
||||
}
|
||||
|
||||
static void Initialize(Arguments args)
|
||||
{
|
||||
var engineDirArg = args.GetValue("Engine.EngineDir", null);
|
||||
if (!string.IsNullOrEmpty(engineDirArg))
|
||||
Platform.OverrideEngineDir(engineDirArg);
|
||||
|
||||
var supportDirArg = args.GetValue("Engine.SupportDir", null);
|
||||
if (!string.IsNullOrEmpty(supportDirArg))
|
||||
Platform.OverrideSupportDir(supportDirArg);
|
||||
|
||||
Console.WriteLine("Platform is {0}", Platform.CurrentPlatform);
|
||||
|
||||
// Load the engine version as early as possible so it can be written to exception logs
|
||||
try
|
||||
{
|
||||
EngineVersion = File.ReadAllText(Path.Combine(Platform.EngineDir, "VERSION")).Trim();
|
||||
}
|
||||
catch { }
|
||||
|
||||
if (string.IsNullOrEmpty(EngineVersion))
|
||||
EngineVersion = "Unknown";
|
||||
|
||||
Console.WriteLine("Engine version is {0}", EngineVersion);
|
||||
|
||||
// Special case handling of Game.Mod argument: if it matches a real filesystem path
|
||||
// then we use this to override the mod search path, and replace it with the mod id
|
||||
var modID = args.GetValue("Game.Mod", null);
|
||||
var explicitModPaths = new string[0];
|
||||
if (modID != null && (File.Exists(modID) || Directory.Exists(modID)))
|
||||
{
|
||||
explicitModPaths = new[] { modID };
|
||||
modID = Path.GetFileNameWithoutExtension(modID);
|
||||
}
|
||||
|
||||
InitializeSettings(args);
|
||||
|
||||
Log.AddChannel("perf", "perf.log");
|
||||
Log.AddChannel("debug", "debug.log");
|
||||
Log.AddChannel("server", "server.log", true);
|
||||
Log.AddChannel("sync", "syncreport.log");
|
||||
Log.AddChannel("server", "server.log");
|
||||
Log.AddChannel("sound", "sound.log");
|
||||
Log.AddChannel("graphics", "graphics.log");
|
||||
Log.AddChannel("geoip", "geoip.log");
|
||||
Log.AddChannel("nat", "nat.log");
|
||||
Log.AddChannel("client", "client.log");
|
||||
Log.AddChannel("irc", "irc.log");
|
||||
|
||||
var platforms = new[] { Settings.Game.Platform, "Default", null };
|
||||
foreach (var p in platforms)
|
||||
if (Settings.Server.DiscoverNatDevices)
|
||||
UPnP.TryNatDiscovery();
|
||||
else
|
||||
{
|
||||
if (p == null)
|
||||
throw new InvalidOperationException("Failed to initialize platform-integration library. Check graphics.log for details.");
|
||||
Settings.Server.NatDeviceAvailable = false;
|
||||
Settings.Server.AllowPortForward = false;
|
||||
}
|
||||
|
||||
Settings.Game.Platform = p;
|
||||
GeoIP.Initialize();
|
||||
|
||||
var renderers = new[] { Settings.Graphics.Renderer, "Default", null };
|
||||
foreach (var r in renderers)
|
||||
{
|
||||
if (r == null)
|
||||
throw new InvalidOperationException("No suitable renderers were found. Check graphics.log for details.");
|
||||
|
||||
Settings.Graphics.Renderer = r;
|
||||
try
|
||||
{
|
||||
var rendererPath = Path.Combine(Platform.BinDir, "OpenRA.Platforms." + p + ".dll");
|
||||
var assembly = Assembly.LoadFile(rendererPath);
|
||||
|
||||
var platformType = assembly.GetTypes().SingleOrDefault(t => typeof(IPlatform).IsAssignableFrom(t));
|
||||
if (platformType == null)
|
||||
throw new InvalidOperationException("Platform dll must include exactly one IPlatform implementation.");
|
||||
|
||||
var platform = (IPlatform)platformType.GetConstructor(Type.EmptyTypes).Invoke(null);
|
||||
Renderer = new Renderer(platform, Settings.Graphics);
|
||||
Sound = new Sound(platform, Settings.Sound);
|
||||
|
||||
Renderer = new Renderer(Settings.Graphics, Settings.Server);
|
||||
break;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Write("graphics", "{0}", e);
|
||||
Console.WriteLine("Renderer initialization failed. Check graphics.log for details.");
|
||||
|
||||
Renderer?.Dispose();
|
||||
|
||||
Sound?.Dispose();
|
||||
Console.WriteLine("Renderer initialization failed. Fallback in place. Check graphics.log for details.");
|
||||
}
|
||||
}
|
||||
|
||||
if (Settings.Server.DiscoverNatDevices)
|
||||
discoverNat = UPnP.DiscoverNatDevices(Settings.Server.NatDiscoveryTimeout);
|
||||
Sound = new Sound(Settings.Sound.Engine);
|
||||
|
||||
var modSearchArg = args.GetValue("Engine.ModSearchPaths", null);
|
||||
var modSearchPaths = modSearchArg != null ?
|
||||
FieldLoader.GetValue<string[]>("Engine.ModsPath", modSearchArg) :
|
||||
new[] { Path.Combine(Platform.EngineDir, "mods") };
|
||||
GlobalChat = new GlobalChat();
|
||||
|
||||
Mods = new InstalledMods(modSearchPaths, explicitModPaths);
|
||||
Console.WriteLine("Internal mods:");
|
||||
foreach (var mod in Mods)
|
||||
Console.WriteLine("\t{0}: {1} ({2})", mod.Key, mod.Value.Metadata.Title, mod.Value.Metadata.Version);
|
||||
|
||||
modLaunchWrapper = args.GetValue("Engine.LaunchWrapper", null);
|
||||
|
||||
ExternalMods = new ExternalMods();
|
||||
|
||||
if (modID != null && Mods.TryGetValue(modID, out _))
|
||||
{
|
||||
var launchPath = args.GetValue("Engine.LaunchPath", null);
|
||||
var launchArgs = new List<string>();
|
||||
|
||||
// Sanitize input from platform-specific launchers
|
||||
// Process.Start requires paths to not be quoted, even if they contain spaces
|
||||
if (launchPath != null && launchPath.First() == '"' && launchPath.Last() == '"')
|
||||
launchPath = launchPath.Substring(1, launchPath.Length - 2);
|
||||
|
||||
if (launchPath == null)
|
||||
{
|
||||
// When launching the assembly directly we must propagate the Engine.EngineDir argument if defined
|
||||
// Platform-specific launchers are expected to manage this internally.
|
||||
launchPath = Assembly.GetEntryAssembly().Location;
|
||||
if (!string.IsNullOrEmpty(engineDirArg))
|
||||
launchArgs.Add("Engine.EngineDir=\"" + engineDirArg + "\"");
|
||||
}
|
||||
|
||||
ExternalMods.Register(Mods[modID], launchPath, launchArgs, ModRegistration.User);
|
||||
|
||||
if (ExternalMods.TryGetValue(ExternalMod.MakeKey(Mods[modID]), out var activeMod))
|
||||
ExternalMods.ClearInvalidRegistrations(activeMod, ModRegistration.User);
|
||||
}
|
||||
|
||||
Console.WriteLine("External mods:");
|
||||
foreach (var mod in ExternalMods)
|
||||
Console.WriteLine("Available mods:");
|
||||
foreach (var mod in ModMetadata.AllMods)
|
||||
Console.WriteLine("\t{0}: {1} ({2})", mod.Key, mod.Value.Title, mod.Value.Version);
|
||||
|
||||
InitializeMod(modID, args);
|
||||
InitializeMod(Settings.Game.Mod, args);
|
||||
|
||||
if (Settings.Server.DiscoverNatDevices)
|
||||
RunAfterDelay(Settings.Server.NatDiscoveryTimeout, UPnP.StoppingNatDiscovery);
|
||||
}
|
||||
|
||||
public static bool IsModInstalled(string modId)
|
||||
{
|
||||
return ModMetadata.AllMods[modId].RequiresMods.All(IsModInstalled);
|
||||
}
|
||||
|
||||
public static bool IsModInstalled(KeyValuePair<string, string> mod)
|
||||
{
|
||||
return ModMetadata.AllMods.ContainsKey(mod.Key)
|
||||
&& ModMetadata.AllMods[mod.Key].Version == mod.Value
|
||||
&& IsModInstalled(mod.Key);
|
||||
}
|
||||
|
||||
public static void InitializeMod(string mod, Arguments args)
|
||||
@@ -407,15 +311,18 @@ namespace OpenRA
|
||||
LobbyInfoChanged = () => { };
|
||||
ConnectionStateChanged = om => { };
|
||||
BeforeGameStart = () => { };
|
||||
OnRemoteDirectConnect = endpoint => { };
|
||||
OnRemoteDirectConnect = (a, b) => { };
|
||||
delayedActions = new ActionQueue();
|
||||
|
||||
Ui.ResetAll();
|
||||
|
||||
worldRenderer?.Dispose();
|
||||
if (worldRenderer != null)
|
||||
worldRenderer.Dispose();
|
||||
worldRenderer = null;
|
||||
server?.Shutdown();
|
||||
OrderManager?.Dispose();
|
||||
if (server != null)
|
||||
server.Shutdown();
|
||||
if (OrderManager != null)
|
||||
OrderManager.Dispose();
|
||||
|
||||
if (ModData != null)
|
||||
{
|
||||
@@ -425,58 +332,63 @@ namespace OpenRA
|
||||
|
||||
ModData = null;
|
||||
|
||||
if (mod == null)
|
||||
throw new InvalidOperationException("Game.Mod argument missing.");
|
||||
|
||||
if (!Mods.ContainsKey(mod))
|
||||
throw new InvalidOperationException("Unknown or invalid mod '{0}'.".F(mod));
|
||||
// Fall back to default if the mod doesn't exist or has missing prerequisites.
|
||||
if (!ModMetadata.AllMods.ContainsKey(mod) || !IsModInstalled(mod))
|
||||
mod = new GameSettings().Mod;
|
||||
|
||||
Console.WriteLine("Loading mod: {0}", mod);
|
||||
Settings.Game.Mod = mod;
|
||||
|
||||
Sound.StopVideo();
|
||||
|
||||
ModData = new ModData(Mods[mod], Mods, true);
|
||||
|
||||
LocalPlayerProfile = new LocalPlayerProfile(Path.Combine(Platform.SupportDir, Settings.Game.AuthProfile), ModData.Manifest.Get<PlayerDatabase>());
|
||||
|
||||
if (!ModData.LoadScreen.BeforeLoad())
|
||||
return;
|
||||
ModData = new ModData(mod, true);
|
||||
|
||||
using (new PerfTimer("LoadMaps"))
|
||||
ModData.MapCache.LoadMaps();
|
||||
|
||||
var installData = ModData.Manifest.Get<ContentInstaller>();
|
||||
var isModContentInstalled = installData.TestFiles.All(f => File.Exists(Platform.ResolvePath(f)));
|
||||
|
||||
// Mod assets are missing!
|
||||
if (!isModContentInstalled)
|
||||
{
|
||||
InitializeMod("modchooser", new Arguments());
|
||||
return;
|
||||
}
|
||||
|
||||
ModData.InitializeLoaders(ModData.DefaultFileSystem);
|
||||
Renderer.InitializeFonts(ModData);
|
||||
|
||||
var grid = ModData.Manifest.Contains<MapGrid>() ? ModData.Manifest.Get<MapGrid>() : null;
|
||||
Renderer.InitializeDepthBuffer(grid);
|
||||
if (Cursor != null)
|
||||
Cursor.Dispose();
|
||||
|
||||
Cursor?.Dispose();
|
||||
if (Settings.Graphics.HardwareCursors)
|
||||
{
|
||||
try
|
||||
{
|
||||
Cursor = new HardwareCursor(ModData.CursorProvider);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Write("debug", "Failed to initialize hardware cursors. Falling back to software cursors.");
|
||||
Log.Write("debug", "Error was: " + e.Message);
|
||||
|
||||
Cursor = new CursorManager(ModData.CursorProvider);
|
||||
Console.WriteLine("Failed to initialize hardware cursors. Falling back to software cursors.");
|
||||
Console.WriteLine("Error was: " + e.Message);
|
||||
|
||||
Cursor = new SoftwareCursor(ModData.CursorProvider);
|
||||
}
|
||||
}
|
||||
else
|
||||
Cursor = new SoftwareCursor(ModData.CursorProvider);
|
||||
|
||||
PerfHistory.Items["render"].HasNormalTick = false;
|
||||
PerfHistory.Items["batches"].HasNormalTick = false;
|
||||
PerfHistory.Items["render_world"].HasNormalTick = false;
|
||||
PerfHistory.Items["render_widgets"].HasNormalTick = false;
|
||||
PerfHistory.Items["render_flip"].HasNormalTick = false;
|
||||
PerfHistory.Items["terrain_lighting"].HasNormalTick = false;
|
||||
|
||||
JoinLocal();
|
||||
|
||||
try
|
||||
{
|
||||
discoverNat?.Wait();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine("NAT discovery failed: {0}", e.Message);
|
||||
Log.Write("nat", e.ToString());
|
||||
}
|
||||
|
||||
ChromeMetrics.TryGet("ChatMessageColor", out chatMessageColor);
|
||||
ChromeMetrics.TryGet("SystemMessageColor", out systemMessageColor);
|
||||
|
||||
ModData.LoadScreen.StartGame(args);
|
||||
}
|
||||
|
||||
@@ -490,10 +402,7 @@ namespace OpenRA
|
||||
var shellmap = ChooseShellmap();
|
||||
|
||||
using (new PerfTimer("StartGame"))
|
||||
{
|
||||
StartGame(shellmap, WorldType.Shellmap);
|
||||
OnShellmapLoaded();
|
||||
}
|
||||
}
|
||||
|
||||
static string ChooseShellmap()
|
||||
@@ -508,61 +417,42 @@ namespace OpenRA
|
||||
return shellmaps.Random(CosmeticRandom);
|
||||
}
|
||||
|
||||
public static void SwitchToExternalMod(ExternalMod mod, string[] launchArguments = null, Action onFailed = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var path = mod.LaunchPath;
|
||||
var args = launchArguments != null ? mod.LaunchArgs.Append(launchArguments) : mod.LaunchArgs;
|
||||
if (modLaunchWrapper != null)
|
||||
{
|
||||
path = modLaunchWrapper;
|
||||
args = new[] { mod.LaunchPath }.Concat(args);
|
||||
}
|
||||
|
||||
var p = Process.Start(path, args.Select(a => "\"" + a + "\"").JoinWith(" "));
|
||||
if (p == null || p.HasExited)
|
||||
onFailed();
|
||||
else
|
||||
{
|
||||
p.Close();
|
||||
Exit();
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Write("debug", "Failed to switch to external mod.");
|
||||
Log.Write("debug", "Error was: " + e.Message);
|
||||
onFailed();
|
||||
}
|
||||
}
|
||||
|
||||
static RunStatus state = RunStatus.Running;
|
||||
public static event Action OnQuit = () => { };
|
||||
|
||||
// Note: These delayed actions should only be used by widgets or disposing objects
|
||||
// - things that depend on a particular world should be queuing them on the world actor.
|
||||
static volatile ActionQueue delayedActions = new ActionQueue();
|
||||
static Color systemMessageColor = Color.White;
|
||||
static Color chatMessageColor = Color.White;
|
||||
public static void RunAfterTick(Action a) { delayedActions.Add(a, RunTime); }
|
||||
public static void RunAfterDelay(int delayMilliseconds, Action a) { delayedActions.Add(a, RunTime + delayMilliseconds); }
|
||||
|
||||
static void TakeScreenshotInner()
|
||||
{
|
||||
using (new PerfTimer("Renderer.SaveScreenshot"))
|
||||
Log.Write("debug", "Taking screenshot");
|
||||
|
||||
Bitmap bitmap;
|
||||
using (new PerfTimer("Renderer.TakeScreenshot"))
|
||||
bitmap = Renderer.Device.TakeScreenshot();
|
||||
|
||||
ThreadPool.QueueUserWorkItem(_ =>
|
||||
{
|
||||
var mod = ModData.Manifest.Metadata;
|
||||
var directory = Path.Combine(Platform.SupportDir, "Screenshots", ModData.Manifest.Id, mod.Version);
|
||||
var mod = ModData.Manifest.Mod;
|
||||
var directory = Platform.ResolvePath("^", "Screenshots", mod.Id, mod.Version);
|
||||
Directory.CreateDirectory(directory);
|
||||
|
||||
var filename = TimestampedFilename(true);
|
||||
var path = Path.Combine(directory, string.Concat(filename, ".png"));
|
||||
Log.Write("debug", "Taking screenshot " + path);
|
||||
var filename = TimestampedFilename();
|
||||
var format = Settings.Graphics.ScreenshotFormat;
|
||||
var extension = ImageCodecInfo.GetImageEncoders().FirstOrDefault(x => x.FormatID == format.Guid)
|
||||
.FilenameExtension.Split(';').First().ToLowerInvariant().Substring(1);
|
||||
var destination = Path.Combine(directory, string.Concat(filename, extension));
|
||||
|
||||
Renderer.SaveScreenshot(path);
|
||||
Debug("Saved screenshot " + filename);
|
||||
}
|
||||
using (new PerfTimer("Save Screenshot ({0})".F(format)))
|
||||
bitmap.Save(destination, format);
|
||||
|
||||
bitmap.Dispose();
|
||||
|
||||
RunAfterTick(() => Debug("Saved screenshot " + filename));
|
||||
});
|
||||
}
|
||||
|
||||
static void InnerLogicTick(OrderManager orderManager)
|
||||
@@ -578,11 +468,13 @@ namespace OpenRA
|
||||
var integralTickTimestep = (uiTickDelta / Timestep) * Timestep;
|
||||
Ui.LastTickTime += integralTickTimestep >= TimestepJankThreshold ? integralTickTimestep : Timestep;
|
||||
|
||||
Sync.RunUnsynced(Settings.Debug.SyncCheckUnsyncedCode, world, Ui.Tick);
|
||||
Viewport.TicksSinceLastMove += uiTickDelta / Timestep;
|
||||
|
||||
Sync.CheckSyncUnchanged(world, Ui.Tick);
|
||||
Cursor.Tick();
|
||||
}
|
||||
|
||||
var worldTimestep = world == null ? Timestep : world.IsLoadingGameSave ? 1 : world.Timestep;
|
||||
var worldTimestep = world == null ? Timestep : world.Timestep;
|
||||
var worldTickDelta = tick - orderManager.LastTickTime;
|
||||
if (worldTimestep != 0 && worldTickDelta >= worldTimestep)
|
||||
{
|
||||
@@ -596,46 +488,52 @@ namespace OpenRA
|
||||
orderManager.LastTickTime += integralTickTimestep >= TimestepJankThreshold ? integralTickTimestep : worldTimestep;
|
||||
|
||||
Sound.Tick();
|
||||
Sync.RunUnsynced(Settings.Debug.SyncCheckUnsyncedCode, world, orderManager.TickImmediate);
|
||||
Sync.CheckSyncUnchanged(world, orderManager.TickImmediate);
|
||||
|
||||
if (world == null)
|
||||
return;
|
||||
|
||||
var isNetTick = LocalTick % NetTickScale == 0;
|
||||
|
||||
if (!isNetTick || orderManager.IsReadyForNextFrame)
|
||||
// Don't tick when the shellmap is disabled
|
||||
if (world.ShouldTick)
|
||||
{
|
||||
++orderManager.LocalFrameNumber;
|
||||
var isNetTick = LocalTick % NetTickScale == 0;
|
||||
|
||||
Log.Write("debug", "--Tick: {0} ({1})", LocalTick, isNetTick ? "net" : "local");
|
||||
|
||||
if (isNetTick)
|
||||
orderManager.Tick();
|
||||
|
||||
Sync.RunUnsynced(Settings.Debug.SyncCheckUnsyncedCode, world, () =>
|
||||
if (!isNetTick || orderManager.IsReadyForNextFrame)
|
||||
{
|
||||
world.OrderGenerator.Tick(world);
|
||||
});
|
||||
++orderManager.LocalFrameNumber;
|
||||
|
||||
world.Tick();
|
||||
Log.Write("debug", "--Tick: {0} ({1})", LocalTick, isNetTick ? "net" : "local");
|
||||
|
||||
PerfHistory.Tick();
|
||||
if (BenchmarkMode)
|
||||
Log.Write("cpu", "{0};{1}".F(LocalTick, PerfHistory.Items["tick_time"].LastValue));
|
||||
|
||||
if (isNetTick)
|
||||
orderManager.Tick();
|
||||
|
||||
Sync.CheckSyncUnchanged(world, () =>
|
||||
{
|
||||
world.OrderGenerator.Tick(world);
|
||||
world.Selection.Tick(world);
|
||||
});
|
||||
|
||||
world.Tick();
|
||||
|
||||
PerfHistory.Tick();
|
||||
}
|
||||
else if (orderManager.NetFrameNumber == 0)
|
||||
orderManager.LastTickTime = RunTime;
|
||||
|
||||
Sync.CheckSyncUnchanged(world, () => world.TickRender(worldRenderer));
|
||||
}
|
||||
else if (orderManager.NetFrameNumber == 0)
|
||||
orderManager.LastTickTime = RunTime;
|
||||
|
||||
// Wait until we have done our first world Tick before TickRendering
|
||||
if (orderManager.LocalFrameNumber > 0)
|
||||
Sync.RunUnsynced(Settings.Debug.SyncCheckUnsyncedCode, world, () => world.TickRender(worldRenderer));
|
||||
else
|
||||
PerfHistory.Tick();
|
||||
}
|
||||
|
||||
benchmark?.Tick(LocalTick);
|
||||
}
|
||||
}
|
||||
|
||||
static void LogicTick()
|
||||
{
|
||||
PerformDelayedActions();
|
||||
delayedActions.PerformActions(RunTime);
|
||||
|
||||
if (OrderManager.Connection.ConnectionState != lastConnectionState)
|
||||
{
|
||||
@@ -648,15 +546,7 @@ namespace OpenRA
|
||||
InnerLogicTick(worldRenderer.World.OrderManager);
|
||||
}
|
||||
|
||||
public static void PerformDelayedActions()
|
||||
{
|
||||
delayedActions.PerformActions(RunTime);
|
||||
}
|
||||
|
||||
public static void TakeScreenshot()
|
||||
{
|
||||
takeScreenshot = true;
|
||||
}
|
||||
public static bool TakeScreenshot = false;
|
||||
|
||||
static void RenderTick()
|
||||
{
|
||||
@@ -664,70 +554,48 @@ namespace OpenRA
|
||||
{
|
||||
++RenderFrame;
|
||||
|
||||
// Prepare renderables (i.e. render voxels) before calling BeginFrame
|
||||
using (new PerfSample("render_prepare"))
|
||||
{
|
||||
Renderer.WorldModelRenderer.BeginFrame();
|
||||
|
||||
// World rendering is disabled while the loading screen is displayed
|
||||
if (worldRenderer != null && !worldRenderer.World.IsLoadingGameSave)
|
||||
{
|
||||
worldRenderer.Viewport.Tick();
|
||||
worldRenderer.PrepareRenderables();
|
||||
}
|
||||
|
||||
Ui.PrepareRenderables();
|
||||
Renderer.WorldModelRenderer.EndFrame();
|
||||
}
|
||||
|
||||
// worldRenderer is null during the initial install/download screen
|
||||
// World rendering is disabled while the loading screen is displayed
|
||||
// Use worldRenderer.World instead of OrderManager.World to avoid a rendering mismatch while processing orders
|
||||
if (worldRenderer != null && !worldRenderer.World.IsLoadingGameSave)
|
||||
if (worldRenderer != null)
|
||||
{
|
||||
Renderer.BeginWorld(worldRenderer.Viewport.Rectangle);
|
||||
Renderer.BeginFrame(worldRenderer.Viewport.TopLeft, worldRenderer.Viewport.Zoom);
|
||||
Sound.SetListenerPosition(worldRenderer.Viewport.CenterPosition);
|
||||
using (new PerfSample("render_world"))
|
||||
worldRenderer.Draw();
|
||||
worldRenderer.Draw();
|
||||
}
|
||||
else
|
||||
Renderer.BeginFrame(int2.Zero, 1f);
|
||||
|
||||
using (new PerfSample("render_widgets"))
|
||||
{
|
||||
Renderer.BeginUI();
|
||||
|
||||
if (worldRenderer != null && !worldRenderer.World.IsLoadingGameSave)
|
||||
worldRenderer.DrawAnnotations();
|
||||
Renderer.WorldVoxelRenderer.BeginFrame();
|
||||
Ui.PrepareRenderables();
|
||||
Renderer.WorldVoxelRenderer.EndFrame();
|
||||
|
||||
Ui.Draw();
|
||||
|
||||
if (ModData != null && ModData.CursorProvider != null)
|
||||
{
|
||||
if (HideCursor)
|
||||
Cursor.SetCursor(null);
|
||||
else
|
||||
{
|
||||
Cursor.SetCursor(Ui.Root.GetCursorOuter(Viewport.LastMousePos) ?? "default");
|
||||
Cursor.Render(Renderer);
|
||||
}
|
||||
Cursor.SetCursor(Ui.Root.GetCursorOuter(Viewport.LastMousePos) ?? "default");
|
||||
Cursor.Render(Renderer);
|
||||
}
|
||||
}
|
||||
|
||||
using (new PerfSample("render_flip"))
|
||||
Renderer.EndFrame(new DefaultInputHandler(OrderManager.World));
|
||||
|
||||
if (takeScreenshot)
|
||||
if (TakeScreenshot)
|
||||
{
|
||||
takeScreenshot = false;
|
||||
TakeScreenshot = false;
|
||||
TakeScreenshotInner();
|
||||
}
|
||||
}
|
||||
|
||||
PerfHistory.Items["render"].Tick();
|
||||
PerfHistory.Items["batches"].Tick();
|
||||
PerfHistory.Items["render_world"].Tick();
|
||||
PerfHistory.Items["render_widgets"].Tick();
|
||||
PerfHistory.Items["render_flip"].Tick();
|
||||
PerfHistory.Items["terrain_lighting"].Tick();
|
||||
|
||||
if (BenchmarkMode)
|
||||
Log.Write("render", "{0};{1}".F(RenderFrame, PerfHistory.Items["render"].LastValue));
|
||||
}
|
||||
|
||||
static void Loop()
|
||||
@@ -773,7 +641,6 @@ namespace OpenRA
|
||||
var nextLogic = RunTime;
|
||||
var nextRender = RunTime;
|
||||
var forcedNextRender = RunTime;
|
||||
var renderBeforeNextTick = false;
|
||||
|
||||
while (state == RunStatus.Running)
|
||||
{
|
||||
@@ -785,13 +652,6 @@ namespace OpenRA
|
||||
var maxFramerate = Settings.Graphics.CapFramerate ? Settings.Graphics.MaxFramerate.Clamp(1, 1000) : 1000;
|
||||
var renderInterval = 1000 / maxFramerate;
|
||||
|
||||
// Tick as fast as possible while restoring game saves, capping rendering at 5 FPS
|
||||
if (OrderManager.World != null && OrderManager.World.IsLoadingGameSave)
|
||||
{
|
||||
logicInterval = 1;
|
||||
renderInterval = 200;
|
||||
}
|
||||
|
||||
var now = RunTime;
|
||||
|
||||
// If the logic has fallen behind too much, skip it and catch up
|
||||
@@ -802,17 +662,17 @@ namespace OpenRA
|
||||
var nextUpdate = Math.Min(nextLogic, nextRender);
|
||||
if (now >= nextUpdate)
|
||||
{
|
||||
var forceRender = renderBeforeNextTick || now >= forcedNextRender;
|
||||
var forceRender = now >= forcedNextRender;
|
||||
|
||||
if (now >= nextLogic && !renderBeforeNextTick)
|
||||
if (now >= nextLogic)
|
||||
{
|
||||
nextLogic += logicInterval;
|
||||
|
||||
LogicTick();
|
||||
|
||||
// Force at least one render per tick during regular gameplay
|
||||
if (OrderManager.World != null && !OrderManager.World.IsLoadingGameSave && !OrderManager.World.IsReplay)
|
||||
renderBeforeNextTick = true;
|
||||
if (OrderManager.World != null && !OrderManager.World.IsReplay)
|
||||
forceRender = true;
|
||||
}
|
||||
|
||||
var haveSomeTimeUntilNextLogic = now < nextLogic;
|
||||
@@ -831,15 +691,14 @@ namespace OpenRA
|
||||
forcedNextRender = now + maxRenderInterval;
|
||||
|
||||
RenderTick();
|
||||
renderBeforeNextTick = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
Thread.Sleep((int)(nextUpdate - now));
|
||||
Thread.Sleep(nextUpdate - now);
|
||||
}
|
||||
}
|
||||
|
||||
static RunStatus Run()
|
||||
internal static RunStatus Run()
|
||||
{
|
||||
if (Settings.Graphics.MaxFramerate < 1)
|
||||
{
|
||||
@@ -854,13 +713,16 @@ namespace OpenRA
|
||||
finally
|
||||
{
|
||||
// Ensure that the active replay is properly saved
|
||||
OrderManager?.Dispose();
|
||||
if (OrderManager != null)
|
||||
OrderManager.Dispose();
|
||||
}
|
||||
|
||||
worldRenderer?.Dispose();
|
||||
if (worldRenderer != null)
|
||||
worldRenderer.Dispose();
|
||||
ModData.Dispose();
|
||||
ChromeProvider.Deinitialize();
|
||||
|
||||
GlobalChat.Dispose();
|
||||
Sound.Dispose();
|
||||
Renderer.Dispose();
|
||||
|
||||
@@ -874,29 +736,25 @@ namespace OpenRA
|
||||
state = RunStatus.Success;
|
||||
}
|
||||
|
||||
public static void AddSystemLine(string text)
|
||||
public static void Restart()
|
||||
{
|
||||
AddSystemLine("Battlefield Control", text);
|
||||
state = RunStatus.Restart;
|
||||
}
|
||||
|
||||
public static void AddSystemLine(string name, string text)
|
||||
public static void AddChatLine(Color color, string name, string text)
|
||||
{
|
||||
OrderManager.AddChatLine(name, systemMessageColor, text, systemMessageColor);
|
||||
}
|
||||
|
||||
public static void AddChatLine(string name, Color nameColor, string text)
|
||||
{
|
||||
OrderManager.AddChatLine(name, nameColor, text, chatMessageColor);
|
||||
OrderManager.AddChatLine(color, name, text);
|
||||
}
|
||||
|
||||
public static void Debug(string s, params object[] args)
|
||||
{
|
||||
AddSystemLine("Debug", string.Format(s, args));
|
||||
AddChatLine(Color.White, "Debug", string.Format(s, args));
|
||||
}
|
||||
|
||||
public static void Disconnect()
|
||||
{
|
||||
OrderManager.World?.TraitDict.PrintReport();
|
||||
if (OrderManager.World != null)
|
||||
OrderManager.World.TraitDict.PrintReport();
|
||||
|
||||
OrderManager.Dispose();
|
||||
CloseServer();
|
||||
@@ -905,7 +763,8 @@ namespace OpenRA
|
||||
|
||||
public static void CloseServer()
|
||||
{
|
||||
server?.Shutdown();
|
||||
if (server != null)
|
||||
server.Shutdown();
|
||||
}
|
||||
|
||||
public static T CreateObject<T>(string name)
|
||||
@@ -913,79 +772,29 @@ namespace OpenRA
|
||||
return ModData.ObjectCreator.CreateObject<T>(name);
|
||||
}
|
||||
|
||||
public static ConnectionTarget CreateServer(ServerSettings settings)
|
||||
public static void CreateServer(ServerSettings settings)
|
||||
{
|
||||
var endpoints = new List<IPEndPoint>
|
||||
{
|
||||
new IPEndPoint(IPAddress.IPv6Any, settings.ListenPort),
|
||||
new IPEndPoint(IPAddress.Any, settings.ListenPort)
|
||||
};
|
||||
server = new Server.Server(endpoints, settings, ModData, ServerType.Multiplayer);
|
||||
|
||||
return server.GetEndpointForLocalConnection();
|
||||
server = new Server.Server(new IPEndPoint(IPAddress.Any, settings.ListenPort), settings, ModData, false);
|
||||
}
|
||||
|
||||
public static ConnectionTarget CreateLocalServer(string map)
|
||||
public static int CreateLocalServer(string map)
|
||||
{
|
||||
var settings = new ServerSettings()
|
||||
{
|
||||
Name = "Skirmish Game",
|
||||
Map = map,
|
||||
AdvertiseOnline = false
|
||||
AdvertiseOnline = false,
|
||||
AllowPortForward = false
|
||||
};
|
||||
|
||||
// Always connect to local games using the same loopback connection
|
||||
// Exposing multiple endpoints introduces a race condition on the client's PlayerIndex (sometimes 0, sometimes 1)
|
||||
// This would break the Restart button, which relies on the PlayerIndex always being the same for local servers
|
||||
var endpoints = new List<IPEndPoint>
|
||||
{
|
||||
new IPEndPoint(IPAddress.Loopback, 0)
|
||||
};
|
||||
server = new Server.Server(endpoints, settings, ModData, ServerType.Local);
|
||||
server = new Server.Server(new IPEndPoint(IPAddress.Loopback, 0), settings, ModData, false);
|
||||
|
||||
return server.GetEndpointForLocalConnection();
|
||||
return server.Port;
|
||||
}
|
||||
|
||||
public static bool IsCurrentWorld(World world)
|
||||
{
|
||||
return OrderManager != null && OrderManager.World == world && !world.Disposing;
|
||||
}
|
||||
|
||||
public static bool SetClipboardText(string text)
|
||||
{
|
||||
return Renderer.Window.SetClipboardText(text);
|
||||
}
|
||||
|
||||
public static void BenchmarkMode(string prefix)
|
||||
{
|
||||
benchmark = new Benchmark(prefix);
|
||||
}
|
||||
|
||||
public static void LoadMap(string launchMap)
|
||||
{
|
||||
var orders = new List<Order>
|
||||
{
|
||||
Order.Command("option gamespeed {0}".F("default")),
|
||||
Order.Command("state {0}".F(Session.ClientState.Ready))
|
||||
};
|
||||
|
||||
var path = Platform.ResolvePath(launchMap);
|
||||
var map = ModData.MapCache.SingleOrDefault(m => m.Uid == launchMap) ??
|
||||
ModData.MapCache.SingleOrDefault(m => m.Package.Name == path);
|
||||
|
||||
if (map == null)
|
||||
throw new InvalidOperationException("Could not find map '{0}'.".F(launchMap));
|
||||
|
||||
CreateAndStartLocalServer(map.Uid, orders);
|
||||
}
|
||||
|
||||
public static void FinishBenchmark()
|
||||
{
|
||||
if (benchmark != null)
|
||||
{
|
||||
benchmark.Write();
|
||||
Exit();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
@@ -12,8 +12,8 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Network;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA
|
||||
{
|
||||
@@ -24,7 +24,6 @@ namespace OpenRA
|
||||
|
||||
public string MapUid;
|
||||
public string MapTitle;
|
||||
public int FinalGameTick;
|
||||
|
||||
/// <summary>Game start timestamp (when the recoding started).</summary>
|
||||
public DateTime StartTimeUtc;
|
||||
@@ -35,7 +34,6 @@ namespace OpenRA
|
||||
/// <summary>Gets the game's duration, from the time the game started until the replay recording stopped.</summary>
|
||||
public TimeSpan Duration { get { return EndTimeUtc > StartTimeUtc ? EndTimeUtc - StartTimeUtc : TimeSpan.Zero; } }
|
||||
public IList<Player> Players { get; private set; }
|
||||
public HashSet<int> DisabledSpawnPoints = new HashSet<int>();
|
||||
public MapPreview MapPreview { get { return Game.ModData.MapCache[MapUid]; } }
|
||||
public IEnumerable<Player> HumanPlayers { get { return Players.Where(p => p.IsHuman); } }
|
||||
public bool IsSinglePlayer { get { return HumanPlayers.Count() == 1; } }
|
||||
@@ -119,15 +117,11 @@ namespace OpenRA
|
||||
IsBot = runtimePlayer.IsBot,
|
||||
FactionName = runtimePlayer.Faction.Name,
|
||||
FactionId = runtimePlayer.Faction.InternalName,
|
||||
DisplayFactionName = runtimePlayer.DisplayFaction.Name,
|
||||
DisplayFactionId = runtimePlayer.DisplayFaction.InternalName,
|
||||
Color = runtimePlayer.Color,
|
||||
Team = client.Team,
|
||||
Handicap = client.Handicap,
|
||||
SpawnPoint = runtimePlayer.SpawnPoint,
|
||||
IsRandomFaction = runtimePlayer.Faction.InternalName != client.Faction,
|
||||
IsRandomSpawnPoint = runtimePlayer.DisplaySpawnPoint == 0,
|
||||
Fingerprint = client.Fingerprint
|
||||
IsRandomSpawnPoint = runtimePlayer.SpawnPoint != client.SpawnPoint
|
||||
};
|
||||
|
||||
playersByRuntime.Add(runtimePlayer, player);
|
||||
@@ -137,7 +131,9 @@ namespace OpenRA
|
||||
/// <summary>Gets the player information for the specified runtime player instance.</summary>
|
||||
public Player GetPlayer(OpenRA.Player runtimePlayer)
|
||||
{
|
||||
playersByRuntime.TryGetValue(runtimePlayer, out var player);
|
||||
Player player;
|
||||
|
||||
playersByRuntime.TryGetValue(runtimePlayer, out player);
|
||||
|
||||
return player;
|
||||
}
|
||||
@@ -158,16 +154,11 @@ namespace OpenRA
|
||||
|
||||
/// <summary>The faction ID, a.k.a. the faction's internal name.</summary>
|
||||
public string FactionId;
|
||||
public Color Color;
|
||||
|
||||
/// <summary>The faction (including Random, etc.) that was selected in the lobby.</summary>
|
||||
public string DisplayFactionName;
|
||||
public string DisplayFactionId;
|
||||
public HSLColor Color;
|
||||
|
||||
/// <summary>The team ID on start-up, or 0 if the player is not part of a team.</summary>
|
||||
public int Team;
|
||||
public int SpawnPoint;
|
||||
public int Handicap;
|
||||
|
||||
/// <summary>True if the faction was chosen at random; otherwise, false.</summary>
|
||||
public bool IsRandomFaction;
|
||||
@@ -175,9 +166,6 @@ namespace OpenRA
|
||||
/// <summary>True if the spawn point was chosen at random; otherwise, false.</summary>
|
||||
public bool IsRandomSpawnPoint;
|
||||
|
||||
/// <summary>Player authentication fingerprint for the OpenRA forum.</summary>
|
||||
public string Fingerprint;
|
||||
|
||||
#endregion
|
||||
|
||||
#region
|
||||
@@ -188,9 +176,6 @@ namespace OpenRA
|
||||
/// <summary>The time when this player won or lost the game.</summary>
|
||||
public DateTime OutcomeTimestampUtc;
|
||||
|
||||
/// <summary>The frame at which this player disconnected.</summary>
|
||||
public int DisconnectFrame;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
@@ -22,9 +22,6 @@ namespace OpenRA
|
||||
/// </summary>
|
||||
public class ActorInfo
|
||||
{
|
||||
public const string AbstractActorPrefix = "^";
|
||||
public const char TraitInstanceSeparator = '@';
|
||||
|
||||
/// <summary>
|
||||
/// The actor name can be anything, but the sprites used in the Render*: traits default to this one.
|
||||
/// If you add an ^ in front of the name, the engine will recognize this as a collection of traits
|
||||
@@ -33,7 +30,7 @@ namespace OpenRA
|
||||
/// </summary>
|
||||
public readonly string Name;
|
||||
readonly TypeDictionary traits = new TypeDictionary();
|
||||
List<TraitInfo> constructOrderCache = null;
|
||||
List<ITraitInfo> constructOrderCache = null;
|
||||
|
||||
public ActorInfo(ObjectCreator creator, string name, MiniYaml node)
|
||||
{
|
||||
@@ -41,23 +38,19 @@ namespace OpenRA
|
||||
{
|
||||
Name = name;
|
||||
|
||||
var abstractActorType = name.StartsWith("^");
|
||||
foreach (var t in node.Nodes)
|
||||
{
|
||||
try
|
||||
{
|
||||
// HACK: The linter does not want to crash when a trait doesn't exist but only print an error instead
|
||||
// LoadTraitInfo will only return null to signal us to abort here if the linter is running
|
||||
var trait = LoadTraitInfo(creator, t.Key, t.Value);
|
||||
if (trait != null)
|
||||
traits.Add(trait);
|
||||
traits.Add(LoadTraitInfo(creator, t.Key.Split('@')[0], t.Value));
|
||||
}
|
||||
catch (FieldLoader.MissingFieldsException e)
|
||||
{
|
||||
throw new YamlException(e.Message);
|
||||
if (!abstractActorType)
|
||||
throw new YamlException(e.Message);
|
||||
}
|
||||
}
|
||||
|
||||
traits.TrimExcess();
|
||||
}
|
||||
catch (YamlException e)
|
||||
{
|
||||
@@ -65,32 +58,21 @@ namespace OpenRA
|
||||
}
|
||||
}
|
||||
|
||||
public ActorInfo(string name, params TraitInfo[] traitInfos)
|
||||
public ActorInfo(string name, params ITraitInfo[] traitInfos)
|
||||
{
|
||||
Name = name;
|
||||
foreach (var t in traitInfos)
|
||||
traits.Add(t);
|
||||
traits.TrimExcess();
|
||||
}
|
||||
|
||||
static TraitInfo LoadTraitInfo(ObjectCreator creator, string traitName, MiniYaml my)
|
||||
static ITraitInfo LoadTraitInfo(ObjectCreator creator, string traitName, MiniYaml my)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(my.Value))
|
||||
throw new YamlException("Junk value `{0}` on trait node {1}"
|
||||
.F(my.Value, traitName));
|
||||
|
||||
// HACK: The linter does not want to crash when a trait doesn't exist but only print an error instead
|
||||
// ObjectCreator will only return null to signal us to abort here if the linter is running
|
||||
var traitInstance = traitName.Split(TraitInstanceSeparator);
|
||||
var info = creator.CreateObject<TraitInfo>(traitInstance[0] + "Info");
|
||||
if (info == null)
|
||||
return null;
|
||||
|
||||
var info = creator.CreateObject<ITraitInfo>(traitName + "Info");
|
||||
try
|
||||
{
|
||||
if (traitInstance.Length > 1)
|
||||
info.GetType().GetField("InstanceName").SetValue(info, traitInstance[1]);
|
||||
|
||||
FieldLoader.Load(info, my);
|
||||
}
|
||||
catch (FieldLoader.MissingFieldsException e)
|
||||
@@ -102,12 +84,12 @@ namespace OpenRA
|
||||
return info;
|
||||
}
|
||||
|
||||
public IEnumerable<TraitInfo> TraitsInConstructOrder()
|
||||
public IEnumerable<ITraitInfo> TraitsInConstructOrder()
|
||||
{
|
||||
if (constructOrderCache != null)
|
||||
return constructOrderCache;
|
||||
|
||||
var source = traits.WithInterface<TraitInfo>().Select(i => new
|
||||
var source = traits.WithInterface<ITraitInfo>().Select(i => new
|
||||
{
|
||||
Trait = i,
|
||||
Type = i.GetType(),
|
||||
@@ -153,7 +135,7 @@ namespace OpenRA
|
||||
return constructOrderCache;
|
||||
}
|
||||
|
||||
public static IEnumerable<Type> PrerequisitesOf(TraitInfo info)
|
||||
public static IEnumerable<Type> PrerequisitesOf(ITraitInfo info)
|
||||
{
|
||||
return info
|
||||
.GetType()
|
||||
@@ -162,18 +144,23 @@ namespace OpenRA
|
||||
.Select(t => t.GetGenericArguments()[0]);
|
||||
}
|
||||
|
||||
public IEnumerable<Pair<string, Type>> GetInitKeys()
|
||||
{
|
||||
var inits = traits.WithInterface<ITraitInfo>().SelectMany(
|
||||
t => t.GetType().GetInterfaces()
|
||||
.Where(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(UsesInit<>))
|
||||
.Select(i => i.GetGenericArguments()[0])).ToList();
|
||||
|
||||
inits.Add(typeof(OwnerInit)); /* not exposed by a trait; this is used by the Actor itself */
|
||||
|
||||
return inits.Select(
|
||||
i => Pair.New(
|
||||
i.Name.Replace("Init", ""), i));
|
||||
}
|
||||
|
||||
public bool HasTraitInfo<T>() where T : ITraitInfoInterface { return traits.Contains<T>(); }
|
||||
public T TraitInfo<T>() where T : ITraitInfoInterface { return traits.Get<T>(); }
|
||||
public T TraitInfoOrDefault<T>() where T : ITraitInfoInterface { return traits.GetOrDefault<T>(); }
|
||||
public IEnumerable<T> TraitInfos<T>() where T : ITraitInfoInterface { return traits.WithInterface<T>(); }
|
||||
|
||||
public BitSet<TargetableType> GetAllTargetTypes()
|
||||
{
|
||||
// PERF: Avoid LINQ.
|
||||
var targetTypes = default(BitSet<TargetableType>);
|
||||
foreach (var targetable in TraitInfos<ITargetableInfo>())
|
||||
targetTypes = targetTypes.Union(targetable.GetTargetTypes());
|
||||
return targetTypes;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
@@ -10,6 +10,7 @@
|
||||
#endregion
|
||||
|
||||
using System.IO;
|
||||
using OpenRA.FileFormats;
|
||||
using OpenRA.FileSystem;
|
||||
|
||||
namespace OpenRA.GameRules
|
||||
@@ -19,7 +20,6 @@ namespace OpenRA.GameRules
|
||||
public readonly string Filename;
|
||||
public readonly string Title;
|
||||
public readonly bool Hidden;
|
||||
public readonly float VolumeModifier = 1f;
|
||||
|
||||
public int Length { get; private set; } // seconds
|
||||
public bool Exists { get; private set; }
|
||||
@@ -32,35 +32,28 @@ namespace OpenRA.GameRules
|
||||
if (nd.ContainsKey("Hidden"))
|
||||
bool.TryParse(nd["Hidden"].Value, out Hidden);
|
||||
|
||||
if (nd.ContainsKey("VolumeModifier"))
|
||||
VolumeModifier = FieldLoader.GetValue<float>("VolumeModifier", nd["VolumeModifier"].Value);
|
||||
|
||||
var ext = nd.ContainsKey("Extension") ? nd["Extension"].Value : "aud";
|
||||
Filename = (nd.ContainsKey("Filename") ? nd["Filename"].Value : key) + "." + ext;
|
||||
}
|
||||
|
||||
public void Load(IReadOnlyFileSystem fileSystem)
|
||||
{
|
||||
if (!fileSystem.TryOpen(Filename, out var stream))
|
||||
Stream stream;
|
||||
if (!fileSystem.TryOpen(Filename, out stream))
|
||||
return;
|
||||
|
||||
try
|
||||
Exists = true;
|
||||
ISoundFormat soundFormat;
|
||||
foreach (var loader in Game.ModData.SoundLoaders)
|
||||
{
|
||||
Exists = true;
|
||||
foreach (var loader in Game.ModData.SoundLoaders)
|
||||
if (loader.TryParseSound(stream, out soundFormat))
|
||||
{
|
||||
if (loader.TryParseSound(stream, out var soundFormat))
|
||||
{
|
||||
Length = (int)soundFormat.LengthInSeconds;
|
||||
soundFormat.Dispose();
|
||||
break;
|
||||
}
|
||||
Length = (int)soundFormat.LengthInSeconds;
|
||||
break;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
stream.Dispose();
|
||||
}
|
||||
|
||||
stream.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
@@ -29,7 +29,6 @@ namespace OpenRA
|
||||
public readonly IReadOnlyDictionary<string, MusicInfo> Music;
|
||||
public readonly TileSet TileSet;
|
||||
public readonly SequenceProvider Sequences;
|
||||
public readonly IReadOnlyDictionary<string, MiniYamlNode> ModelSequences;
|
||||
|
||||
public Ruleset(
|
||||
IReadOnlyDictionary<string, ActorInfo> actors,
|
||||
@@ -38,8 +37,7 @@ namespace OpenRA
|
||||
IReadOnlyDictionary<string, SoundInfo> notifications,
|
||||
IReadOnlyDictionary<string, MusicInfo> music,
|
||||
TileSet tileSet,
|
||||
SequenceProvider sequences,
|
||||
IReadOnlyDictionary<string, MiniYamlNode> modelSequences)
|
||||
SequenceProvider sequences)
|
||||
{
|
||||
Actors = actors;
|
||||
Weapons = weapons;
|
||||
@@ -48,7 +46,6 @@ namespace OpenRA
|
||||
Music = music;
|
||||
TileSet = tileSet;
|
||||
Sequences = sequences;
|
||||
ModelSequences = modelSequences;
|
||||
|
||||
foreach (var a in Actors.Values)
|
||||
{
|
||||
@@ -67,19 +64,6 @@ namespace OpenRA
|
||||
|
||||
foreach (var weapon in Weapons)
|
||||
{
|
||||
var projectileLoaded = weapon.Value.Projectile as IRulesetLoaded<WeaponInfo>;
|
||||
if (projectileLoaded != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
projectileLoaded.RulesetLoaded(this, weapon.Value);
|
||||
}
|
||||
catch (YamlException e)
|
||||
{
|
||||
throw new YamlException("Projectile type {0}: {1}".F(weapon.Key, e.Message));
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var warhead in weapon.Value.Warheads)
|
||||
{
|
||||
var cacher = warhead as IRulesetLoaded<WeaponInfo>;
|
||||
@@ -100,24 +84,16 @@ namespace OpenRA
|
||||
|
||||
public IEnumerable<KeyValuePair<string, MusicInfo>> InstalledMusic { get { return Music.Where(m => m.Value.Exists); } }
|
||||
|
||||
static IReadOnlyDictionary<string, T> MergeOrDefault<T>(string name,
|
||||
IReadOnlyFileSystem fileSystem,
|
||||
IEnumerable<string> files,
|
||||
MiniYaml additional,
|
||||
IReadOnlyDictionary<string, T> defaults,
|
||||
Func<MiniYamlNode, T> makeObject,
|
||||
Func<MiniYamlNode, bool> filterNode = null)
|
||||
static IReadOnlyDictionary<string, T> MergeOrDefault<T>(string name, IReadOnlyFileSystem fileSystem, IEnumerable<string> files, MiniYaml additional,
|
||||
IReadOnlyDictionary<string, T> defaults, Func<MiniYamlNode, T> makeObject)
|
||||
{
|
||||
if (additional == null && defaults != null)
|
||||
return defaults;
|
||||
|
||||
IEnumerable<MiniYamlNode> yamlNodes = MiniYaml.Load(fileSystem, files, additional);
|
||||
var result = MiniYaml.Load(fileSystem, files, additional)
|
||||
.ToDictionaryWithConflictLog(k => k.Key.ToLowerInvariant(), makeObject, "LoadFromManifest<" + name + ">");
|
||||
|
||||
// Optionally, the caller can filter out elements from the loaded set of nodes. Default behavior is unfiltered.
|
||||
if (filterNode != null)
|
||||
yamlNodes = yamlNodes.Where(k => !filterNode(k));
|
||||
|
||||
return new ReadOnlyDictionary<string, T>(yamlNodes.ToDictionaryWithConflictLog(k => k.Key.ToLowerInvariant(), makeObject, "LoadFromManifest<" + name + ">"));
|
||||
return new ReadOnlyDictionary<string, T>(result);
|
||||
}
|
||||
|
||||
public static Ruleset LoadDefaults(ModData modData)
|
||||
@@ -129,8 +105,7 @@ namespace OpenRA
|
||||
Action f = () =>
|
||||
{
|
||||
var actors = MergeOrDefault("Manifest,Rules", fs, m.Rules, null, null,
|
||||
k => new ActorInfo(modData.ObjectCreator, k.Key.ToLowerInvariant(), k.Value),
|
||||
filterNode: n => n.Key.StartsWith(ActorInfo.AbstractActorPrefix, StringComparison.Ordinal));
|
||||
k => new ActorInfo(modData.ObjectCreator, k.Key.ToLowerInvariant(), k.Value));
|
||||
|
||||
var weapons = MergeOrDefault("Manifest,Weapons", fs, m.Weapons, null, null,
|
||||
k => new WeaponInfo(k.Key.ToLowerInvariant(), k.Value));
|
||||
@@ -144,11 +119,8 @@ namespace OpenRA
|
||||
var music = MergeOrDefault("Manifest,Music", fs, m.Music, null, null,
|
||||
k => new MusicInfo(k.Key, k.Value));
|
||||
|
||||
var modelSequences = MergeOrDefault("Manifest,ModelSequences", fs, m.ModelSequences, null, null,
|
||||
k => k);
|
||||
|
||||
// The default ruleset does not include a preferred tileset or sequence set
|
||||
ruleset = new Ruleset(actors, weapons, voices, notifications, music, null, null, modelSequences);
|
||||
ruleset = new Ruleset(actors, weapons, voices, notifications, music, null, null);
|
||||
};
|
||||
|
||||
if (modData.IsOnMainThread)
|
||||
@@ -173,13 +145,12 @@ namespace OpenRA
|
||||
var dr = modData.DefaultRules;
|
||||
var ts = modData.DefaultTileSets[tileSet];
|
||||
var sequences = modData.DefaultSequences[tileSet];
|
||||
|
||||
return new Ruleset(dr.Actors, dr.Weapons, dr.Voices, dr.Notifications, dr.Music, ts, sequences, dr.ModelSequences);
|
||||
return new Ruleset(dr.Actors, dr.Weapons, dr.Voices, dr.Notifications, dr.Music, ts, sequences);
|
||||
}
|
||||
|
||||
public static Ruleset Load(ModData modData, IReadOnlyFileSystem fileSystem, string tileSet,
|
||||
MiniYaml mapRules, MiniYaml mapWeapons, MiniYaml mapVoices, MiniYaml mapNotifications,
|
||||
MiniYaml mapMusic, MiniYaml mapSequences, MiniYaml mapModelSequences)
|
||||
MiniYaml mapMusic, MiniYaml mapSequences)
|
||||
{
|
||||
var m = modData.Manifest;
|
||||
var dr = modData.DefaultRules;
|
||||
@@ -188,8 +159,7 @@ namespace OpenRA
|
||||
Action f = () =>
|
||||
{
|
||||
var actors = MergeOrDefault("Rules", fileSystem, m.Rules, mapRules, dr.Actors,
|
||||
k => new ActorInfo(modData.ObjectCreator, k.Key.ToLowerInvariant(), k.Value),
|
||||
filterNode: n => n.Key.StartsWith(ActorInfo.AbstractActorPrefix, StringComparison.Ordinal));
|
||||
k => new ActorInfo(modData.ObjectCreator, k.Key.ToLowerInvariant(), k.Value));
|
||||
|
||||
var weapons = MergeOrDefault("Weapons", fileSystem, m.Weapons, mapWeapons, dr.Weapons,
|
||||
k => new WeaponInfo(k.Key.ToLowerInvariant(), k.Value));
|
||||
@@ -208,14 +178,10 @@ namespace OpenRA
|
||||
|
||||
// TODO: Top-level dictionary should be moved into the Ruleset instead of in its own object
|
||||
var sequences = mapSequences == null ? modData.DefaultSequences[tileSet] :
|
||||
new SequenceProvider(fileSystem, modData, tileSet, mapSequences);
|
||||
new SequenceProvider(fileSystem, modData, ts, mapSequences);
|
||||
|
||||
var modelSequences = dr.ModelSequences;
|
||||
if (mapModelSequences != null)
|
||||
modelSequences = MergeOrDefault("ModelSequences", fileSystem, m.ModelSequences, mapModelSequences, dr.ModelSequences,
|
||||
k => k);
|
||||
|
||||
ruleset = new Ruleset(actors, weapons, voices, notifications, music, ts, sequences, modelSequences);
|
||||
// TODO: Add support for custom voxel sequences
|
||||
ruleset = new Ruleset(actors, weapons, voices, notifications, music, ts, sequences);
|
||||
};
|
||||
|
||||
if (modData.IsOnMainThread)
|
||||
@@ -234,58 +200,5 @@ namespace OpenRA
|
||||
|
||||
return ruleset;
|
||||
}
|
||||
|
||||
static bool AnyCustomYaml(MiniYaml yaml)
|
||||
{
|
||||
return yaml != null && (yaml.Value != null || yaml.Nodes.Any());
|
||||
}
|
||||
|
||||
static bool AnyFlaggedTraits(ModData modData, List<MiniYamlNode> actors)
|
||||
{
|
||||
foreach (var actorNode in actors)
|
||||
{
|
||||
foreach (var traitNode in actorNode.Value.Nodes)
|
||||
{
|
||||
try
|
||||
{
|
||||
var traitName = traitNode.Key.Split('@')[0];
|
||||
var traitType = modData.ObjectCreator.FindType(traitName + "Info");
|
||||
if (traitType != null && traitType.GetInterface("ILobbyCustomRulesIgnore") == null)
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Write("debug", "Error in AnyFlaggedTraits\r\n" + ex.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool DefinesUnsafeCustomRules(ModData modData, IReadOnlyFileSystem fileSystem,
|
||||
MiniYaml mapRules, MiniYaml mapWeapons, MiniYaml mapVoices, MiniYaml mapNotifications, MiniYaml mapSequences)
|
||||
{
|
||||
// Maps that define any weapon, voice, notification, or sequence overrides are always flagged
|
||||
if (AnyCustomYaml(mapWeapons) || AnyCustomYaml(mapVoices) || AnyCustomYaml(mapNotifications) || AnyCustomYaml(mapSequences))
|
||||
return true;
|
||||
|
||||
// Any trait overrides that aren't explicitly whitelisted are flagged
|
||||
if (mapRules != null)
|
||||
{
|
||||
if (AnyFlaggedTraits(modData, mapRules.Nodes))
|
||||
return true;
|
||||
|
||||
if (mapRules.Value != null)
|
||||
{
|
||||
var mapFiles = FieldLoader.GetValue<string[]>("value", mapRules.Value);
|
||||
foreach (var f in mapFiles)
|
||||
if (AnyFlaggedTraits(modData, MiniYaml.FromStream(fileSystem.Open(f), f)))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
@@ -33,39 +33,18 @@ namespace OpenRA.GameRules
|
||||
{
|
||||
FieldLoader.Load(this, y);
|
||||
|
||||
VoicePools = Exts.Lazy(() => Voices.ToDictionary(a => a.Key, a => new SoundPool(1f, a.Value)));
|
||||
NotificationsPools = Exts.Lazy(() => ParseSoundPool(y, "Notifications"));
|
||||
}
|
||||
|
||||
Dictionary<string, SoundPool> ParseSoundPool(MiniYaml y, string key)
|
||||
{
|
||||
var ret = new Dictionary<string, SoundPool>();
|
||||
var classifiction = y.Nodes.First(x => x.Key == key);
|
||||
foreach (var t in classifiction.Value.Nodes)
|
||||
{
|
||||
var volumeModifier = 1f;
|
||||
var volumeModifierNode = t.Value.Nodes.FirstOrDefault(x => x.Key == "VolumeModifier");
|
||||
if (volumeModifierNode != null)
|
||||
volumeModifier = FieldLoader.GetValue<float>(volumeModifierNode.Key, volumeModifierNode.Value.Value);
|
||||
|
||||
var names = FieldLoader.GetValue<string[]>(t.Key, t.Value.Value);
|
||||
var sp = new SoundPool(volumeModifier, names);
|
||||
ret.Add(t.Key, sp);
|
||||
}
|
||||
|
||||
return ret;
|
||||
VoicePools = Exts.Lazy(() => Voices.ToDictionary(a => a.Key, a => new SoundPool(a.Value)));
|
||||
NotificationsPools = Exts.Lazy(() => Notifications.ToDictionary(a => a.Key, a => new SoundPool(a.Value)));
|
||||
}
|
||||
}
|
||||
|
||||
public class SoundPool
|
||||
{
|
||||
public readonly float VolumeModifier;
|
||||
readonly string[] clips;
|
||||
readonly List<string> liveclips = new List<string>();
|
||||
|
||||
public SoundPool(float volumeModifier, params string[] clips)
|
||||
public SoundPool(params string[] clips)
|
||||
{
|
||||
VolumeModifier = volumeModifier;
|
||||
this.clips = clips;
|
||||
}
|
||||
|
||||
@@ -74,9 +53,8 @@ namespace OpenRA.GameRules
|
||||
if (liveclips.Count == 0)
|
||||
liveclips.AddRange(clips);
|
||||
|
||||
// Avoid crashing if there's no clips at all
|
||||
if (liveclips.Count == 0)
|
||||
return null;
|
||||
return null; /* avoid crashing if there's no clips at all */
|
||||
|
||||
var i = Game.CosmeticRandom.Next(liveclips.Count);
|
||||
var s = liveclips[i];
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
@@ -13,7 +13,6 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenRA.Effects;
|
||||
using OpenRA.Primitives;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.GameRules
|
||||
@@ -24,8 +23,7 @@ namespace OpenRA.GameRules
|
||||
public int[] DamageModifiers;
|
||||
public int[] InaccuracyModifiers;
|
||||
public int[] RangeModifiers;
|
||||
public WAngle Facing;
|
||||
public Func<WAngle> CurrentMuzzleFacing;
|
||||
public int Facing;
|
||||
public WPos Source;
|
||||
public Func<WPos> CurrentSource;
|
||||
public Actor SourceActor;
|
||||
@@ -33,66 +31,16 @@ namespace OpenRA.GameRules
|
||||
public Target GuidedTarget;
|
||||
}
|
||||
|
||||
public class WarheadArgs
|
||||
{
|
||||
public WeaponInfo Weapon;
|
||||
public int[] DamageModifiers = { };
|
||||
public WPos? Source;
|
||||
public WRot ImpactOrientation;
|
||||
public WPos ImpactPosition;
|
||||
public Actor SourceActor;
|
||||
public Target WeaponTarget;
|
||||
|
||||
public WarheadArgs(ProjectileArgs args)
|
||||
{
|
||||
Weapon = args.Weapon;
|
||||
DamageModifiers = args.DamageModifiers;
|
||||
ImpactPosition = args.PassiveTarget;
|
||||
Source = args.Source;
|
||||
SourceActor = args.SourceActor;
|
||||
WeaponTarget = args.GuidedTarget;
|
||||
}
|
||||
|
||||
// For places that only want to update some of the fields (usually DamageModifiers)
|
||||
public WarheadArgs(WarheadArgs args)
|
||||
{
|
||||
Weapon = args.Weapon;
|
||||
DamageModifiers = args.DamageModifiers;
|
||||
Source = args.Source;
|
||||
SourceActor = args.SourceActor;
|
||||
WeaponTarget = args.WeaponTarget;
|
||||
}
|
||||
|
||||
// Default empty constructor for callers that want to initialize fields themselves
|
||||
public WarheadArgs() { }
|
||||
}
|
||||
|
||||
public interface IProjectile : IEffect { }
|
||||
public interface IProjectileInfo { IProjectile Create(ProjectileArgs args); }
|
||||
public interface IProjectileInfo { IEffect Create(ProjectileArgs args); }
|
||||
|
||||
public sealed class WeaponInfo
|
||||
{
|
||||
[Desc("The maximum range the weapon can fire.")]
|
||||
public readonly WDist Range = WDist.Zero;
|
||||
|
||||
[Desc("First burst is aimed at this offset relative to target position.")]
|
||||
public readonly WVec FirstBurstTargetOffset = WVec.Zero;
|
||||
|
||||
[Desc("Each burst after the first lands by this offset away from the previous burst.")]
|
||||
public readonly WVec FollowingBurstTargetOffset = WVec.Zero;
|
||||
|
||||
[Desc("The sound played each time the weapon is fired.")]
|
||||
[Desc("The sound played when the weapon is fired.")]
|
||||
public readonly string[] Report = null;
|
||||
|
||||
[Desc("Sound played only on first burst in a salvo.")]
|
||||
public readonly string[] StartBurstReport = null;
|
||||
|
||||
[Desc("The sound played when the weapon is reloaded.")]
|
||||
public readonly string[] AfterFireSound = null;
|
||||
|
||||
[Desc("Delay in ticks to play reloading sound.")]
|
||||
public readonly int AfterFireSoundDelay = 0;
|
||||
|
||||
[Desc("Delay in ticks between reloading ammo magazines.")]
|
||||
public readonly int ReloadDelay = 1;
|
||||
|
||||
@@ -100,27 +48,17 @@ namespace OpenRA.GameRules
|
||||
public readonly int Burst = 1;
|
||||
|
||||
[Desc("What types of targets are affected.")]
|
||||
public readonly BitSet<TargetableType> ValidTargets = new BitSet<TargetableType>("Ground", "Water");
|
||||
public readonly HashSet<string> ValidTargets = new HashSet<string> { "Ground", "Water" };
|
||||
|
||||
[Desc("What types of targets are unaffected.", "Overrules ValidTargets.")]
|
||||
public readonly BitSet<TargetableType> InvalidTargets;
|
||||
public readonly HashSet<string> InvalidTargets = new HashSet<string>();
|
||||
|
||||
static readonly BitSet<TargetableType> TargetTypeAir = new BitSet<TargetableType>("Air");
|
||||
|
||||
[Desc("If weapon is not directly targeting an actor and targeted position is above this altitude,",
|
||||
"the weapon will ignore terrain target types and only check TargetTypeAir for validity.")]
|
||||
public readonly WDist AirThreshold = new WDist(128);
|
||||
|
||||
[Desc("Delay in ticks between firing shots from the same ammo magazine. If one entry, it will be used for all bursts.",
|
||||
"If multiple entries, their number needs to match Burst - 1.")]
|
||||
public readonly int[] BurstDelays = { 5 };
|
||||
[Desc("Delay in ticks between firing shots from the same ammo magazine.")]
|
||||
public readonly int BurstDelay = 5;
|
||||
|
||||
[Desc("The minimum range the weapon can fire.")]
|
||||
public readonly WDist MinRange = WDist.Zero;
|
||||
|
||||
[Desc("Does this weapon aim at the target's center regardless of other targetable offsets?")]
|
||||
public readonly bool TargetActorCenter = false;
|
||||
|
||||
[FieldLoader.LoadUsing("LoadProjectile")]
|
||||
public readonly IProjectileInfo Projectile;
|
||||
|
||||
@@ -129,15 +67,13 @@ namespace OpenRA.GameRules
|
||||
|
||||
public WeaponInfo(string name, MiniYaml content)
|
||||
{
|
||||
// Resolve any weapon-level yaml inheritance or removals
|
||||
// HACK: The "Defaults" sequence syntax prevents us from doing this generally during yaml parsing
|
||||
content.Nodes = MiniYaml.Merge(new[] { content.Nodes });
|
||||
FieldLoader.Load(this, content);
|
||||
}
|
||||
|
||||
static object LoadProjectile(MiniYaml yaml)
|
||||
{
|
||||
if (!yaml.ToDictionary().TryGetValue("Projectile", out var proj))
|
||||
MiniYaml proj;
|
||||
if (!yaml.ToDictionary().TryGetValue("Projectile", out proj))
|
||||
return null;
|
||||
var ret = Game.CreateObject<IProjectileInfo>(proj.Value + "Info");
|
||||
FieldLoader.Load(ret, proj);
|
||||
@@ -157,13 +93,13 @@ namespace OpenRA.GameRules
|
||||
return retList;
|
||||
}
|
||||
|
||||
public bool IsValidTarget(BitSet<TargetableType> targetTypes)
|
||||
public bool IsValidTarget(IEnumerable<string> targetTypes)
|
||||
{
|
||||
return ValidTargets.Overlaps(targetTypes) && !InvalidTargets.Overlaps(targetTypes);
|
||||
}
|
||||
|
||||
/// <summary>Checks if the weapon is valid against (can target) the target.</summary>
|
||||
public bool IsValidAgainst(in Target target, World world, Actor firedBy)
|
||||
public bool IsValidAgainst(Target target, World world, Actor firedBy)
|
||||
{
|
||||
if (target.Type == TargetType.Actor)
|
||||
return IsValidAgainst(target.Actor, firedBy);
|
||||
@@ -173,10 +109,6 @@ namespace OpenRA.GameRules
|
||||
|
||||
if (target.Type == TargetType.Terrain)
|
||||
{
|
||||
var dat = world.Map.DistanceAboveTerrain(target.CenterPosition);
|
||||
if (dat > AirThreshold)
|
||||
return IsValidTarget(TargetTypeAir);
|
||||
|
||||
var cell = world.Map.CellContaining(target.CenterPosition);
|
||||
if (!world.Map.Contains(cell))
|
||||
return false;
|
||||
@@ -220,37 +152,18 @@ namespace OpenRA.GameRules
|
||||
}
|
||||
|
||||
/// <summary>Applies all the weapon's warheads to the target.</summary>
|
||||
public void Impact(in Target target, WarheadArgs args)
|
||||
public void Impact(Target target, Actor firedBy, IEnumerable<int> damageModifiers)
|
||||
{
|
||||
var world = args.SourceActor.World;
|
||||
foreach (var warhead in Warheads)
|
||||
{
|
||||
if (warhead.Delay > 0)
|
||||
{
|
||||
// Lambdas can't use 'in' variables, so capture a copy for later
|
||||
var delayedTarget = target;
|
||||
world.AddFrameEndTask(w => w.Add(new DelayedImpact(warhead.Delay, warhead, delayedTarget, args)));
|
||||
}
|
||||
var wh = warhead; // force the closure to bind to the current warhead
|
||||
|
||||
Action a = () => wh.DoImpact(target, firedBy, damageModifiers);
|
||||
if (wh.Delay > 0)
|
||||
firedBy.World.AddFrameEndTask(w => w.Add(new DelayedAction(wh.Delay, a)));
|
||||
else
|
||||
warhead.DoImpact(target, args);
|
||||
a();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Applies all the weapon's warheads to the target. Only use for projectile-less, special-case impacts.</summary>
|
||||
public void Impact(in Target target, Actor firedBy)
|
||||
{
|
||||
// The impact will happen immediately at target.CenterPosition.
|
||||
var args = new WarheadArgs
|
||||
{
|
||||
Weapon = this,
|
||||
SourceActor = firedBy,
|
||||
WeaponTarget = target
|
||||
};
|
||||
|
||||
if (firedBy.OccupiesSpace != null)
|
||||
args.Source = firedBy.CenterPosition;
|
||||
|
||||
Impact(target, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
|
||||
391
OpenRA.Game/GlobalChat.cs
Normal file
391
OpenRA.Game/GlobalChat.cs
Normal file
@@ -0,0 +1,391 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using Meebey.SmartIrc4net;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.Chat
|
||||
{
|
||||
public enum ChatConnectionStatus { Disconnected, Connecting, Connected, Disconnecting, Joined, Error }
|
||||
public enum ChatMessageType { Message, Notification }
|
||||
|
||||
public sealed class ChatUser
|
||||
{
|
||||
public readonly string Name;
|
||||
public bool IsOp;
|
||||
public bool IsVoiced;
|
||||
|
||||
public ChatUser(string name, bool isOp, bool isVoice)
|
||||
{
|
||||
Name = name;
|
||||
IsOp = isOp;
|
||||
IsVoiced = isVoice;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class ChatMessage
|
||||
{
|
||||
static long nextUID;
|
||||
|
||||
public readonly DateTime Time;
|
||||
public readonly ChatMessageType Type;
|
||||
public readonly string Nick;
|
||||
public readonly string Message;
|
||||
public readonly string UID;
|
||||
|
||||
public ChatMessage(DateTime time, ChatMessageType type, string nick, string message)
|
||||
{
|
||||
Time = time;
|
||||
Type = type;
|
||||
Nick = nick;
|
||||
Message = message;
|
||||
|
||||
UID = Interlocked.Increment(ref nextUID).ToString();
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
var time = Time.ToString(Game.Settings.Chat.TimestampFormat);
|
||||
if (Type == ChatMessageType.Notification)
|
||||
return "{0} {1}".F(time, Message);
|
||||
|
||||
return "{0} {1}: {2}".F(time, Nick, Message);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class GlobalChat : IDisposable
|
||||
{
|
||||
readonly IrcClient client = new IrcClient();
|
||||
volatile Channel channel;
|
||||
|
||||
public readonly ObservableSortedDictionary<string, ChatUser> Users = new ObservableSortedDictionary<string, ChatUser>(StringComparer.InvariantCultureIgnoreCase);
|
||||
public readonly ObservableList<ChatMessage> History = new ObservableList<ChatMessage>();
|
||||
|
||||
volatile string topic;
|
||||
public string Topic { get { return topic; } }
|
||||
|
||||
volatile ChatConnectionStatus connectionStatus = ChatConnectionStatus.Disconnected;
|
||||
public ChatConnectionStatus ConnectionStatus { get { return connectionStatus; } }
|
||||
|
||||
public GlobalChat()
|
||||
{
|
||||
client.Encoding = System.Text.Encoding.UTF8;
|
||||
client.SendDelay = 100;
|
||||
client.ActiveChannelSyncing = true;
|
||||
|
||||
client.OnConnecting += OnConnecting;
|
||||
client.OnConnected += OnConnected;
|
||||
client.OnDisconnecting += OnDisconnecting;
|
||||
client.OnDisconnected += OnDisconnected;
|
||||
client.OnError += OnError;
|
||||
client.OnKick += OnKick;
|
||||
|
||||
client.OnRawMessage += (_, e) => Game.RunAfterTick(() => Log.Write("irc", e.Data.RawMessage));
|
||||
client.OnJoin += OnJoin;
|
||||
client.OnChannelActiveSynced += OnChannelActiveSynced;
|
||||
client.OnTopic += (_, e) => topic = e.Topic;
|
||||
client.OnTopicChange += (_, e) => topic = e.NewTopic;
|
||||
client.OnNickChange += OnNickChange;
|
||||
|
||||
client.OnChannelMessage += (_, e) => AddMessage(e.Data.Nick, e.Data.Message);
|
||||
client.OnOp += (_, e) => SetUserOp(e.Whom, true);
|
||||
client.OnDeop += (_, e) => SetUserOp(e.Whom, false);
|
||||
client.OnVoice += (_, e) => SetUserVoiced(e.Whom, true);
|
||||
client.OnDevoice += (_, e) => SetUserVoiced(e.Whom, false);
|
||||
client.OnPart += OnPart;
|
||||
client.OnQuit += OnQuit;
|
||||
|
||||
TrySetNickname(Game.Settings.Player.Name);
|
||||
}
|
||||
|
||||
void SetUserOp(string whom, bool isOp)
|
||||
{
|
||||
Game.RunAfterTick(() =>
|
||||
{
|
||||
ChatUser user;
|
||||
if (Users.TryGetValue(whom, out user))
|
||||
user.IsOp = isOp;
|
||||
});
|
||||
}
|
||||
|
||||
void SetUserVoiced(string whom, bool isVoiced)
|
||||
{
|
||||
Game.RunAfterTick(() =>
|
||||
{
|
||||
ChatUser user;
|
||||
if (Users.TryGetValue(whom, out user))
|
||||
user.IsVoiced = isVoiced;
|
||||
});
|
||||
}
|
||||
|
||||
public void Connect()
|
||||
{
|
||||
if (client.IsConnected)
|
||||
return;
|
||||
|
||||
new Thread(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
client.Connect(Game.Settings.Chat.Hostname, Game.Settings.Chat.Port);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
connectionStatus = ChatConnectionStatus.Error;
|
||||
AddNotification(e.Message);
|
||||
Game.RunAfterTick(() => Log.Write("irc", e.ToString()));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
client.Listen();
|
||||
}) { Name = "IrcListenThread", IsBackground = true }.Start();
|
||||
}
|
||||
|
||||
void AddNotification(string text)
|
||||
{
|
||||
var message = new ChatMessage(DateTime.Now, ChatMessageType.Notification, null, text);
|
||||
Game.RunAfterTick(() =>
|
||||
{
|
||||
History.Add(message);
|
||||
Log.Write("irc", text);
|
||||
});
|
||||
}
|
||||
|
||||
void AddMessage(string nick, string text)
|
||||
{
|
||||
var message = new ChatMessage(DateTime.Now, ChatMessageType.Message, nick, text);
|
||||
Game.RunAfterTick(() =>
|
||||
{
|
||||
History.Add(message);
|
||||
Log.Write("irc", text);
|
||||
});
|
||||
}
|
||||
|
||||
void OnConnecting(object sender, EventArgs e)
|
||||
{
|
||||
AddNotification("Connecting to {0}:{1}...".F(Game.Settings.Chat.Hostname, Game.Settings.Chat.Port));
|
||||
connectionStatus = ChatConnectionStatus.Connecting;
|
||||
}
|
||||
|
||||
void OnConnected(object sender, EventArgs e)
|
||||
{
|
||||
AddNotification("Connected.");
|
||||
connectionStatus = ChatConnectionStatus.Connected;
|
||||
|
||||
// Guard against settings.yaml modification
|
||||
var nick = SanitizedName(Game.Settings.Chat.Nickname);
|
||||
if (nick != Game.Settings.Chat.Nickname)
|
||||
Game.RunAfterTick(() => Game.Settings.Chat.Nickname = nick);
|
||||
|
||||
client.Login(nick, "in-game IRC client", 0, "OpenRA");
|
||||
client.RfcJoin("#" + Game.Settings.Chat.Channel);
|
||||
}
|
||||
|
||||
void OnDisconnecting(object sender, EventArgs e)
|
||||
{
|
||||
if (connectionStatus != ChatConnectionStatus.Error)
|
||||
connectionStatus = ChatConnectionStatus.Disconnecting;
|
||||
}
|
||||
|
||||
void OnDisconnected(object sender, EventArgs e)
|
||||
{
|
||||
Game.RunAfterTick(Users.Clear);
|
||||
|
||||
// Keep the chat window open if there is an error
|
||||
// It will be cleared by the Disconnect button
|
||||
if (connectionStatus != ChatConnectionStatus.Error)
|
||||
{
|
||||
Game.RunAfterTick(History.Clear);
|
||||
topic = null;
|
||||
connectionStatus = ChatConnectionStatus.Disconnected;
|
||||
}
|
||||
}
|
||||
|
||||
void OnError(object sender, ErrorEventArgs e)
|
||||
{
|
||||
// Ignore any errors that happen during disconnect
|
||||
if (connectionStatus != ChatConnectionStatus.Disconnecting)
|
||||
{
|
||||
connectionStatus = ChatConnectionStatus.Error;
|
||||
AddNotification("Error: " + e.ErrorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
void OnKick(object sender, KickEventArgs e)
|
||||
{
|
||||
if (e.Whom == client.Nickname)
|
||||
{
|
||||
Disconnect();
|
||||
connectionStatus = ChatConnectionStatus.Error;
|
||||
AddNotification("You were kicked from the chat by {0}. ({1})".F(e.Who, e.KickReason));
|
||||
}
|
||||
else
|
||||
{
|
||||
Users.Remove(e.Whom);
|
||||
AddNotification("{0} was kicked from the chat by {1}. ({2})".F(e.Whom, e.Who, e.KickReason));
|
||||
}
|
||||
}
|
||||
|
||||
void OnJoin(object sender, JoinEventArgs e)
|
||||
{
|
||||
if (e.Who == client.Nickname || e.Channel != channel.Name)
|
||||
return;
|
||||
|
||||
AddNotification("{0} joined the chat.".F(e.Who));
|
||||
Game.RunAfterTick(() => Users.Add(e.Who, new ChatUser(e.Who, false, false)));
|
||||
}
|
||||
|
||||
void OnChannelActiveSynced(object sender, IrcEventArgs e)
|
||||
{
|
||||
channel = client.GetChannel(e.Data.Channel);
|
||||
AddNotification("{0} users online".F(channel.Users.Count));
|
||||
connectionStatus = ChatConnectionStatus.Joined;
|
||||
|
||||
foreach (DictionaryEntry user in channel.Users)
|
||||
{
|
||||
var u = (ChannelUser)user.Value;
|
||||
Game.RunAfterTick(() => Users.Add(u.Nick, new ChatUser(u.Nick, u.IsOp, u.IsVoice)));
|
||||
}
|
||||
}
|
||||
|
||||
void OnNickChange(object sender, NickChangeEventArgs e)
|
||||
{
|
||||
AddNotification("{0} is now known as {1}.".F(e.OldNickname, e.NewNickname));
|
||||
|
||||
Game.RunAfterTick(() =>
|
||||
{
|
||||
ChatUser user;
|
||||
if (!Users.TryGetValue(e.OldNickname, out user))
|
||||
return;
|
||||
|
||||
Users.Remove(e.OldNickname);
|
||||
Users.Add(e.NewNickname, new ChatUser(e.NewNickname, user.IsOp, user.IsVoiced));
|
||||
});
|
||||
}
|
||||
|
||||
void OnQuit(object sender, QuitEventArgs e)
|
||||
{
|
||||
AddNotification("{0} left the chat.".F(e.Who));
|
||||
Game.RunAfterTick(() => Users.Remove(e.Who));
|
||||
}
|
||||
|
||||
void OnPart(object sender, PartEventArgs e)
|
||||
{
|
||||
if (e.Data.Channel != channel.Name)
|
||||
return;
|
||||
|
||||
AddNotification("{0} left the chat.".F(e.Who));
|
||||
Game.RunAfterTick(() => Users.Remove(e.Who));
|
||||
}
|
||||
|
||||
public string SanitizedName(string dirty)
|
||||
{
|
||||
if (string.IsNullOrEmpty(dirty))
|
||||
return null;
|
||||
|
||||
// There is no need to mangle the nick if it is already valid
|
||||
if (Rfc2812.IsValidNickname(dirty))
|
||||
return dirty;
|
||||
|
||||
// TODO: some special chars are allowed as well, but not at every position
|
||||
var clean = new string(dirty.Where(c => char.IsLetterOrDigit(c)).ToArray());
|
||||
|
||||
if (string.IsNullOrEmpty(clean))
|
||||
return null;
|
||||
|
||||
if (char.IsDigit(clean[0]))
|
||||
return SanitizedName(clean.Substring(1));
|
||||
|
||||
// Source: https://tools.ietf.org/html/rfc2812#section-1.2.1
|
||||
if (clean.Length > 9)
|
||||
clean = clean.Substring(0, 9);
|
||||
|
||||
return clean;
|
||||
}
|
||||
|
||||
public bool IsValidNickname(string name)
|
||||
{
|
||||
return Rfc2812.IsValidNickname(name);
|
||||
}
|
||||
|
||||
public void SendMessage(string text)
|
||||
{
|
||||
if (connectionStatus != ChatConnectionStatus.Joined)
|
||||
return;
|
||||
|
||||
// Guard against a last-moment disconnection
|
||||
try
|
||||
{
|
||||
client.SendMessage(SendType.Message, channel.Name, text);
|
||||
AddMessage(client.Nickname, text);
|
||||
}
|
||||
catch (NotConnectedException) { }
|
||||
}
|
||||
|
||||
public bool TrySetNickname(string nick)
|
||||
{
|
||||
// TODO: This is inconsistent with the other check
|
||||
if (Rfc2812.IsValidNickname(nick))
|
||||
{
|
||||
client.RfcNick(nick);
|
||||
Game.Settings.Chat.Nickname = nick;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Disconnect()
|
||||
{
|
||||
// Error is an alias for disconnect, but keeps the panel open
|
||||
// so that clients can see the error
|
||||
if (connectionStatus == ChatConnectionStatus.Error)
|
||||
{
|
||||
Game.RunAfterTick(History.Clear);
|
||||
topic = null;
|
||||
connectionStatus = ChatConnectionStatus.Disconnected;
|
||||
}
|
||||
else
|
||||
connectionStatus = ChatConnectionStatus.Disconnecting;
|
||||
|
||||
if (!client.IsConnected)
|
||||
return;
|
||||
|
||||
client.RfcQuit(Game.Settings.Chat.QuitMessage);
|
||||
|
||||
AddNotification("Disconnecting from {0}...".F(client.Address));
|
||||
|
||||
Game.RunAfterTick(() => Game.Settings.Chat.ConnectAutomatically = false);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// HACK: The IRC library we are using has terrible thread-handling code that relies on Thread.Abort.
|
||||
// There is a thread reading from the network socket which is aborted, however on Windows this is inside
|
||||
// native code so this abort call hangs until the network socket reads something and returns to managed
|
||||
// code where it can then be aborted.
|
||||
//
|
||||
// This means we may hang for several seconds during shutdown (until we receive something over IRC!) before
|
||||
// closing.
|
||||
//
|
||||
// Since our IRC client currently lives forever, the only time we call this Dispose method is during the
|
||||
// shutdown of our process. Therefore, we can work around the problem by just not bothering to disconnect
|
||||
// properly. Since our process is about to die anyway, it's not like anyone will care.
|
||||
////if (client.IsConnected)
|
||||
//// client.Disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
@@ -10,8 +10,8 @@
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenRA.Primitives;
|
||||
using OpenRA.Support;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
@@ -23,7 +23,7 @@ namespace OpenRA.Graphics
|
||||
public bool IsDecoration { get; set; }
|
||||
|
||||
readonly SequenceProvider sequenceProvider;
|
||||
readonly Func<WAngle> facingFunc;
|
||||
readonly Func<int> facingFunc;
|
||||
readonly Func<bool> paused;
|
||||
|
||||
int frame;
|
||||
@@ -33,15 +33,15 @@ namespace OpenRA.Graphics
|
||||
Action tickFunc = () => { };
|
||||
|
||||
public Animation(World world, string name)
|
||||
: this(world, name, () => WAngle.Zero) { }
|
||||
: this(world, name, () => 0) { }
|
||||
|
||||
public Animation(World world, string name, Func<WAngle> facingFunc)
|
||||
public Animation(World world, string name, Func<int> facingFunc)
|
||||
: this(world, name, facingFunc, null) { }
|
||||
|
||||
public Animation(World world, string name, Func<bool> paused)
|
||||
: this(world, name, () => WAngle.Zero, paused) { }
|
||||
: this(world, name, () => 0, paused) { }
|
||||
|
||||
public Animation(World world, string name, Func<WAngle> facingFunc, Func<bool> paused)
|
||||
public Animation(World world, string name, Func<int> facingFunc, Func<bool> paused)
|
||||
{
|
||||
sequenceProvider = world.Map.Rules.Sequences;
|
||||
Name = name.ToLowerInvariant();
|
||||
@@ -49,52 +49,24 @@ namespace OpenRA.Graphics
|
||||
this.paused = paused;
|
||||
}
|
||||
|
||||
public int CurrentFrame { get { return backwards ? CurrentSequence.Length - frame - 1 : frame; } }
|
||||
public int CurrentFrame { get { return backwards ? CurrentSequence.Start + CurrentSequence.Length - frame - 1 : frame; } }
|
||||
public Sprite Image { get { return CurrentSequence.GetSprite(CurrentFrame, facingFunc()); } }
|
||||
|
||||
public IRenderable[] Render(WPos pos, WVec offset, int zOffset, PaletteReference palette, float scale)
|
||||
public IEnumerable<IRenderable> Render(WPos pos, WVec offset, int zOffset, PaletteReference palette, float scale)
|
||||
{
|
||||
var imageRenderable = new SpriteRenderable(Image, pos, offset, CurrentSequence.ZOffset + zOffset, palette, scale, IsDecoration, CurrentSequence.IgnoreWorldTint);
|
||||
var imageRenderable = new SpriteRenderable(Image, pos, offset, CurrentSequence.ZOffset + zOffset, palette, scale, IsDecoration);
|
||||
|
||||
if (CurrentSequence.ShadowStart >= 0)
|
||||
{
|
||||
var shadow = CurrentSequence.GetShadow(CurrentFrame, facingFunc());
|
||||
var shadowRenderable = new SpriteRenderable(shadow, pos, offset, CurrentSequence.ShadowZOffset + zOffset, palette, scale, true, CurrentSequence.IgnoreWorldTint);
|
||||
var shadowRenderable = new SpriteRenderable(shadow, pos, offset, CurrentSequence.ShadowZOffset + zOffset, palette, scale, true);
|
||||
return new IRenderable[] { shadowRenderable, imageRenderable };
|
||||
}
|
||||
|
||||
return new IRenderable[] { imageRenderable };
|
||||
}
|
||||
|
||||
public IRenderable[] RenderUI(WorldRenderer wr, int2 pos, WVec offset, int zOffset, PaletteReference palette, float scale)
|
||||
{
|
||||
var screenOffset = (scale * wr.ScreenVectorComponents(offset)).XY.ToInt2();
|
||||
var imagePos = pos + screenOffset - new int2((int)(scale * Image.Size.X / 2), (int)(scale * Image.Size.Y / 2));
|
||||
var imageRenderable = new UISpriteRenderable(Image, WPos.Zero + offset, imagePos, CurrentSequence.ZOffset + zOffset, palette, scale);
|
||||
|
||||
if (CurrentSequence.ShadowStart >= 0)
|
||||
{
|
||||
var shadow = CurrentSequence.GetShadow(CurrentFrame, facingFunc());
|
||||
var shadowPos = pos - new int2((int)(scale * shadow.Size.X / 2), (int)(scale * shadow.Size.Y / 2));
|
||||
var shadowRenderable = new UISpriteRenderable(shadow, WPos.Zero + offset, shadowPos, CurrentSequence.ShadowZOffset + zOffset, palette, scale);
|
||||
return new IRenderable[] { shadowRenderable, imageRenderable };
|
||||
}
|
||||
|
||||
return new IRenderable[] { imageRenderable };
|
||||
}
|
||||
|
||||
public Rectangle ScreenBounds(WorldRenderer wr, WPos pos, WVec offset, float scale)
|
||||
{
|
||||
var xy = wr.ScreenPxPosition(pos) + wr.ScreenPxOffset(offset);
|
||||
var cb = CurrentSequence.Bounds;
|
||||
return Rectangle.FromLTRB(
|
||||
xy.X + (int)(cb.Left * scale),
|
||||
xy.Y + (int)(cb.Top * scale),
|
||||
xy.X + (int)(cb.Right * scale),
|
||||
xy.Y + (int)(cb.Bottom * scale));
|
||||
}
|
||||
|
||||
public IRenderable[] Render(WPos pos, PaletteReference palette)
|
||||
public IEnumerable<IRenderable> Render(WPos pos, PaletteReference palette)
|
||||
{
|
||||
return Render(pos, WVec.Zero, 0, palette, 1f);
|
||||
}
|
||||
@@ -156,7 +128,7 @@ namespace OpenRA.Graphics
|
||||
{
|
||||
frame = CurrentSequence.Length - 1;
|
||||
tickFunc = () => { };
|
||||
after?.Invoke();
|
||||
if (after != null) after();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
@@ -10,7 +10,7 @@
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using OpenRA.Primitives;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
{
|
||||
@@ -35,7 +35,7 @@ namespace OpenRA.Graphics
|
||||
ZOffset = zOffset;
|
||||
}
|
||||
|
||||
public IRenderable[] Render(Actor self, WorldRenderer wr, PaletteReference pal, float scale)
|
||||
public IEnumerable<IRenderable> Render(Actor self, WorldRenderer wr, PaletteReference pal, float scale)
|
||||
{
|
||||
var center = self.CenterPosition;
|
||||
var offset = OffsetFunc != null ? OffsetFunc() : WVec.Zero;
|
||||
@@ -44,17 +44,9 @@ namespace OpenRA.Graphics
|
||||
return Animation.Render(center, offset, z, pal, scale);
|
||||
}
|
||||
|
||||
public Rectangle ScreenBounds(Actor self, WorldRenderer wr, float scale)
|
||||
{
|
||||
var center = self.CenterPosition;
|
||||
var offset = OffsetFunc != null ? OffsetFunc() : WVec.Zero;
|
||||
|
||||
return Animation.ScreenBounds(wr, center, offset, scale);
|
||||
}
|
||||
|
||||
public static implicit operator AnimationWithOffset(Animation a)
|
||||
{
|
||||
return new AnimationWithOffset(a, null, null, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
@@ -9,140 +9,81 @@
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenRA.FileSystem;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
{
|
||||
[Flags]
|
||||
public enum PanelSides
|
||||
{
|
||||
Left = 1,
|
||||
Top = 2,
|
||||
Right = 4,
|
||||
Bottom = 8,
|
||||
Center = 16,
|
||||
|
||||
Edges = Left | Top | Right | Bottom,
|
||||
All = Edges | Center,
|
||||
}
|
||||
|
||||
public static class PanelSidesExts
|
||||
{
|
||||
public static bool HasSide(this PanelSides self, PanelSides m)
|
||||
{
|
||||
// PERF: Enum.HasFlag is slower and requires allocations.
|
||||
return (self & m) == m;
|
||||
}
|
||||
}
|
||||
|
||||
public static class ChromeProvider
|
||||
{
|
||||
public class Collection
|
||||
struct Collection
|
||||
{
|
||||
public readonly string Image = null;
|
||||
public readonly string Image2x = null;
|
||||
public readonly string Image3x = null;
|
||||
|
||||
public readonly int[] PanelRegion = null;
|
||||
public readonly PanelSides PanelSides = PanelSides.All;
|
||||
public readonly Dictionary<string, Rectangle> Regions = new Dictionary<string, Rectangle>();
|
||||
public string Src;
|
||||
public Dictionary<string, MappedImage> Regions;
|
||||
}
|
||||
|
||||
public static IReadOnlyDictionary<string, Collection> Collections { get; private set; }
|
||||
static Dictionary<string, Collection> collections;
|
||||
static Dictionary<string, (Sheet Sheet, int Density)> cachedSheets;
|
||||
static Dictionary<string, Sheet> cachedSheets;
|
||||
static Dictionary<string, Dictionary<string, Sprite>> cachedSprites;
|
||||
static Dictionary<string, Sprite[]> cachedPanelSprites;
|
||||
static Dictionary<Collection, (Sheet Sheet, int)> cachedCollectionSheets;
|
||||
|
||||
static IReadOnlyFileSystem fileSystem;
|
||||
static float dpiScale = 1;
|
||||
|
||||
public static void Initialize(ModData modData)
|
||||
{
|
||||
Deinitialize();
|
||||
|
||||
// Load higher resolution images if available on HiDPI displays
|
||||
if (Game.Renderer != null)
|
||||
dpiScale = Game.Renderer.WindowScale;
|
||||
|
||||
fileSystem = modData.DefaultFileSystem;
|
||||
collections = new Dictionary<string, Collection>();
|
||||
cachedSheets = new Dictionary<string, (Sheet, int)>();
|
||||
cachedSheets = new Dictionary<string, Sheet>();
|
||||
cachedSprites = new Dictionary<string, Dictionary<string, Sprite>>();
|
||||
cachedPanelSprites = new Dictionary<string, Sprite[]>();
|
||||
cachedCollectionSheets = new Dictionary<Collection, (Sheet, int)>();
|
||||
|
||||
Collections = new ReadOnlyDictionary<string, Collection>(collections);
|
||||
|
||||
var chrome = MiniYaml.Merge(modData.Manifest.Chrome
|
||||
.Select(s => MiniYaml.FromStream(fileSystem.Open(s), s)));
|
||||
.Select(s => MiniYaml.FromStream(fileSystem.Open(s))));
|
||||
|
||||
foreach (var c in chrome)
|
||||
if (!c.Key.StartsWith("^", StringComparison.Ordinal))
|
||||
LoadCollection(c.Key, c.Value);
|
||||
LoadCollection(c.Key, c.Value);
|
||||
}
|
||||
|
||||
public static void Deinitialize()
|
||||
{
|
||||
if (cachedSheets != null)
|
||||
foreach (var sheet in cachedSheets.Values)
|
||||
sheet.Sheet.Dispose();
|
||||
sheet.Dispose();
|
||||
|
||||
collections = null;
|
||||
cachedSheets = null;
|
||||
cachedSprites = null;
|
||||
cachedPanelSprites = null;
|
||||
cachedCollectionSheets = null;
|
||||
}
|
||||
|
||||
public static void Save(string file)
|
||||
{
|
||||
var root = new List<MiniYamlNode>();
|
||||
foreach (var kv in collections)
|
||||
root.Add(new MiniYamlNode(kv.Key, SaveCollection(kv.Value)));
|
||||
|
||||
root.WriteToFile(file);
|
||||
}
|
||||
|
||||
static MiniYaml SaveCollection(Collection collection)
|
||||
{
|
||||
var root = new List<MiniYamlNode>();
|
||||
foreach (var kv in collection.Regions)
|
||||
root.Add(new MiniYamlNode(kv.Key, kv.Value.Save(collection.Src)));
|
||||
|
||||
return new MiniYaml(collection.Src, root);
|
||||
}
|
||||
|
||||
static void LoadCollection(string name, MiniYaml yaml)
|
||||
{
|
||||
if (Game.ModData.LoadScreen != null)
|
||||
Game.ModData.LoadScreen.Display();
|
||||
|
||||
collections.Add(name, FieldLoader.Load<Collection>(yaml));
|
||||
}
|
||||
|
||||
static (Sheet Sheet, int Density) SheetForCollection(Collection c)
|
||||
{
|
||||
// Outer cache avoids recalculating image names
|
||||
if (!cachedCollectionSheets.TryGetValue(c, out (Sheet, int) sheetDensity))
|
||||
var collection = new Collection()
|
||||
{
|
||||
var image = c.Image;
|
||||
var density = 1;
|
||||
if (dpiScale > 2 && !string.IsNullOrEmpty(c.Image3x))
|
||||
{
|
||||
image = c.Image3x;
|
||||
density = 3;
|
||||
}
|
||||
else if (dpiScale > 1 && !string.IsNullOrEmpty(c.Image2x))
|
||||
{
|
||||
image = c.Image2x;
|
||||
density = 2;
|
||||
}
|
||||
Src = yaml.Value,
|
||||
Regions = yaml.Nodes.ToDictionary(n => n.Key, n => new MappedImage(yaml.Value, n.Value))
|
||||
};
|
||||
|
||||
// Inner cache makes sure we share sheets between collections
|
||||
if (!cachedSheets.TryGetValue(image, out sheetDensity))
|
||||
{
|
||||
Sheet sheet;
|
||||
using (var stream = fileSystem.Open(image))
|
||||
sheet = new Sheet(SheetType.BGRA, stream);
|
||||
|
||||
sheet.GetTexture().ScaleFilter = TextureScaleFilter.Linear;
|
||||
|
||||
sheetDensity = (sheet, density);
|
||||
cachedSheets.Add(image, sheetDensity);
|
||||
}
|
||||
|
||||
cachedCollectionSheets.Add(c, sheetDensity);
|
||||
}
|
||||
|
||||
return sheetDensity;
|
||||
collections.Add(name, collection);
|
||||
}
|
||||
|
||||
public static Sprite GetImage(string collectionName, string imageName)
|
||||
@@ -151,135 +92,45 @@ namespace OpenRA.Graphics
|
||||
return null;
|
||||
|
||||
// Cached sprite
|
||||
if (cachedSprites.TryGetValue(collectionName, out var cachedCollection) && cachedCollection.TryGetValue(imageName, out var sprite))
|
||||
Dictionary<string, Sprite> cachedCollection;
|
||||
Sprite sprite;
|
||||
if (cachedSprites.TryGetValue(collectionName, out cachedCollection) && cachedCollection.TryGetValue(imageName, out sprite))
|
||||
return sprite;
|
||||
|
||||
if (!collections.TryGetValue(collectionName, out var collection))
|
||||
Collection collection;
|
||||
if (!collections.TryGetValue(collectionName, out collection))
|
||||
{
|
||||
Log.Write("debug", "Could not find collection '{0}'", collectionName);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!collection.Regions.TryGetValue(imageName, out var mi))
|
||||
MappedImage mi;
|
||||
if (!collection.Regions.TryGetValue(imageName, out mi))
|
||||
return null;
|
||||
|
||||
// Cached sheet
|
||||
Sheet sheet;
|
||||
if (cachedSheets.ContainsKey(mi.Src))
|
||||
sheet = cachedSheets[mi.Src];
|
||||
else
|
||||
{
|
||||
using (var stream = fileSystem.Open(mi.Src))
|
||||
sheet = new Sheet(SheetType.BGRA, stream);
|
||||
|
||||
cachedSheets.Add(mi.Src, sheet);
|
||||
}
|
||||
|
||||
// Cache the sprite
|
||||
var sheetDensity = SheetForCollection(collection);
|
||||
if (cachedCollection == null)
|
||||
{
|
||||
cachedCollection = new Dictionary<string, Sprite>();
|
||||
cachedSprites.Add(collectionName, cachedCollection);
|
||||
}
|
||||
|
||||
var image = new Sprite(sheetDensity.Sheet, sheetDensity.Density * mi, TextureChannel.RGBA, 1f / sheetDensity.Density);
|
||||
var image = mi.GetImage(sheet);
|
||||
cachedCollection.Add(imageName, image);
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
public static Sprite[] GetPanelImages(string collectionName)
|
||||
{
|
||||
if (string.IsNullOrEmpty(collectionName))
|
||||
return null;
|
||||
|
||||
// Cached sprite
|
||||
if (cachedPanelSprites.TryGetValue(collectionName, out var cachedSprites))
|
||||
return cachedSprites;
|
||||
|
||||
if (!collections.TryGetValue(collectionName, out var collection))
|
||||
{
|
||||
Log.Write("debug", "Could not find collection '{0}'", collectionName);
|
||||
return null;
|
||||
}
|
||||
|
||||
Sprite[] sprites;
|
||||
if (collection.PanelRegion != null)
|
||||
{
|
||||
if (collection.PanelRegion.Length != 8)
|
||||
{
|
||||
Log.Write("debug", "Collection '{0}' does not define a valid PanelRegion", collectionName);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Cache the sprites
|
||||
var sheetDensity = SheetForCollection(collection);
|
||||
var pr = collection.PanelRegion;
|
||||
var ps = collection.PanelSides;
|
||||
|
||||
var sides = new (PanelSides PanelSides, Rectangle Bounds)[]
|
||||
{
|
||||
(PanelSides.Top | PanelSides.Left, new Rectangle(pr[0], pr[1], pr[2], pr[3])),
|
||||
(PanelSides.Top, new Rectangle(pr[0] + pr[2], pr[1], pr[4], pr[3])),
|
||||
(PanelSides.Top | PanelSides.Right, new Rectangle(pr[0] + pr[2] + pr[4], pr[1], pr[6], pr[3])),
|
||||
(PanelSides.Left, new Rectangle(pr[0], pr[1] + pr[3], pr[2], pr[5])),
|
||||
(PanelSides.Center, new Rectangle(pr[0] + pr[2], pr[1] + pr[3], pr[4], pr[5])),
|
||||
(PanelSides.Right, new Rectangle(pr[0] + pr[2] + pr[4], pr[1] + pr[3], pr[6], pr[5])),
|
||||
(PanelSides.Bottom | PanelSides.Left, new Rectangle(pr[0], pr[1] + pr[3] + pr[5], pr[2], pr[7])),
|
||||
(PanelSides.Bottom, new Rectangle(pr[0] + pr[2], pr[1] + pr[3] + pr[5], pr[4], pr[7])),
|
||||
(PanelSides.Bottom | PanelSides.Right, new Rectangle(pr[0] + pr[2] + pr[4], pr[1] + pr[3] + pr[5], pr[6], pr[7]))
|
||||
};
|
||||
|
||||
sprites = sides.Select(x => ps.HasSide(x.PanelSides) ? new Sprite(sheetDensity.Sheet, sheetDensity.Density * x.Bounds, TextureChannel.RGBA, 1f / sheetDensity.Density) : null)
|
||||
.ToArray();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Support manual definitions for unusual dialog layouts
|
||||
sprites = new[]
|
||||
{
|
||||
GetImage(collectionName, "corner-tl"),
|
||||
GetImage(collectionName, "border-t"),
|
||||
GetImage(collectionName, "corner-tr"),
|
||||
GetImage(collectionName, "border-l"),
|
||||
GetImage(collectionName, "background"),
|
||||
GetImage(collectionName, "border-r"),
|
||||
GetImage(collectionName, "corner-bl"),
|
||||
GetImage(collectionName, "border-b"),
|
||||
GetImage(collectionName, "corner-br")
|
||||
};
|
||||
}
|
||||
|
||||
cachedPanelSprites.Add(collectionName, sprites);
|
||||
return sprites;
|
||||
}
|
||||
|
||||
public static Size GetMinimumPanelSize(string collectionName)
|
||||
{
|
||||
if (string.IsNullOrEmpty(collectionName))
|
||||
return new Size(0, 0);
|
||||
|
||||
if (!collections.TryGetValue(collectionName, out var collection))
|
||||
{
|
||||
Log.Write("debug", "Could not find collection '{0}'", collectionName);
|
||||
return new Size(0, 0);
|
||||
}
|
||||
|
||||
if (collection.PanelRegion == null || collection.PanelRegion.Length != 8)
|
||||
{
|
||||
Log.Write("debug", "Collection '{0}' does not define a valid PanelRegion", collectionName);
|
||||
return new Size(0, 0);
|
||||
}
|
||||
|
||||
var pr = collection.PanelRegion;
|
||||
return new Size(pr[2] + pr[6], pr[3] + pr[7]);
|
||||
}
|
||||
|
||||
public static void SetDPIScale(float scale)
|
||||
{
|
||||
if (dpiScale == scale)
|
||||
return;
|
||||
|
||||
dpiScale = scale;
|
||||
|
||||
// Clear the sprite caches so the new artwork can be loaded
|
||||
// Sheets are not cleared: we assume that the extra memory overhead
|
||||
// of having the same sheet in memory in multiple DPIs is better than
|
||||
// the overhead of having to dispose and reload everything.
|
||||
// Changing the DPI scale is rare, but if it does happen then there
|
||||
// is a reasonable chance that it may happen again this session.
|
||||
cachedSprites.Clear();
|
||||
cachedPanelSprites.Clear();
|
||||
cachedCollectionSheets.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,298 +0,0 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
{
|
||||
public sealed class CursorManager
|
||||
{
|
||||
class Cursor
|
||||
{
|
||||
public string Name;
|
||||
public int2 PaddedSize;
|
||||
public Rectangle Bounds;
|
||||
|
||||
public int Length;
|
||||
public Sprite[] Sprites;
|
||||
public IHardwareCursor[] Cursors;
|
||||
}
|
||||
|
||||
readonly Dictionary<string, Cursor> cursors = new Dictionary<string, Cursor>();
|
||||
readonly SheetBuilder sheetBuilder;
|
||||
readonly GraphicSettings graphicSettings;
|
||||
|
||||
Cursor cursor;
|
||||
bool isLocked = false;
|
||||
int2 lockedPosition;
|
||||
bool hardwareCursorsDisabled = false;
|
||||
bool hardwareCursorsDoubled = false;
|
||||
|
||||
public CursorManager(CursorProvider cursorProvider)
|
||||
{
|
||||
hardwareCursorsDisabled = Game.Settings.Graphics.DisableHardwareCursors;
|
||||
|
||||
graphicSettings = Game.Settings.Graphics;
|
||||
sheetBuilder = new SheetBuilder(SheetType.BGRA);
|
||||
foreach (var kv in cursorProvider.Cursors)
|
||||
{
|
||||
var frames = kv.Value.Frames;
|
||||
var palette = !string.IsNullOrEmpty(kv.Value.Palette) ? cursorProvider.Palettes[kv.Value.Palette] : null;
|
||||
|
||||
var c = new Cursor
|
||||
{
|
||||
Name = kv.Key,
|
||||
Bounds = Rectangle.FromLTRB(0, 0, 1, 1),
|
||||
|
||||
Length = 0,
|
||||
Sprites = new Sprite[frames.Length],
|
||||
Cursors = new IHardwareCursor[frames.Length]
|
||||
};
|
||||
|
||||
// Hardware cursors have a number of odd platform-specific bugs/limitations.
|
||||
// Reduce the number of edge cases by padding the individual frames such that:
|
||||
// - the hotspot is inside the frame bounds (enforced by SDL)
|
||||
// - all frames within a sequence have the same size (needed for macOS 10.15)
|
||||
// - the frame size is a multiple of 8 (needed for Windows)
|
||||
foreach (var f in frames)
|
||||
{
|
||||
// Hotspot is specified relative to the center of the frame
|
||||
var hotspot = f.Offset.ToInt2() - kv.Value.Hotspot - new int2(f.Size) / 2;
|
||||
|
||||
// SheetBuilder expects data in BGRA
|
||||
var data = FrameToBGRA(kv.Key, f, palette);
|
||||
c.Sprites[c.Length++] = sheetBuilder.Add(data, f.Size, 0, hotspot);
|
||||
|
||||
// Bounds relative to the hotspot
|
||||
c.Bounds = Rectangle.Union(c.Bounds, new Rectangle(hotspot, f.Size));
|
||||
}
|
||||
|
||||
// Pad bottom-right edge to make the frame size a multiple of 8
|
||||
c.PaddedSize = 8 * new int2((c.Bounds.Width + 7) / 8, (c.Bounds.Height + 7) / 8);
|
||||
|
||||
cursors.Add(kv.Key, c);
|
||||
}
|
||||
|
||||
CreateOrUpdateHardwareCursors();
|
||||
|
||||
foreach (var s in sheetBuilder.AllSheets)
|
||||
s.ReleaseBuffer();
|
||||
|
||||
Update();
|
||||
}
|
||||
|
||||
void CreateOrUpdateHardwareCursors()
|
||||
{
|
||||
if (hardwareCursorsDisabled)
|
||||
return;
|
||||
|
||||
// Dispose any existing cursors to avoid leaking native resources
|
||||
ClearHardwareCursors();
|
||||
|
||||
try
|
||||
{
|
||||
foreach (var kv in cursors)
|
||||
{
|
||||
var template = kv.Value;
|
||||
for (var i = 0; i < template.Sprites.Length; i++)
|
||||
{
|
||||
if (template.Cursors[i] != null)
|
||||
template.Cursors[i].Dispose();
|
||||
|
||||
// Calculate the padding to position the frame within sequenceBounds
|
||||
var paddingTL = -(template.Bounds.Location - template.Sprites[i].Offset.XY.ToInt2());
|
||||
var paddingBR = template.PaddedSize - new int2(template.Sprites[i].Bounds.Size) - paddingTL;
|
||||
|
||||
template.Cursors[i] = CreateHardwareCursor(kv.Key, template.Sprites[i], paddingTL, paddingBR, -template.Bounds.Location);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Write("debug", "Failed to initialize hardware cursors. Falling back to software cursors.");
|
||||
Log.Write("debug", "Error was: " + e.Message);
|
||||
|
||||
Console.WriteLine("Failed to initialize hardware cursors. Falling back to software cursors.");
|
||||
Console.WriteLine("Error was: " + e.Message);
|
||||
|
||||
ClearHardwareCursors();
|
||||
}
|
||||
|
||||
hardwareCursorsDoubled = graphicSettings.CursorDouble;
|
||||
}
|
||||
|
||||
public void SetCursor(string cursorName)
|
||||
{
|
||||
if ((cursorName == null && cursor == null) || (cursor != null && cursorName == cursor.Name))
|
||||
return;
|
||||
|
||||
if (cursorName == null || !cursors.TryGetValue(cursorName, out cursor))
|
||||
cursor = null;
|
||||
|
||||
Update();
|
||||
}
|
||||
|
||||
int frame;
|
||||
int ticks;
|
||||
|
||||
public void Tick()
|
||||
{
|
||||
if (hardwareCursorsDoubled != graphicSettings.CursorDouble)
|
||||
{
|
||||
CreateOrUpdateHardwareCursors();
|
||||
Update();
|
||||
}
|
||||
|
||||
if (cursor == null || cursor.Cursors.Length == 1)
|
||||
return;
|
||||
|
||||
if (++ticks > 2)
|
||||
{
|
||||
ticks -= 2;
|
||||
frame++;
|
||||
|
||||
Update();
|
||||
}
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
if (cursor != null && frame >= cursor.Cursors.Length)
|
||||
frame %= cursor.Cursors.Length;
|
||||
|
||||
if (cursor == null || isLocked)
|
||||
Game.Renderer.Window.SetHardwareCursor(null);
|
||||
else
|
||||
Game.Renderer.Window.SetHardwareCursor(cursor.Cursors[frame]);
|
||||
}
|
||||
|
||||
public void Render(Renderer renderer)
|
||||
{
|
||||
// Cursor is hidden
|
||||
if (cursor == null)
|
||||
return;
|
||||
|
||||
// Hardware cursor is enabled
|
||||
if (!isLocked && cursor.Cursors[frame % cursor.Length] != null)
|
||||
return;
|
||||
|
||||
// Render cursor in software
|
||||
var doubleCursor = graphicSettings.CursorDouble;
|
||||
var cursorSprite = cursor.Sprites[frame % cursor.Length];
|
||||
var cursorSize = doubleCursor ? 2.0f * cursorSprite.Size : cursorSprite.Size;
|
||||
|
||||
// Cursor is rendered in native window coordinates
|
||||
// Apply same scaling rules as hardware cursors
|
||||
if (Game.Renderer.NativeWindowScale > 1.5f)
|
||||
cursorSize = 2 * cursorSize;
|
||||
|
||||
var mousePos = isLocked ? lockedPosition : Viewport.LastMousePos;
|
||||
renderer.RgbaSpriteRenderer.DrawSprite(cursorSprite,
|
||||
mousePos,
|
||||
cursorSize / Game.Renderer.WindowScale);
|
||||
}
|
||||
|
||||
public void Lock()
|
||||
{
|
||||
lockedPosition = Viewport.LastMousePos;
|
||||
Game.Renderer.Window.SetRelativeMouseMode(true);
|
||||
isLocked = true;
|
||||
Update();
|
||||
}
|
||||
|
||||
public void Unlock()
|
||||
{
|
||||
Game.Renderer.Window.SetRelativeMouseMode(false);
|
||||
isLocked = false;
|
||||
Update();
|
||||
}
|
||||
|
||||
public static byte[] FrameToBGRA(string name, ISpriteFrame frame, ImmutablePalette palette)
|
||||
{
|
||||
// Data is already in BGRA format
|
||||
if (frame.Type == SpriteFrameType.BGRA)
|
||||
return frame.Data;
|
||||
|
||||
// Cursors may be either native BGRA or Indexed.
|
||||
// Indexed sprites are converted to BGRA using the referenced palette.
|
||||
// All palettes must be explicitly referenced, even if they are embedded in the sprite.
|
||||
if (frame.Type == SpriteFrameType.Indexed && palette == null)
|
||||
throw new InvalidOperationException("Cursor sequence `{0}` attempted to load an indexed sprite but does not define Palette".F(name));
|
||||
|
||||
var width = frame.Size.Width;
|
||||
var height = frame.Size.Height;
|
||||
var data = new byte[4 * width * height];
|
||||
for (var j = 0; j < height; j++)
|
||||
{
|
||||
for (var i = 0; i < width; i++)
|
||||
{
|
||||
var rgba = palette[frame.Data[j * width + i]];
|
||||
var k = 4 * (j * width + i);
|
||||
|
||||
// Convert RGBA to BGRA
|
||||
data[k] = (byte)(rgba >> 16);
|
||||
data[k + 1] = (byte)(rgba >> 8);
|
||||
data[k + 2] = (byte)(rgba >> 0);
|
||||
data[k + 3] = (byte)(rgba >> 24);
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
IHardwareCursor CreateHardwareCursor(string name, Sprite data, int2 paddingTL, int2 paddingBR, int2 hotspot)
|
||||
{
|
||||
var size = data.Bounds.Size;
|
||||
var srcStride = data.Sheet.Size.Width;
|
||||
var srcData = data.Sheet.GetData();
|
||||
var newWidth = paddingTL.X + size.Width + paddingBR.X;
|
||||
var newHeight = paddingTL.Y + size.Height + paddingBR.Y;
|
||||
var rgbaData = new byte[4 * newWidth * newHeight];
|
||||
|
||||
for (var j = 0; j < size.Height; j++)
|
||||
{
|
||||
for (var i = 0; i < size.Width; i++)
|
||||
{
|
||||
var src = 4 * ((j + data.Bounds.Top) * srcStride + data.Bounds.Left + i);
|
||||
var dest = 4 * ((j + paddingTL.Y) * newWidth + i + paddingTL.X);
|
||||
Array.Copy(srcData, src, rgbaData, dest, 4);
|
||||
}
|
||||
}
|
||||
|
||||
return Game.Renderer.Window.CreateHardwareCursor(name, new Size(newWidth, newHeight), rgbaData, hotspot, graphicSettings.CursorDouble);
|
||||
}
|
||||
|
||||
void ClearHardwareCursors()
|
||||
{
|
||||
foreach (var c in cursors.Values)
|
||||
{
|
||||
for (var i = 0; i < c.Cursors.Length; i++)
|
||||
{
|
||||
if (c.Cursors[i] != null)
|
||||
{
|
||||
c.Cursors[i].Dispose();
|
||||
c.Cursors[i] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
ClearHardwareCursors();
|
||||
|
||||
cursors.Clear();
|
||||
sheetBuilder.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
@@ -12,7 +12,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
{
|
||||
@@ -25,21 +24,23 @@ namespace OpenRA.Graphics
|
||||
{
|
||||
var fileSystem = modData.DefaultFileSystem;
|
||||
var sequenceYaml = MiniYaml.Merge(modData.Manifest.Cursors.Select(
|
||||
s => MiniYaml.FromStream(fileSystem.Open(s), s)));
|
||||
s => MiniYaml.FromStream(fileSystem.Open(s))));
|
||||
|
||||
var shadowIndex = new int[] { };
|
||||
|
||||
var nodesDict = new MiniYaml(null, sequenceYaml).ToDictionary();
|
||||
if (nodesDict.ContainsKey("ShadowIndex"))
|
||||
{
|
||||
Array.Resize(ref shadowIndex, shadowIndex.Length + 1);
|
||||
Exts.TryParseIntegerInvariant(nodesDict["ShadowIndex"].Value,
|
||||
out shadowIndex[shadowIndex.Length - 1]);
|
||||
}
|
||||
|
||||
// Overwrite previous definitions if there are duplicates
|
||||
var pals = new Dictionary<string, IProvidesCursorPaletteInfo>();
|
||||
foreach (var p in modData.DefaultRules.Actors["world"].TraitInfos<IProvidesCursorPaletteInfo>())
|
||||
if (p.Palette != null)
|
||||
pals[p.Palette] = p;
|
||||
var palettes = new Dictionary<string, ImmutablePalette>();
|
||||
foreach (var p in nodesDict["Palettes"].Nodes)
|
||||
palettes.Add(p.Key, new ImmutablePalette(fileSystem.Open(p.Value.Value), shadowIndex));
|
||||
|
||||
Palettes = nodesDict["Cursors"].Nodes.Select(n => n.Value.Value)
|
||||
.Where(p => p != null)
|
||||
.Distinct()
|
||||
.ToDictionary(p => p, p => pals[p].ReadPalette(modData.DefaultFileSystem))
|
||||
.AsReadOnly();
|
||||
Palettes = palettes.AsReadOnly();
|
||||
|
||||
var frameCache = new FrameCache(fileSystem, modData.SpriteLoaders);
|
||||
var cursors = new Dictionary<string, CursorSequence>();
|
||||
@@ -50,6 +51,8 @@ namespace OpenRA.Graphics
|
||||
Cursors = cursors.AsReadOnly();
|
||||
}
|
||||
|
||||
public static bool CursorViewportZoomed { get { return Game.Settings.Graphics.CursorDouble && Game.Settings.Graphics.PixelDouble; } }
|
||||
|
||||
public bool HasCursorSequence(string cursor)
|
||||
{
|
||||
return Cursors.ContainsKey(cursor);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
@@ -31,10 +31,8 @@ namespace OpenRA.Graphics
|
||||
Palette = palette;
|
||||
Name = name;
|
||||
|
||||
Frames = cache[cursorSrc].Skip(Start).ToArray();
|
||||
|
||||
if ((d.ContainsKey("Length") && d["Length"].Value == "*") || (d.ContainsKey("End") && d["End"].Value == "*"))
|
||||
Length = Frames.Length;
|
||||
Length = Frames.Length - Start;
|
||||
else if (d.ContainsKey("Length"))
|
||||
Length = Exts.ParseIntegerInvariant(d["Length"].Value);
|
||||
else if (d.ContainsKey("End"))
|
||||
@@ -42,17 +40,22 @@ namespace OpenRA.Graphics
|
||||
else
|
||||
Length = 1;
|
||||
|
||||
Frames = Frames.Take(Length).ToArray();
|
||||
Frames = cache[cursorSrc]
|
||||
.Skip(Start)
|
||||
.Take(Length)
|
||||
.ToArray();
|
||||
|
||||
if (d.ContainsKey("X"))
|
||||
{
|
||||
Exts.TryParseIntegerInvariant(d["X"].Value, out var x);
|
||||
int x;
|
||||
Exts.TryParseIntegerInvariant(d["X"].Value, out x);
|
||||
Hotspot = Hotspot.WithX(x);
|
||||
}
|
||||
|
||||
if (d.ContainsKey("Y"))
|
||||
{
|
||||
Exts.TryParseIntegerInvariant(d["Y"].Value, out var y);
|
||||
int y;
|
||||
Exts.TryParseIntegerInvariant(d["Y"].Value, out y);
|
||||
Hotspot = Hotspot.WithY(y);
|
||||
}
|
||||
}
|
||||
|
||||
146
OpenRA.Game/Graphics/HSLColor.cs
Normal file
146
OpenRA.Game/Graphics/HSLColor.cs
Normal file
@@ -0,0 +1,146 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.Drawing;
|
||||
using System.Globalization;
|
||||
using OpenRA.Scripting;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
{
|
||||
public struct HSLColor : IScriptBindable
|
||||
{
|
||||
public readonly byte H;
|
||||
public readonly byte S;
|
||||
public readonly byte L;
|
||||
public readonly Color RGB;
|
||||
|
||||
public static HSLColor FromHSV(float h, float s, float v)
|
||||
{
|
||||
var ll = 0.5f * (2 - s) * v;
|
||||
var ss = (ll >= 1 || v <= 0) ? 0 : 0.5f * s * v / (ll <= 0.5f ? ll : 1 - ll);
|
||||
return new HSLColor((byte)(255 * h), (byte)(255 * ss), (byte)(255 * ll));
|
||||
}
|
||||
|
||||
public HSLColor(Color color)
|
||||
{
|
||||
RGB = color;
|
||||
H = (byte)((color.GetHue() / 360.0f) * 255);
|
||||
S = (byte)(color.GetSaturation() * 255);
|
||||
L = (byte)(color.GetBrightness() * 255);
|
||||
}
|
||||
|
||||
public static HSLColor FromRGB(int r, int g, int b)
|
||||
{
|
||||
return new HSLColor(Color.FromArgb(r, g, b));
|
||||
}
|
||||
|
||||
public static Color RGBFromHSL(float h, float s, float l)
|
||||
{
|
||||
// Convert from HSL to RGB
|
||||
var q = (l < 0.5f) ? l * (1 + s) : l + s - (l * s);
|
||||
var p = 2 * l - q;
|
||||
|
||||
float[] trgb = { h + 1 / 3.0f, h, h - 1 / 3.0f };
|
||||
float[] rgb = { 0, 0, 0 };
|
||||
|
||||
for (var k = 0; k < 3; k++)
|
||||
{
|
||||
while (trgb[k] < 0) trgb[k] += 1.0f;
|
||||
while (trgb[k] > 1) trgb[k] -= 1.0f;
|
||||
}
|
||||
|
||||
for (var k = 0; k < 3; k++)
|
||||
{
|
||||
if (trgb[k] < 1 / 6.0f)
|
||||
rgb[k] = p + ((q - p) * 6 * trgb[k]);
|
||||
else if (trgb[k] >= 1 / 6.0f && trgb[k] < 0.5)
|
||||
rgb[k] = q;
|
||||
else if (trgb[k] >= 0.5f && trgb[k] < 2.0f / 3)
|
||||
rgb[k] = p + ((q - p) * 6 * (2.0f / 3 - trgb[k]));
|
||||
else
|
||||
rgb[k] = p;
|
||||
}
|
||||
|
||||
return Color.FromArgb((int)(rgb[0] * 255), (int)(rgb[1] * 255), (int)(rgb[2] * 255));
|
||||
}
|
||||
|
||||
public static bool TryParseRGB(string value, out Color color)
|
||||
{
|
||||
color = new Color();
|
||||
value = value.Trim();
|
||||
if (value.Length != 6 && value.Length != 8)
|
||||
return false;
|
||||
|
||||
byte red, green, blue, alpha = 255;
|
||||
if (!byte.TryParse(value.Substring(0, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out red)
|
||||
|| !byte.TryParse(value.Substring(2, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out green)
|
||||
|| !byte.TryParse(value.Substring(4, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out blue))
|
||||
return false;
|
||||
|
||||
if (value.Length == 8
|
||||
&& !byte.TryParse(value.Substring(6, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out alpha))
|
||||
return false;
|
||||
|
||||
color = Color.FromArgb(alpha, red, green, blue);
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool operator ==(HSLColor me, HSLColor other)
|
||||
{
|
||||
return me.H == other.H && me.S == other.S && me.L == other.L;
|
||||
}
|
||||
|
||||
public static bool operator !=(HSLColor me, HSLColor other) { return !(me == other); }
|
||||
|
||||
public HSLColor(byte h, byte s, byte l)
|
||||
{
|
||||
H = h;
|
||||
S = s;
|
||||
L = l;
|
||||
RGB = RGBFromHSL(H / 255f, S / 255f, L / 255f);
|
||||
}
|
||||
|
||||
public void ToHSV(out float h, out float s, out float v)
|
||||
{
|
||||
var ll = 2 * L / 255f;
|
||||
var ss = S / 255f * ((ll <= 1) ? ll : 2 - ll);
|
||||
|
||||
h = H / 255f;
|
||||
s = (2 * ss) / (ll + ss);
|
||||
v = (ll + ss) / 2;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "{0},{1},{2}".F(H, S, L);
|
||||
}
|
||||
|
||||
public static string ToHexString(Color color)
|
||||
{
|
||||
if (color.A == 255)
|
||||
return color.R.ToString("X2") + color.G.ToString("X2") + color.B.ToString("X2");
|
||||
return color.R.ToString("X2") + color.G.ToString("X2") + color.B.ToString("X2") + color.A.ToString("X2");
|
||||
}
|
||||
|
||||
public string ToHexString()
|
||||
{
|
||||
return ToHexString(RGB);
|
||||
}
|
||||
|
||||
public override int GetHashCode() { return H.GetHashCode() ^ S.GetHashCode() ^ L.GetHashCode(); }
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
var o = obj as HSLColor?;
|
||||
return o != null && o == this;
|
||||
}
|
||||
}
|
||||
}
|
||||
136
OpenRA.Game/Graphics/HardwareCursor.cs
Normal file
136
OpenRA.Game/Graphics/HardwareCursor.cs
Normal file
@@ -0,0 +1,136 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2015 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
{
|
||||
public sealed class HardwareCursor : ICursor
|
||||
{
|
||||
readonly Dictionary<string, IHardwareCursor[]> hardwareCursors = new Dictionary<string, IHardwareCursor[]>();
|
||||
readonly CursorProvider cursorProvider;
|
||||
CursorSequence cursor;
|
||||
|
||||
public HardwareCursor(CursorProvider cursorProvider)
|
||||
{
|
||||
this.cursorProvider = cursorProvider;
|
||||
|
||||
foreach (var kv in cursorProvider.Cursors)
|
||||
{
|
||||
var palette = cursorProvider.Palettes[kv.Value.Palette];
|
||||
var hc = kv.Value.Frames
|
||||
.Select(f => CreateCursor(f, palette, kv.Key, kv.Value))
|
||||
.ToArray();
|
||||
|
||||
hardwareCursors.Add(kv.Key, hc);
|
||||
}
|
||||
|
||||
Update();
|
||||
}
|
||||
|
||||
IHardwareCursor CreateCursor(ISpriteFrame f, ImmutablePalette palette, string name, CursorSequence sequence)
|
||||
{
|
||||
var hotspot = sequence.Hotspot - f.Offset.ToInt2() + new int2(f.Size) / 2;
|
||||
|
||||
// Expand the frame if required to include the hotspot
|
||||
var frameWidth = f.Size.Width;
|
||||
var dataWidth = f.Size.Width;
|
||||
var dataX = 0;
|
||||
if (hotspot.X < 0)
|
||||
{
|
||||
dataX = -hotspot.X;
|
||||
dataWidth += dataX;
|
||||
hotspot = hotspot.WithX(0);
|
||||
}
|
||||
else if (hotspot.X >= frameWidth)
|
||||
dataWidth = hotspot.X + 1;
|
||||
|
||||
var frameHeight = f.Size.Height;
|
||||
var dataHeight = f.Size.Height;
|
||||
var dataY = 0;
|
||||
if (hotspot.Y < 0)
|
||||
{
|
||||
dataY = -hotspot.Y;
|
||||
dataHeight += dataY;
|
||||
hotspot = hotspot.WithY(0);
|
||||
}
|
||||
else if (hotspot.Y >= frameHeight)
|
||||
dataHeight = hotspot.Y + 1;
|
||||
|
||||
var data = new byte[4 * dataWidth * dataHeight];
|
||||
for (var j = 0; j < frameHeight; j++)
|
||||
{
|
||||
for (var i = 0; i < frameWidth; i++)
|
||||
{
|
||||
var bytes = BitConverter.GetBytes(palette[f.Data[j * frameWidth + i]]);
|
||||
var start = 4 * ((j + dataY) * dataWidth + dataX + i);
|
||||
for (var k = 0; k < 4; k++)
|
||||
data[start + k] = bytes[k];
|
||||
}
|
||||
}
|
||||
|
||||
return Game.Renderer.Device.CreateHardwareCursor(name, new Size(dataWidth, dataHeight), data, hotspot);
|
||||
}
|
||||
|
||||
public void SetCursor(string cursorName)
|
||||
{
|
||||
if ((cursorName == null && cursor == null) || (cursor != null && cursorName == cursor.Name))
|
||||
return;
|
||||
|
||||
if (cursorName == null || !cursorProvider.Cursors.TryGetValue(cursorName, out cursor))
|
||||
cursor = null;
|
||||
|
||||
Update();
|
||||
}
|
||||
|
||||
int frame;
|
||||
int ticks;
|
||||
public void Tick()
|
||||
{
|
||||
if (cursor == null || cursor.Length == 1)
|
||||
return;
|
||||
|
||||
if (++ticks > 2)
|
||||
{
|
||||
ticks -= 2;
|
||||
frame++;
|
||||
|
||||
Update();
|
||||
}
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
if (cursor == null)
|
||||
Game.Renderer.Device.SetHardwareCursor(null);
|
||||
else
|
||||
{
|
||||
if (frame >= cursor.Length)
|
||||
frame = frame % cursor.Length;
|
||||
|
||||
Game.Renderer.Device.SetHardwareCursor(hardwareCursors[cursor.Name][frame]);
|
||||
}
|
||||
}
|
||||
|
||||
public void Render(Renderer renderer) { }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (var cursors in hardwareCursors)
|
||||
foreach (var cursor in cursors.Value)
|
||||
cursor.Dispose();
|
||||
|
||||
hardwareCursors.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
@@ -27,7 +27,7 @@ namespace OpenRA.Graphics
|
||||
|
||||
public HardwarePalette()
|
||||
{
|
||||
Texture = Game.Renderer.Context.CreateTexture();
|
||||
Texture = Game.Renderer.Device.CreateTexture();
|
||||
readOnlyModifiablePalettes = modifiablePalettes.AsReadOnly();
|
||||
}
|
||||
|
||||
@@ -38,16 +38,19 @@ namespace OpenRA.Graphics
|
||||
|
||||
public IPalette GetPalette(string name)
|
||||
{
|
||||
if (modifiablePalettes.TryGetValue(name, out var mutable))
|
||||
MutablePalette mutable;
|
||||
if (modifiablePalettes.TryGetValue(name, out mutable))
|
||||
return mutable.AsReadOnly();
|
||||
if (palettes.TryGetValue(name, out var immutable))
|
||||
ImmutablePalette immutable;
|
||||
if (palettes.TryGetValue(name, out immutable))
|
||||
return immutable;
|
||||
throw new InvalidOperationException("Palette `{0}` does not exist".F(name));
|
||||
}
|
||||
|
||||
public int GetPaletteIndex(string name)
|
||||
{
|
||||
if (!indices.TryGetValue(name, out var ret))
|
||||
int ret;
|
||||
if (!indices.TryGetValue(name, out ret))
|
||||
throw new InvalidOperationException("Palette `{0}` does not exist".F(name));
|
||||
return ret;
|
||||
}
|
||||
|
||||
142
OpenRA.Game/Graphics/IGraphicsDevice.cs
Normal file
142
OpenRA.Game/Graphics/IGraphicsDevice.cs
Normal file
@@ -0,0 +1,142 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using OpenRA.Graphics;
|
||||
|
||||
namespace OpenRA
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Assembly)]
|
||||
public sealed class PlatformAttribute : Attribute
|
||||
{
|
||||
public readonly Type Type;
|
||||
|
||||
public PlatformAttribute(Type graphicsDeviceType)
|
||||
{
|
||||
if (!typeof(IDeviceFactory).IsAssignableFrom(graphicsDeviceType))
|
||||
throw new InvalidOperationException("Incorrect type in RendererAttribute");
|
||||
Type = graphicsDeviceType;
|
||||
}
|
||||
}
|
||||
|
||||
public interface IDeviceFactory
|
||||
{
|
||||
IGraphicsDevice CreateGraphics(Size size, WindowMode windowMode);
|
||||
ISoundEngine CreateSound();
|
||||
}
|
||||
|
||||
public interface IHardwareCursor : IDisposable { }
|
||||
|
||||
public enum BlendMode : byte
|
||||
{
|
||||
None,
|
||||
Alpha,
|
||||
Additive,
|
||||
Subtractive,
|
||||
Multiply,
|
||||
Multiplicative,
|
||||
DoubleMultiplicative
|
||||
}
|
||||
|
||||
public interface IGraphicsDevice : IDisposable
|
||||
{
|
||||
IVertexBuffer<Vertex> CreateVertexBuffer(int length);
|
||||
ITexture CreateTexture(Bitmap bitmap);
|
||||
ITexture CreateTexture();
|
||||
IFrameBuffer CreateFrameBuffer(Size s);
|
||||
IShader CreateShader(string name);
|
||||
|
||||
Size WindowSize { get; }
|
||||
|
||||
void Clear();
|
||||
void Present();
|
||||
Bitmap TakeScreenshot();
|
||||
void PumpInput(IInputHandler inputHandler);
|
||||
string GetClipboardText();
|
||||
bool SetClipboardText(string text);
|
||||
void DrawPrimitives(PrimitiveType type, int firstVertex, int numVertices);
|
||||
|
||||
void EnableScissor(int left, int top, int width, int height);
|
||||
void DisableScissor();
|
||||
|
||||
void EnableDepthBuffer();
|
||||
void DisableDepthBuffer();
|
||||
|
||||
void SetBlendMode(BlendMode mode);
|
||||
|
||||
void GrabWindowMouseFocus();
|
||||
void ReleaseWindowMouseFocus();
|
||||
|
||||
IHardwareCursor CreateHardwareCursor(string name, Size size, byte[] data, int2 hotspot);
|
||||
void SetHardwareCursor(IHardwareCursor cursor);
|
||||
|
||||
string GLVersion { get; }
|
||||
}
|
||||
|
||||
public interface IVertexBuffer<T> : IDisposable
|
||||
{
|
||||
void Bind();
|
||||
void SetData(T[] vertices, int length);
|
||||
void SetData(T[] vertices, int start, int length);
|
||||
void SetData(IntPtr data, int start, int length);
|
||||
}
|
||||
|
||||
public interface IShader
|
||||
{
|
||||
void SetBool(string name, bool value);
|
||||
void SetVec(string name, float x);
|
||||
void SetVec(string name, float x, float y);
|
||||
void SetVec(string name, float[] vec, int length);
|
||||
void SetTexture(string param, ITexture texture);
|
||||
void SetMatrix(string param, float[] mtx);
|
||||
void Render(Action a);
|
||||
}
|
||||
|
||||
public enum TextureScaleFilter { Nearest, Linear }
|
||||
|
||||
public interface ITexture : IDisposable
|
||||
{
|
||||
void SetData(Bitmap bitmap);
|
||||
void SetData(uint[,] colors);
|
||||
void SetData(byte[] colors, int width, int height);
|
||||
byte[] GetData();
|
||||
Size Size { get; }
|
||||
TextureScaleFilter ScaleFilter { get; set; }
|
||||
}
|
||||
|
||||
public interface IFrameBuffer : IDisposable
|
||||
{
|
||||
void Bind();
|
||||
void Unbind();
|
||||
ITexture Texture { get; }
|
||||
}
|
||||
|
||||
public enum PrimitiveType
|
||||
{
|
||||
PointList,
|
||||
LineList,
|
||||
TriangleList,
|
||||
}
|
||||
|
||||
public struct Range<T>
|
||||
{
|
||||
public readonly T Start, End;
|
||||
public Range(T start, T end) { Start = start; End = end; }
|
||||
}
|
||||
|
||||
public enum WindowMode
|
||||
{
|
||||
Windowed,
|
||||
Fullscreen,
|
||||
PseudoFullscreen,
|
||||
}
|
||||
}
|
||||
44
OpenRA.Game/Graphics/MappedImage.cs
Normal file
44
OpenRA.Game/Graphics/MappedImage.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
{
|
||||
class MappedImage
|
||||
{
|
||||
readonly Rectangle rect = Rectangle.Empty;
|
||||
public readonly string Src;
|
||||
|
||||
public MappedImage(string defaultSrc, MiniYaml info)
|
||||
{
|
||||
FieldLoader.LoadField(this, "rect", info.Value);
|
||||
FieldLoader.Load(this, info);
|
||||
if (Src == null)
|
||||
Src = defaultSrc;
|
||||
}
|
||||
|
||||
public Sprite GetImage(Sheet s)
|
||||
{
|
||||
return new Sprite(s, rect, TextureChannel.Alpha);
|
||||
}
|
||||
|
||||
public MiniYaml Save(string defaultSrc)
|
||||
{
|
||||
var root = new List<MiniYamlNode>();
|
||||
if (defaultSrc != Src)
|
||||
root.Add(new MiniYamlNode("Src", Src));
|
||||
|
||||
return new MiniYaml(FieldSaver.FormatValue(this, GetType().GetField("rect")), root);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using OpenRA.FileSystem;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
{
|
||||
public interface IModel
|
||||
{
|
||||
uint Frames { get; }
|
||||
uint Sections { get; }
|
||||
|
||||
float[] TransformationMatrix(uint section, uint frame);
|
||||
float[] Size { get; }
|
||||
float[] Bounds(uint frame);
|
||||
ModelRenderData RenderData(uint section);
|
||||
|
||||
/// <summary>Returns the smallest rectangle that covers all rotations of all frames in a model</summary>
|
||||
Rectangle AggregateBounds { get; }
|
||||
}
|
||||
|
||||
public struct ModelRenderData
|
||||
{
|
||||
public readonly int Start;
|
||||
public readonly int Count;
|
||||
public readonly Sheet Sheet;
|
||||
|
||||
public ModelRenderData(int start, int count, Sheet sheet)
|
||||
{
|
||||
Start = start;
|
||||
Count = count;
|
||||
Sheet = sheet;
|
||||
}
|
||||
}
|
||||
|
||||
public interface IModelCache : IDisposable
|
||||
{
|
||||
IModel GetModelSequence(string model, string sequence);
|
||||
bool HasModelSequence(string model, string sequence);
|
||||
IVertexBuffer<Vertex> VertexBuffer { get; }
|
||||
}
|
||||
|
||||
public interface IModelSequenceLoader
|
||||
{
|
||||
Action<string> OnMissingModelError { get; set; }
|
||||
IModelCache CacheModels(IReadOnlyFileSystem fileSystem, ModData modData, IReadOnlyDictionary<string, MiniYamlNode> modelDefinitions);
|
||||
}
|
||||
|
||||
public class PlaceholderModelSequenceLoader : IModelSequenceLoader
|
||||
{
|
||||
public Action<string> OnMissingModelError { get; set; }
|
||||
|
||||
class PlaceholderModelCache : IModelCache
|
||||
{
|
||||
public IVertexBuffer<Vertex> VertexBuffer { get { throw new NotImplementedException(); } }
|
||||
|
||||
public void Dispose() { }
|
||||
|
||||
public IModel GetModelSequence(string model, string sequence)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public bool HasModelSequence(string model, string sequence)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
public PlaceholderModelSequenceLoader(ModData modData) { }
|
||||
|
||||
public IModelCache CacheModels(IReadOnlyFileSystem fileSystem, ModData modData, IReadOnlyDictionary<string, MiniYamlNode> modelDefinitions)
|
||||
{
|
||||
return new PlaceholderModelCache();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
{
|
||||
public struct ModelAnimation
|
||||
{
|
||||
public readonly IModel Model;
|
||||
public readonly Func<WVec> OffsetFunc;
|
||||
public readonly Func<WRot> RotationFunc;
|
||||
public readonly Func<bool> DisableFunc;
|
||||
public readonly Func<uint> FrameFunc;
|
||||
public readonly bool ShowShadow;
|
||||
|
||||
public ModelAnimation(IModel model, Func<WVec> offset, Func<WRot> rotation, Func<bool> disable, Func<uint> frame, bool showshadow)
|
||||
{
|
||||
Model = model;
|
||||
OffsetFunc = offset;
|
||||
RotationFunc = rotation;
|
||||
DisableFunc = disable;
|
||||
FrameFunc = frame;
|
||||
ShowShadow = showshadow;
|
||||
}
|
||||
|
||||
public Rectangle ScreenBounds(WPos pos, WorldRenderer wr, float scale)
|
||||
{
|
||||
var r = Model.AggregateBounds;
|
||||
var offset = OffsetFunc != null ? OffsetFunc() : WVec.Zero;
|
||||
var xy = wr.ScreenPxPosition(pos) + wr.ScreenPxOffset(offset);
|
||||
|
||||
return Rectangle.FromLTRB(
|
||||
xy.X + (int)(r.Left * scale),
|
||||
xy.Y + (int)(r.Top * scale),
|
||||
xy.X + (int)(r.Right * scale),
|
||||
xy.Y + (int)(r.Bottom * scale));
|
||||
}
|
||||
|
||||
public bool IsVisible
|
||||
{
|
||||
get
|
||||
{
|
||||
return DisableFunc == null || !DisableFunc();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,373 +0,0 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
{
|
||||
public class ModelRenderProxy
|
||||
{
|
||||
public readonly Sprite Sprite;
|
||||
public readonly Sprite ShadowSprite;
|
||||
public readonly float ShadowDirection;
|
||||
public readonly float3[] ProjectedShadowBounds;
|
||||
|
||||
public ModelRenderProxy(Sprite sprite, Sprite shadowSprite, float3[] projectedShadowBounds, float shadowDirection)
|
||||
{
|
||||
Sprite = sprite;
|
||||
ShadowSprite = shadowSprite;
|
||||
ProjectedShadowBounds = projectedShadowBounds;
|
||||
ShadowDirection = shadowDirection;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class ModelRenderer : IDisposable
|
||||
{
|
||||
// Static constants
|
||||
static readonly float[] ShadowDiffuse = new float[] { 0, 0, 0 };
|
||||
static readonly float[] ShadowAmbient = new float[] { 1, 1, 1 };
|
||||
static readonly float2 SpritePadding = new float2(2, 2);
|
||||
static readonly float[] ZeroVector = new float[] { 0, 0, 0, 1 };
|
||||
static readonly float[] ZVector = new float[] { 0, 0, 1, 1 };
|
||||
static readonly float[] FlipMtx = Util.ScaleMatrix(1, -1, 1);
|
||||
static readonly float[] ShadowScaleFlipMtx = Util.ScaleMatrix(2, -2, 2);
|
||||
|
||||
readonly Renderer renderer;
|
||||
readonly IShader shader;
|
||||
|
||||
readonly Dictionary<Sheet, IFrameBuffer> mappedBuffers = new Dictionary<Sheet, IFrameBuffer>();
|
||||
readonly Stack<KeyValuePair<Sheet, IFrameBuffer>> unmappedBuffers = new Stack<KeyValuePair<Sheet, IFrameBuffer>>();
|
||||
readonly List<(Sheet Sheet, Action Func)> doRender = new List<(Sheet, Action)>();
|
||||
|
||||
SheetBuilder sheetBuilderForFrame;
|
||||
bool isInFrame;
|
||||
|
||||
public ModelRenderer(Renderer renderer, IShader shader)
|
||||
{
|
||||
this.renderer = renderer;
|
||||
this.shader = shader;
|
||||
}
|
||||
|
||||
public void SetPalette(ITexture palette)
|
||||
{
|
||||
shader.SetTexture("Palette", palette);
|
||||
}
|
||||
|
||||
public void SetViewportParams(Size screen, int2 scroll)
|
||||
{
|
||||
var a = 2f / renderer.SheetSize;
|
||||
var view = new[]
|
||||
{
|
||||
a, 0, 0, 0,
|
||||
0, -a, 0, 0,
|
||||
0, 0, -2 * a, 0,
|
||||
-1, 1, 0, 1
|
||||
};
|
||||
|
||||
shader.SetMatrix("View", view);
|
||||
}
|
||||
|
||||
public ModelRenderProxy RenderAsync(
|
||||
WorldRenderer wr, IEnumerable<ModelAnimation> models, in WRot camera, float scale,
|
||||
float[] groundNormal, in WRot lightSource, float[] lightAmbientColor, float[] lightDiffuseColor,
|
||||
PaletteReference color, PaletteReference normals, PaletteReference shadowPalette)
|
||||
{
|
||||
if (!isInFrame)
|
||||
throw new InvalidOperationException("BeginFrame has not been called. You cannot render until a frame has been started.");
|
||||
|
||||
// Correct for inverted y-axis
|
||||
var scaleTransform = Util.ScaleMatrix(scale, scale, scale);
|
||||
|
||||
// Correct for bogus light source definition
|
||||
var lightYaw = Util.MakeFloatMatrix(new WRot(WAngle.Zero, WAngle.Zero, -lightSource.Yaw).AsMatrix());
|
||||
var lightPitch = Util.MakeFloatMatrix(new WRot(WAngle.Zero, -lightSource.Pitch, WAngle.Zero).AsMatrix());
|
||||
var shadowTransform = Util.MatrixMultiply(lightPitch, lightYaw);
|
||||
|
||||
var invShadowTransform = Util.MatrixInverse(shadowTransform);
|
||||
var cameraTransform = Util.MakeFloatMatrix(camera.AsMatrix());
|
||||
var invCameraTransform = Util.MatrixInverse(cameraTransform);
|
||||
if (invCameraTransform == null)
|
||||
throw new InvalidOperationException("Failed to invert the cameraTransform matrix during RenderAsync.");
|
||||
|
||||
// Sprite rectangle
|
||||
var tl = new float2(float.MaxValue, float.MaxValue);
|
||||
var br = new float2(float.MinValue, float.MinValue);
|
||||
|
||||
// Shadow sprite rectangle
|
||||
var stl = new float2(float.MaxValue, float.MaxValue);
|
||||
var sbr = new float2(float.MinValue, float.MinValue);
|
||||
|
||||
foreach (var m in models)
|
||||
{
|
||||
// Convert screen offset back to world coords
|
||||
var offsetVec = Util.MatrixVectorMultiply(invCameraTransform, wr.ScreenVector(m.OffsetFunc()));
|
||||
var offsetTransform = Util.TranslationMatrix(offsetVec[0], offsetVec[1], offsetVec[2]);
|
||||
|
||||
var worldTransform = Util.MakeFloatMatrix(m.RotationFunc().AsMatrix());
|
||||
worldTransform = Util.MatrixMultiply(scaleTransform, worldTransform);
|
||||
worldTransform = Util.MatrixMultiply(offsetTransform, worldTransform);
|
||||
|
||||
var bounds = m.Model.Bounds(m.FrameFunc());
|
||||
var worldBounds = Util.MatrixAABBMultiply(worldTransform, bounds);
|
||||
var screenBounds = Util.MatrixAABBMultiply(cameraTransform, worldBounds);
|
||||
var shadowBounds = Util.MatrixAABBMultiply(shadowTransform, worldBounds);
|
||||
|
||||
// Aggregate bounds rects
|
||||
tl = float2.Min(tl, new float2(screenBounds[0], screenBounds[1]));
|
||||
br = float2.Max(br, new float2(screenBounds[3], screenBounds[4]));
|
||||
stl = float2.Min(stl, new float2(shadowBounds[0], shadowBounds[1]));
|
||||
sbr = float2.Max(sbr, new float2(shadowBounds[3], shadowBounds[4]));
|
||||
}
|
||||
|
||||
// Inflate rects to ensure rendering is within bounds
|
||||
tl -= SpritePadding;
|
||||
br += SpritePadding;
|
||||
stl -= SpritePadding;
|
||||
sbr += SpritePadding;
|
||||
|
||||
// Corners of the shadow quad, in shadow-space
|
||||
var corners = new float[][]
|
||||
{
|
||||
new[] { stl.X, stl.Y, 0, 1 },
|
||||
new[] { sbr.X, sbr.Y, 0, 1 },
|
||||
new[] { sbr.X, stl.Y, 0, 1 },
|
||||
new[] { stl.X, sbr.Y, 0, 1 }
|
||||
};
|
||||
|
||||
var shadowScreenTransform = Util.MatrixMultiply(cameraTransform, invShadowTransform);
|
||||
var shadowGroundNormal = Util.MatrixVectorMultiply(shadowTransform, groundNormal);
|
||||
var screenCorners = new float3[4];
|
||||
for (var j = 0; j < 4; j++)
|
||||
{
|
||||
// Project to ground plane
|
||||
corners[j][2] = -(corners[j][1] * shadowGroundNormal[1] / shadowGroundNormal[2] +
|
||||
corners[j][0] * shadowGroundNormal[0] / shadowGroundNormal[2]);
|
||||
|
||||
// Rotate to camera-space
|
||||
corners[j] = Util.MatrixVectorMultiply(shadowScreenTransform, corners[j]);
|
||||
screenCorners[j] = new float3(corners[j][0], corners[j][1], 0);
|
||||
}
|
||||
|
||||
// Shadows are rendered at twice the resolution to reduce artifacts
|
||||
CalculateSpriteGeometry(tl, br, 1, out var spriteSize, out var spriteOffset);
|
||||
CalculateSpriteGeometry(stl, sbr, 2, out var shadowSpriteSize, out var shadowSpriteOffset);
|
||||
|
||||
if (sheetBuilderForFrame == null)
|
||||
sheetBuilderForFrame = new SheetBuilder(SheetType.BGRA, AllocateSheet);
|
||||
|
||||
var sprite = sheetBuilderForFrame.Allocate(spriteSize, 0, spriteOffset);
|
||||
var shadowSprite = sheetBuilderForFrame.Allocate(shadowSpriteSize, 0, shadowSpriteOffset);
|
||||
var sb = sprite.Bounds;
|
||||
var ssb = shadowSprite.Bounds;
|
||||
var spriteCenter = new float2(sb.Left + sb.Width / 2, sb.Top + sb.Height / 2);
|
||||
var shadowCenter = new float2(ssb.Left + ssb.Width / 2, ssb.Top + ssb.Height / 2);
|
||||
|
||||
var translateMtx = Util.TranslationMatrix(spriteCenter.X - spriteOffset.X, renderer.SheetSize - (spriteCenter.Y - spriteOffset.Y), 0);
|
||||
var shadowTranslateMtx = Util.TranslationMatrix(shadowCenter.X - shadowSpriteOffset.X, renderer.SheetSize - (shadowCenter.Y - shadowSpriteOffset.Y), 0);
|
||||
var correctionTransform = Util.MatrixMultiply(translateMtx, FlipMtx);
|
||||
var shadowCorrectionTransform = Util.MatrixMultiply(shadowTranslateMtx, ShadowScaleFlipMtx);
|
||||
|
||||
doRender.Add((sprite.Sheet, () =>
|
||||
{
|
||||
foreach (var m in models)
|
||||
{
|
||||
// Convert screen offset to world offset
|
||||
var offsetVec = Util.MatrixVectorMultiply(invCameraTransform, wr.ScreenVector(m.OffsetFunc()));
|
||||
var offsetTransform = Util.TranslationMatrix(offsetVec[0], offsetVec[1], offsetVec[2]);
|
||||
|
||||
var rotations = Util.MakeFloatMatrix(m.RotationFunc().AsMatrix());
|
||||
var worldTransform = Util.MatrixMultiply(scaleTransform, rotations);
|
||||
worldTransform = Util.MatrixMultiply(offsetTransform, worldTransform);
|
||||
|
||||
var transform = Util.MatrixMultiply(cameraTransform, worldTransform);
|
||||
transform = Util.MatrixMultiply(correctionTransform, transform);
|
||||
|
||||
var shadow = Util.MatrixMultiply(shadowTransform, worldTransform);
|
||||
shadow = Util.MatrixMultiply(shadowCorrectionTransform, shadow);
|
||||
|
||||
var lightTransform = Util.MatrixMultiply(Util.MatrixInverse(rotations), invShadowTransform);
|
||||
|
||||
var frame = m.FrameFunc();
|
||||
for (uint i = 0; i < m.Model.Sections; i++)
|
||||
{
|
||||
var rd = m.Model.RenderData(i);
|
||||
var t = m.Model.TransformationMatrix(i, frame);
|
||||
var it = Util.MatrixInverse(t);
|
||||
if (it == null)
|
||||
throw new InvalidOperationException("Failed to invert the transformed matrix of frame {0} during RenderAsync.".F(i));
|
||||
|
||||
// Transform light vector from shadow -> world -> limb coords
|
||||
var lightDirection = ExtractRotationVector(Util.MatrixMultiply(it, lightTransform));
|
||||
|
||||
Render(rd, wr.World.ModelCache, Util.MatrixMultiply(transform, t), lightDirection,
|
||||
lightAmbientColor, lightDiffuseColor, color.TextureMidIndex, normals.TextureMidIndex);
|
||||
|
||||
// Disable shadow normals by forcing zero diffuse and identity ambient light
|
||||
if (m.ShowShadow)
|
||||
Render(rd, wr.World.ModelCache, Util.MatrixMultiply(shadow, t), lightDirection,
|
||||
ShadowAmbient, ShadowDiffuse, shadowPalette.TextureMidIndex, normals.TextureMidIndex);
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
var screenLightVector = Util.MatrixVectorMultiply(invShadowTransform, ZVector);
|
||||
screenLightVector = Util.MatrixVectorMultiply(cameraTransform, screenLightVector);
|
||||
return new ModelRenderProxy(sprite, shadowSprite, screenCorners, -screenLightVector[2] / screenLightVector[1]);
|
||||
}
|
||||
|
||||
static void CalculateSpriteGeometry(float2 tl, float2 br, float scale, out Size size, out int2 offset)
|
||||
{
|
||||
var width = (int)(scale * (br.X - tl.X));
|
||||
var height = (int)(scale * (br.Y - tl.Y));
|
||||
offset = (0.5f * scale * (br + tl)).ToInt2();
|
||||
|
||||
// Width and height must be even to avoid rendering glitches
|
||||
if ((width & 1) == 1)
|
||||
width += 1;
|
||||
if ((height & 1) == 1)
|
||||
height += 1;
|
||||
|
||||
size = new Size(width, height);
|
||||
}
|
||||
|
||||
static float[] ExtractRotationVector(float[] mtx)
|
||||
{
|
||||
var tVec = Util.MatrixVectorMultiply(mtx, ZVector);
|
||||
var tOrigin = Util.MatrixVectorMultiply(mtx, ZeroVector);
|
||||
tVec[0] -= tOrigin[0] * tVec[3] / tOrigin[3];
|
||||
tVec[1] -= tOrigin[1] * tVec[3] / tOrigin[3];
|
||||
tVec[2] -= tOrigin[2] * tVec[3] / tOrigin[3];
|
||||
|
||||
// Renormalize
|
||||
var w = (float)Math.Sqrt(tVec[0] * tVec[0] + tVec[1] * tVec[1] + tVec[2] * tVec[2]);
|
||||
tVec[0] /= w;
|
||||
tVec[1] /= w;
|
||||
tVec[2] /= w;
|
||||
tVec[3] = 1f;
|
||||
|
||||
return tVec;
|
||||
}
|
||||
|
||||
void Render(
|
||||
ModelRenderData renderData,
|
||||
IModelCache cache,
|
||||
float[] t, float[] lightDirection,
|
||||
float[] ambientLight, float[] diffuseLight,
|
||||
float colorPaletteTextureMidIndex, float normalsPaletteTextureMidIndex)
|
||||
{
|
||||
shader.SetTexture("DiffuseTexture", renderData.Sheet.GetTexture());
|
||||
shader.SetVec("PaletteRows", colorPaletteTextureMidIndex, normalsPaletteTextureMidIndex);
|
||||
shader.SetMatrix("TransformMatrix", t);
|
||||
shader.SetVec("LightDirection", lightDirection, 4);
|
||||
shader.SetVec("AmbientLight", ambientLight, 3);
|
||||
shader.SetVec("DiffuseLight", diffuseLight, 3);
|
||||
|
||||
shader.PrepareRender();
|
||||
renderer.DrawBatch(cache.VertexBuffer, renderData.Start, renderData.Count, PrimitiveType.TriangleList);
|
||||
}
|
||||
|
||||
public void BeginFrame()
|
||||
{
|
||||
if (isInFrame)
|
||||
throw new InvalidOperationException("BeginFrame has already been called. A new frame cannot be started until EndFrame has been called.");
|
||||
|
||||
isInFrame = true;
|
||||
|
||||
foreach (var kv in mappedBuffers)
|
||||
unmappedBuffers.Push(kv);
|
||||
mappedBuffers.Clear();
|
||||
}
|
||||
|
||||
IFrameBuffer EnableFrameBuffer(Sheet s)
|
||||
{
|
||||
var fbo = mappedBuffers[s];
|
||||
Game.Renderer.Flush();
|
||||
fbo.Bind();
|
||||
|
||||
Game.Renderer.Context.EnableDepthBuffer();
|
||||
return fbo;
|
||||
}
|
||||
|
||||
void DisableFrameBuffer(IFrameBuffer fbo)
|
||||
{
|
||||
Game.Renderer.Flush();
|
||||
Game.Renderer.Context.DisableDepthBuffer();
|
||||
fbo.Unbind();
|
||||
}
|
||||
|
||||
public void EndFrame()
|
||||
{
|
||||
if (!isInFrame)
|
||||
throw new InvalidOperationException("BeginFrame has not been called. There is no frame to end.");
|
||||
|
||||
isInFrame = false;
|
||||
sheetBuilderForFrame = null;
|
||||
|
||||
if (doRender.Count == 0)
|
||||
return;
|
||||
|
||||
Sheet currentSheet = null;
|
||||
IFrameBuffer fbo = null;
|
||||
foreach (var v in doRender)
|
||||
{
|
||||
// Change sheet
|
||||
if (v.Sheet != currentSheet)
|
||||
{
|
||||
if (fbo != null)
|
||||
DisableFrameBuffer(fbo);
|
||||
|
||||
currentSheet = v.Sheet;
|
||||
fbo = EnableFrameBuffer(currentSheet);
|
||||
}
|
||||
|
||||
v.Func();
|
||||
}
|
||||
|
||||
if (fbo != null)
|
||||
DisableFrameBuffer(fbo);
|
||||
|
||||
doRender.Clear();
|
||||
}
|
||||
|
||||
public Sheet AllocateSheet()
|
||||
{
|
||||
// Reuse cached fbo
|
||||
if (unmappedBuffers.Count > 0)
|
||||
{
|
||||
var kv = unmappedBuffers.Pop();
|
||||
mappedBuffers.Add(kv.Key, kv.Value);
|
||||
return kv.Key;
|
||||
}
|
||||
|
||||
var size = new Size(renderer.SheetSize, renderer.SheetSize);
|
||||
var framebuffer = renderer.Context.CreateFrameBuffer(size);
|
||||
var sheet = new Sheet(SheetType.BGRA, framebuffer.Texture);
|
||||
mappedBuffers.Add(sheet, framebuffer);
|
||||
|
||||
return sheet;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (var kvp in mappedBuffers.Concat(unmappedBuffers))
|
||||
{
|
||||
kvp.Key.Dispose();
|
||||
kvp.Value.Dispose();
|
||||
}
|
||||
|
||||
mappedBuffers.Clear();
|
||||
unmappedBuffers.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
@@ -11,8 +11,11 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Imaging;
|
||||
using System.IO;
|
||||
using OpenRA.Primitives;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
{
|
||||
@@ -33,6 +36,35 @@ namespace OpenRA.Graphics
|
||||
return Color.FromArgb((int)palette[index]);
|
||||
}
|
||||
|
||||
public static ColorPalette AsSystemPalette(this IPalette palette)
|
||||
{
|
||||
ColorPalette pal;
|
||||
using (var b = new Bitmap(1, 1, PixelFormat.Format8bppIndexed))
|
||||
pal = b.Palette;
|
||||
|
||||
for (var i = 0; i < Size; i++)
|
||||
pal.Entries[i] = palette.GetColor(i);
|
||||
|
||||
// hack around a mono bug -- the palette flags get set wrong.
|
||||
if (Platform.CurrentPlatform != PlatformType.Windows)
|
||||
typeof(ColorPalette).GetField("flags",
|
||||
BindingFlags.Instance | BindingFlags.NonPublic).SetValue(pal, 1);
|
||||
|
||||
return pal;
|
||||
}
|
||||
|
||||
public static Bitmap AsBitmap(this IPalette palette)
|
||||
{
|
||||
var b = new Bitmap(Size, 1, PixelFormat.Format32bppArgb);
|
||||
var data = b.LockBits(new Rectangle(0, 0, b.Width, b.Height),
|
||||
ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
|
||||
var temp = new uint[Size];
|
||||
palette.CopyToArray(temp, 0);
|
||||
Marshal.Copy((int[])(object)temp, 0, data.Scan0, Size);
|
||||
b.UnlockBits(data);
|
||||
return b;
|
||||
}
|
||||
|
||||
public static IPalette AsReadOnly(this IPalette palette)
|
||||
{
|
||||
if (palette is ImmutablePalette)
|
||||
@@ -85,12 +117,6 @@ namespace OpenRA.Graphics
|
||||
var r = (byte)(reader.ReadByte() << 2);
|
||||
var g = (byte)(reader.ReadByte() << 2);
|
||||
var b = (byte)(reader.ReadByte() << 2);
|
||||
|
||||
// Replicate high bits into the (currently zero) low bits.
|
||||
r |= (byte)(r >> 6);
|
||||
g |= (byte)(g >> 6);
|
||||
b |= (byte)(b >> 6);
|
||||
|
||||
colors[i] = (uint)((255 << 24) | (r << 16) | (g << 8) | b);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
|
||||
@@ -1,173 +0,0 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA
|
||||
{
|
||||
public enum GLProfile
|
||||
{
|
||||
Automatic,
|
||||
ANGLE,
|
||||
Modern,
|
||||
Embedded,
|
||||
Legacy
|
||||
}
|
||||
|
||||
public interface IPlatform
|
||||
{
|
||||
IPlatformWindow CreateWindow(Size size, WindowMode windowMode, float scaleModifier, int batchSize, int videoDisplay, GLProfile profile, bool enableLegacyGL);
|
||||
ISoundEngine CreateSound(string device);
|
||||
IFont CreateFont(byte[] data);
|
||||
}
|
||||
|
||||
public interface IHardwareCursor : IDisposable { }
|
||||
|
||||
public enum BlendMode : byte
|
||||
{
|
||||
None,
|
||||
Alpha,
|
||||
Additive,
|
||||
Subtractive,
|
||||
Multiply,
|
||||
Multiplicative,
|
||||
DoubleMultiplicative,
|
||||
LowAdditive,
|
||||
Screen,
|
||||
Translucent
|
||||
}
|
||||
|
||||
public interface IPlatformWindow : IDisposable
|
||||
{
|
||||
IGraphicsContext Context { get; }
|
||||
|
||||
Size NativeWindowSize { get; }
|
||||
Size EffectiveWindowSize { get; }
|
||||
float NativeWindowScale { get; }
|
||||
float EffectiveWindowScale { get; }
|
||||
Size SurfaceSize { get; }
|
||||
int DisplayCount { get; }
|
||||
int CurrentDisplay { get; }
|
||||
bool HasInputFocus { get; }
|
||||
|
||||
event Action<float, float, float, float> OnWindowScaleChanged;
|
||||
|
||||
void PumpInput(IInputHandler inputHandler);
|
||||
string GetClipboardText();
|
||||
bool SetClipboardText(string text);
|
||||
|
||||
void GrabWindowMouseFocus();
|
||||
void ReleaseWindowMouseFocus();
|
||||
|
||||
IHardwareCursor CreateHardwareCursor(string name, Size size, byte[] data, int2 hotspot, bool pixelDouble);
|
||||
void SetHardwareCursor(IHardwareCursor cursor);
|
||||
void SetRelativeMouseMode(bool mode);
|
||||
void SetScaleModifier(float scale);
|
||||
|
||||
GLProfile GLProfile { get; }
|
||||
|
||||
GLProfile[] SupportedGLProfiles { get; }
|
||||
}
|
||||
|
||||
public interface IGraphicsContext : IDisposable
|
||||
{
|
||||
IVertexBuffer<Vertex> CreateVertexBuffer(int size);
|
||||
ITexture CreateTexture();
|
||||
IFrameBuffer CreateFrameBuffer(Size s);
|
||||
IFrameBuffer CreateFrameBuffer(Size s, Color clearColor);
|
||||
IShader CreateShader(string name);
|
||||
void EnableScissor(int x, int y, int width, int height);
|
||||
void DisableScissor();
|
||||
void Present();
|
||||
void DrawPrimitives(PrimitiveType pt, int firstVertex, int numVertices);
|
||||
void Clear();
|
||||
void EnableDepthBuffer();
|
||||
void DisableDepthBuffer();
|
||||
void ClearDepthBuffer();
|
||||
void SetBlendMode(BlendMode mode);
|
||||
void SetVSyncEnabled(bool enabled);
|
||||
string GLVersion { get; }
|
||||
}
|
||||
|
||||
public interface IVertexBuffer<T> : IDisposable
|
||||
{
|
||||
void Bind();
|
||||
void SetData(T[] vertices, int length);
|
||||
void SetData(T[] vertices, int offset, int start, int length);
|
||||
}
|
||||
|
||||
public interface IShader
|
||||
{
|
||||
void SetBool(string name, bool value);
|
||||
void SetVec(string name, float x);
|
||||
void SetVec(string name, float x, float y);
|
||||
void SetVec(string name, float x, float y, float z);
|
||||
void SetVec(string name, float[] vec, int length);
|
||||
void SetTexture(string param, ITexture texture);
|
||||
void SetMatrix(string param, float[] mtx);
|
||||
void PrepareRender();
|
||||
}
|
||||
|
||||
public enum TextureScaleFilter { Nearest, Linear }
|
||||
|
||||
public interface ITexture : IDisposable
|
||||
{
|
||||
void SetData(uint[,] colors);
|
||||
void SetData(byte[] colors, int width, int height);
|
||||
byte[] GetData();
|
||||
Size Size { get; }
|
||||
TextureScaleFilter ScaleFilter { get; set; }
|
||||
}
|
||||
|
||||
public interface IFrameBuffer : IDisposable
|
||||
{
|
||||
void Bind();
|
||||
void Unbind();
|
||||
void EnableScissor(Rectangle rect);
|
||||
void DisableScissor();
|
||||
ITexture Texture { get; }
|
||||
}
|
||||
|
||||
public enum PrimitiveType
|
||||
{
|
||||
PointList,
|
||||
LineList,
|
||||
TriangleList,
|
||||
}
|
||||
|
||||
public struct Range<T>
|
||||
{
|
||||
public readonly T Start, End;
|
||||
public Range(T start, T end) { Start = start; End = end; }
|
||||
}
|
||||
|
||||
public enum WindowMode
|
||||
{
|
||||
Windowed,
|
||||
Fullscreen,
|
||||
PseudoFullscreen,
|
||||
}
|
||||
|
||||
public interface IFont : IDisposable
|
||||
{
|
||||
FontGlyph CreateGlyph(char c, int size, float deviceScale);
|
||||
}
|
||||
|
||||
public struct FontGlyph
|
||||
{
|
||||
public int2 Offset;
|
||||
public Size Size;
|
||||
public float Advance;
|
||||
public byte[] Data;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user