Compare commits
71 Commits
devtest-20
...
playtest-2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bd6bcda6d0 | ||
|
|
f12deca9ca | ||
|
|
5ea82b4e5e | ||
|
|
2d210a97df | ||
|
|
f03a100115 | ||
|
|
1e30684782 | ||
|
|
abb45197b1 | ||
|
|
a7935f6166 | ||
|
|
7b75a9c2f7 | ||
|
|
9b694f6044 | ||
|
|
0a75019aa0 | ||
|
|
d3e41b6d50 | ||
|
|
18f3e1f57b | ||
|
|
1760f8acd6 | ||
|
|
4154553318 | ||
|
|
e5e8e64ace | ||
|
|
0ae58c10ad | ||
|
|
89ea810cbe | ||
|
|
f6cf35c99c | ||
|
|
5fbaeabb6d | ||
|
|
581907f53a | ||
|
|
7cf4be9d78 | ||
|
|
bdf9f5e201 | ||
|
|
b26b1f410f | ||
|
|
ce69abe1f2 | ||
|
|
b3d2b36875 | ||
|
|
f28b37323f | ||
|
|
7c82ab60e8 | ||
|
|
d14fbd0f84 | ||
|
|
c567f5cbdd | ||
|
|
51a9831fc2 | ||
|
|
4d5777cbc9 | ||
|
|
a6a835d95f | ||
|
|
e16be14ec7 | ||
|
|
11939d3af1 | ||
|
|
feb133c2eb | ||
|
|
f6361b031b | ||
|
|
36967951e6 | ||
|
|
012a9d71ba | ||
|
|
cff369850c | ||
|
|
136b4c0fa9 | ||
|
|
aae2459d0b | ||
|
|
d47ba1d135 | ||
|
|
133cffff24 | ||
|
|
52f6c993d5 | ||
|
|
21eecb048d | ||
|
|
b97398a671 | ||
|
|
a285c908db | ||
|
|
35b39162ff | ||
|
|
0b5b6adc7c | ||
|
|
5357eb6f29 | ||
|
|
e274ac5ff8 | ||
|
|
f8e39bd142 | ||
|
|
48881f4e73 | ||
|
|
d2eb56183d | ||
|
|
afd75c7f55 | ||
|
|
6d1551c9fd | ||
|
|
a015b0990b | ||
|
|
6dd08bbc98 | ||
|
|
343616e924 | ||
|
|
91242a832f | ||
|
|
1c53bfed11 | ||
|
|
e5e7d037a3 | ||
|
|
2a301392c6 | ||
|
|
e35bb8ea95 | ||
|
|
7a4002ff08 | ||
|
|
523fe20255 | ||
|
|
31e0c8b2e3 | ||
|
|
7c37d1cfcc | ||
|
|
8e76a109bd | ||
|
|
ba9b18ccb7 |
@@ -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).
|
||||
55
.github/workflows/ci.yaml
vendored
55
.github/workflows/ci.yaml
vendored
@@ -1,55 +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
|
||||
|
||||
- name: Check Mods
|
||||
run: |
|
||||
sudo apt-get install lua5.1
|
||||
make check-scripts
|
||||
make test
|
||||
|
||||
windows:
|
||||
name: Windows (Net 5.0)
|
||||
runs-on: windows-2019
|
||||
|
||||
steps:
|
||||
- name: Clone Repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install .NET 5
|
||||
uses: actions/setup-dotnet@v1
|
||||
with:
|
||||
dotnet-version: '5.0.x'
|
||||
|
||||
- name: Check Code
|
||||
shell: powershell
|
||||
run: |
|
||||
# Work around runtime failures on the GH Actions runner
|
||||
dotnet nuget locals all --clear
|
||||
.\make.ps1 check
|
||||
dotnet build OpenRA.Test\OpenRA.Test.csproj -c Debug --nologo -p:TargetPlatform=win-x64
|
||||
dotnet test bin\OpenRA.Test.dll --test-adapter-path:.
|
||||
|
||||
- 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
|
||||
|
||||
88
.github/workflows/itch.yml
vendored
88
.github/workflows/itch.yml
vendored
@@ -1,88 +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
|
||||
env:
|
||||
GIT_TAG: ${{ github.event.inputs.tag }}
|
||||
run: |
|
||||
wget -q "https://github.com/${{ github.repository }}/releases/download/${GIT_TAG}/OpenRA-${GIT_TAG}-x64.exe"
|
||||
wget -q "https://github.com/${{ github.repository }}/releases/download/${GIT_TAG}/OpenRA-${GIT_TAG}-x64-winportable.zip" -O "OpenRA-${GIT_TAG}-x64-win-itch.zip"
|
||||
wget -q "https://github.com${{ github.repository }}/releases/download/${GIT_TAG}/OpenRA-${GIT_TAG}.dmg"
|
||||
wget -q "https://github.com/${{ github.repository }}/releases/download/${GIT_TAG}/OpenRA-Dune-2000-x86_64.AppImage"
|
||||
wget -q "https://github.com/${{ github.repository }}/releases/download/${GIT_TAG}/OpenRA-Red-Alert-x86_64.AppImage"
|
||||
wget -q "https://github.com/${{ github.repository }}/releases/download/${GIT_TAG}/OpenRA-Tiberian-Dawn-x86_64.AppImage"
|
||||
wget -q "https://raw.githubusercontent.com/${{ github.repository }}/${GIT_TAG}/packaging/.itch.toml"
|
||||
zip -u "OpenRA-${GIT_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
|
||||
94
.github/workflows/packaging.yml
vendored
94
.github/workflows/packaging.yml
vendored
@@ -1,94 +0,0 @@
|
||||
name: Release Packaging
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'release-*'
|
||||
- 'playtest-*'
|
||||
- 'devtest-*'
|
||||
|
||||
jobs:
|
||||
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: Install .NET 5
|
||||
uses: actions/setup-dotnet@v1
|
||||
with:
|
||||
dotnet-version: '5.0.x'
|
||||
|
||||
- name: Prepare Environment
|
||||
run: |
|
||||
echo "GIT_TAG=${GITHUB_REF#refs/tags/}" >> ${GITHUB_ENV}
|
||||
sudo apt install nsis wine64
|
||||
|
||||
- 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/*
|
||||
142
.gitignore
vendored
142
.gitignore
vendored
@@ -1,69 +1,73 @@
|
||||
# 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
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"name": "OpenRA"
|
||||
, "files": [ { "git": 1 } ]
|
||||
, "build": {
|
||||
"directory": "."
|
||||
, "build": "make all"
|
||||
, "clean": "make clean"
|
||||
}
|
||||
}
|
||||
79
.travis.yml
Normal file
79
.travis.yml
Normal file
@@ -0,0 +1,79 @@
|
||||
# Travis-CI Build for OpenRA
|
||||
# see travis-ci.org for details
|
||||
|
||||
language: csharp
|
||||
mono: 4.0.0
|
||||
|
||||
# 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
|
||||
script:
|
||||
- travis_retry make all-dependencies
|
||||
- make all
|
||||
- make check
|
||||
- make check-scripts
|
||||
- make test
|
||||
|
||||
# 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"
|
||||
]
|
||||
}
|
||||
65
.vscode/launch.json
vendored
65
.vscode/launch.json
vendored
@@ -1,65 +0,0 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Launch (TD)",
|
||||
"type": "clr",
|
||||
"linux": {
|
||||
"type": "mono"
|
||||
},
|
||||
"osx": {
|
||||
"type": "mono"
|
||||
},
|
||||
"request": "launch",
|
||||
"program": "${workspaceRoot}/OpenRA.Game.exe",
|
||||
"cwd": "${workspaceRoot}",
|
||||
"args": ["Game.Mod=cnc"],
|
||||
"preLaunchTask": "build",
|
||||
},
|
||||
{
|
||||
"name": "Launch (RA)",
|
||||
"type": "clr",
|
||||
"linux": {
|
||||
"type": "mono"
|
||||
},
|
||||
"osx": {
|
||||
"type": "mono"
|
||||
},
|
||||
"request": "launch",
|
||||
"program": "${workspaceRoot}/OpenRA.Game.exe",
|
||||
"cwd": "${workspaceRoot}",
|
||||
"args": ["Game.Mod=ra"],
|
||||
"preLaunchTask": "build",
|
||||
},
|
||||
{
|
||||
"name": "Launch (D2k)",
|
||||
"type": "clr",
|
||||
"linux": {
|
||||
"type": "mono"
|
||||
},
|
||||
"osx": {
|
||||
"type": "mono"
|
||||
},
|
||||
"request": "launch",
|
||||
"program": "${workspaceRoot}/OpenRA.Game.exe",
|
||||
"cwd": "${workspaceRoot}",
|
||||
"args": ["Game.Mod=d2k"],
|
||||
"preLaunchTask": "build",
|
||||
},
|
||||
{
|
||||
"name": "Launch (TS)",
|
||||
"type": "clr",
|
||||
"linux": {
|
||||
"type": "mono"
|
||||
},
|
||||
"osx": {
|
||||
"type": "mono"
|
||||
},
|
||||
"request": "launch",
|
||||
"program": "${workspaceRoot}/OpenRA.Game.exe",
|
||||
"cwd": "${workspaceRoot}",
|
||||
"args": ["Game.Mod=ts"],
|
||||
"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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
86
AUTHORS
86
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,64 +51,48 @@ Also thanks to:
|
||||
* David Russell (DavidARussell)
|
||||
* DeadlySurprise
|
||||
* Dmitri Suvorov (suvjunmd)
|
||||
* dtluna
|
||||
* Erasmus Schroder (rasco)
|
||||
* Eric Bajumpaa (SteelPhase)
|
||||
* Evgeniy Sergeev (evgeniysergeev)
|
||||
* Fahrradkette
|
||||
* 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
|
||||
* Kanar
|
||||
* 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,35 +100,23 @@ Also thanks to:
|
||||
* Paolo Chiodi (paolochiodi)
|
||||
* Paul Dovydaitis (pdovy)
|
||||
* Pavlos Touboulidis (pav)
|
||||
* Pedro Ferreira Ramos (bateramos)
|
||||
* Peter Amrehn (jongleur1983)
|
||||
* Pizzaoverhead
|
||||
* Pi Delport (pjdelport)
|
||||
* Psydev
|
||||
* Raphael Vogt (TheRaffy, Yellow)
|
||||
* Raymond Bedrossian (Squiggles211)
|
||||
* Raymond Martineau (mart0258)
|
||||
* Riderr3
|
||||
* riiga
|
||||
* Rikhardur Bjarni Einarsson (WolfGaming)
|
||||
* Sascha Biedermann (bidifx)
|
||||
* Sean Hunt (coppro)
|
||||
* 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,8 +130,20 @@ FreeType License.
|
||||
|
||||
Using OpenAL Soft distributed under the GNU LGPL.
|
||||
|
||||
Using SDL2-CS and OpenAL-CS created by Ethan
|
||||
Lee and released under the zlib license.
|
||||
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 the Open Toolkit distributed under the
|
||||
MIT license.
|
||||
|
||||
Using SDL2# created by Ethan Lee and released
|
||||
under the zlib license.
|
||||
|
||||
Using Eluant created by Chris Howie and released
|
||||
under the MIT license.
|
||||
@@ -173,27 +151,17 @@ 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
|
||||
games which OpenRA aims to reimagine.
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
128
INSTALL.md
128
INSTALL.md
@@ -6,104 +6,68 @@ 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 5 SDK](https://dotnet.microsoft.com/download/dotnet/5.0) (or via Visual Studio)
|
||||
* [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)
|
||||
|
||||
To compile OpenRA, open the `OpenRA.sln` solution in the main folder, build it from the command-line with `dotnet` or use the Makefile analogue command `make all` scripted in PowerShell syntax.
|
||||
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.
|
||||
|
||||
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.
|
||||
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 `OpenRA.Game.exe Game.Mod=ra` for Red Alert or `OpenRA.Game.exe Game.Mod=cnc` for Tiberian Dawn.
|
||||
|
||||
Linux
|
||||
=====
|
||||
|
||||
Mono, version 6.4 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
|
||||
```
|
||||
* mono-dmcs
|
||||
* libmono-system-windows-forms4.0-cil
|
||||
* nuget
|
||||
* cli-common-dev (>= 2.10)
|
||||
* 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 >= 6.4](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
|
||||
|
||||
483
Makefile
483
Makefile
@@ -1,45 +1,60 @@
|
||||
############################# 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 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 install Linux startup scripts, desktop files, icons, and MIME metadata
|
||||
# make install-linux-shortcuts
|
||||
# to install with development tools, run:
|
||||
# make [prefix=/foo] [bindir=/bar/bin] install-all
|
||||
#
|
||||
# to install Linux AppStream metadata
|
||||
# make install-linux-appdata
|
||||
# to install Linux startup scripts, desktop files and icons:
|
||||
# make install-linux-shortcuts [DEBUG=false]
|
||||
#
|
||||
# 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.dll OpenRA.Utility.dll OpenRA.Server.dll OpenRA.Platforms.Default.dll OpenRA.Game.dll OpenRA.Mods.Common.dll OpenRA.Mods.Cnc.dll OpenRA.Mods.D2k.dll
|
||||
CSC = dmcs
|
||||
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
|
||||
|
||||
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 Microsoft.Win32.Registry.dll System.Security.AccessControl.dll System.Security.Principal.Windows.dll System.Xml.Linq.dll System.Runtime.Serialization.dll
|
||||
|
||||
######################### UTILITIES/SETTINGS ###########################
|
||||
#
|
||||
# Install locations for local installs and downstream packaging
|
||||
# install locations
|
||||
prefix ?= /usr/local
|
||||
datarootdir ?= $(prefix)/share
|
||||
datadir ?= $(datarootdir)
|
||||
@@ -49,129 +64,413 @@ libdir ?= $(prefix)/lib
|
||||
gameinstalldir ?= $(libdir)/openra
|
||||
|
||||
BIN_INSTALL_DIR = $(DESTDIR)$(bindir)
|
||||
DATA_INSTALL_DIR = $(DESTDIR)$(datadir)
|
||||
OPENRA_INSTALL_DIR = $(DESTDIR)$(gameinstalldir)
|
||||
DATA_INSTALL_DIR = $(DESTDIR)$(gameinstalldir)
|
||||
|
||||
# Toolchain
|
||||
CWD = $(shell pwd)
|
||||
MSBUILD = msbuild -verbosity:m -nologo
|
||||
MONO = mono
|
||||
# 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
|
||||
|
||||
VERSION = $(shell git name-rev --name-only --tags --no-undefined HEAD 2>/dev/null || echo git-`git rev-parse --short HEAD`)
|
||||
# program targets
|
||||
CORE = pdefault pnull game utility
|
||||
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.dll
|
||||
|
||||
##################### 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) -p:Mono=true -p:DefineConstants="MONO"
|
||||
ifeq ($(TARGETPLATFORM), unix-generic)
|
||||
@./configure-system-libraries.sh
|
||||
endif
|
||||
@./fetch-geoip.sh
|
||||
# Core binaries
|
||||
|
||||
clean:
|
||||
@-$(RM_RF) ./bin ./*/bin ./*/obj
|
||||
@$(MSBUILD) -t:Clean -p:Mono=true
|
||||
@-$(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) -p:Mono=true -p:DefineConstants="MONO"
|
||||
@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 $(pdefault_DEPS)
|
||||
|
||||
pnull_SRCS := $(shell find OpenRA.Platforms.Null/ -iname '*.cs')
|
||||
pnull_TARGET = OpenRA.Platforms.Null.dll
|
||||
pnull_KIND = library
|
||||
pnull_DEPS = $(game_TARGET)
|
||||
pnull_LIBS = $(COMMON_LIBS) $(pnull_DEPS)
|
||||
PROGRAMS += pdefault pnull
|
||||
platforms: $(pdefault_TARGET) $(pnull_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)
|
||||
|
||||
##### 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.Platforms.Null..."
|
||||
@mono --debug OpenRA.Utility.exe ra --check-code-style OpenRA.Platforms.Null
|
||||
@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
|
||||
|
||||
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)
|
||||
|
||||
# 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
|
||||
@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'
|
||||
default: core
|
||||
|
||||
install:
|
||||
@sh -c '. ./packaging/functions.sh; install_assemblies_mono $(CWD) $(OPENRA_INSTALL_DIR) $(TARGETPLATFORM) True True True'
|
||||
@sh -c '. ./packaging/functions.sh; install_data $(CWD) $(OPENRA_INSTALL_DIR) cnc d2k ra'
|
||||
core: game platforms mods utility
|
||||
|
||||
install-linux-shortcuts:
|
||||
@sh -c '. ./packaging/functions.sh; install_linux_shortcuts $(CWD) $(OPENRA_INSTALL_DIR) $(BIN_INSTALL_DIR) $(DATA_INSTALL_DIR) $(VERSION) cnc d2k ra'
|
||||
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 .
|
||||
@ $(CP) thirdparty/SDL2-CS.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 && \
|
||||
mv -f $${i}.tmp $${i} ; \
|
||||
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) 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
|
||||
|
||||
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-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) $(DATA_INSTALL_DIR) 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
|
||||
|
||||
uninstall:
|
||||
@-$(RM_R) "$(DATA_INSTALL_DIR)"
|
||||
@-$(RM_F) "$(BIN_INSTALL_DIR)/openra"
|
||||
@-$(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 to compile, run:
|
||||
@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 the official mods for erroneous yaml files, run:'
|
||||
@echo ' make test'
|
||||
@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] install'
|
||||
@echo to install, run:
|
||||
@echo \ \ make \[prefix=/foo\] \[bindir=/bar/bin\] install
|
||||
@echo
|
||||
@echo 'to install Linux startup scripts, desktop files, icons, and MIME metadata'
|
||||
@echo ' make install-linux-shortcuts'
|
||||
@echo to install with development tools, run:
|
||||
@echo \ \ make \[prefix=/foo\] \[bindir=/bar/bin\] install-all
|
||||
@echo
|
||||
@echo 'to install Linux AppStream metadata'
|
||||
@echo ' make install-linux-appdata'
|
||||
@echo to install Linux startup scripts, desktop files and icons
|
||||
@echo \ \ make install-linux-shortcuts [DEBUG=false]
|
||||
@echo
|
||||
@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)
|
||||
|
||||
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,210 +1,33 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
* as published by the Free Software Foundation. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#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 +35,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,11 +1,10 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
* as published by the Free Software Foundation. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
@@ -16,18 +15,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,17 +1,16 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
* as published by the Free Software Foundation. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using Eluant;
|
||||
using Eluant.ObjectBinding;
|
||||
@@ -25,41 +24,28 @@ namespace OpenRA
|
||||
{
|
||||
public sealed class Actor : IScriptBindable, IScriptNotifyBind, ILuaTableBinding, ILuaEqualityBinding, ILuaToStringBinding, IEquatable<Actor>, IDisposable
|
||||
{
|
||||
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 ActorInfo Info;
|
||||
|
||||
public readonly World World;
|
||||
|
||||
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 +55,28 @@ 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; }
|
||||
|
||||
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,150 +86,69 @@ 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);
|
||||
}
|
||||
|
||||
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>();
|
||||
}
|
||||
|
||||
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 = Traits.Util.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)
|
||||
{
|
||||
// PERF: Avoid LINQ.
|
||||
var renderables = Renderables(wr);
|
||||
foreach (var modifier in renderModifiers)
|
||||
renderables = modifier.ModifyRender(this, wr, renderables);
|
||||
@@ -293,69 +157,35 @@ namespace OpenRA
|
||||
|
||||
IEnumerable<IRenderable> Renderables(WorldRenderer wr)
|
||||
{
|
||||
// PERF: Avoid LINQ.
|
||||
// Implementations of Render are permitted to return both an eagerly materialized collection or a lazily
|
||||
// generated sequence.
|
||||
// For large amounts of renderables, a lazily generated sequence (e.g. as returned by LINQ, or by using
|
||||
// `yield`) will avoid the need to allocate a large collection.
|
||||
// For small amounts of renderables, allocating a small collection can often be faster and require less
|
||||
// memory than creating the objects needed to represent a sequence.
|
||||
foreach (var render in renders)
|
||||
foreach (var renderable in render.Render(this, wr))
|
||||
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()
|
||||
@@ -376,7 +206,6 @@ namespace OpenRA
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
// PERF: Avoid format strings.
|
||||
var name = Info.Name + " " + ActorID;
|
||||
if (!IsInWorld)
|
||||
name += " (not in world)";
|
||||
@@ -405,13 +234,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 +248,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 this.TraitsImplementing<INotifyOwnerChanged>())
|
||||
t.OnOwnerChanged(this, oldOwner, newOwner);
|
||||
});
|
||||
}
|
||||
|
||||
public DamageState GetDamageState()
|
||||
@@ -479,25 +287,32 @@ 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()
|
||||
{
|
||||
foreach (var disable in disables)
|
||||
if (disable.Disabled)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool CanBeViewedByPlayer(Player player)
|
||||
{
|
||||
// PERF: Avoid LINQ.
|
||||
foreach (var visibilityModifier in visibilityModifiers)
|
||||
if (!visibilityModifier.IsVisible(this, player))
|
||||
return false;
|
||||
@@ -505,105 +320,6 @@ namespace OpenRA
|
||||
return defaultVisibility.IsVisible(this, player);
|
||||
}
|
||||
|
||||
public BitSet<TargetableType> GetAllTargetTypes()
|
||||
{
|
||||
// PERF: Avoid LINQ.
|
||||
var targetTypes = default(BitSet<TargetableType>);
|
||||
foreach (var targetable in Targetables)
|
||||
targetTypes = targetTypes.Union(targetable.TargetTypes);
|
||||
return targetTypes;
|
||||
}
|
||||
|
||||
public BitSet<TargetableType> GetEnabledTargetTypes()
|
||||
{
|
||||
// PERF: Avoid LINQ.
|
||||
var targetTypes = default(BitSet<TargetableType>);
|
||||
foreach (var targetable in Targetables)
|
||||
if (targetable.IsTraitEnabled())
|
||||
targetTypes = targetTypes.Union(targetable.TargetTypes);
|
||||
return targetTypes;
|
||||
}
|
||||
|
||||
public bool IsTargetableBy(Actor byActor)
|
||||
{
|
||||
// PERF: Avoid LINQ.
|
||||
foreach (var targetable in Targetables)
|
||||
if (targetable.IsTraitEnabled() && targetable.TargetableBy(this, byActor))
|
||||
return true;
|
||||
|
||||
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 +337,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<Actor>(out a) || !right.TryGetClrValue<Actor>(out b))
|
||||
return false;
|
||||
|
||||
return a == b;
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
* as published by the Free Software Foundation. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
@@ -18,44 +17,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 +71,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<CPos>(out a) || !right.TryGetClrValue<CVec>(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 +81,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<CPos>(out a) || !right.TryGetClrValue<CVec>(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<CPos>(out a) || !right.TryGetClrValue<CPos>(out b))
|
||||
return false;
|
||||
|
||||
return a == b;
|
||||
@@ -131,7 +106,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 +118,4 @@ namespace OpenRA
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,17 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
* as published by the Free Software Foundation. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using Eluant;
|
||||
using Eluant.ObjectBinding;
|
||||
using OpenRA.Primitives;
|
||||
using OpenRA.Scripting;
|
||||
|
||||
namespace OpenRA
|
||||
@@ -75,7 +74,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<CVec>(out a) || !right.TryGetClrValue<CVec>(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 +83,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<CVec>(out a) || !right.TryGetClrValue<CVec>(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 +97,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<CVec>(out a) || !right.TryGetClrValue<CVec>(out b))
|
||||
return false;
|
||||
|
||||
return a == b;
|
||||
@@ -116,7 +118,7 @@ namespace OpenRA
|
||||
|
||||
set
|
||||
{
|
||||
throw new LuaException("CVec is read-only. Use CVec.New to create a new value");
|
||||
throw new LuaException("WVec is read-only. Use CVec.New to create a new value");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
#region Copyright & License Information
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
* as published by the Free Software Foundation. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
@@ -17,4 +16,4 @@ namespace OpenRA
|
||||
void Store(string key, T data);
|
||||
T Retrieve(string key);
|
||||
}
|
||||
}
|
||||
}
|
||||
37
OpenRA.Game/ContentInstaller.cs
Normal file
37
OpenRA.Game/ContentInstaller.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
#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.Collections.Generic;
|
||||
|
||||
namespace OpenRA
|
||||
{
|
||||
// Referenced from ModMetadata, so needs to be in OpenRA.Game :(
|
||||
public class ContentInstaller : IGlobalModData
|
||||
{
|
||||
public readonly string[] TestFiles = { };
|
||||
public readonly string[] DiskTestFiles = { };
|
||||
public readonly string PackageToExtractFromCD = null;
|
||||
public readonly bool OverwriteFiles = true;
|
||||
|
||||
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,11 +1,10 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
* as published by the Free Software Foundation. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
@@ -17,8 +16,8 @@ namespace OpenRA
|
||||
{
|
||||
public class Download
|
||||
{
|
||||
readonly object syncObject = new object();
|
||||
WebClient wc;
|
||||
bool cancelled;
|
||||
|
||||
public static string FormatErrorMessage(Exception e)
|
||||
{
|
||||
@@ -28,8 +27,6 @@ namespace OpenRA
|
||||
|
||||
switch (ex.Status)
|
||||
{
|
||||
case WebExceptionStatus.RequestCanceled:
|
||||
return "Cancelled";
|
||||
case WebExceptionStatus.NameResolutionFailure:
|
||||
return "DNS lookup failed";
|
||||
case WebExceptionStatus.Timeout:
|
||||
@@ -43,54 +40,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,11 +1,10 @@
|
||||
#region Copyright & License Information
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
* as published by the Free Software Foundation. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
* as published by the Free Software Foundation. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
54
OpenRA.Game/Effects/FlashTarget.cs
Normal file
54
OpenRA.Game/Effects/FlashTarget.cs
Normal file
@@ -0,0 +1,54 @@
|
||||
#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.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,11 +1,10 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
* as published by the Free Software Foundation. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
@@ -19,10 +18,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); }
|
||||
}
|
||||
|
||||
40
OpenRA.Game/Effects/SpriteEffect.cs
Normal file
40
OpenRA.Game/Effects/SpriteEffect.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
#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.Collections.Generic;
|
||||
using OpenRA.Graphics;
|
||||
|
||||
namespace OpenRA.Effects
|
||||
{
|
||||
public class SpriteEffect : IEffect
|
||||
{
|
||||
readonly string palette;
|
||||
readonly Animation anim;
|
||||
readonly WPos pos;
|
||||
|
||||
public SpriteEffect(WPos pos, World world, string image, string palette)
|
||||
{
|
||||
this.pos = pos;
|
||||
this.palette = palette;
|
||||
anim = new Animation(world, image);
|
||||
anim.PlayThen("idle", () => world.AddFrameEndTask(w => w.Remove(this)));
|
||||
}
|
||||
|
||||
public void Tick(World world)
|
||||
{
|
||||
anim.Tick();
|
||||
}
|
||||
|
||||
public IEnumerable<IRenderable> Render(WorldRenderer wr)
|
||||
{
|
||||
return anim.Render(pos, WVec.Zero, 0, wr.Palette(palette), 1f / wr.Viewport.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,20 +1,20 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
* as published by the Free Software Foundation. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
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 +78,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,38 +111,21 @@ 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.
|
||||
return (k & mod) == mod;
|
||||
}
|
||||
|
||||
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 +160,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 +173,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)
|
||||
@@ -357,14 +326,6 @@ namespace OpenRA
|
||||
return root;
|
||||
}
|
||||
|
||||
public static int IntegerDivisionRoundingAwayFromZero(int dividend, int divisor)
|
||||
{
|
||||
var quotient = Math.DivRem(dividend, divisor, out var remainder);
|
||||
if (remainder == 0)
|
||||
return quotient;
|
||||
return quotient + (Math.Sign(dividend) == Math.Sign(divisor) ? 1 : -1);
|
||||
}
|
||||
|
||||
public static string JoinWith<T>(this IEnumerable<T> ts, string j)
|
||||
{
|
||||
return string.Join(j, ts);
|
||||
@@ -389,7 +350,7 @@ namespace OpenRA
|
||||
|
||||
public static Dictionary<TKey, TElement> ToDictionaryWithConflictLog<TSource, TKey, TElement>(
|
||||
this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TSource, TElement> elementSelector,
|
||||
string debugName, Func<TKey, string> logKey = null, Func<TElement, string> logValue = null)
|
||||
string debugName, Func<TKey, string> logKey, Func<TElement, string> logValue)
|
||||
{
|
||||
// Fall back on ToString() if null functions are provided:
|
||||
logKey = logKey ?? (s => s.ToString());
|
||||
@@ -403,14 +364,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 +433,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 +473,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,31 +1,29 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
* as published by the Free Software Foundation. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
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
|
||||
{
|
||||
public static class FieldLoader
|
||||
{
|
||||
[Serializable]
|
||||
public class MissingFieldsException : YamlException
|
||||
{
|
||||
public readonly string[] Missing;
|
||||
@@ -39,19 +37,11 @@ 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;
|
||||
}
|
||||
|
||||
public override void GetObjectData(SerializationInfo info, StreamingContext context)
|
||||
{
|
||||
base.GetObjectData(info, context);
|
||||
info.AddValue("Missing", Missing);
|
||||
info.AddValue("Header", Header);
|
||||
}
|
||||
}
|
||||
|
||||
public static Func<string, Type, string, object> InvalidValueAction = (s, t, f) =>
|
||||
@@ -69,11 +59,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 +106,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 +165,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 +212,77 @@ namespace OpenRA
|
||||
}
|
||||
else if (fieldType == typeof(Color))
|
||||
{
|
||||
if (value != null && Color.TryParse(value, out var color))
|
||||
return color;
|
||||
if (value != null)
|
||||
{
|
||||
var parts = value.Split(',');
|
||||
if (parts.Length == 3)
|
||||
return Color.FromArgb(
|
||||
Exts.ParseIntegerInvariant(parts[0]).Clamp(0, 255),
|
||||
Exts.ParseIntegerInvariant(parts[1]).Clamp(0, 255),
|
||||
Exts.ParseIntegerInvariant(parts[2]).Clamp(0, 255));
|
||||
if (parts.Length == 4)
|
||||
return Color.FromArgb(
|
||||
Exts.ParseIntegerInvariant(parts[0]).Clamp(0, 255),
|
||||
Exts.ParseIntegerInvariant(parts[1]).Clamp(0, 255),
|
||||
Exts.ParseIntegerInvariant(parts[2]).Clamp(0, 255),
|
||||
Exts.ParseIntegerInvariant(parts[3]).Clamp(0, 255));
|
||||
}
|
||||
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
else if (fieldType == typeof(Color[]))
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
var parts = value.Split(',');
|
||||
|
||||
if (parts.Length % 4 != 0)
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
|
||||
var colors = new Color[parts.Length / 4];
|
||||
|
||||
for (var i = 0; i < colors.Length; i++)
|
||||
{
|
||||
colors[i] = Color.FromArgb(
|
||||
Exts.ParseIntegerInvariant(parts[4 * i]).Clamp(0, 255),
|
||||
Exts.ParseIntegerInvariant(parts[4 * i + 1]).Clamp(0, 255),
|
||||
Exts.ParseIntegerInvariant(parts[4 * i + 2]).Clamp(0, 255),
|
||||
Exts.ParseIntegerInvariant(parts[4 * i + 3]).Clamp(0, 255));
|
||||
}
|
||||
|
||||
return colors;
|
||||
}
|
||||
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
else if (fieldType == typeof(HSLColor))
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
var parts = value.Split(',');
|
||||
|
||||
// Allow old ColorRamp format to be parsed as HSLColor
|
||||
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 +294,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 +315,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 +332,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 +342,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 +354,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));
|
||||
}
|
||||
}
|
||||
@@ -333,59 +382,6 @@ namespace OpenRA
|
||||
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
else if (fieldType == typeof(CVec[]))
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
var parts = value.Split(',');
|
||||
|
||||
if (parts.Length % 2 != 0)
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
|
||||
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))
|
||||
vecs[i] = new CVec(rx, ry);
|
||||
}
|
||||
|
||||
return vecs;
|
||||
}
|
||||
|
||||
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 +393,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 +472,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 +484,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 +494,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 +508,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 +553,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 +627,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 +650,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 +684,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;
|
||||
@@ -742,7 +722,7 @@ namespace OpenRA
|
||||
}
|
||||
}
|
||||
|
||||
// Mirrors DescriptionAttribute from System.ComponentModel but we don't want to have to use that everywhere.
|
||||
// mirrors DescriptionAttribute from System.ComponentModel but we dont want to have to use that everywhere.
|
||||
[AttributeUsage(AttributeTargets.All)]
|
||||
public sealed class DescAttribute : Attribute
|
||||
{
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
* as published by the Free Software Foundation. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
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;
|
||||
|
||||
namespace OpenRA
|
||||
{
|
||||
@@ -73,9 +73,19 @@ namespace OpenRA
|
||||
|
||||
var t = v.GetType();
|
||||
|
||||
// Color.ToString() does the wrong thing; force it to format as an array
|
||||
if (t == typeof(Color))
|
||||
{
|
||||
return ((Color)v).ToString();
|
||||
var c = (Color)v;
|
||||
return "{0},{1},{2},{3}".F(((int)c.A).Clamp(0, 255),
|
||||
((int)c.R).Clamp(0, 255),
|
||||
((int)c.G).Clamp(0, 255),
|
||||
((int)c.B).Clamp(0, 255));
|
||||
}
|
||||
|
||||
if (t == typeof(ImageFormat))
|
||||
{
|
||||
return ((ImageFormat)v).ToString();
|
||||
}
|
||||
|
||||
if (t == typeof(Rectangle))
|
||||
@@ -84,17 +94,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(", ");
|
||||
}
|
||||
@@ -118,7 +123,7 @@ namespace OpenRA
|
||||
return result;
|
||||
}
|
||||
|
||||
if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Primitives.Cache<,>))
|
||||
if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(OpenRA.Primitives.Cache<,>))
|
||||
return ""; // TODO
|
||||
|
||||
if (t == typeof(DateTime))
|
||||
|
||||
159
OpenRA.Game/FileFormats/AudLoader.cs
Normal file
159
OpenRA.Game/FileFormats/AudLoader.cs
Normal file
@@ -0,0 +1,159 @@
|
||||
#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.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 static class AudLoader
|
||||
{
|
||||
static int[] indexAdjust = { -1, -1, -1, -1, 2, 4, 6, 8 };
|
||||
static 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 byte[] LoadSound(Stream s)
|
||||
{
|
||||
/*var sampleRate =*/ s.ReadUInt16();
|
||||
var dataSize = s.ReadInt32();
|
||||
var outputSize = s.ReadInt32();
|
||||
/*var flags = (SoundFlags)*/ s.ReadByte();
|
||||
/*var format = (SoundFormat)*/ s.ReadByte();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,11 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
#region Additional Copyright & License Information
|
||||
/*
|
||||
* as published by the Free Software Foundation. For more information,
|
||||
* see COPYING.
|
||||
*
|
||||
* This file is based on the blast routines (version 1.1 by Mark Adler)
|
||||
* included in zlib/contrib
|
||||
*/
|
||||
@@ -18,15 +14,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 +35,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,19 +56,16 @@ 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);
|
||||
|
||||
if (coded < 0 || coded > 1)
|
||||
throw new NotImplementedException("Invalid data stream");
|
||||
throw new NotImplementedException("Invalid datastream");
|
||||
var encodedLiterals = coded == 1;
|
||||
|
||||
// log2(dictionary size) - 6
|
||||
@@ -87,9 +77,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 +93,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 +136,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,18 +150,17 @@ 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.
|
||||
// Decode a code using huffman table h.
|
||||
static int Decode(Huffman h, BitReader br)
|
||||
{
|
||||
var code = 0; // len bits being decoded
|
||||
@@ -202,13 +184,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 +202,7 @@ namespace OpenRA.Mods.Common.FileFormats
|
||||
{
|
||||
if (bitCount == 0)
|
||||
{
|
||||
bitBuffer = stream.ReadUInt8();
|
||||
bitBuffer = src[offset++];
|
||||
bitCount = 8;
|
||||
}
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
* as published by the Free Software Foundation. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
namespace OpenRA.Mods.Cnc.FileFormats
|
||||
namespace OpenRA.FileFormats
|
||||
{
|
||||
class Blowfish
|
||||
{
|
||||
@@ -130,8 +129,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 +137,7 @@ namespace OpenRA.Mods.Cnc.FileFormats
|
||||
0x9216d5d9, 0x8979fb1b
|
||||
};
|
||||
|
||||
uint[,] lookupMfromS =
|
||||
{
|
||||
uint[,] lookupMfromS = new uint[,] {
|
||||
{
|
||||
0xd1310ba6, 0x98dfb5ac, 0x2ffd72db, 0xd01adfb7,
|
||||
0xb8e1afed, 0x6a267e96, 0xba7c9045, 0xf12c7f99,
|
||||
@@ -1,18 +1,17 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
* as published by the Free Software Foundation. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace OpenRA.Mods.Cnc.FileFormats
|
||||
namespace OpenRA.FileFormats
|
||||
{
|
||||
/* TODO: Convert this direct C port into readable code. */
|
||||
|
||||
@@ -56,7 +55,7 @@ namespace OpenRA.Mods.Cnc.FileFormats
|
||||
{
|
||||
var pn = (byte*)tempPn;
|
||||
var i = blen * 4;
|
||||
for (; i > klen; i--) pn[i - 1] = sign;
|
||||
for (; i > klen; i--) pn[i - 1] = (byte)sign;
|
||||
for (; i > 0; i--) pn[i - 1] = key[klen - i];
|
||||
}
|
||||
}
|
||||
@@ -230,10 +229,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 +301,6 @@ namespace OpenRA.Mods.Cnc.FileFormats
|
||||
pn2++;
|
||||
tmp >>= 16;
|
||||
}
|
||||
|
||||
*pn1 += (ushort)tmp;
|
||||
}
|
||||
}
|
||||
@@ -386,7 +384,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 +430,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,15 +1,14 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
* as published by the Free Software Foundation. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
namespace OpenRA.Mods.Cnc.FileFormats
|
||||
namespace OpenRA.FileFormats
|
||||
{
|
||||
/// <summary>
|
||||
/// Static class that uses a lookup table to calculates CRC32
|
||||
@@ -112,11 +111,11 @@ namespace OpenRA.Mods.Cnc.FileFormats
|
||||
|
||||
/// <summary>
|
||||
/// A fast (native) CRC32 implementation that can be used on a pinned byte array using
|
||||
/// default polynomial.
|
||||
/// default polynomal.
|
||||
/// </summary>
|
||||
/// <param name="data"> [in,out] If non-null, the.</param>
|
||||
/// <param name="len"> The length of the data.</param>
|
||||
/// <param name="polynomial">The polynomial to XOR with.</param>
|
||||
/// <param name="len"> The length of the data data.</param>
|
||||
/// <param name="polynomial">The polynomal to xor with.</param>
|
||||
/// <returns>The calculated checksum.</returns>
|
||||
public static unsafe uint Calculate(byte* data, uint len, uint polynomial)
|
||||
{
|
||||
@@ -132,4 +131,4 @@ namespace OpenRA.Mods.Cnc.FileFormats
|
||||
return Calculate(data, len, 0xFFFFFFFF);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
33
OpenRA.Game/FileFormats/Format2.cs
Normal file
33
OpenRA.Game/FileFormats/Format2.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
#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
|
||||
|
||||
namespace OpenRA.FileFormats
|
||||
{
|
||||
public static class Format2
|
||||
{
|
||||
public static void DecodeInto(byte[] src, byte[] dest, int destIndex)
|
||||
{
|
||||
var r = new FastByteReader(src);
|
||||
|
||||
while (!r.Done())
|
||||
{
|
||||
var cmd = r.ReadByte();
|
||||
if (cmd == 0)
|
||||
{
|
||||
var count = r.ReadByte();
|
||||
while (count-- > 0)
|
||||
dest[destIndex++] = 0;
|
||||
}
|
||||
else
|
||||
dest[destIndex++] = cmd;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
78
OpenRA.Game/FileFormats/Format40.cs
Normal file
78
OpenRA.Game/FileFormats/Format40.cs
Normal file
@@ -0,0 +1,78 @@
|
||||
#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
|
||||
|
||||
namespace OpenRA.FileFormats
|
||||
{
|
||||
public static class Format40
|
||||
{
|
||||
public static int DecodeInto(byte[] src, byte[] dest, int srcOffset)
|
||||
{
|
||||
var ctx = new FastByteReader(src, srcOffset);
|
||||
var destIndex = 0;
|
||||
|
||||
while (true)
|
||||
{
|
||||
var i = ctx.ReadByte();
|
||||
if ((i & 0x80) == 0)
|
||||
{
|
||||
var count = i & 0x7F;
|
||||
if (count == 0)
|
||||
{
|
||||
// case 6
|
||||
count = ctx.ReadByte();
|
||||
var value = ctx.ReadByte();
|
||||
for (var end = destIndex + count; destIndex < end; destIndex++)
|
||||
dest[destIndex] ^= value;
|
||||
}
|
||||
else
|
||||
{
|
||||
// case 5
|
||||
for (var end = destIndex + count; destIndex < end; destIndex++)
|
||||
dest[destIndex] ^= ctx.ReadByte();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var count = i & 0x7F;
|
||||
if (count == 0)
|
||||
{
|
||||
count = ctx.ReadWord();
|
||||
if (count == 0)
|
||||
return destIndex;
|
||||
|
||||
if ((count & 0x8000) == 0)
|
||||
{
|
||||
// case 2
|
||||
destIndex += count & 0x7FFF;
|
||||
}
|
||||
else if ((count & 0x4000) == 0)
|
||||
{
|
||||
// case 3
|
||||
for (var end = destIndex + (count & 0x3FFF); destIndex < end; destIndex++)
|
||||
dest[destIndex] ^= ctx.ReadByte();
|
||||
}
|
||||
else
|
||||
{
|
||||
// case 4
|
||||
var value = ctx.ReadByte();
|
||||
for (var end = destIndex + (count & 0x3FFF); destIndex < end; destIndex++)
|
||||
dest[destIndex] ^= value;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// case 1
|
||||
destIndex += count;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
206
OpenRA.Game/FileFormats/Format80.cs
Normal file
206
OpenRA.Game/FileFormats/Format80.cs
Normal file
@@ -0,0 +1,206 @@
|
||||
#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.IO;
|
||||
|
||||
namespace OpenRA.FileFormats
|
||||
{
|
||||
class FastByteReader
|
||||
{
|
||||
readonly byte[] src;
|
||||
int offset;
|
||||
|
||||
public FastByteReader(byte[] src, int offset = 0)
|
||||
{
|
||||
this.src = src;
|
||||
this.offset = offset;
|
||||
}
|
||||
|
||||
public bool Done() { return offset >= src.Length; }
|
||||
public byte ReadByte() { return src[offset++]; }
|
||||
public int ReadWord()
|
||||
{
|
||||
var x = ReadByte();
|
||||
return x | (ReadByte() << 8);
|
||||
}
|
||||
|
||||
public void CopyTo(byte[] dest, int offset, int count)
|
||||
{
|
||||
Array.Copy(src, this.offset, dest, offset, count);
|
||||
this.offset += count;
|
||||
}
|
||||
|
||||
public int Remaining() { return src.Length - offset; }
|
||||
}
|
||||
|
||||
public static class Format80
|
||||
{
|
||||
static void ReplicatePrevious(byte[] dest, int destIndex, int srcIndex, int count)
|
||||
{
|
||||
if (srcIndex > destIndex)
|
||||
throw new NotImplementedException("srcIndex > destIndex {0} {1}".F(srcIndex, destIndex));
|
||||
|
||||
if (destIndex - srcIndex == 1)
|
||||
{
|
||||
for (var i = 0; i < count; i++)
|
||||
dest[destIndex + i] = dest[destIndex - 1];
|
||||
}
|
||||
else
|
||||
{
|
||||
for (var i = 0; i < count; i++)
|
||||
dest[destIndex + i] = dest[srcIndex + i];
|
||||
}
|
||||
}
|
||||
|
||||
public static int DecodeInto(byte[] src, byte[] dest, int srcOffset = 0, bool reverse = false)
|
||||
{
|
||||
var ctx = new FastByteReader(src, srcOffset);
|
||||
var destIndex = 0;
|
||||
while (true)
|
||||
{
|
||||
var i = ctx.ReadByte();
|
||||
if ((i & 0x80) == 0)
|
||||
{
|
||||
// case 2
|
||||
var secondByte = ctx.ReadByte();
|
||||
var count = ((i & 0x70) >> 4) + 3;
|
||||
var rpos = ((i & 0xf) << 8) + secondByte;
|
||||
|
||||
if (destIndex + count > dest.Length)
|
||||
return destIndex;
|
||||
|
||||
ReplicatePrevious(dest, destIndex, destIndex - rpos, count);
|
||||
destIndex += count;
|
||||
}
|
||||
else if ((i & 0x40) == 0)
|
||||
{
|
||||
// case 1
|
||||
var count = i & 0x3F;
|
||||
if (count == 0)
|
||||
return destIndex;
|
||||
|
||||
ctx.CopyTo(dest, destIndex, count);
|
||||
destIndex += count;
|
||||
}
|
||||
else
|
||||
{
|
||||
var count3 = i & 0x3F;
|
||||
if (count3 == 0x3E)
|
||||
{
|
||||
// case 4
|
||||
var count = ctx.ReadWord();
|
||||
var color = ctx.ReadByte();
|
||||
|
||||
for (var end = destIndex + count; destIndex < end; destIndex++)
|
||||
dest[destIndex] = color;
|
||||
}
|
||||
else if (count3 == 0x3F)
|
||||
{
|
||||
// case 5
|
||||
var count = ctx.ReadWord();
|
||||
var srcIndex = reverse ? destIndex - ctx.ReadWord() : ctx.ReadWord();
|
||||
if (srcIndex >= destIndex)
|
||||
throw new NotImplementedException("srcIndex >= destIndex {0} {1}".F(srcIndex, destIndex));
|
||||
|
||||
for (var end = destIndex + count; destIndex < end; destIndex++)
|
||||
dest[destIndex] = dest[srcIndex++];
|
||||
}
|
||||
else
|
||||
{
|
||||
// case 3
|
||||
var count = count3 + 3;
|
||||
var srcIndex = reverse ? destIndex - ctx.ReadWord() : ctx.ReadWord();
|
||||
if (srcIndex >= destIndex)
|
||||
throw new NotImplementedException("srcIndex >= destIndex {0} {1}".F(srcIndex, destIndex));
|
||||
|
||||
for (var end = destIndex + count; destIndex < end; destIndex++)
|
||||
dest[destIndex] = dest[srcIndex++];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int CountSame(byte[] src, int offset, int maxCount)
|
||||
{
|
||||
maxCount = Math.Min(src.Length - offset, maxCount);
|
||||
if (maxCount <= 0)
|
||||
return 0;
|
||||
|
||||
var first = src[offset++];
|
||||
var count = 1;
|
||||
|
||||
while (count < maxCount && src[offset++] == first)
|
||||
count++;
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static void WriteCopyBlocks(byte[] src, int offset, int count, MemoryStream output)
|
||||
{
|
||||
while (count > 0)
|
||||
{
|
||||
var writeNow = Math.Min(count, 0x3F);
|
||||
output.WriteByte((byte)(0x80 | writeNow));
|
||||
output.Write(src, offset, writeNow);
|
||||
|
||||
count -= writeNow;
|
||||
offset += writeNow;
|
||||
}
|
||||
}
|
||||
|
||||
// Quick and dirty Format80 encoder version 2
|
||||
// Uses raw copy and RLE compression
|
||||
public static byte[] Encode(byte[] src)
|
||||
{
|
||||
using (var ms = new MemoryStream())
|
||||
{
|
||||
var offset = 0;
|
||||
var left = src.Length;
|
||||
var blockStart = 0;
|
||||
|
||||
while (offset < left)
|
||||
{
|
||||
var repeatCount = CountSame(src, offset, 0xFFFF);
|
||||
if (repeatCount >= 4)
|
||||
{
|
||||
// Write what we haven't written up to now
|
||||
WriteCopyBlocks(src, blockStart, offset - blockStart, ms);
|
||||
|
||||
// Command 4: Repeat byte n times
|
||||
ms.WriteByte(0xFE);
|
||||
|
||||
// Low byte
|
||||
ms.WriteByte((byte)(repeatCount & 0xFF));
|
||||
|
||||
// High byte
|
||||
ms.WriteByte((byte)(repeatCount >> 8));
|
||||
|
||||
// Value to repeat
|
||||
ms.WriteByte(src[offset]);
|
||||
|
||||
offset += repeatCount;
|
||||
blockStart = offset;
|
||||
}
|
||||
else
|
||||
offset++;
|
||||
}
|
||||
|
||||
// Write what we haven't written up to now
|
||||
WriteCopyBlocks(src, blockStart, offset - blockStart, ms);
|
||||
|
||||
// Write terminator
|
||||
ms.WriteByte(0x80);
|
||||
|
||||
return ms.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,20 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
* as published by the Free Software Foundation. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
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));
|
||||
44
OpenRA.Game/FileFormats/IdxReader.cs
Normal file
44
OpenRA.Game/FileFormats/IdxReader.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
#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.Collections.Generic;
|
||||
using System.IO;
|
||||
using OpenRA.FileSystem;
|
||||
|
||||
namespace OpenRA.FileFormats
|
||||
{
|
||||
public class IdxReader
|
||||
{
|
||||
public readonly int SoundCount;
|
||||
public List<IdxEntry> Entries;
|
||||
|
||||
public IdxReader(Stream s)
|
||||
{
|
||||
s.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
var id = s.ReadASCII(4);
|
||||
|
||||
if (id != "GABA")
|
||||
throw new InvalidDataException("Unable to load Idx file, did not find magic id, found {0} instead".F(id));
|
||||
|
||||
var two = s.ReadInt32();
|
||||
|
||||
if (two != 2)
|
||||
throw new InvalidDataException("Unable to load Idx file, did not find magic number 2, found {0} instead".F(two));
|
||||
|
||||
SoundCount = s.ReadInt32();
|
||||
|
||||
Entries = new List<IdxEntry>();
|
||||
|
||||
for (var i = 0; i < SoundCount; i++)
|
||||
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-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.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,11 +1,10 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
* as published by the Free Software Foundation. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
@@ -15,7 +14,7 @@ using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace OpenRA.Mods.Common.FileFormats
|
||||
namespace OpenRA.FileFormats
|
||||
{
|
||||
public class IniFile
|
||||
{
|
||||
@@ -62,7 +61,8 @@ namespace OpenRA.Mods.Common.FileFormats
|
||||
return null;
|
||||
var sectionName = m.Groups[1].Value.ToLowerInvariant();
|
||||
|
||||
if (!sections.TryGetValue(sectionName, out var ret))
|
||||
IniSection ret;
|
||||
if (!sections.TryGetValue(sectionName, out ret))
|
||||
sections.Add(sectionName, ret = new IniSection(sectionName));
|
||||
return ret;
|
||||
}
|
||||
@@ -101,7 +101,8 @@ namespace OpenRA.Mods.Common.FileFormats
|
||||
|
||||
public IniSection GetSection(string s, bool allowFail)
|
||||
{
|
||||
if (sections.TryGetValue(s.ToLowerInvariant(), out var section))
|
||||
IniSection section;
|
||||
if (sections.TryGetValue(s.ToLowerInvariant(), out section))
|
||||
return section;
|
||||
|
||||
if (allowFail)
|
||||
@@ -134,7 +135,8 @@ namespace OpenRA.Mods.Common.FileFormats
|
||||
|
||||
public string GetValue(string key, string defaultValue)
|
||||
{
|
||||
return values.TryGetValue(key, out var s) ? s : defaultValue;
|
||||
string s;
|
||||
return values.TryGetValue(key, out s) ? s : defaultValue;
|
||||
}
|
||||
|
||||
public IEnumerator<KeyValuePair<string, string>> GetEnumerator()
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
193
OpenRA.Game/FileFormats/PngLoader.cs
Normal file
193
OpenRA.Game/FileFormats/PngLoader.cs
Normal file
@@ -0,0 +1,193 @@
|
||||
#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.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>();
|
||||
|
||||
for (;;)
|
||||
{
|
||||
var length = IPAddress.NetworkToHostOrder(br.ReadInt32());
|
||||
var type = Encoding.UTF8.GetString(br.ReadBytes(4));
|
||||
var content = br.ReadBytes(length);
|
||||
/*var crc = */br.ReadInt32();
|
||||
|
||||
using (var ms = new MemoryStream(content))
|
||||
using (var cr = new BinaryReader(ms))
|
||||
switch (type)
|
||||
{
|
||||
case "IHDR":
|
||||
{
|
||||
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":
|
||||
{
|
||||
if (bitmap == null)
|
||||
throw new InvalidDataException("Image header not found.");
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 pixelformat");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,10 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
* as published by the Free Software Foundation. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
@@ -79,27 +78,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,18 +1,17 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
* as published by the Free Software Foundation. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace OpenRA.Mods.Common.FileFormats
|
||||
namespace OpenRA.FileFormats
|
||||
{
|
||||
public class VqaReader
|
||||
{
|
||||
@@ -39,7 +38,6 @@ namespace OpenRA.Mods.Common.FileFormats
|
||||
byte[] cbf;
|
||||
byte[] cbp;
|
||||
byte[] cbfBuffer;
|
||||
bool cbpIsCompressed;
|
||||
|
||||
// Buffer for loading file subchunks, the maximum chunk size of a file is not defined
|
||||
// and the header definition for the size of the biggest chunks (color data) isn't accurate.
|
||||
@@ -194,14 +192,14 @@ namespace OpenRA.Mods.Common.FileFormats
|
||||
else if (audioChannels == 1)
|
||||
{
|
||||
var rawAudio = stream.ReadBytes((int)length);
|
||||
audio1.WriteArray(rawAudio);
|
||||
audio1.Write(rawAudio);
|
||||
}
|
||||
else
|
||||
{
|
||||
var rawAudio = stream.ReadBytes((int)length / 2);
|
||||
audio1.WriteArray(rawAudio);
|
||||
audio1.Write(rawAudio);
|
||||
rawAudio = stream.ReadBytes((int)length / 2);
|
||||
audio2.WriteArray(rawAudio);
|
||||
audio2.Write(rawAudio);
|
||||
if (length % 2 != 0)
|
||||
stream.ReadBytes(2);
|
||||
}
|
||||
@@ -221,7 +219,7 @@ namespace OpenRA.Mods.Common.FileFormats
|
||||
}
|
||||
|
||||
if (audioChannels == 1)
|
||||
audioData = compressed ? ImaAdpcmReader.LoadImaAdpcmSound(audio1.ToArray(), ref adpcmIndex) : audio1.ToArray();
|
||||
audioData = compressed ? AudLoader.LoadSound(audio1.ToArray(), ref adpcmIndex) : audio1.ToArray();
|
||||
else
|
||||
{
|
||||
byte[] leftData, rightData;
|
||||
@@ -233,9 +231,9 @@ namespace OpenRA.Mods.Common.FileFormats
|
||||
else
|
||||
{
|
||||
adpcmIndex = 0;
|
||||
leftData = ImaAdpcmReader.LoadImaAdpcmSound(audio1.ToArray(), ref adpcmIndex);
|
||||
leftData = AudLoader.LoadSound(audio1.ToArray(), ref adpcmIndex);
|
||||
adpcmIndex = 0;
|
||||
rightData = ImaAdpcmReader.LoadImaAdpcmSound(audio2.ToArray(), ref adpcmIndex);
|
||||
rightData = AudLoader.LoadSound(audio2.ToArray(), ref adpcmIndex);
|
||||
}
|
||||
|
||||
audioData = new byte[rightData.Length + leftData.Length];
|
||||
@@ -315,20 +313,6 @@ namespace OpenRA.Mods.Common.FileFormats
|
||||
// VQA Frame
|
||||
public void DecodeVQFR(Stream s, string parentType = "VQFR")
|
||||
{
|
||||
// The CBP chunks each contain 1/8th of the full lookup table
|
||||
// Annoyingly, the complete table is not applied until the frame
|
||||
// *after* the one that contains the 8th chunk.
|
||||
// Do we have a set of partial lookup tables ready to apply?
|
||||
if (currentChunkBuffer == chunkBufferParts)
|
||||
{
|
||||
if (!cbpIsCompressed)
|
||||
cbf = (byte[])cbp.Clone();
|
||||
else
|
||||
LCWDecodeInto(cbp, cbf);
|
||||
|
||||
chunkBufferOffset = currentChunkBuffer = 0;
|
||||
}
|
||||
|
||||
while (true)
|
||||
{
|
||||
// Chunks are aligned on even bytes; may be padded with a single null
|
||||
@@ -345,7 +329,7 @@ namespace OpenRA.Mods.Common.FileFormats
|
||||
Array.Clear(cbf, 0, cbf.Length);
|
||||
Array.Clear(cbfBuffer, 0, cbfBuffer.Length);
|
||||
var decodeCount = 0;
|
||||
decodeCount = LCWDecodeInto(fileBuffer, cbfBuffer, decodeMode ? 1 : 0, decodeMode);
|
||||
decodeCount = Format80.DecodeInto(fileBuffer, cbfBuffer, decodeMode ? 1 : 0, decodeMode);
|
||||
if ((videoFlags & 0x10) == 16)
|
||||
{
|
||||
var p = 0;
|
||||
@@ -375,11 +359,21 @@ namespace OpenRA.Mods.Common.FileFormats
|
||||
// frame-modifier chunk
|
||||
case "CBP0":
|
||||
case "CBPZ":
|
||||
// Partial buffer is full; dump and recreate
|
||||
if (currentChunkBuffer == chunkBufferParts)
|
||||
{
|
||||
if (type == "CBP0")
|
||||
cbf = (byte[])cbp.Clone();
|
||||
else
|
||||
Format80.DecodeInto(cbp, cbf);
|
||||
|
||||
chunkBufferOffset = currentChunkBuffer = 0;
|
||||
}
|
||||
|
||||
var bytes = s.ReadBytes(subchunkLength);
|
||||
bytes.CopyTo(cbp, chunkBufferOffset);
|
||||
chunkBufferOffset += subchunkLength;
|
||||
currentChunkBuffer++;
|
||||
cbpIsCompressed = type == "CBPZ";
|
||||
break;
|
||||
|
||||
// Palette
|
||||
@@ -396,7 +390,7 @@ namespace OpenRA.Mods.Common.FileFormats
|
||||
|
||||
// Frame data
|
||||
case "VPTZ":
|
||||
LCWDecodeInto(s.ReadBytes(subchunkLength), origData);
|
||||
Format80.DecodeInto(s.ReadBytes(subchunkLength), origData);
|
||||
|
||||
// This is the last subchunk
|
||||
return;
|
||||
@@ -404,9 +398,9 @@ namespace OpenRA.Mods.Common.FileFormats
|
||||
Array.Clear(origData, 0, origData.Length);
|
||||
s.ReadBytes(fileBuffer, 0, subchunkLength);
|
||||
if (fileBuffer[0] != 0)
|
||||
vtprSize = LCWDecodeInto(fileBuffer, origData);
|
||||
vtprSize = Format80.DecodeInto(fileBuffer, origData);
|
||||
else
|
||||
LCWDecodeInto(fileBuffer, origData, 1, true);
|
||||
Format80.DecodeInto(fileBuffer, origData, 1, true);
|
||||
return;
|
||||
case "VPTR":
|
||||
Array.Clear(origData, 0, origData.Length);
|
||||
@@ -534,75 +528,5 @@ namespace OpenRA.Mods.Common.FileFormats
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Maybe replace this with LCWCompression.DecodeInto again later
|
||||
public static int LCWDecodeInto(byte[] src, byte[] dest, int srcOffset = 0, bool reverse = false)
|
||||
{
|
||||
var ctx = new FastByteReader(src, srcOffset);
|
||||
var destIndex = 0;
|
||||
while (true)
|
||||
{
|
||||
var i = ctx.ReadByte();
|
||||
if ((i & 0x80) == 0)
|
||||
{
|
||||
// case 2
|
||||
var secondByte = ctx.ReadByte();
|
||||
var count = ((i & 0x70) >> 4) + 3;
|
||||
var rpos = ((i & 0xf) << 8) + secondByte;
|
||||
|
||||
if (destIndex + count > dest.Length)
|
||||
return destIndex;
|
||||
|
||||
// Replicate previous
|
||||
var srcIndex = destIndex - rpos;
|
||||
if (srcIndex > destIndex)
|
||||
throw new NotImplementedException("srcIndex > destIndex {0} {1}".F(srcIndex, destIndex));
|
||||
|
||||
for (var j = 0; j < count; j++)
|
||||
{
|
||||
if (destIndex - srcIndex == 1)
|
||||
dest[destIndex + j] = dest[destIndex - 1];
|
||||
else
|
||||
dest[destIndex + j] = dest[srcIndex + j];
|
||||
}
|
||||
|
||||
destIndex += count;
|
||||
}
|
||||
else if ((i & 0x40) == 0)
|
||||
{
|
||||
// case 1
|
||||
var count = i & 0x3F;
|
||||
if (count == 0)
|
||||
return destIndex;
|
||||
|
||||
ctx.CopyTo(dest, destIndex, count);
|
||||
destIndex += count;
|
||||
}
|
||||
else
|
||||
{
|
||||
var count3 = i & 0x3F;
|
||||
if (count3 == 0x3E)
|
||||
{
|
||||
// case 4
|
||||
var count = ctx.ReadWord();
|
||||
var color = ctx.ReadByte();
|
||||
|
||||
for (var end = destIndex + count; destIndex < end; destIndex++)
|
||||
dest[destIndex] = color;
|
||||
}
|
||||
else
|
||||
{
|
||||
// If count3 == 0x3F it's case 5, else case 3
|
||||
var count = count3 == 0x3F ? ctx.ReadWord() : count3 + 3;
|
||||
var srcIndex = reverse ? destIndex - ctx.ReadWord() : ctx.ReadWord();
|
||||
if (srcIndex >= destIndex)
|
||||
throw new NotImplementedException("srcIndex >= destIndex {0} {1}".F(srcIndex, destIndex));
|
||||
|
||||
for (var end = destIndex + count; destIndex < end; destIndex++)
|
||||
dest[destIndex] = dest[srcIndex++];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,18 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
* as published by the Free Software Foundation. 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 +66,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 +99,7 @@ namespace OpenRA.Mods.Cnc.FileFormats
|
||||
|
||||
// Skip duplicate count
|
||||
s.ReadUInt8();
|
||||
}
|
||||
while (z < l.Size[2]);
|
||||
} while (z < l.Size[2]);
|
||||
}
|
||||
}
|
||||
|
||||
180
OpenRA.Game/FileFormats/WavLoader.cs
Normal file
180
OpenRA.Game/FileFormats/WavLoader.cs
Normal file
@@ -0,0 +1,180 @@
|
||||
#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.IO;
|
||||
|
||||
namespace OpenRA.FileFormats
|
||||
{
|
||||
public class WavLoader
|
||||
{
|
||||
public readonly int FileSize;
|
||||
public readonly string Format;
|
||||
|
||||
public readonly int FmtChunkSize;
|
||||
public readonly int AudioFormat;
|
||||
public readonly int Channels;
|
||||
public readonly int SampleRate;
|
||||
public readonly int ByteRate;
|
||||
public readonly int BlockAlign;
|
||||
public readonly int BitsPerSample;
|
||||
|
||||
public readonly int UncompressedSize;
|
||||
public readonly int DataSize;
|
||||
public readonly byte[] RawOutput;
|
||||
|
||||
public enum WaveType { Pcm = 0x1, ImaAdpcm = 0x11 }
|
||||
public static WaveType Type { get; private set; }
|
||||
|
||||
public WavLoader(Stream s)
|
||||
{
|
||||
while (s.Position < s.Length)
|
||||
{
|
||||
if ((s.Position & 1) == 1)
|
||||
s.ReadByte(); // Alignment
|
||||
|
||||
var type = s.ReadASCII(4);
|
||||
switch (type)
|
||||
{
|
||||
case "RIFF":
|
||||
FileSize = s.ReadInt32();
|
||||
Format = s.ReadASCII(4);
|
||||
if (Format != "WAVE")
|
||||
throw new NotSupportedException("Not a canonical WAVE file.");
|
||||
break;
|
||||
case "fmt ":
|
||||
FmtChunkSize = s.ReadInt32();
|
||||
AudioFormat = s.ReadInt16();
|
||||
Type = (WaveType)AudioFormat;
|
||||
if (Type != WaveType.Pcm && Type != WaveType.ImaAdpcm)
|
||||
throw new NotSupportedException("Compression type is not supported.");
|
||||
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:
|
||||
// Ignore unknown chunks
|
||||
{
|
||||
var chunkSize = s.ReadInt32();
|
||||
s.ReadBytes(chunkSize);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (Type == WaveType.ImaAdpcm)
|
||||
{
|
||||
RawOutput = DecodeImaAdpcmData();
|
||||
BitsPerSample = 16;
|
||||
}
|
||||
}
|
||||
|
||||
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-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.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace OpenRA.FileFormats
|
||||
{
|
||||
public class XccGlobalDatabase
|
||||
{
|
||||
public readonly string[] Entries;
|
||||
public XccGlobalDatabase(Stream s)
|
||||
{
|
||||
var entries = new List<string>();
|
||||
var reader = new BinaryReader(s);
|
||||
while (reader.PeekChar() > -1)
|
||||
{
|
||||
var count = reader.ReadInt32();
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var chars = new List<char>();
|
||||
char c;
|
||||
|
||||
// Read filename
|
||||
while ((c = reader.ReadChar()) != 0)
|
||||
chars.Add(c);
|
||||
entries.Add(new string(chars.ToArray()));
|
||||
|
||||
// Skip comment
|
||||
while ((c = reader.ReadChar()) != 0) { }
|
||||
}
|
||||
}
|
||||
|
||||
Entries = entries.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
67
OpenRA.Game/FileFormats/XccLocalDatabase.cs
Normal file
67
OpenRA.Game/FileFormats/XccLocalDatabase.cs
Normal file
@@ -0,0 +1,67 @@
|
||||
#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.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace OpenRA.FileFormats
|
||||
{
|
||||
public class XccLocalDatabase
|
||||
{
|
||||
public readonly string[] Entries;
|
||||
public XccLocalDatabase(Stream s)
|
||||
{
|
||||
// Skip unnecessary header data
|
||||
s.Seek(48, SeekOrigin.Begin);
|
||||
var reader = new BinaryReader(s);
|
||||
var count = reader.ReadInt32();
|
||||
Entries = new string[count];
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
public XccLocalDatabase(IEnumerable<string> filenames)
|
||||
{
|
||||
Entries = filenames.ToArray();
|
||||
}
|
||||
|
||||
public byte[] Data()
|
||||
{
|
||||
var data = new MemoryStream();
|
||||
using (var writer = new BinaryWriter(data))
|
||||
{
|
||||
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((int)(Entries.Aggregate(Entries.Length, (a, b) => a + b.Length) + 52)); // Size
|
||||
writer.Write((int)0); // Type
|
||||
writer.Write((int)0); // Version
|
||||
writer.Write((int)0); // Game/Format (0 == TD)
|
||||
writer.Write((int)Entries.Length); // Entries
|
||||
foreach (var e in Entries)
|
||||
{
|
||||
writer.Write(Encoding.ASCII.GetBytes(e));
|
||||
writer.Write((byte)0);
|
||||
}
|
||||
}
|
||||
|
||||
return data.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
187
OpenRA.Game/FileSystem/BagFile.cs
Normal file
187
OpenRA.Game/FileSystem/BagFile.cs
Normal file
@@ -0,0 +1,187 @@
|
||||
#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.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using OpenRA.FileFormats;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.FileSystem
|
||||
{
|
||||
public sealed class BagFile : IFolder
|
||||
{
|
||||
static readonly uint[] Nothing = { };
|
||||
|
||||
readonly string bagFilename;
|
||||
readonly Stream s;
|
||||
readonly int bagFilePriority;
|
||||
readonly Dictionary<uint, IdxEntry> index;
|
||||
|
||||
public BagFile(string filename, int priority)
|
||||
{
|
||||
bagFilename = filename;
|
||||
bagFilePriority = priority;
|
||||
|
||||
// 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 = GlobalFileSystem.Open(indexFilename))
|
||||
entries = new IdxReader(indexStream).Entries;
|
||||
|
||||
index = entries.ToDictionaryWithConflictLog(x => x.Hash,
|
||||
"{0} (bag format)".F(filename),
|
||||
null, x => "(offs={0}, len={1})".F(x.Offset, x.Length));
|
||||
|
||||
s = GlobalFileSystem.Open(filename);
|
||||
}
|
||||
|
||||
public int Priority { get { return 1000 + bagFilePriority; } }
|
||||
public string Name { get { return bagFilename; } }
|
||||
|
||||
public Stream GetContent(uint hash)
|
||||
{
|
||||
IdxEntry entry;
|
||||
if (!index.TryGetValue(hash, 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)WavLoader.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)WavLoader.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;
|
||||
}
|
||||
|
||||
uint? FindMatchingHash(string filename)
|
||||
{
|
||||
var hash = IdxEntry.HashFilename(filename, PackageHashType.CRC32);
|
||||
if (index.ContainsKey(hash))
|
||||
return hash;
|
||||
|
||||
// Maybe we were given a raw hash?
|
||||
uint raw;
|
||||
if (!uint.TryParse(filename, NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out raw))
|
||||
return null;
|
||||
|
||||
if ("{0:X}".F(raw) == filename && index.ContainsKey(raw))
|
||||
return raw;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public Stream GetContent(string filename)
|
||||
{
|
||||
var hash = FindMatchingHash(filename);
|
||||
return hash.HasValue ? GetContent(hash.Value) : null;
|
||||
}
|
||||
|
||||
public bool Exists(string filename)
|
||||
{
|
||||
return FindMatchingHash(filename).HasValue;
|
||||
}
|
||||
|
||||
public IEnumerable<uint> ClassicHashes()
|
||||
{
|
||||
return Nothing;
|
||||
}
|
||||
|
||||
public IEnumerable<uint> CrcHashes()
|
||||
{
|
||||
return index.Keys;
|
||||
}
|
||||
|
||||
public IEnumerable<string> AllFileNames()
|
||||
{
|
||||
var lookup = new Dictionary<uint, string>();
|
||||
if (GlobalFileSystem.Exists("global mix database.dat"))
|
||||
{
|
||||
var db = new XccGlobalDatabase(GlobalFileSystem.Open("global mix database.dat"));
|
||||
foreach (var e in db.Entries)
|
||||
{
|
||||
var hash = IdxEntry.HashFilename(e, PackageHashType.CRC32);
|
||||
if (!lookup.ContainsKey(hash))
|
||||
lookup.Add(hash, e);
|
||||
}
|
||||
}
|
||||
|
||||
return index.Keys.Select(k => lookup.ContainsKey(k) ? lookup[k] : "{0:X}".F(k));
|
||||
}
|
||||
|
||||
public void Write(Dictionary<string, byte[]> contents)
|
||||
{
|
||||
GlobalFileSystem.Unmount(this);
|
||||
throw new NotImplementedException("Updating bag files unsupported");
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
s.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
124
OpenRA.Game/FileSystem/BigFile.cs
Normal file
124
OpenRA.Game/FileSystem/BigFile.cs
Normal file
@@ -0,0 +1,124 @@
|
||||
#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;
|
||||
|
||||
namespace OpenRA.FileSystem
|
||||
{
|
||||
public sealed class BigFile : IFolder
|
||||
{
|
||||
public string Name { get; private set; }
|
||||
public int Priority { get; private set; }
|
||||
readonly Dictionary<string, Entry> entries = new Dictionary<string, Entry>();
|
||||
readonly Stream s;
|
||||
|
||||
public BigFile(string filename, int priority)
|
||||
{
|
||||
Name = filename;
|
||||
Priority = priority;
|
||||
|
||||
s = GlobalFileSystem.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);
|
||||
entries.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 GetContent(string filename)
|
||||
{
|
||||
return entries[filename].GetData();
|
||||
}
|
||||
|
||||
public bool Exists(string filename)
|
||||
{
|
||||
return entries.ContainsKey(filename);
|
||||
}
|
||||
|
||||
public IEnumerable<uint> ClassicHashes()
|
||||
{
|
||||
return entries.Keys.Select(filename => PackageEntry.HashFilename(filename, PackageHashType.Classic));
|
||||
}
|
||||
|
||||
public IEnumerable<uint> CrcHashes()
|
||||
{
|
||||
return Enumerable.Empty<uint>();
|
||||
}
|
||||
|
||||
public IEnumerable<string> AllFileNames()
|
||||
{
|
||||
return entries.Keys;
|
||||
}
|
||||
|
||||
public void Write(Dictionary<string, byte[]> contents)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
s.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
107
OpenRA.Game/FileSystem/D2kSoundResources.cs
Normal file
107
OpenRA.Game/FileSystem/D2kSoundResources.cs
Normal file
@@ -0,0 +1,107 @@
|
||||
#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;
|
||||
|
||||
namespace OpenRA.FileSystem
|
||||
{
|
||||
public sealed class D2kSoundResources : IFolder
|
||||
{
|
||||
readonly Stream s;
|
||||
|
||||
readonly string filename;
|
||||
readonly List<string> filenames;
|
||||
readonly int priority;
|
||||
|
||||
readonly Dictionary<uint, PackageEntry> index = new Dictionary<uint, PackageEntry>();
|
||||
|
||||
public D2kSoundResources(string filename, int priority)
|
||||
{
|
||||
this.filename = filename;
|
||||
this.priority = priority;
|
||||
|
||||
s = GlobalFileSystem.Open(filename);
|
||||
try
|
||||
{
|
||||
filenames = new List<string>();
|
||||
|
||||
var headerLength = s.ReadUInt32();
|
||||
while (s.Position < headerLength + 4)
|
||||
{
|
||||
var name = s.ReadASCIIZ();
|
||||
var offset = s.ReadUInt32();
|
||||
var length = s.ReadUInt32();
|
||||
|
||||
var hash = PackageEntry.HashFilename(name, PackageHashType.Classic);
|
||||
if (!index.ContainsKey(hash))
|
||||
index.Add(hash, new PackageEntry(hash, offset, length));
|
||||
|
||||
filenames.Add(name);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
Dispose();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public Stream GetContent(uint hash)
|
||||
{
|
||||
PackageEntry e;
|
||||
if (!index.TryGetValue(hash, out e))
|
||||
return null;
|
||||
|
||||
s.Seek(e.Offset, SeekOrigin.Begin);
|
||||
return new MemoryStream(s.ReadBytes((int)e.Length));
|
||||
}
|
||||
|
||||
public Stream GetContent(string filename)
|
||||
{
|
||||
return GetContent(PackageEntry.HashFilename(filename, PackageHashType.Classic));
|
||||
}
|
||||
|
||||
public bool Exists(string filename)
|
||||
{
|
||||
return index.ContainsKey(PackageEntry.HashFilename(filename, PackageHashType.Classic));
|
||||
}
|
||||
|
||||
public IEnumerable<string> AllFileNames()
|
||||
{
|
||||
return filenames;
|
||||
}
|
||||
|
||||
public string Name { get { return filename; } }
|
||||
|
||||
public int Priority { get { return 1000 + priority; } }
|
||||
|
||||
public IEnumerable<uint> ClassicHashes()
|
||||
{
|
||||
return index.Keys;
|
||||
}
|
||||
|
||||
public IEnumerable<uint> CrcHashes()
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
public void Write(Dictionary<string, byte[]> contents)
|
||||
{
|
||||
throw new NotImplementedException("Cannot save Dune 2000 Sound Resources.");
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
s.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,330 +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 OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.FileSystem
|
||||
{
|
||||
public interface IReadOnlyFileSystem
|
||||
{
|
||||
Stream Open(string filename);
|
||||
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
|
||||
{
|
||||
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);
|
||||
|
||||
// Children of another package require special handling
|
||||
if (TryGetPackageContaining(filename, out var parent, out var subPath))
|
||||
return parent.OpenPackage(subPath, this);
|
||||
|
||||
// Try and open it normally
|
||||
var stream = Open(filename);
|
||||
if (TryParsePackage(stream, filename, out var package))
|
||||
return package;
|
||||
|
||||
// No package loaders took ownership of the stream, so clean it up
|
||||
stream.Dispose();
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void Mount(string name, string explicitName = null)
|
||||
{
|
||||
var optional = name.StartsWith("~", StringComparison.Ordinal);
|
||||
if (optional)
|
||||
name = name.Substring(1);
|
||||
|
||||
try
|
||||
{
|
||||
IReadOnlyPackage package;
|
||||
if (name.StartsWith("$", StringComparison.Ordinal))
|
||||
{
|
||||
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;
|
||||
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);
|
||||
}
|
||||
catch
|
||||
{
|
||||
if (!optional)
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public void Mount(IReadOnlyPackage package, string explicitName = null)
|
||||
{
|
||||
if (mountedPackages.TryGetValue(package, out var mountCount))
|
||||
{
|
||||
// Package is already mounted
|
||||
// Increment the mount count and bump up the file loading priority
|
||||
mountedPackages[package] = mountCount + 1;
|
||||
foreach (var filename in package.Contents)
|
||||
{
|
||||
fileIndex[filename].Remove(package);
|
||||
fileIndex[filename].Add(package);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Mounting the package for the first time
|
||||
mountedPackages.Add(package, 1);
|
||||
|
||||
if (explicitName != null)
|
||||
explicitMounts.Add(explicitName, package);
|
||||
|
||||
foreach (var filename in package.Contents)
|
||||
fileIndex[filename].Add(package);
|
||||
}
|
||||
}
|
||||
|
||||
public bool Unmount(IReadOnlyPackage package)
|
||||
{
|
||||
if (!mountedPackages.TryGetValue(package, out var mountCount))
|
||||
return false;
|
||||
|
||||
if (--mountCount <= 0)
|
||||
{
|
||||
foreach (var packagesForFile in fileIndex.Values)
|
||||
packagesForFile.RemoveAll(p => p == package);
|
||||
|
||||
mountedPackages.Remove(package);
|
||||
var explicitKeys = explicitMounts.Where(kv => kv.Value == package)
|
||||
.Select(kv => kv.Key)
|
||||
.ToList();
|
||||
|
||||
foreach (var key in explicitKeys)
|
||||
explicitMounts.Remove(key);
|
||||
|
||||
// Mod packages aren't owned by us, so we shouldn't dispose them
|
||||
if (modPackages.Contains(package))
|
||||
modPackages.Remove(package);
|
||||
else
|
||||
package.Dispose();
|
||||
}
|
||||
else
|
||||
mountedPackages[package] = mountCount;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void UnmountAll()
|
||||
{
|
||||
foreach (var package in mountedPackages.Keys)
|
||||
if (!modPackages.Contains(package))
|
||||
package.Dispose();
|
||||
|
||||
mountedPackages.Clear();
|
||||
explicitMounts.Clear();
|
||||
modPackages.Clear();
|
||||
|
||||
fileIndex = new Cache<string, List<IReadOnlyPackage>>(_ => new List<IReadOnlyPackage>());
|
||||
}
|
||||
|
||||
public void LoadFromManifest(Manifest manifest)
|
||||
{
|
||||
UnmountAll();
|
||||
foreach (var kv in manifest.Packages)
|
||||
Mount(kv.Key, kv.Value);
|
||||
}
|
||||
|
||||
Stream GetFromCache(string filename)
|
||||
{
|
||||
var package = fileIndex[filename]
|
||||
.LastOrDefault(x => x.Contains(filename));
|
||||
|
||||
return package?.GetStream(filename);
|
||||
}
|
||||
|
||||
public Stream Open(string filename)
|
||||
{
|
||||
if (!TryOpen(filename, out var s))
|
||||
throw new FileNotFoundException("File not found: {0}".F(filename), filename);
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
public bool TryGetPackageContaining(string path, out IReadOnlyPackage package, out string filename)
|
||||
{
|
||||
var explicitSplit = path.IndexOf('|');
|
||||
if (explicitSplit > 0 && explicitMounts.TryGetValue(path.Substring(0, explicitSplit), out package))
|
||||
{
|
||||
filename = path.Substring(explicitSplit + 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
package = fileIndex[path].LastOrDefault(x => x.Contains(path));
|
||||
filename = path;
|
||||
|
||||
return package != null;
|
||||
}
|
||||
|
||||
public bool TryOpen(string filename, out Stream s)
|
||||
{
|
||||
var explicitSplit = filename.IndexOf('|');
|
||||
if (explicitSplit > 0)
|
||||
{
|
||||
if (explicitMounts.TryGetValue(filename.Substring(0, explicitSplit), out var explicitPackage))
|
||||
{
|
||||
s = explicitPackage.GetStream(filename.Substring(explicitSplit + 1));
|
||||
if (s != null)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
s = GetFromCache(filename);
|
||||
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));
|
||||
if (package != null)
|
||||
{
|
||||
s = package.GetStream(filename);
|
||||
return s != null;
|
||||
}
|
||||
|
||||
s = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool Exists(string filename)
|
||||
{
|
||||
var explicitSplit = filename.IndexOf('|');
|
||||
if (explicitSplit > 0)
|
||||
if (explicitMounts.TryGetValue(filename.Substring(0, explicitSplit), out var 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,106 +1,82 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
* 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;
|
||||
|
||||
namespace OpenRA.FileSystem
|
||||
{
|
||||
public sealed class Folder : IReadWritePackage
|
||||
public sealed class Folder : IFolder
|
||||
{
|
||||
readonly string path;
|
||||
readonly int priority;
|
||||
|
||||
public Folder(string path)
|
||||
// Create a new folder package
|
||||
public Folder(string path, int priority, Dictionary<string, byte[]> contents)
|
||||
{
|
||||
this.path = path;
|
||||
this.priority = priority;
|
||||
if (Directory.Exists(path))
|
||||
Directory.Delete(path, true);
|
||||
|
||||
Write(contents);
|
||||
}
|
||||
|
||||
public Folder(string path, int priority)
|
||||
{
|
||||
this.path = path;
|
||||
this.priority = priority;
|
||||
if (!Directory.Exists(path))
|
||||
Directory.CreateDirectory(path);
|
||||
}
|
||||
|
||||
public string Name { get { return path; } }
|
||||
|
||||
public IEnumerable<string> Contents
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
public Stream GetStream(string filename)
|
||||
public Stream GetContent(string filename)
|
||||
{
|
||||
try { return File.OpenRead(Path.Combine(path, filename)); }
|
||||
catch { return null; }
|
||||
}
|
||||
|
||||
public bool Contains(string filename)
|
||||
public IEnumerable<uint> ClassicHashes()
|
||||
{
|
||||
var combined = Path.Combine(path, filename);
|
||||
return combined.StartsWith(path, StringComparison.Ordinal) && File.Exists(combined);
|
||||
foreach (var filename in Directory.GetFiles(path, "*", SearchOption.TopDirectoryOnly))
|
||||
yield return PackageEntry.HashFilename(Path.GetFileName(filename), PackageHashType.Classic);
|
||||
}
|
||||
|
||||
public IReadOnlyPackage OpenPackage(string filename, FileSystem context)
|
||||
public IEnumerable<uint> CrcHashes()
|
||||
{
|
||||
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;
|
||||
yield break;
|
||||
}
|
||||
|
||||
public void Update(string filename, byte[] contents)
|
||||
public IEnumerable<string> AllFileNames()
|
||||
{
|
||||
// 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);
|
||||
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(filePath));
|
||||
using (var s = File.Create(filePath))
|
||||
s.Write(contents, 0, contents.Length);
|
||||
foreach (var filename in Directory.GetFiles(path, "*", SearchOption.TopDirectoryOnly))
|
||||
yield return Path.GetFileName(filename);
|
||||
}
|
||||
|
||||
public void Delete(string filename)
|
||||
public bool Exists(string filename)
|
||||
{
|
||||
// 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(filePath))
|
||||
Directory.Delete(filePath, true);
|
||||
else if (File.Exists(filePath))
|
||||
File.Delete(filePath);
|
||||
return File.Exists(Path.Combine(path, filename));
|
||||
}
|
||||
|
||||
public int Priority { get { return priority; } }
|
||||
public string Name { get { return path; } }
|
||||
|
||||
public void Write(Dictionary<string, byte[]> contents)
|
||||
{
|
||||
if (!Directory.Exists(path))
|
||||
Directory.CreateDirectory(path);
|
||||
|
||||
foreach (var file in contents)
|
||||
using (var dataStream = File.Create(Path.Combine(path, file.Key)))
|
||||
using (var writer = new BinaryWriter(dataStream))
|
||||
writer.Write(file.Value);
|
||||
}
|
||||
|
||||
public void Dispose() { }
|
||||
|
||||
273
OpenRA.Game/FileSystem/GlobalFileSystem.cs
Normal file
273
OpenRA.Game/FileSystem/GlobalFileSystem.cs
Normal file
@@ -0,0 +1,273 @@
|
||||
#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.Reflection;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.FileSystem
|
||||
{
|
||||
public interface IFolder : IDisposable
|
||||
{
|
||||
Stream GetContent(string filename);
|
||||
bool Exists(string filename);
|
||||
IEnumerable<uint> ClassicHashes();
|
||||
IEnumerable<uint> CrcHashes();
|
||||
IEnumerable<string> AllFileNames();
|
||||
void Write(Dictionary<string, byte[]> contents);
|
||||
int Priority { get; }
|
||||
string Name { get; }
|
||||
}
|
||||
|
||||
public static class GlobalFileSystem
|
||||
{
|
||||
public static List<IFolder> MountedFolders = new List<IFolder>();
|
||||
static Cache<uint, List<IFolder>> classicHashIndex = new Cache<uint, List<IFolder>>(_ => new List<IFolder>());
|
||||
static Cache<uint, List<IFolder>> crcHashIndex = new Cache<uint, List<IFolder>>(_ => new List<IFolder>());
|
||||
|
||||
public static List<string> FolderPaths = new List<string>();
|
||||
|
||||
static void MountInner(IFolder folder)
|
||||
{
|
||||
MountedFolders.Add(folder);
|
||||
|
||||
foreach (var hash in folder.ClassicHashes())
|
||||
{
|
||||
var l = classicHashIndex[hash];
|
||||
if (!l.Contains(folder))
|
||||
l.Add(folder);
|
||||
}
|
||||
|
||||
foreach (var hash in folder.CrcHashes())
|
||||
{
|
||||
var l = crcHashIndex[hash];
|
||||
if (!l.Contains(folder))
|
||||
l.Add(folder);
|
||||
}
|
||||
}
|
||||
|
||||
static int order = 0;
|
||||
|
||||
public static IFolder CreatePackage(string filename, int order, Dictionary<string, byte[]> content)
|
||||
{
|
||||
if (filename.EndsWith(".mix", StringComparison.InvariantCultureIgnoreCase))
|
||||
return new MixFile(filename, order, content);
|
||||
if (filename.EndsWith(".zip", StringComparison.InvariantCultureIgnoreCase))
|
||||
return new ZipFile(filename, order, content);
|
||||
if (filename.EndsWith(".oramap", StringComparison.InvariantCultureIgnoreCase))
|
||||
return new ZipFile(filename, order, content);
|
||||
if (filename.EndsWith(".RS", StringComparison.InvariantCultureIgnoreCase))
|
||||
throw new NotImplementedException("The creation of .RS archives is unimplemented");
|
||||
if (filename.EndsWith(".Z", StringComparison.InvariantCultureIgnoreCase))
|
||||
throw new NotImplementedException("The creation of .Z archives is unimplemented");
|
||||
if (filename.EndsWith(".PAK", StringComparison.InvariantCultureIgnoreCase))
|
||||
throw new NotImplementedException("The creation of .PAK archives is unimplemented");
|
||||
if (filename.EndsWith(".big", StringComparison.InvariantCultureIgnoreCase))
|
||||
throw new NotImplementedException("The creation of .big archives is unimplemented");
|
||||
if (filename.EndsWith(".cab", StringComparison.InvariantCultureIgnoreCase))
|
||||
throw new NotImplementedException("The creation of .cab archives is unimplemented");
|
||||
|
||||
return new Folder(filename, order, content);
|
||||
}
|
||||
|
||||
public static IFolder OpenPackage(string filename, string annotation, int order)
|
||||
{
|
||||
if (filename.EndsWith(".mix", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
var type = string.IsNullOrEmpty(annotation)
|
||||
? PackageHashType.Classic
|
||||
: FieldLoader.GetValue<PackageHashType>("(value)", annotation);
|
||||
|
||||
return new MixFile(filename, type, order);
|
||||
}
|
||||
|
||||
if (filename.EndsWith(".zip", StringComparison.InvariantCultureIgnoreCase))
|
||||
return new ZipFile(filename, order);
|
||||
if (filename.EndsWith(".oramap", StringComparison.InvariantCultureIgnoreCase))
|
||||
return new ZipFile(filename, order);
|
||||
if (filename.EndsWith(".RS", StringComparison.InvariantCultureIgnoreCase))
|
||||
return new D2kSoundResources(filename, order);
|
||||
if (filename.EndsWith(".Z", StringComparison.InvariantCultureIgnoreCase))
|
||||
return new InstallShieldPackage(filename, order);
|
||||
if (filename.EndsWith(".PAK", StringComparison.InvariantCultureIgnoreCase))
|
||||
return new PakFile(filename, order);
|
||||
if (filename.EndsWith(".big", StringComparison.InvariantCultureIgnoreCase))
|
||||
return new BigFile(filename, order);
|
||||
if (filename.EndsWith(".bag", StringComparison.InvariantCultureIgnoreCase))
|
||||
return new BagFile(filename, order);
|
||||
if (filename.EndsWith(".hdr", StringComparison.InvariantCultureIgnoreCase))
|
||||
return new InstallShieldCABExtractor(filename, order);
|
||||
|
||||
return new Folder(filename, order);
|
||||
}
|
||||
|
||||
public static void Mount(string name, string annotation = null)
|
||||
{
|
||||
var optional = name.StartsWith("~");
|
||||
if (optional)
|
||||
name = name.Substring(1);
|
||||
|
||||
name = Platform.ResolvePath(name);
|
||||
|
||||
FolderPaths.Add(name);
|
||||
Action a = () => MountInner(OpenPackage(name, annotation, order++));
|
||||
|
||||
if (optional)
|
||||
try { a(); }
|
||||
catch { }
|
||||
else
|
||||
a();
|
||||
}
|
||||
|
||||
public static void UnmountAll()
|
||||
{
|
||||
foreach (var folder in MountedFolders)
|
||||
folder.Dispose();
|
||||
|
||||
MountedFolders.Clear();
|
||||
FolderPaths.Clear();
|
||||
classicHashIndex = new Cache<uint, List<IFolder>>(_ => new List<IFolder>());
|
||||
crcHashIndex = new Cache<uint, List<IFolder>>(_ => new List<IFolder>());
|
||||
}
|
||||
|
||||
public static bool Unmount(IFolder mount)
|
||||
{
|
||||
if (MountedFolders.Contains(mount))
|
||||
mount.Dispose();
|
||||
|
||||
return MountedFolders.RemoveAll(f => f == mount) > 0;
|
||||
}
|
||||
|
||||
public static void Mount(IFolder mount)
|
||||
{
|
||||
if (!MountedFolders.Contains(mount)) MountedFolders.Add(mount);
|
||||
}
|
||||
|
||||
public static void LoadFromManifest(Manifest manifest)
|
||||
{
|
||||
UnmountAll();
|
||||
foreach (var dir in manifest.Folders)
|
||||
Mount(dir);
|
||||
|
||||
foreach (var pkg in manifest.Packages)
|
||||
Mount(pkg.Key, pkg.Value);
|
||||
}
|
||||
|
||||
static Stream GetFromCache(PackageHashType type, string filename)
|
||||
{
|
||||
var index = type == PackageHashType.CRC32 ? crcHashIndex : classicHashIndex;
|
||||
var folder = index[PackageEntry.HashFilename(filename, type)]
|
||||
.Where(x => x.Exists(filename))
|
||||
.MinByOrDefault(x => x.Priority);
|
||||
|
||||
if (folder != null)
|
||||
return folder.GetContent(filename);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Stream Open(string filename)
|
||||
{
|
||||
Stream s;
|
||||
if (!TryOpen(filename, out s))
|
||||
throw new FileNotFoundException("File not found: {0}".F(filename), filename);
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
public static bool TryOpen(string name, out Stream s)
|
||||
{
|
||||
var filename = name;
|
||||
var foldername = string.Empty;
|
||||
|
||||
// Used for faction specific packages; rule out false positive on Windows C:\ drive notation
|
||||
var explicitFolder = name.Contains(':') && !Directory.Exists(Path.GetDirectoryName(name));
|
||||
if (explicitFolder)
|
||||
{
|
||||
var divide = name.Split(':');
|
||||
foldername = divide.First();
|
||||
filename = divide.Last();
|
||||
}
|
||||
|
||||
// Check the cache for a quick lookup if the folder name is unknown
|
||||
// TODO: This disables caching for explicit folder requests
|
||||
if (filename.IndexOfAny(new char[] { '/', '\\' }) == -1 && !explicitFolder)
|
||||
{
|
||||
s = GetFromCache(PackageHashType.Classic, filename);
|
||||
if (s != null)
|
||||
return true;
|
||||
|
||||
s = GetFromCache(PackageHashType.CRC32, filename);
|
||||
if (s != null)
|
||||
return true;
|
||||
}
|
||||
|
||||
// Ask each package individually
|
||||
IFolder folder;
|
||||
if (explicitFolder && !string.IsNullOrEmpty(foldername))
|
||||
folder = MountedFolders.Where(x => x.Name == foldername).MaxByOrDefault(x => x.Priority);
|
||||
else
|
||||
folder = MountedFolders.Where(x => x.Exists(filename)).MaxByOrDefault(x => x.Priority);
|
||||
|
||||
if (folder != null)
|
||||
{
|
||||
s = folder.GetContent(filename);
|
||||
return true;
|
||||
}
|
||||
|
||||
s = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool Exists(string name)
|
||||
{
|
||||
var explicitFolder = name.Contains(':') && !Directory.Exists(Path.GetDirectoryName(name));
|
||||
if (explicitFolder)
|
||||
{
|
||||
var divide = name.Split(':');
|
||||
var foldername = divide.First();
|
||||
var filename = divide.Last();
|
||||
return MountedFolders.Where(n => n.Name == foldername).Any(f => f.Exists(filename));
|
||||
}
|
||||
else
|
||||
return MountedFolders.Any(f => f.Exists(name));
|
||||
}
|
||||
|
||||
static Dictionary<string, Assembly> assemblyCache = new Dictionary<string, Assembly>();
|
||||
|
||||
public static Assembly ResolveAssembly(object sender, ResolveEventArgs e)
|
||||
{
|
||||
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
|
||||
if (assembly.FullName == e.Name)
|
||||
return assembly;
|
||||
|
||||
var frags = e.Name.Split(',');
|
||||
var filename = frags[0] + ".dll";
|
||||
|
||||
Assembly a;
|
||||
if (assemblyCache.TryGetValue(filename, out a))
|
||||
return a;
|
||||
|
||||
if (Exists(filename))
|
||||
using (var s = Open(filename))
|
||||
{
|
||||
var buf = s.ReadBytes((int)s.Length);
|
||||
a = Assembly.Load(buf);
|
||||
assemblyCache.Add(filename, a);
|
||||
return a;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,42 +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;
|
||||
|
||||
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
|
||||
{
|
||||
void Update(string filename, byte[] contents);
|
||||
void Delete(string filename);
|
||||
}
|
||||
}
|
||||
94
OpenRA.Game/FileSystem/IdxEntry.cs
Normal file
94
OpenRA.Game/FileSystem/IdxEntry.cs
Normal file
@@ -0,0 +1,94 @@
|
||||
#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.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace OpenRA.FileSystem
|
||||
{
|
||||
public class IdxEntry
|
||||
{
|
||||
public const string DefaultExtension = "wav";
|
||||
|
||||
public readonly uint Hash;
|
||||
public readonly string Name;
|
||||
public readonly string Extension;
|
||||
public readonly uint Offset;
|
||||
public readonly uint Length;
|
||||
public readonly uint SampleRate;
|
||||
public readonly uint Flags;
|
||||
public readonly uint ChunkSize;
|
||||
|
||||
public IdxEntry(uint hash, uint offset, uint length, uint sampleRate, uint flags, uint chuckSize)
|
||||
{
|
||||
Hash = hash;
|
||||
Offset = offset;
|
||||
Length = length;
|
||||
SampleRate = sampleRate;
|
||||
Flags = flags;
|
||||
ChunkSize = chuckSize;
|
||||
}
|
||||
|
||||
public IdxEntry(Stream s)
|
||||
{
|
||||
var asciiname = s.ReadASCII(16);
|
||||
|
||||
var pos = asciiname.IndexOf('\0');
|
||||
if (pos != 0)
|
||||
asciiname = asciiname.Substring(0, pos);
|
||||
|
||||
Name = asciiname;
|
||||
Extension = DefaultExtension;
|
||||
Offset = s.ReadUInt32();
|
||||
Length = s.ReadUInt32();
|
||||
SampleRate = s.ReadUInt32();
|
||||
Flags = s.ReadUInt32();
|
||||
ChunkSize = s.ReadUInt32();
|
||||
Hash = HashFilename(string.Concat(Name, ".", Extension), PackageHashType.CRC32);
|
||||
}
|
||||
|
||||
public void Write(BinaryWriter w)
|
||||
{
|
||||
w.Write(Name.PadRight(16, '\0'));
|
||||
w.Write(Offset);
|
||||
w.Write(Length);
|
||||
w.Write(SampleRate);
|
||||
w.Write(Flags);
|
||||
w.Write(ChunkSize);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
public static uint HashFilename(string name, PackageHashType type)
|
||||
{
|
||||
return PackageEntry.HashFilename(name, type);
|
||||
}
|
||||
|
||||
static Dictionary<uint, string> names = new Dictionary<uint, string>();
|
||||
|
||||
public static void AddStandardName(string s)
|
||||
{
|
||||
// RA1 and TD
|
||||
var hash = HashFilename(s, PackageHashType.Classic);
|
||||
names.Add(hash, s);
|
||||
|
||||
// TS
|
||||
var crcHash = HashFilename(s, PackageHashType.CRC32);
|
||||
names.Add(crcHash, s);
|
||||
}
|
||||
}
|
||||
}
|
||||
527
OpenRA.Game/FileSystem/InstallShieldCABExtractor.cs
Normal file
527
OpenRA.Game/FileSystem/InstallShieldCABExtractor.cs
Normal file
@@ -0,0 +1,527 @@
|
||||
#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.RegularExpressions;
|
||||
using ICSharpCode.SharpZipLib.Zip.Compression;
|
||||
using ICSharpCode.SharpZipLib.Zip.Compression.Streams;
|
||||
|
||||
namespace OpenRA.FileSystem
|
||||
{
|
||||
public class InstallShieldCABExtractor : IDisposable, IFolder
|
||||
{
|
||||
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 + (long)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 = (long)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 = (long)reader.ReadUInt32();
|
||||
/* unknown */ reader.ReadUInt32();
|
||||
FileTableSize = reader.ReadUInt32();
|
||||
|
||||
FileTableSize2 = reader.ReadUInt32();
|
||||
DirectoryCount = reader.ReadUInt32();
|
||||
/* unknown */ reader.ReadBytes(8);
|
||||
FileCount = reader.ReadUInt32();
|
||||
|
||||
FileTableOffset2 = (long)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 FileDescriptor fileDes;
|
||||
public uint RemainingArchiveStream;
|
||||
public uint RemainingFileStream;
|
||||
readonly uint index;
|
||||
readonly string commonName;
|
||||
ushort volumeNumber;
|
||||
Stream cabFile;
|
||||
|
||||
public CabReader(FileDescriptor fileDes, uint index, string commonName)
|
||||
{
|
||||
this.fileDes = fileDes;
|
||||
this.index = index;
|
||||
this.commonName = commonName;
|
||||
volumeNumber = (ushort)((uint)fileDes.Volume - 1u);
|
||||
RemainingArchiveStream = 0;
|
||||
if ((fileDes.Flags & FileCompressed) > 0)
|
||||
RemainingFileStream = fileDes.CompressedSize;
|
||||
else
|
||||
RemainingFileStream = fileDes.ExpandedSize;
|
||||
|
||||
cabFile = null;
|
||||
NextFile();
|
||||
}
|
||||
|
||||
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();
|
||||
RemainingArchiveStream -= (uint)cabFile.Read(outArray, read, (int)count - read);
|
||||
}
|
||||
|
||||
return outArray;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
cabFile.Dispose();
|
||||
}
|
||||
|
||||
public void NextFile()
|
||||
{
|
||||
if (cabFile != null)
|
||||
cabFile.Dispose();
|
||||
|
||||
++volumeNumber;
|
||||
cabFile = GlobalFileSystem.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> fileLookup = new Dictionary<string, uint>();
|
||||
int priority;
|
||||
string commonName;
|
||||
public int Priority { get { return priority; } }
|
||||
|
||||
public string Name { get { return commonName; } }
|
||||
|
||||
public InstallShieldCABExtractor(string hdrFilename, int priority = -1)
|
||||
{
|
||||
var fileGroups = new List<FileGroup>();
|
||||
var fileGroupOffsets = new List<uint>();
|
||||
|
||||
this.priority = priority;
|
||||
hdrFile = GlobalFileSystem.Open(hdrFilename);
|
||||
|
||||
// Strips archive number AND file extension
|
||||
commonName = 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((long)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 index = fileGroup.FirstFile; index <= fileGroup.LastFile; ++index)
|
||||
{
|
||||
AddFileDescriptorToList(index);
|
||||
var fileDescriptor = fileDescriptors[index];
|
||||
var fullFilePath = "{0}\\{1}\\{2}".F(fileGroup.Name, DirectoryName((uint)fileDescriptor.DirectoryIndex), fileDescriptor.Filename);
|
||||
fileLookup.Add(fullFilePath, index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 Exists(string filename)
|
||||
{
|
||||
return fileLookup.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 void Write(Dictionary<string, byte[]> input)
|
||||
{
|
||||
throw new NotImplementedException("Cannot Add Files To Cab");
|
||||
}
|
||||
|
||||
public IEnumerable<uint> ClassicHashes()
|
||||
{
|
||||
return fileLookup.Keys.Select(k => PackageEntry.HashFilename(k, PackageHashType.Classic));
|
||||
}
|
||||
|
||||
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 obfustcated files");
|
||||
|
||||
var output = new MemoryStream((int)fileDes.ExpandedSize);
|
||||
|
||||
using (var reader = new CabReader(fileDes, index, commonName))
|
||||
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 obfustcated files");
|
||||
|
||||
using (var reader = new CabReader(fileDes, index, commonName))
|
||||
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 GetContent(string fileName)
|
||||
{
|
||||
return GetContentById(fileLookup[fileName]);
|
||||
}
|
||||
|
||||
public IEnumerable<uint> CrcHashes()
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
public IEnumerable<string> AllFileNames()
|
||||
{
|
||||
return fileLookup.Keys;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
hdrFile.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
158
OpenRA.Game/FileSystem/InstallShieldPackage.cs
Normal file
158
OpenRA.Game/FileSystem/InstallShieldPackage.cs
Normal file
@@ -0,0 +1,158 @@
|
||||
#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 OpenRA.FileFormats;
|
||||
|
||||
namespace OpenRA.FileSystem
|
||||
{
|
||||
public sealed class InstallShieldPackage : IFolder
|
||||
{
|
||||
readonly Dictionary<uint, PackageEntry> index = new Dictionary<uint, PackageEntry>();
|
||||
readonly List<string> filenames;
|
||||
readonly Stream s;
|
||||
readonly long dataStart = 255;
|
||||
readonly int priority;
|
||||
readonly string filename;
|
||||
|
||||
public InstallShieldPackage(string filename, int priority)
|
||||
{
|
||||
this.filename = filename;
|
||||
this.priority = priority;
|
||||
|
||||
filenames = new List<string>();
|
||||
|
||||
s = GlobalFileSystem.Open(filename);
|
||||
try
|
||||
{
|
||||
// Parse package header
|
||||
var reader = new BinaryReader(s);
|
||||
var signature = reader.ReadUInt32();
|
||||
if (signature != 0x8C655D13)
|
||||
throw new InvalidDataException("Not an Installshield package");
|
||||
|
||||
reader.ReadBytes(8);
|
||||
/*var FileCount = */reader.ReadUInt16();
|
||||
reader.ReadBytes(4);
|
||||
/*var ArchiveSize = */reader.ReadUInt32();
|
||||
reader.ReadBytes(19);
|
||||
var tocAddress = reader.ReadInt32();
|
||||
reader.ReadBytes(4);
|
||||
var dirCount = reader.ReadUInt16();
|
||||
|
||||
// Parse the directory list
|
||||
s.Seek(tocAddress, SeekOrigin.Begin);
|
||||
var tocReader = new BinaryReader(s);
|
||||
|
||||
var fileCountInDirs = new List<uint>();
|
||||
|
||||
// Parse directories
|
||||
for (var i = 0; i < dirCount; i++)
|
||||
fileCountInDirs.Add(ParseDirectory(tocReader));
|
||||
|
||||
// Parse files
|
||||
foreach (var fileCount in fileCountInDirs)
|
||||
for (var i = 0; i < fileCount; i++)
|
||||
ParseFile(reader);
|
||||
}
|
||||
catch
|
||||
{
|
||||
Dispose();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
static uint ParseDirectory(BinaryReader reader)
|
||||
{
|
||||
// Parse directory header
|
||||
var fileCount = reader.ReadUInt16();
|
||||
var chunkSize = reader.ReadUInt16();
|
||||
var nameLength = reader.ReadUInt16();
|
||||
reader.ReadChars(nameLength); // var DirName = new String(reader.ReadChars(NameLength));
|
||||
|
||||
// Skip to the end of the chunk
|
||||
reader.ReadBytes(chunkSize - nameLength - 6);
|
||||
return fileCount;
|
||||
}
|
||||
|
||||
uint accumulatedData = 0;
|
||||
void ParseFile(BinaryReader reader)
|
||||
{
|
||||
reader.ReadBytes(7);
|
||||
var compressedSize = reader.ReadUInt32();
|
||||
reader.ReadBytes(12);
|
||||
var chunkSize = reader.ReadUInt16();
|
||||
reader.ReadBytes(4);
|
||||
var nameLength = reader.ReadByte();
|
||||
var fileName = new string(reader.ReadChars(nameLength));
|
||||
|
||||
var hash = PackageEntry.HashFilename(fileName, PackageHashType.Classic);
|
||||
if (!index.ContainsKey(hash))
|
||||
index.Add(hash, new PackageEntry(hash, accumulatedData, compressedSize));
|
||||
filenames.Add(fileName);
|
||||
accumulatedData += compressedSize;
|
||||
|
||||
// Skip to the end of the chunk
|
||||
reader.ReadBytes(chunkSize - nameLength - 30);
|
||||
}
|
||||
|
||||
public Stream GetContent(uint hash)
|
||||
{
|
||||
PackageEntry e;
|
||||
if (!index.TryGetValue(hash, 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 Stream GetContent(string filename)
|
||||
{
|
||||
return GetContent(PackageEntry.HashFilename(filename, PackageHashType.Classic));
|
||||
}
|
||||
|
||||
public IEnumerable<uint> ClassicHashes()
|
||||
{
|
||||
return index.Keys;
|
||||
}
|
||||
|
||||
public IEnumerable<uint> CrcHashes()
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
public IEnumerable<string> AllFileNames()
|
||||
{
|
||||
return filenames;
|
||||
}
|
||||
|
||||
public bool Exists(string filename)
|
||||
{
|
||||
return index.ContainsKey(PackageEntry.HashFilename(filename, PackageHashType.Classic));
|
||||
}
|
||||
|
||||
public int Priority { get { return 2000 + priority; } }
|
||||
public string Name { get { return filename; } }
|
||||
|
||||
public void Write(Dictionary<string, byte[]> contents)
|
||||
{
|
||||
throw new NotImplementedException("Cannot save InstallShieldPackages.");
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
s.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
290
OpenRA.Game/FileSystem/MixFile.cs
Normal file
290
OpenRA.Game/FileSystem/MixFile.cs
Normal file
@@ -0,0 +1,290 @@
|
||||
#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.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using OpenRA.FileFormats;
|
||||
|
||||
namespace OpenRA.FileSystem
|
||||
{
|
||||
public sealed class MixFile : IFolder
|
||||
{
|
||||
readonly Dictionary<uint, PackageEntry> index;
|
||||
readonly long dataStart;
|
||||
readonly Stream s;
|
||||
readonly int priority;
|
||||
readonly string filename;
|
||||
readonly PackageHashType type;
|
||||
|
||||
// Save a mix to disk with the given contents
|
||||
public MixFile(string filename, int priority, Dictionary<string, byte[]> contents)
|
||||
{
|
||||
this.filename = filename;
|
||||
this.priority = priority;
|
||||
this.type = PackageHashType.Classic;
|
||||
|
||||
if (File.Exists(filename))
|
||||
File.Delete(filename);
|
||||
|
||||
s = File.Create(filename);
|
||||
try
|
||||
{
|
||||
index = new Dictionary<uint, PackageEntry>();
|
||||
contents.Add("local mix database.dat", new XccLocalDatabase(contents.Keys.Append("local mix database.dat")).Data());
|
||||
Write(contents);
|
||||
}
|
||||
catch
|
||||
{
|
||||
Dispose();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public MixFile(string filename, PackageHashType type, int priority)
|
||||
{
|
||||
this.filename = filename;
|
||||
this.priority = priority;
|
||||
this.type = type;
|
||||
|
||||
s = GlobalFileSystem.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 = 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;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
uint? FindMatchingHash(string filename)
|
||||
{
|
||||
var hash = PackageEntry.HashFilename(filename, type);
|
||||
if (index.ContainsKey(hash))
|
||||
return hash;
|
||||
|
||||
// Maybe we were given a raw hash?
|
||||
uint raw;
|
||||
if (!uint.TryParse(filename, NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out raw))
|
||||
return null;
|
||||
|
||||
if ("{0:X}".F(raw) == filename && index.ContainsKey(raw))
|
||||
return raw;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public Stream GetContent(uint hash)
|
||||
{
|
||||
PackageEntry e;
|
||||
if (!index.TryGetValue(hash, out e))
|
||||
return null;
|
||||
|
||||
s.Seek(dataStart + e.Offset, SeekOrigin.Begin);
|
||||
var data = s.ReadBytes((int)e.Length);
|
||||
return new MemoryStream(data);
|
||||
}
|
||||
|
||||
public Stream GetContent(string filename)
|
||||
{
|
||||
var hash = FindMatchingHash(filename);
|
||||
return hash.HasValue ? GetContent(hash.Value) : null;
|
||||
}
|
||||
|
||||
static readonly uint[] Nothing = { };
|
||||
public IEnumerable<uint> ClassicHashes()
|
||||
{
|
||||
if (type == PackageHashType.Classic)
|
||||
return index.Keys;
|
||||
|
||||
return Nothing;
|
||||
}
|
||||
|
||||
public IEnumerable<uint> CrcHashes()
|
||||
{
|
||||
if (type == PackageHashType.CRC32)
|
||||
return index.Keys;
|
||||
|
||||
return Nothing;
|
||||
}
|
||||
|
||||
public IEnumerable<string> AllFileNames()
|
||||
{
|
||||
var lookup = new Dictionary<uint, string>();
|
||||
if (Exists("local mix database.dat"))
|
||||
{
|
||||
var db = new XccLocalDatabase(GetContent("local mix database.dat"));
|
||||
foreach (var e in db.Entries)
|
||||
{
|
||||
var hash = PackageEntry.HashFilename(e, type);
|
||||
if (!lookup.ContainsKey(hash))
|
||||
lookup.Add(hash, e);
|
||||
}
|
||||
}
|
||||
|
||||
if (GlobalFileSystem.Exists("global mix database.dat"))
|
||||
{
|
||||
var db = new XccGlobalDatabase(GlobalFileSystem.Open("global mix database.dat"));
|
||||
foreach (var e in db.Entries)
|
||||
{
|
||||
var hash = PackageEntry.HashFilename(e, type);
|
||||
if (!lookup.ContainsKey(hash))
|
||||
lookup.Add(hash, e);
|
||||
}
|
||||
}
|
||||
|
||||
return index.Keys.Select(k => lookup.ContainsKey(k) ? lookup[k] : "{0:X}".F(k));
|
||||
}
|
||||
|
||||
public bool Exists(string filename)
|
||||
{
|
||||
return FindMatchingHash(filename).HasValue;
|
||||
}
|
||||
|
||||
public int Priority { get { return 1000 + priority; } }
|
||||
public string Name { get { return filename; } }
|
||||
|
||||
public void Write(Dictionary<string, byte[]> contents)
|
||||
{
|
||||
// Cannot modify existing mixfile - rename existing file and
|
||||
// create a new one with original content plus modifications
|
||||
GlobalFileSystem.Unmount(this);
|
||||
|
||||
// TODO: Add existing data to the contents list
|
||||
if (index.Count > 0)
|
||||
throw new NotImplementedException("Updating mix files unfinished");
|
||||
|
||||
// Construct a list of entries for the file header
|
||||
uint dataSize = 0;
|
||||
var items = new List<PackageEntry>();
|
||||
foreach (var kv in contents)
|
||||
{
|
||||
var length = (uint)kv.Value.Length;
|
||||
var hash = PackageEntry.HashFilename(Path.GetFileName(kv.Key), type);
|
||||
items.Add(new PackageEntry(hash, dataSize, length));
|
||||
dataSize += length;
|
||||
}
|
||||
|
||||
// Write the new file
|
||||
s.Seek(0, SeekOrigin.Begin);
|
||||
using (var writer = new BinaryWriter(s))
|
||||
{
|
||||
// Write file header
|
||||
writer.Write((ushort)items.Count);
|
||||
writer.Write(dataSize);
|
||||
foreach (var item in items)
|
||||
item.Write(writer);
|
||||
|
||||
writer.Flush();
|
||||
|
||||
// Copy file data
|
||||
foreach (var file in contents)
|
||||
s.Write(file.Value);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
s.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,10 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
* as published by the Free Software Foundation. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
@@ -13,9 +12,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 +48,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 +65,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:
|
||||
109
OpenRA.Game/FileSystem/Pak.cs
Normal file
109
OpenRA.Game/FileSystem/Pak.cs
Normal file
@@ -0,0 +1,109 @@
|
||||
#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;
|
||||
|
||||
namespace OpenRA.FileSystem
|
||||
{
|
||||
struct Entry
|
||||
{
|
||||
public uint Offset;
|
||||
public uint Length;
|
||||
public string Filename;
|
||||
}
|
||||
|
||||
public sealed class PakFile : IFolder
|
||||
{
|
||||
readonly string filename;
|
||||
readonly int priority;
|
||||
readonly Dictionary<string, Entry> index;
|
||||
readonly Stream stream;
|
||||
|
||||
public PakFile(string filename, int priority)
|
||||
{
|
||||
this.filename = filename;
|
||||
this.priority = priority;
|
||||
index = new Dictionary<string, Entry>();
|
||||
|
||||
stream = GlobalFileSystem.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 GetContent(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 IEnumerable<uint> ClassicHashes()
|
||||
{
|
||||
foreach (var filename in index.Keys)
|
||||
yield return PackageEntry.HashFilename(filename, PackageHashType.Classic);
|
||||
}
|
||||
|
||||
public IEnumerable<uint> CrcHashes()
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
public IEnumerable<string> AllFileNames()
|
||||
{
|
||||
foreach (var filename in index.Keys)
|
||||
yield return filename;
|
||||
}
|
||||
|
||||
public bool Exists(string filename)
|
||||
{
|
||||
return index.ContainsKey(filename);
|
||||
}
|
||||
|
||||
public void Write(Dictionary<string, byte[]> contents)
|
||||
{
|
||||
throw new NotImplementedException("Cannot save Pak archives.");
|
||||
}
|
||||
|
||||
public int Priority { get { return 1000 + priority; } }
|
||||
public string Name { get { return filename; } }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
stream.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,236 +1,134 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
* 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 class ZipFileLoader : IPackageLoader
|
||||
public sealed class ZipFile : IFolder
|
||||
{
|
||||
static readonly string[] Extensions = { ".zip", ".oramap" };
|
||||
readonly string filename;
|
||||
readonly int priority;
|
||||
SZipFile pkg;
|
||||
|
||||
class ReadOnlyZipFile : IReadOnlyPackage
|
||||
static ZipFile()
|
||||
{
|
||||
public string Name { get; protected set; }
|
||||
protected ZipFile pkg;
|
||||
ZipConstants.DefaultCodePage = Encoding.Default.CodePage;
|
||||
}
|
||||
|
||||
// Dummy constructor for use with ReadWriteZipFile
|
||||
protected ReadOnlyZipFile() { }
|
||||
|
||||
public ReadOnlyZipFile(Stream s, string filename)
|
||||
public ZipFile(string filename, int priority)
|
||||
{
|
||||
this.filename = filename;
|
||||
this.priority = priority;
|
||||
try
|
||||
{
|
||||
Name = filename;
|
||||
pkg = new ZipFile(s);
|
||||
// pull the file into memory, dont keep it open.
|
||||
pkg = new SZipFile(new MemoryStream(File.ReadAllBytes(filename)));
|
||||
}
|
||||
|
||||
public Stream GetStream(string filename)
|
||||
catch (ZipException e)
|
||||
{
|
||||
var entry = pkg.GetEntry(filename);
|
||||
if (entry == null)
|
||||
return null;
|
||||
|
||||
using (var z = pkg.GetInputStream(entry))
|
||||
{
|
||||
var ms = new MemoryStream((int)entry.Size);
|
||||
z.CopyTo(ms);
|
||||
ms.Seek(0, SeekOrigin.Begin);
|
||||
return ms;
|
||||
}
|
||||
Log.Write("debug", "Couldn't load zip file: {0}", e.Message);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<string> Contents
|
||||
{
|
||||
get
|
||||
{
|
||||
foreach (ZipEntry entry in pkg)
|
||||
yield return entry.Name;
|
||||
}
|
||||
}
|
||||
// Create a new zip with the specified contents
|
||||
public ZipFile(string filename, int priority, Dictionary<string, byte[]> contents)
|
||||
{
|
||||
this.priority = priority;
|
||||
this.filename = filename;
|
||||
|
||||
public bool Contains(string filename)
|
||||
{
|
||||
return pkg.GetEntry(filename) != null;
|
||||
}
|
||||
if (File.Exists(filename))
|
||||
File.Delete(filename);
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
pkg?.Close();
|
||||
}
|
||||
pkg = SZipFile.Create(filename);
|
||||
Write(contents);
|
||||
}
|
||||
|
||||
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 GetContent(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<uint> ClassicHashes()
|
||||
{
|
||||
readonly MemoryStream pkgStream = new MemoryStream();
|
||||
|
||||
public ReadWriteZipFile(string filename, bool create = false)
|
||||
{
|
||||
// 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 PackageEntry.HashFilename(entry.Name, PackageHashType.Classic);
|
||||
}
|
||||
|
||||
sealed class ZipFolder : IReadOnlyPackage
|
||||
public IEnumerable<uint> CrcHashes()
|
||||
{
|
||||
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 */ }
|
||||
yield break;
|
||||
}
|
||||
|
||||
class StaticStreamDataSource : IStaticDataSource
|
||||
public IEnumerable<string> AllFileNames()
|
||||
{
|
||||
readonly Stream s;
|
||||
public StaticStreamDataSource(Stream s)
|
||||
{
|
||||
this.s = s;
|
||||
}
|
||||
|
||||
public Stream GetSource()
|
||||
{
|
||||
return s;
|
||||
}
|
||||
foreach (ZipEntry entry in pkg)
|
||||
yield return entry.Name;
|
||||
}
|
||||
|
||||
public bool TryParsePackage(Stream s, string filename, FileSystem context, out IReadOnlyPackage package)
|
||||
public bool Exists(string filename)
|
||||
{
|
||||
if (!Extensions.Any(e => filename.EndsWith(e, StringComparison.InvariantCultureIgnoreCase)))
|
||||
{
|
||||
package = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
package = new ReadOnlyZipFile(s, filename);
|
||||
return true;
|
||||
return pkg.GetEntry(filename) != null;
|
||||
}
|
||||
|
||||
public static bool TryParseReadWritePackage(string filename, out IReadWritePackage package)
|
||||
{
|
||||
if (!Extensions.Any(e => filename.EndsWith(e, StringComparison.InvariantCultureIgnoreCase)))
|
||||
{
|
||||
package = null;
|
||||
return false;
|
||||
}
|
||||
public int Priority { get { return 500 + priority; } }
|
||||
public string Name { get { return filename; } }
|
||||
|
||||
package = new ReadWriteZipFile(filename);
|
||||
return true;
|
||||
public void Write(Dictionary<string, byte[]> contents)
|
||||
{
|
||||
// TODO: Clear existing content?
|
||||
pkg.Close();
|
||||
pkg = SZipFile.Create(filename);
|
||||
pkg.BeginUpdate();
|
||||
|
||||
foreach (var kvp in contents)
|
||||
pkg.Add(new StaticMemoryDataSource(kvp.Value), kvp.Key);
|
||||
|
||||
pkg.CommitUpdate();
|
||||
pkg.Close();
|
||||
pkg = new SZipFile(new MemoryStream(File.ReadAllBytes(filename)));
|
||||
}
|
||||
|
||||
public static IReadWritePackage Create(string filename)
|
||||
public void Dispose()
|
||||
{
|
||||
return new ReadWriteZipFile(filename, true);
|
||||
if (pkg != null)
|
||||
pkg.Close();
|
||||
}
|
||||
}
|
||||
|
||||
class StaticMemoryDataSource : IStaticDataSource
|
||||
{
|
||||
byte[] data;
|
||||
public StaticMemoryDataSource(byte[] data)
|
||||
{
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public Stream GetSource()
|
||||
{
|
||||
return new MemoryStream(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,19 +1,18 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
* as published by the Free Software Foundation. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Network;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA
|
||||
{
|
||||
@@ -24,7 +23,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 +33,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,14 +116,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,
|
||||
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);
|
||||
@@ -136,7 +130,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;
|
||||
}
|
||||
@@ -157,11 +153,7 @@ 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;
|
||||
@@ -173,9 +165,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
|
||||
@@ -186,9 +175,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,11 +1,10 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
* as published by the Free Software Foundation. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
@@ -22,42 +21,45 @@ 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
|
||||
/// that can be inherited by others (using Inherits:) and not a real unit.
|
||||
/// You can remove inherited traits by adding a - in front of them as in -TraitName: to inherit everything, but this trait.
|
||||
/// You can remove inherited traits by adding a - infront of them as in -TraitName: to inherit everything, but this trait.
|
||||
/// </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)
|
||||
public ActorInfo(string name, MiniYaml node, Dictionary<string, MiniYaml> allUnits)
|
||||
{
|
||||
try
|
||||
{
|
||||
var allParents = new HashSet<string>();
|
||||
var abstractActorType = name.StartsWith("^");
|
||||
|
||||
// Guard against circular inheritance
|
||||
allParents.Add(name);
|
||||
var mergedNode = MergeWithParents(node, allUnits, allParents).ToDictionary();
|
||||
|
||||
Name = name;
|
||||
|
||||
foreach (var t in node.Nodes)
|
||||
foreach (var t in mergedNode)
|
||||
{
|
||||
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);
|
||||
}
|
||||
catch (FieldLoader.MissingFieldsException e)
|
||||
{
|
||||
throw new YamlException(e.Message);
|
||||
}
|
||||
}
|
||||
if (t.Key[0] == '-')
|
||||
throw new YamlException("Bogus trait removal: " + t.Key);
|
||||
|
||||
traits.TrimExcess();
|
||||
if (t.Key != "Inherits" && !t.Key.StartsWith("Inherits@"))
|
||||
try
|
||||
{
|
||||
traits.Add(LoadTraitInfo(t.Key.Split('@')[0], t.Value));
|
||||
}
|
||||
catch (FieldLoader.MissingFieldsException e)
|
||||
{
|
||||
if (!abstractActorType)
|
||||
throw new YamlException(e.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (YamlException e)
|
||||
{
|
||||
@@ -65,32 +67,51 @@ 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 Dictionary<string, MiniYaml> GetParents(MiniYaml node, Dictionary<string, MiniYaml> allUnits)
|
||||
{
|
||||
return node.Nodes.Where(n => n.Key == "Inherits" || n.Key.StartsWith("Inherits@"))
|
||||
.ToDictionary(n => n.Value.Value, n =>
|
||||
{
|
||||
MiniYaml i;
|
||||
if (!allUnits.TryGetValue(n.Value.Value, out i))
|
||||
throw new YamlException(
|
||||
"Bogus inheritance -- parent type {0} does not exist".F(n.Value.Value));
|
||||
|
||||
return i;
|
||||
});
|
||||
}
|
||||
|
||||
static MiniYaml MergeWithParents(MiniYaml node, Dictionary<string, MiniYaml> allUnits, HashSet<string> allParents)
|
||||
{
|
||||
var parents = GetParents(node, allUnits);
|
||||
|
||||
foreach (var kv in parents)
|
||||
{
|
||||
if (!allParents.Add(kv.Key))
|
||||
throw new YamlException(
|
||||
"Bogus inheritance -- duplicate inheritance of {0}.".F(kv.Key));
|
||||
|
||||
node = MiniYaml.MergeStrict(node, MergeWithParents(kv.Value, allUnits, allParents));
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
static ITraitInfo LoadTraitInfo(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 = Game.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 +123,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(),
|
||||
@@ -118,15 +139,9 @@ namespace OpenRA
|
||||
var unresolved = source.Except(resolved);
|
||||
|
||||
var testResolve = new Func<Type, Type, bool>((a, b) => a == b || a.IsAssignableFrom(b));
|
||||
var more = unresolved.Where(u => u.Dependencies.All(d => resolved.Exists(r => testResolve(d, r.Type))));
|
||||
|
||||
// This query detects which unresolved traits can be immediately resolved as all their direct dependencies are met.
|
||||
var more = unresolved.Where(u =>
|
||||
u.Dependencies.All(d => // To be resolvable, all dependencies must be satisfied according to the following conditions:
|
||||
resolved.Exists(r => testResolve(d, r.Type)) && // There must exist a resolved trait that meets the dependency.
|
||||
!unresolved.Any(u1 => testResolve(d, u1.Type)))); // All matching traits that meet this dependency must be resolved first.
|
||||
|
||||
// Continue resolving traits as long as possible.
|
||||
// Each time we resolve some traits, this means dependencies for other traits may then be possible to satisfy in the next pass.
|
||||
// Re-evaluate the vars above until sorted
|
||||
while (more.Any())
|
||||
resolved.AddRange(more);
|
||||
|
||||
@@ -146,14 +161,14 @@ namespace OpenRA
|
||||
exceptionString += u.Type + ": { " + string.Join(", ", deps) + " }\r\n";
|
||||
}
|
||||
|
||||
throw new YamlException(exceptionString);
|
||||
throw new Exception(exceptionString);
|
||||
}
|
||||
|
||||
constructOrderCache = resolved.Select(r => r.Trait).ToList();
|
||||
return constructOrderCache;
|
||||
}
|
||||
|
||||
public static IEnumerable<Type> PrerequisitesOf(TraitInfo info)
|
||||
static IEnumerable<Type> PrerequisitesOf(ITraitInfo info)
|
||||
{
|
||||
return info
|
||||
.GetType()
|
||||
@@ -162,18 +177,23 @@ namespace OpenRA
|
||||
.Select(t => t.GetGenericArguments()[0]);
|
||||
}
|
||||
|
||||
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()
|
||||
public IEnumerable<Pair<string, Type>> GetInitKeys()
|
||||
{
|
||||
// PERF: Avoid LINQ.
|
||||
var targetTypes = default(BitSet<TargetableType>);
|
||||
foreach (var targetable in TraitInfos<ITargetableInfo>())
|
||||
targetTypes = targetTypes.Union(targetable.GetTargetTypes());
|
||||
return targetTypes;
|
||||
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 : ITraitInfo { return traits.Contains<T>(); }
|
||||
public T TraitInfo<T>() where T : ITraitInfo { return traits.Get<T>(); }
|
||||
public T TraitInfoOrDefault<T>() where T : ITraitInfo { return traits.GetOrDefault<T>(); }
|
||||
public IEnumerable<T> TraitInfos<T>() where T : ITraitInfo { return traits.WithInterface<T>(); }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
* as published by the Free Software Foundation. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.IO;
|
||||
using OpenRA.FileFormats;
|
||||
using OpenRA.FileSystem;
|
||||
|
||||
namespace OpenRA.GameRules
|
||||
@@ -19,7 +18,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,34 +30,22 @@ 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)
|
||||
public void Load()
|
||||
{
|
||||
if (!fileSystem.TryOpen(Filename, out var stream))
|
||||
if (!GlobalFileSystem.Exists(Filename))
|
||||
return;
|
||||
|
||||
try
|
||||
Exists = true;
|
||||
using (var s = GlobalFileSystem.Open(Filename))
|
||||
{
|
||||
Exists = true;
|
||||
foreach (var loader in Game.ModData.SoundLoaders)
|
||||
{
|
||||
if (loader.TryParseSound(stream, out var soundFormat))
|
||||
{
|
||||
Length = (int)soundFormat.LengthInSeconds;
|
||||
soundFormat.Dispose();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
stream.Dispose();
|
||||
if (Filename.ToLowerInvariant().EndsWith("wav"))
|
||||
Length = (int)WavLoader.WaveLength(s);
|
||||
else
|
||||
Length = (int)AudLoader.SoundLength(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,15 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
* as published by the Free Software Foundation. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using OpenRA.FileSystem;
|
||||
using OpenRA.GameRules;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Traits;
|
||||
@@ -27,28 +23,25 @@ namespace OpenRA
|
||||
public readonly IReadOnlyDictionary<string, SoundInfo> Voices;
|
||||
public readonly IReadOnlyDictionary<string, SoundInfo> Notifications;
|
||||
public readonly IReadOnlyDictionary<string, MusicInfo> Music;
|
||||
public readonly TileSet TileSet;
|
||||
public readonly SequenceProvider Sequences;
|
||||
public readonly IReadOnlyDictionary<string, MiniYamlNode> ModelSequences;
|
||||
public readonly IReadOnlyDictionary<string, TileSet> TileSets;
|
||||
public readonly IReadOnlyDictionary<string, SequenceProvider> Sequences;
|
||||
|
||||
public Ruleset(
|
||||
IReadOnlyDictionary<string, ActorInfo> actors,
|
||||
IReadOnlyDictionary<string, WeaponInfo> weapons,
|
||||
IReadOnlyDictionary<string, SoundInfo> voices,
|
||||
IReadOnlyDictionary<string, SoundInfo> notifications,
|
||||
IReadOnlyDictionary<string, MusicInfo> music,
|
||||
TileSet tileSet,
|
||||
SequenceProvider sequences,
|
||||
IReadOnlyDictionary<string, MiniYamlNode> modelSequences)
|
||||
IDictionary<string, ActorInfo> actors,
|
||||
IDictionary<string, WeaponInfo> weapons,
|
||||
IDictionary<string, SoundInfo> voices,
|
||||
IDictionary<string, SoundInfo> notifications,
|
||||
IDictionary<string, MusicInfo> music,
|
||||
IDictionary<string, TileSet> tileSets,
|
||||
IDictionary<string, SequenceProvider> sequences)
|
||||
{
|
||||
Actors = actors;
|
||||
Weapons = weapons;
|
||||
Voices = voices;
|
||||
Notifications = notifications;
|
||||
Music = music;
|
||||
TileSet = tileSet;
|
||||
Sequences = sequences;
|
||||
ModelSequences = modelSequences;
|
||||
Actors = new ReadOnlyDictionary<string, ActorInfo>(actors);
|
||||
Weapons = new ReadOnlyDictionary<string, WeaponInfo>(weapons);
|
||||
Voices = new ReadOnlyDictionary<string, SoundInfo>(voices);
|
||||
Notifications = new ReadOnlyDictionary<string, SoundInfo>(notifications);
|
||||
Music = new ReadOnlyDictionary<string, MusicInfo>(music);
|
||||
TileSets = new ReadOnlyDictionary<string, TileSet>(tileSets);
|
||||
Sequences = new ReadOnlyDictionary<string, SequenceProvider>(sequences);
|
||||
|
||||
foreach (var a in Actors.Values)
|
||||
{
|
||||
@@ -67,19 +60,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>;
|
||||
@@ -99,193 +79,5 @@ 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)
|
||||
{
|
||||
if (additional == null && defaults != null)
|
||||
return defaults;
|
||||
|
||||
IEnumerable<MiniYamlNode> yamlNodes = MiniYaml.Load(fileSystem, files, additional);
|
||||
|
||||
// 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 + ">"));
|
||||
}
|
||||
|
||||
public static Ruleset LoadDefaults(ModData modData)
|
||||
{
|
||||
var m = modData.Manifest;
|
||||
var fs = modData.DefaultFileSystem;
|
||||
|
||||
Ruleset ruleset = null;
|
||||
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));
|
||||
|
||||
var weapons = MergeOrDefault("Manifest,Weapons", fs, m.Weapons, null, null,
|
||||
k => new WeaponInfo(k.Key.ToLowerInvariant(), k.Value));
|
||||
|
||||
var voices = MergeOrDefault("Manifest,Voices", fs, m.Voices, null, null,
|
||||
k => new SoundInfo(k.Value));
|
||||
|
||||
var notifications = MergeOrDefault("Manifest,Notifications", fs, m.Notifications, null, null,
|
||||
k => new SoundInfo(k.Value));
|
||||
|
||||
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);
|
||||
};
|
||||
|
||||
if (modData.IsOnMainThread)
|
||||
{
|
||||
modData.HandleLoadingProgress();
|
||||
|
||||
var loader = new Task(f);
|
||||
loader.Start();
|
||||
|
||||
// Animate the loadscreen while we wait
|
||||
while (!loader.Wait(40))
|
||||
modData.HandleLoadingProgress();
|
||||
}
|
||||
else
|
||||
f();
|
||||
|
||||
return ruleset;
|
||||
}
|
||||
|
||||
public static Ruleset LoadDefaultsForTileSet(ModData modData, string tileSet)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
public static Ruleset Load(ModData modData, IReadOnlyFileSystem fileSystem, string tileSet,
|
||||
MiniYaml mapRules, MiniYaml mapWeapons, MiniYaml mapVoices, MiniYaml mapNotifications,
|
||||
MiniYaml mapMusic, MiniYaml mapSequences, MiniYaml mapModelSequences)
|
||||
{
|
||||
var m = modData.Manifest;
|
||||
var dr = modData.DefaultRules;
|
||||
|
||||
Ruleset ruleset = null;
|
||||
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));
|
||||
|
||||
var weapons = MergeOrDefault("Weapons", fileSystem, m.Weapons, mapWeapons, dr.Weapons,
|
||||
k => new WeaponInfo(k.Key.ToLowerInvariant(), k.Value));
|
||||
|
||||
var voices = MergeOrDefault("Voices", fileSystem, m.Voices, mapVoices, dr.Voices,
|
||||
k => new SoundInfo(k.Value));
|
||||
|
||||
var notifications = MergeOrDefault("Notifications", fileSystem, m.Notifications, mapNotifications, dr.Notifications,
|
||||
k => new SoundInfo(k.Value));
|
||||
|
||||
var music = MergeOrDefault("Music", fileSystem, m.Music, mapMusic, dr.Music,
|
||||
k => new MusicInfo(k.Key, k.Value));
|
||||
|
||||
// TODO: Add support for merging custom tileset modifications
|
||||
var ts = modData.DefaultTileSets[tileSet];
|
||||
|
||||
// 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);
|
||||
|
||||
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);
|
||||
};
|
||||
|
||||
if (modData.IsOnMainThread)
|
||||
{
|
||||
modData.HandleLoadingProgress();
|
||||
|
||||
var loader = new Task(f);
|
||||
loader.Start();
|
||||
|
||||
// Animate the loadscreen while we wait
|
||||
while (!loader.Wait(40))
|
||||
modData.HandleLoadingProgress();
|
||||
}
|
||||
else
|
||||
f();
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
159
OpenRA.Game/GameRules/RulesetCache.cs
Normal file
159
OpenRA.Game/GameRules/RulesetCache.cs
Normal file
@@ -0,0 +1,159 @@
|
||||
#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.Linq;
|
||||
using OpenRA.GameRules;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Support;
|
||||
|
||||
namespace OpenRA
|
||||
{
|
||||
public sealed class RulesetCache : IDisposable
|
||||
{
|
||||
static readonly List<MiniYamlNode> NoMapRules = new List<MiniYamlNode>();
|
||||
|
||||
readonly ModData modData;
|
||||
|
||||
readonly Dictionary<string, ActorInfo> actorCache = new Dictionary<string, ActorInfo>();
|
||||
readonly Dictionary<string, WeaponInfo> weaponCache = new Dictionary<string, WeaponInfo>();
|
||||
readonly Dictionary<string, SoundInfo> voiceCache = new Dictionary<string, SoundInfo>();
|
||||
readonly Dictionary<string, SoundInfo> notificationCache = new Dictionary<string, SoundInfo>();
|
||||
readonly Dictionary<string, MusicInfo> musicCache = new Dictionary<string, MusicInfo>();
|
||||
readonly Dictionary<string, TileSet> tileSetCache = new Dictionary<string, TileSet>();
|
||||
readonly Dictionary<string, SequenceCache> sequenceCaches = new Dictionary<string, SequenceCache>();
|
||||
|
||||
public event EventHandler LoadingProgress;
|
||||
void RaiseProgress()
|
||||
{
|
||||
if (LoadingProgress != null)
|
||||
LoadingProgress(this, new EventArgs());
|
||||
}
|
||||
|
||||
public RulesetCache(ModData modData)
|
||||
{
|
||||
this.modData = modData;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cache and return the Ruleset for a given map.
|
||||
/// If a map isn't specified then return the default mod Ruleset.
|
||||
/// </summary>
|
||||
public Ruleset Load(Map map = null)
|
||||
{
|
||||
var m = modData.Manifest;
|
||||
|
||||
Dictionary<string, ActorInfo> actors;
|
||||
Dictionary<string, WeaponInfo> weapons;
|
||||
Dictionary<string, SoundInfo> voices;
|
||||
Dictionary<string, SoundInfo> notifications;
|
||||
Dictionary<string, MusicInfo> music;
|
||||
Dictionary<string, TileSet> tileSets;
|
||||
|
||||
using (new PerfTimer("Actors"))
|
||||
actors = LoadYamlRules(actorCache, m.Rules,
|
||||
map != null ? map.RuleDefinitions : NoMapRules,
|
||||
(k, y) => new ActorInfo(k.Key.ToLowerInvariant(), k.Value, y));
|
||||
|
||||
using (new PerfTimer("Weapons"))
|
||||
weapons = LoadYamlRules(weaponCache, m.Weapons,
|
||||
map != null ? map.WeaponDefinitions : NoMapRules,
|
||||
(k, _) => new WeaponInfo(k.Key.ToLowerInvariant(), k.Value));
|
||||
|
||||
using (new PerfTimer("Voices"))
|
||||
voices = LoadYamlRules(voiceCache, m.Voices,
|
||||
map != null ? map.VoiceDefinitions : NoMapRules,
|
||||
(k, _) => new SoundInfo(k.Value));
|
||||
|
||||
using (new PerfTimer("Notifications"))
|
||||
notifications = LoadYamlRules(notificationCache, m.Notifications,
|
||||
map != null ? map.NotificationDefinitions : NoMapRules,
|
||||
(k, _) => new SoundInfo(k.Value));
|
||||
|
||||
using (new PerfTimer("Music"))
|
||||
music = LoadYamlRules(musicCache, m.Music,
|
||||
map != null ? map.MusicDefinitions : NoMapRules,
|
||||
(k, _) => new MusicInfo(k.Key, k.Value));
|
||||
|
||||
using (new PerfTimer("TileSets"))
|
||||
tileSets = LoadTileSets(tileSetCache, sequenceCaches, m.TileSets);
|
||||
|
||||
var sequences = sequenceCaches.ToDictionary(kvp => kvp.Key, kvp => new SequenceProvider(kvp.Value, map));
|
||||
return new Ruleset(actors, weapons, voices, notifications, music, tileSets, sequences);
|
||||
}
|
||||
|
||||
Dictionary<string, T> LoadYamlRules<T>(
|
||||
Dictionary<string, T> itemCache,
|
||||
string[] files, List<MiniYamlNode> nodes,
|
||||
Func<MiniYamlNode, Dictionary<string, MiniYaml>, T> f)
|
||||
{
|
||||
RaiseProgress();
|
||||
|
||||
var inputKey = string.Concat(string.Join("|", files), "|", nodes.WriteToString());
|
||||
|
||||
var mergedNodes = files
|
||||
.Select(s => MiniYaml.FromFile(s))
|
||||
.Aggregate(nodes, MiniYaml.MergeLiberal);
|
||||
|
||||
Func<MiniYamlNode, Dictionary<string, MiniYaml>, T> wrap = (wkv, wyy) =>
|
||||
{
|
||||
var key = inputKey + wkv.Value.ToLines(wkv.Key).JoinWith("|");
|
||||
T t;
|
||||
if (itemCache.TryGetValue(key, out t))
|
||||
return t;
|
||||
|
||||
t = f(wkv, wyy);
|
||||
itemCache.Add(key, t);
|
||||
|
||||
RaiseProgress();
|
||||
return t;
|
||||
};
|
||||
|
||||
var yy = mergedNodes.ToDictionary(x => x.Key, x => x.Value);
|
||||
var itemSet = mergedNodes.ToDictionaryWithConflictLog(kv => kv.Key.ToLowerInvariant(), kv => wrap(kv, yy), "LoadYamlRules", null, null);
|
||||
|
||||
RaiseProgress();
|
||||
return itemSet;
|
||||
}
|
||||
|
||||
Dictionary<string, TileSet> LoadTileSets(Dictionary<string, TileSet> itemCache, Dictionary<string, SequenceCache> sequenceCaches, string[] files)
|
||||
{
|
||||
var items = new Dictionary<string, TileSet>();
|
||||
|
||||
foreach (var file in files)
|
||||
{
|
||||
TileSet t;
|
||||
if (itemCache.TryGetValue(file, out t))
|
||||
items.Add(t.Id, t);
|
||||
else
|
||||
{
|
||||
t = new TileSet(modData, file);
|
||||
itemCache.Add(file, t);
|
||||
|
||||
// every time we load a tile set, we create a sequence cache for it
|
||||
var sc = new SequenceCache(modData, t);
|
||||
sequenceCaches.Add(t.Id, sc);
|
||||
|
||||
items.Add(t.Id, t);
|
||||
}
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (var cache in sequenceCaches.Values)
|
||||
cache.Dispose();
|
||||
sequenceCaches.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,10 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
* as published by the Free Software Foundation. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
@@ -33,39 +32,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 +52,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,11 +1,10 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
* as published by the Free Software Foundation. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
@@ -13,7 +12,6 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenRA.Effects;
|
||||
using OpenRA.Primitives;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.GameRules
|
||||
@@ -24,103 +22,43 @@ 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;
|
||||
public WPos PassiveTarget;
|
||||
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;
|
||||
|
||||
[Desc("Number of shots in a single ammo magazine.")]
|
||||
public readonly int Burst = 1;
|
||||
|
||||
public readonly bool Charges = false;
|
||||
|
||||
[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;
|
||||
@@ -194,17 +126,14 @@ namespace OpenRA.GameRules
|
||||
/// <summary>Checks if the weapon is valid against (can target) the actor.</summary>
|
||||
public bool IsValidAgainst(Actor victim, Actor firedBy)
|
||||
{
|
||||
var targetTypes = victim.GetEnabledTargetTypes();
|
||||
|
||||
if (!IsValidTarget(targetTypes))
|
||||
var targetable = victim.TraitsImplementing<ITargetable>().Where(Exts.IsTraitEnabled);
|
||||
if (!IsValidTarget(targetable.SelectMany(t => t.TargetTypes)))
|
||||
return false;
|
||||
|
||||
// PERF: Avoid LINQ.
|
||||
foreach (var warhead in Warheads)
|
||||
if (warhead.IsValidAgainst(victim, firedBy))
|
||||
return true;
|
||||
if (!Warheads.Any(w => w.IsValidAgainst(victim, firedBy)))
|
||||
return false;
|
||||
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>Checks if the weapon is valid against (can target) the frozen actor.</summary>
|
||||
@@ -220,37 +149,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,15 +1,17 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
* as published by the Free Software Foundation. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenRA.Graphics;
|
||||
|
||||
namespace OpenRA
|
||||
{
|
||||
|
||||
382
OpenRA.Game/GlobalChat.cs
Normal file
382
OpenRA.Game/GlobalChat.cs
Normal file
@@ -0,0 +1,382 @@
|
||||
#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;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using Meebey.SmartIrc4net;
|
||||
using OpenRA;
|
||||
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 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;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
Disconnect();
|
||||
connectionStatus = ChatConnectionStatus.Error;
|
||||
AddNotification("Error: You were kicked from the chat by {0}".F(e.Who));
|
||||
}
|
||||
|
||||
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,100 +1,72 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
* as published by the Free Software Foundation. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenRA.Primitives;
|
||||
using OpenRA.Support;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
{
|
||||
public class Animation
|
||||
{
|
||||
readonly int defaultTick = 40; // 25 fps == 40 ms
|
||||
public ISpriteSequence CurrentSequence { get; private set; }
|
||||
public string Name { get; private set; }
|
||||
public bool IsDecoration { get; set; }
|
||||
public bool IsDecoration = false;
|
||||
public Func<bool> Paused;
|
||||
|
||||
readonly Func<int> facingFunc;
|
||||
|
||||
int frame = 0;
|
||||
bool backwards = false;
|
||||
|
||||
string name;
|
||||
|
||||
bool tickAlways;
|
||||
|
||||
public string Name { get { return name; } }
|
||||
|
||||
readonly SequenceProvider sequenceProvider;
|
||||
readonly Func<WAngle> facingFunc;
|
||||
readonly Func<bool> paused;
|
||||
|
||||
int frame;
|
||||
bool backwards;
|
||||
bool tickAlways;
|
||||
int timeUntilNextFrame;
|
||||
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)
|
||||
: this(world, name, facingFunc, null) { }
|
||||
public Animation(World world, string name, Func<int> facingFunc)
|
||||
: this(world.Map.SequenceProvider, name, facingFunc) { }
|
||||
|
||||
public Animation(World world, string name, Func<bool> paused)
|
||||
: this(world, name, () => WAngle.Zero, paused) { }
|
||||
|
||||
public Animation(World world, string name, Func<WAngle> facingFunc, Func<bool> paused)
|
||||
public Animation(SequenceProvider sequenceProvider, string name, Func<int> facingFunc)
|
||||
{
|
||||
sequenceProvider = world.Map.Rules.Sequences;
|
||||
Name = name.ToLowerInvariant();
|
||||
this.sequenceProvider = sequenceProvider;
|
||||
this.name = name.ToLowerInvariant();
|
||||
this.tickFunc = () => { };
|
||||
this.facingFunc = facingFunc;
|
||||
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);
|
||||
}
|
||||
@@ -104,23 +76,12 @@ namespace OpenRA.Graphics
|
||||
PlayThen(sequenceName, null);
|
||||
}
|
||||
|
||||
int CurrentSequenceTickOrDefault()
|
||||
{
|
||||
const int DefaultTick = 40; // 25 fps == 40 ms
|
||||
return CurrentSequence != null ? CurrentSequence.Tick : DefaultTick;
|
||||
}
|
||||
|
||||
void PlaySequence(string sequenceName)
|
||||
{
|
||||
CurrentSequence = GetSequence(sequenceName);
|
||||
timeUntilNextFrame = CurrentSequenceTickOrDefault();
|
||||
}
|
||||
|
||||
public void PlayRepeating(string sequenceName)
|
||||
{
|
||||
backwards = false;
|
||||
tickAlways = false;
|
||||
PlaySequence(sequenceName);
|
||||
CurrentSequence = sequenceProvider.GetSequence(name, sequenceName);
|
||||
timeUntilNextFrame = CurrentSequence != null ? CurrentSequence.Tick : defaultTick;
|
||||
|
||||
frame = 0;
|
||||
tickFunc = () =>
|
||||
@@ -136,8 +97,9 @@ namespace OpenRA.Graphics
|
||||
if (!HasSequence(sequenceName))
|
||||
return false;
|
||||
|
||||
CurrentSequence = GetSequence(sequenceName);
|
||||
timeUntilNextFrame = Math.Min(CurrentSequenceTickOrDefault(), timeUntilNextFrame);
|
||||
CurrentSequence = sequenceProvider.GetSequence(name, sequenceName);
|
||||
var tick = CurrentSequence != null ? CurrentSequence.Tick : defaultTick;
|
||||
timeUntilNextFrame = Math.Min(tick, timeUntilNextFrame);
|
||||
frame %= CurrentSequence.Length;
|
||||
return true;
|
||||
}
|
||||
@@ -146,7 +108,8 @@ namespace OpenRA.Graphics
|
||||
{
|
||||
backwards = false;
|
||||
tickAlways = false;
|
||||
PlaySequence(sequenceName);
|
||||
CurrentSequence = sequenceProvider.GetSequence(name, sequenceName);
|
||||
timeUntilNextFrame = CurrentSequence != null ? CurrentSequence.Tick : defaultTick;
|
||||
|
||||
frame = 0;
|
||||
tickFunc = () =>
|
||||
@@ -156,7 +119,7 @@ namespace OpenRA.Graphics
|
||||
{
|
||||
frame = CurrentSequence.Length - 1;
|
||||
tickFunc = () => { };
|
||||
after?.Invoke();
|
||||
if (after != null) after();
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -171,7 +134,8 @@ namespace OpenRA.Graphics
|
||||
{
|
||||
backwards = false;
|
||||
tickAlways = true;
|
||||
PlaySequence(sequenceName);
|
||||
CurrentSequence = sequenceProvider.GetSequence(name, sequenceName);
|
||||
timeUntilNextFrame = CurrentSequence != null ? CurrentSequence.Tick : defaultTick;
|
||||
|
||||
frame = func();
|
||||
tickFunc = () => frame = func();
|
||||
@@ -180,7 +144,8 @@ namespace OpenRA.Graphics
|
||||
public void PlayFetchDirection(string sequenceName, Func<int> direction)
|
||||
{
|
||||
tickAlways = false;
|
||||
PlaySequence(sequenceName);
|
||||
CurrentSequence = sequenceProvider.GetSequence(name, sequenceName);
|
||||
timeUntilNextFrame = CurrentSequence != null ? CurrentSequence.Tick : defaultTick;
|
||||
|
||||
frame = 0;
|
||||
tickFunc = () =>
|
||||
@@ -194,12 +159,17 @@ namespace OpenRA.Graphics
|
||||
};
|
||||
}
|
||||
|
||||
int timeUntilNextFrame;
|
||||
Action tickFunc;
|
||||
|
||||
public void Tick()
|
||||
{
|
||||
if (paused == null || !paused())
|
||||
if (Paused == null || !Paused())
|
||||
Tick(40); // tick one frame
|
||||
}
|
||||
|
||||
public bool HasSequence(string seq) { return sequenceProvider.HasSequence(name, seq); }
|
||||
|
||||
public void Tick(int t)
|
||||
{
|
||||
if (tickAlways)
|
||||
@@ -210,7 +180,7 @@ namespace OpenRA.Graphics
|
||||
while (timeUntilNextFrame <= 0)
|
||||
{
|
||||
tickFunc();
|
||||
timeUntilNextFrame += CurrentSequenceTickOrDefault();
|
||||
timeUntilNextFrame += CurrentSequence != null ? CurrentSequence.Tick : defaultTick;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -219,19 +189,17 @@ namespace OpenRA.Graphics
|
||||
{
|
||||
newImage = newImage.ToLowerInvariant();
|
||||
|
||||
if (Name != newImage)
|
||||
if (name != newImage)
|
||||
{
|
||||
Name = newImage;
|
||||
name = newImage.ToLowerInvariant();
|
||||
if (!ReplaceAnim(CurrentSequence.Name))
|
||||
ReplaceAnim(newAnimIfMissing);
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasSequence(string seq) { return sequenceProvider.HasSequence(Name, seq); }
|
||||
|
||||
public ISpriteSequence GetSequence(string sequenceName)
|
||||
{
|
||||
return sequenceProvider.GetSequence(Name, sequenceName);
|
||||
return sequenceProvider.GetSequence(name, sequenceName);
|
||||
}
|
||||
|
||||
public string GetRandomExistingSequence(string[] sequences, MersenneTwister random)
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
* as published by the Free Software Foundation. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using OpenRA.Primitives;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
{
|
||||
@@ -19,23 +18,25 @@ namespace OpenRA.Graphics
|
||||
public readonly Animation Animation;
|
||||
public readonly Func<WVec> OffsetFunc;
|
||||
public readonly Func<bool> DisableFunc;
|
||||
public readonly Func<bool> Paused;
|
||||
public readonly Func<WPos, int> ZOffset;
|
||||
|
||||
public AnimationWithOffset(Animation a, Func<WVec> offset, Func<bool> disable)
|
||||
: this(a, offset, disable, null) { }
|
||||
: this(a, offset, disable, () => false, null) { }
|
||||
|
||||
public AnimationWithOffset(Animation a, Func<WVec> offset, Func<bool> disable, int zOffset)
|
||||
: this(a, offset, disable, _ => zOffset) { }
|
||||
: this(a, offset, disable, () => false, _ => zOffset) { }
|
||||
|
||||
public AnimationWithOffset(Animation a, Func<WVec> offset, Func<bool> disable, Func<WPos, int> zOffset)
|
||||
public AnimationWithOffset(Animation a, Func<WVec> offset, Func<bool> disable, Func<bool> pause, Func<WPos, int> zOffset)
|
||||
{
|
||||
Animation = a;
|
||||
OffsetFunc = offset;
|
||||
DisableFunc = disable;
|
||||
ZOffset = zOffset;
|
||||
this.Animation = a;
|
||||
this.Animation.Paused = pause;
|
||||
this.OffsetFunc = offset;
|
||||
this.DisableFunc = disable;
|
||||
this.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 +45,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);
|
||||
return new AnimationWithOffset(a, null, null, null, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,285 +1,126 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
* as published by the Free Software Foundation. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#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)
|
||||
public static void Initialize(IEnumerable<string> chromeFiles)
|
||||
{
|
||||
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)));
|
||||
var chrome = chromeFiles.Select(s => MiniYaml.FromFile(s)).Aggregate(MiniYaml.MergeLiberal);
|
||||
|
||||
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)
|
||||
{
|
||||
if (string.IsNullOrEmpty(collectionName))
|
||||
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
|
||||
{
|
||||
sheet = new Sheet(SheetType.BGRA, mi.Src);
|
||||
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,18 +1,19 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
* 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;
|
||||
using OpenRA.Traits;
|
||||
using OpenRA.FileSystem;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
{
|
||||
@@ -23,25 +24,25 @@ namespace OpenRA.Graphics
|
||||
|
||||
public CursorProvider(ModData modData)
|
||||
{
|
||||
var fileSystem = modData.DefaultFileSystem;
|
||||
var sequenceYaml = MiniYaml.Merge(modData.Manifest.Cursors.Select(
|
||||
s => MiniYaml.FromStream(fileSystem.Open(s), s)));
|
||||
var sequenceFiles = modData.Manifest.Cursors;
|
||||
var sequences = new MiniYaml(null, sequenceFiles.Select(s => MiniYaml.FromFile(s)).Aggregate(MiniYaml.MergeLiberal));
|
||||
var shadowIndex = new int[] { };
|
||||
|
||||
var nodesDict = new MiniYaml(null, sequenceYaml).ToDictionary();
|
||||
var nodesDict = sequences.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(GlobalFileSystem.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 frameCache = new FrameCache(modData.SpriteLoaders);
|
||||
var cursors = new Dictionary<string, CursorSequence>();
|
||||
foreach (var s in nodesDict["Cursors"].Nodes)
|
||||
foreach (var sequence in s.Value.Nodes)
|
||||
@@ -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,11 +1,10 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
* as published by the Free Software Foundation. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
@@ -31,10 +30,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 +39,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);
|
||||
}
|
||||
}
|
||||
|
||||
107
OpenRA.Game/Graphics/HSLColor.cs
Normal file
107
OpenRA.Game/Graphics/HSLColor.cs
Normal file
@@ -0,0 +1,107 @@
|
||||
#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.Drawing;
|
||||
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 static HSLColor FromRGB(int r, int g, int b)
|
||||
{
|
||||
var c = Color.FromArgb(r, g, b);
|
||||
var h = (byte)((c.GetHue() / 360.0f) * 255);
|
||||
var s = (byte)(c.GetSaturation() * 255);
|
||||
var l = (byte)(c.GetBrightness() * 255);
|
||||
return new HSLColor(h, s, l);
|
||||
}
|
||||
|
||||
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 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 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
138
OpenRA.Game/Graphics/HardwareCursor.cs
Normal file
138
OpenRA.Game/Graphics/HardwareCursor.cs
Normal file
@@ -0,0 +1,138 @@
|
||||
#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;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
{
|
||||
public 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,11 +1,10 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* 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, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
* as published by the Free Software Foundation. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
@@ -27,7 +26,7 @@ namespace OpenRA.Graphics
|
||||
|
||||
public HardwarePalette()
|
||||
{
|
||||
Texture = Game.Renderer.Context.CreateTexture();
|
||||
Texture = Game.Renderer.Device.CreateTexture();
|
||||
readOnlyModifiablePalettes = modifiablePalettes.AsReadOnly();
|
||||
}
|
||||
|
||||
@@ -38,16 +37,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;
|
||||
}
|
||||
|
||||
140
OpenRA.Game/Graphics/IGraphicsDevice.cs
Normal file
140
OpenRA.Game/Graphics/IGraphicsDevice.cs
Normal file
@@ -0,0 +1,140 @@
|
||||
#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.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();
|
||||
void DrawPrimitives(PrimitiveType type, int firstVertex, int numVertices);
|
||||
|
||||
void SetLineWidth(float width);
|
||||
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);
|
||||
}
|
||||
|
||||
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,
|
||||
QuadList,
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
174
OpenRA.Game/Graphics/LineRenderer.cs
Normal file
174
OpenRA.Game/Graphics/LineRenderer.cs
Normal file
@@ -0,0 +1,174 @@
|
||||
#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;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
{
|
||||
public class LineRenderer : Renderer.IBatchRenderer
|
||||
{
|
||||
static readonly float2 Offset = new float2(0.5f, 0.5f);
|
||||
|
||||
readonly Renderer renderer;
|
||||
readonly IShader shader;
|
||||
readonly Action renderAction;
|
||||
|
||||
readonly Vertex[] vertices;
|
||||
int nv = 0;
|
||||
|
||||
float lineWidth = 1f;
|
||||
|
||||
public LineRenderer(Renderer renderer, IShader shader)
|
||||
{
|
||||
this.renderer = renderer;
|
||||
this.shader = shader;
|
||||
vertices = new Vertex[renderer.TempBufferSize];
|
||||
renderAction = () =>
|
||||
{
|
||||
renderer.SetLineWidth(LineWidth);
|
||||
renderer.DrawBatch(vertices, nv, PrimitiveType.LineList);
|
||||
};
|
||||
}
|
||||
|
||||
public float LineWidth
|
||||
{
|
||||
get
|
||||
{
|
||||
return lineWidth;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (LineWidth != value)
|
||||
Flush();
|
||||
|
||||
lineWidth = value;
|
||||
}
|
||||
}
|
||||
|
||||
public void Flush()
|
||||
{
|
||||
if (nv > 0)
|
||||
{
|
||||
renderer.Device.SetBlendMode(BlendMode.Alpha);
|
||||
shader.Render(renderAction);
|
||||
renderer.Device.SetBlendMode(BlendMode.None);
|
||||
|
||||
nv = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public void DrawRect(float2 tl, float2 br, Color c)
|
||||
{
|
||||
var tr = new float2(br.X, tl.Y);
|
||||
var bl = new float2(tl.X, br.Y);
|
||||
DrawLine(tl, tr, c);
|
||||
DrawLine(tl, bl, c);
|
||||
DrawLine(tr, br, c);
|
||||
DrawLine(bl, br, c);
|
||||
}
|
||||
|
||||
public void DrawLine(float2 start, float2 end, Color color)
|
||||
{
|
||||
renderer.CurrentBatchRenderer = this;
|
||||
|
||||
if (nv + 2 > renderer.TempBufferSize)
|
||||
Flush();
|
||||
|
||||
color = Util.PremultiplyAlpha(color);
|
||||
var r = color.R / 255.0f;
|
||||
var g = color.G / 255.0f;
|
||||
var b = color.B / 255.0f;
|
||||
var a = color.A / 255.0f;
|
||||
vertices[nv++] = new Vertex(start + Offset, r, g, b, a);
|
||||
vertices[nv++] = new Vertex(end + Offset, r, g, b, a);
|
||||
}
|
||||
|
||||
public void DrawLine(float2 start, float2 end, Color startColor, Color endColor)
|
||||
{
|
||||
renderer.CurrentBatchRenderer = this;
|
||||
|
||||
if (nv + 2 > renderer.TempBufferSize)
|
||||
Flush();
|
||||
|
||||
startColor = Util.PremultiplyAlpha(startColor);
|
||||
var r = startColor.R / 255.0f;
|
||||
var g = startColor.G / 255.0f;
|
||||
var b = startColor.B / 255.0f;
|
||||
var a = startColor.A / 255.0f;
|
||||
vertices[nv++] = new Vertex(start + Offset, r, g, b, a);
|
||||
|
||||
endColor = Util.PremultiplyAlpha(endColor);
|
||||
r = endColor.R / 255.0f;
|
||||
g = endColor.G / 255.0f;
|
||||
b = endColor.B / 255.0f;
|
||||
a = endColor.A / 255.0f;
|
||||
vertices[nv++] = new Vertex(end + Offset, r, g, b, a);
|
||||
}
|
||||
|
||||
public void DrawLineStrip(IEnumerable<float2> points, Color color)
|
||||
{
|
||||
renderer.CurrentBatchRenderer = this;
|
||||
|
||||
color = Util.PremultiplyAlpha(color);
|
||||
var r = color.R / 255.0f;
|
||||
var g = color.G / 255.0f;
|
||||
var b = color.B / 255.0f;
|
||||
var a = color.A / 255.0f;
|
||||
|
||||
var first = true;
|
||||
var prev = new Vertex();
|
||||
foreach (var point in points)
|
||||
{
|
||||
if (first)
|
||||
{
|
||||
first = false;
|
||||
prev = new Vertex(point + Offset, r, g, b, a);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (nv + 2 > renderer.TempBufferSize)
|
||||
Flush();
|
||||
|
||||
vertices[nv++] = prev;
|
||||
prev = new Vertex(point + Offset, r, g, b, a);
|
||||
vertices[nv++] = prev;
|
||||
}
|
||||
}
|
||||
|
||||
public void FillRect(RectangleF r, Color color)
|
||||
{
|
||||
for (var y = r.Top; y < r.Bottom; y++)
|
||||
DrawLine(new float2(r.Left, y), new float2(r.Right, y), color);
|
||||
}
|
||||
|
||||
public void FillEllipse(RectangleF r, Color color)
|
||||
{
|
||||
var a = (r.Right - r.Left) / 2;
|
||||
var b = (r.Bottom - r.Top) / 2;
|
||||
var xc = (r.Right + r.Left) / 2;
|
||||
var yc = (r.Bottom + r.Top) / 2;
|
||||
for (var y = r.Top; y <= r.Bottom; y++)
|
||||
{
|
||||
var dx = a * (float)Math.Sqrt(1 - (y - yc) * (y - yc) / b / b);
|
||||
DrawLine(new float2(xc - dx, y), new float2(xc + dx, y), color);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetViewportParams(Size screen, float zoom, int2 scroll)
|
||||
{
|
||||
shader.SetVec("Scroll", scroll.X, scroll.Y);
|
||||
shader.SetVec("r1", zoom * 2f / screen.Width, -zoom * 2f / screen.Height);
|
||||
shader.SetVec("r2", -1, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user