Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 72 additions & 0 deletions .github/scripts/steam-workshop.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# Logs in to Steam with steamcmd using a cached login and, in Upload mode, updates the Workshop item.
#
# Environment (repository secrets):
# STEAM_USERNAME Steam account that owns the Workshop item.
# STEAM_CONFIG_VDF base64 of config/config.vdf from a steamcmd that has logged in to that account,
# so no password or Steam Guard confirmation is needed. Refresh it when it expires.
param(
[Parameter(Mandatory)] [ValidateSet('LoginCheck', 'Upload')] [string] $Mode,
[string] $ContentFolder,
[string] $ChangeNote = ''
)

$ErrorActionPreference = 'Stop'

if (-not $env:STEAM_USERNAME -or -not $env:STEAM_CONFIG_VDF) {
throw 'The STEAM_USERNAME and STEAM_CONFIG_VDF secrets are required.'
}

$steamcmdDir = Join-Path $env:RUNNER_TEMP 'steamcmd'
New-Item -ItemType Directory -Force $steamcmdDir, "$steamcmdDir\config" | Out-Null
Invoke-WebRequest https://steamcdn-a.akamaihd.net/client/installer/steamcmd.zip -OutFile "$steamcmdDir\steamcmd.zip"
Expand-Archive "$steamcmdDir\steamcmd.zip" $steamcmdDir -Force
# The first run only updates steamcmd itself.
& "$steamcmdDir\steamcmd.exe" +quit | Out-Null

$steamArgs = @('+login', $env:STEAM_USERNAME)

if ($Mode -eq 'Upload') {
# Forward slashes avoid any question of backslash escaping in the VDF.
$content = (Resolve-Path $ContentFolder).Path -replace '\\', '/'
$publishedId = (Get-Content (Join-Path $ContentFolder 'About/PublishedFileId.txt') -Raw).Trim()
$note = $ChangeNote -replace '"', "'"

# Only the content and change note are set; title, description, preview and visibility stay as they are.
$itemVdf = Join-Path $env:RUNNER_TEMP 'workshop_item.vdf'
@"
"workshopitem"
{
"appid" "294100"
"publishedfileid" "$publishedId"
"contentfolder" "$content"
"changenote" "$note"
}
"@ | Set-Content -Path $itemVdf -Encoding utf8

Write-Host "Updating Workshop item $publishedId from $content"
$steamArgs += @('+workshop_build_item', $itemVdf)
}

$steamArgs += '+quit'

[IO.File]::WriteAllBytes("$steamcmdDir\config\config.vdf", [Convert]::FromBase64String($env:STEAM_CONFIG_VDF))
try {
# Print each line as it arrives and keep it for the checks below.
$output = & "$steamcmdDir\steamcmd.exe" @steamArgs | ForEach-Object { Write-Host $_; $_ }
$exitCode = $LASTEXITCODE
}
finally {
Remove-Item -Force "$steamcmdDir\config\config.vdf" -ErrorAction SilentlyContinue
}

$text = $output -join "`n"
if ($text -notmatch 'Waiting for user info\.\.\.OK') {
throw 'Steam login failed. The cached login was probably rejected; log in with steamcmd locally and update STEAM_CONFIG_VDF.'
}
if ($Mode -eq 'Upload' -and ($exitCode -ne 0 -or $text -match 'ERROR|FAILED')) {
throw "Workshop upload failed (steamcmd exit code $exitCode)."
}

Write-Host "Steam $Mode succeeded."
# steamcmd's exit code was checked above; do not let the runner fail the step on it.
$global:LASTEXITCODE = 0
134 changes: 134 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
name: release

# Assemblies are built locally (the 1.7 target needs game files) and committed.
# Pushing a tag like v1.2.0 checks, tests and packages the mod, creates a GitHub release and
# updates the Steam Workshop item. Tags with a suffix (v1.2.0-beta.1) become GitHub pre-releases
# and are not uploaded to the Workshop.
# Running the workflow manually only checks that the Steam login in the secrets still works.

on:
push:
tags: ['v*']
workflow_dispatch:

permissions:
contents: write

jobs:
release:
if: github.event_name == 'push'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0 # the assembly freshness check reads history

- uses: actions/setup-dotnet@v6
with:
dotnet-version: 10.0.x

- name: Check version
run: |
version="${GITHUB_REF_NAME#v}"
version="${version%%-*}"
manifest=$(python3 -c "import xml.etree.ElementTree as ET; print(ET.parse('About/Manifest.xml').getroot().findtext('version'))")
project=$(python3 -c "import xml.etree.ElementTree as ET; print(ET.parse('Source/ModMismatchFormatter/ModMismatchFormatter.csproj').getroot().findtext('PropertyGroup/Version'))")
echo "tag $version, About/Manifest.xml $manifest, csproj $project"
if [ "$version" != "$manifest" ] || [ "$version" != "$project" ]; then
echo "::error::Tag $GITHUB_REF_NAME must match About/Manifest.xml ($manifest) and the csproj <Version> ($project)."
exit 1
fi

- name: Check assemblies
run: |
source_time=$(git log -1 --format=%ct -- Source/ModMismatchFormatter)
for v in 1.6 1.7; do
dll="$v/Assemblies/ModMisMatchWindowPatch.dll"
if [ ! -f "$dll" ]; then
echo "::error::$dll is missing. Build locally and commit it."
exit 1
fi
# A warning, not an error: a source change that does not change the IL leaves the DLL untouched.
if [ "$(git log -1 --format=%ct -- "$dll")" -lt "$source_time" ]; then
echo "::warning::$dll was committed before the last change in Source/ModMismatchFormatter. Make sure it was rebuilt."
fi
done

- name: Validate mod XML
run: |
python3 - <<'PY'
import pathlib, sys, xml.etree.ElementTree as ET
files = [p for p in pathlib.Path('.').rglob('*.xml') if not any(part in ('bin', 'obj', '.git') for part in p.parts)]
failed = False
for path in files:
try:
ET.parse(path)
except ET.ParseError as e:
print(f'::error file={path}::{e}')
failed = True
print(f'checked {len(files)} XML files')
sys.exit(1 if failed else 0)
PY

- name: Test
run: dotnet test --project Source/ModMismatchFormatter.Tests/ModMismatchFormatter.Tests.csproj -c Release

- name: Package
run: |
shopt -s nullglob
stage=dist/ModMismatchFormatter
mkdir -p "$stage"
# Mod content only: metadata, translations and the per-version assembly folders.
cp -r About Languages LoadFolders.xml README.md [0-9]* v[0-9]* "$stage/"
asset="dist/ModMismatchFormatter-$GITHUB_REF_NAME.zip"
(cd dist && zip -qr "../$asset" ModMismatchFormatter)
unzip -l "$asset"
echo "ASSET=$asset" >> "$GITHUB_ENV"

- uses: actions/upload-artifact@v7
with:
name: mod
path: dist/ModMismatchFormatter

- name: Create release
env:
GH_TOKEN: ${{ github.token }}
run: |
flags=()
if [[ "$GITHUB_REF_NAME" == *-* ]]; then flags+=(--prerelease); fi
gh release create "$GITHUB_REF_NAME" "$ASSET" --title "$GITHUB_REF_NAME" --generate-notes --verify-tag "${flags[@]}"

workshop:
needs: release
if: ${{ !contains(github.ref_name, '-') }}
runs-on: windows-latest
# A rejected cached login makes steamcmd wait for a password or a phone confirmation.
timeout-minutes: 15
steps:
- uses: actions/checkout@v7

- uses: actions/download-artifact@v8
with:
name: mod
path: mod

- name: Upload to Steam Workshop
shell: pwsh
env:
STEAM_USERNAME: ${{ secrets.STEAM_USERNAME }}
STEAM_CONFIG_VDF: ${{ secrets.STEAM_CONFIG_VDF }}
run: ./.github/scripts/steam-workshop.ps1 -Mode Upload -ContentFolder mod -ChangeNote "${{ github.ref_name }}: https://github.com/${{ github.repository }}/releases/tag/${{ github.ref_name }}"

steam-login-check:
if: github.event_name == 'workflow_dispatch'
runs-on: windows-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v7

- name: Check Steam login
shell: pwsh
env:
STEAM_USERNAME: ${{ secrets.STEAM_USERNAME }}
STEAM_CONFIG_VDF: ${{ secrets.STEAM_CONFIG_VDF }}
run: ./.github/scripts/steam-workshop.ps1 -Mode LoginCheck
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -355,4 +355,6 @@ MigrationBackup/
.ionide/

#custom folders
obj
obj
# Machine-specific MSBuild overrides
Source/Local.props
Binary file modified 1.6/Assemblies/ModMisMatchWindowPatch.dll
Binary file not shown.
Binary file added 1.7/Assemblies/ModMisMatchWindowPatch.dll
Binary file not shown.
1 change: 1 addition & 0 deletions About/About.xml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
<li>1.4</li>
<li>1.5</li>
<li>1.6</li>
<li>1.7</li>
</supportedVersions>
<packageId>Madeline.ModMismatchFormatter</packageId>
<description>==english description==
Expand Down
2 changes: 1 addition & 1 deletion About/Manifest.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Manifest>
<identifier>Better ModMismatch Window</identifier>
<version>1.1.3</version>
<version>1.2.0</version>
</Manifest>
11 changes: 11 additions & 0 deletions Languages/ChineseSimplified/Keyed/Translation.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,15 @@
<writeMetaDescription>将Mod元数据写入存档(元数据包含Mod版本等信息)。推荐启用。</writeMetaDescription>
<!-- contributed by HawnHan -->

<ModMismatchFormatter_Added>新增:{0}</ModMismatchFormatter_Added>
<ModMismatchFormatter_Removed>移除:{0}</ModMismatchFormatter_Removed>
<ModMismatchFormatter_Moved>加载顺序变更:{0}</ModMismatchFormatter_Moved>
<ModMismatchFormatter_VersionChanged>版本变更:{0}</ModMismatchFormatter_VersionChanged>
<ModMismatchFormatter_ShowUnchanged>显示未变更的Mod</ModMismatchFormatter_ShowUnchanged>
<ModMismatchFormatter_ShowOrderChanges>显示加载顺序变更</ModMismatchFormatter_ShowOrderChanges>
<ModMismatchFormatter_State_Added>存档中没有此Mod,是之后新增的。</ModMismatchFormatter_State_Added>
<ModMismatchFormatter_State_Removed>存档中有此Mod,但当前未载入。</ModMismatchFormatter_State_Removed>
<ModMismatchFormatter_State_MovedFrom>存档时的位置。此Mod仍在载入,但加载顺序已改变。</ModMismatchFormatter_State_MovedFrom>
<ModMismatchFormatter_State_MovedTo>当前位置。存档时此Mod位于其他位置。</ModMismatchFormatter_State_MovedTo>
<ModMismatchFormatter_State_VersionChanged>与存档中记录的版本不同。</ModMismatchFormatter_State_VersionChanged>
</LanguageData>
11 changes: 11 additions & 0 deletions Languages/English/Keyed/Translation.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,15 @@
<useVersionCompareDescription>Enable Version checking between saved file and current version. check steam workshop for the detailed description.</useVersionCompareDescription>
<EnableWritingCustomVersionHeader>Enable writing mod metadata to the save</EnableWritingCustomVersionHeader>
<writeMetaDescription>Enable writing mod metadata to the save (mod versions, etc). enable recommended</writeMetaDescription>
<ModMismatchFormatter_Added>Added: {0}</ModMismatchFormatter_Added>
<ModMismatchFormatter_Removed>Removed: {0}</ModMismatchFormatter_Removed>
<ModMismatchFormatter_Moved>Load order changed: {0}</ModMismatchFormatter_Moved>
<ModMismatchFormatter_VersionChanged>Version changed: {0}</ModMismatchFormatter_VersionChanged>
<ModMismatchFormatter_ShowUnchanged>Show unchanged mods</ModMismatchFormatter_ShowUnchanged>
<ModMismatchFormatter_ShowOrderChanges>Show load order changes</ModMismatchFormatter_ShowOrderChanges>
<ModMismatchFormatter_State_Added>Not in the save; added since.</ModMismatchFormatter_State_Added>
<ModMismatchFormatter_State_Removed>In the save, but not running now.</ModMismatchFormatter_State_Removed>
<ModMismatchFormatter_State_MovedFrom>Position in the save. This mod still runs, but at a different place in the load order now.</ModMismatchFormatter_State_MovedFrom>
<ModMismatchFormatter_State_MovedTo>Current position. This mod was at a different place in the save's load order.</ModMismatchFormatter_State_MovedTo>
<ModMismatchFormatter_State_VersionChanged>The version differs from the one recorded in the save.</ModMismatchFormatter_State_VersionChanged>
</LanguageData>
11 changes: 11 additions & 0 deletions Languages/Japanese/Keyed/Translation.xml
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,15 @@
<EnableWritingCustomVersionHeader>セーブデータにModのメタデータを書き込むことを有効化する:</EnableWritingCustomVersionHeader>
<writeMetaDescription>セーブデータへのModのメタデータ(各Modのバージョン情報など)の書き込みを有効化します。比較時にエラーを起こさないために有効化することを推奨</writeMetaDescription>
<!-- contributed by Proxyer -->
<ModMismatchFormatter_Added>追加: {0}</ModMismatchFormatter_Added>
<ModMismatchFormatter_Removed>削除: {0}</ModMismatchFormatter_Removed>
<ModMismatchFormatter_Moved>ロード順の変更: {0}</ModMismatchFormatter_Moved>
<ModMismatchFormatter_VersionChanged>バージョン変更: {0}</ModMismatchFormatter_VersionChanged>
<ModMismatchFormatter_ShowUnchanged>変更のないModを表示</ModMismatchFormatter_ShowUnchanged>
<ModMismatchFormatter_ShowOrderChanges>ロード順の変更を表示</ModMismatchFormatter_ShowOrderChanges>
<ModMismatchFormatter_State_Added>セーブにはなく、その後追加されたModです。</ModMismatchFormatter_State_Added>
<ModMismatchFormatter_State_Removed>セーブにはありますが、現在は読み込まれていません。</ModMismatchFormatter_State_Removed>
<ModMismatchFormatter_State_MovedFrom>セーブ時の位置です。このModは現在も読み込まれていますが、ロード順が変わりました。</ModMismatchFormatter_State_MovedFrom>
<ModMismatchFormatter_State_MovedTo>現在の位置です。セーブ時は別の位置にありました。</ModMismatchFormatter_State_MovedTo>
<ModMismatchFormatter_State_VersionChanged>セーブに記録されたバージョンと異なります。</ModMismatchFormatter_State_VersionChanged>
</LanguageData>
13 changes: 12 additions & 1 deletion Languages/Korean/Keyed/Translation.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,18 @@
<ActiveModListTitle>현재 실행중인 모드배열</ActiveModListTitle>
<SettingsLabel>모드 불일치 창 설정</SettingsLabel>
<EnableVersionChecking>버전 비교 활성화</EnableVersionChecking>
<useVersionCompareDescription>버전 비교 기능을 활성화 합니다. Manifest.xml에서 데이터를 읽어옵니다.</useVersionCompareDescription>
<useVersionCompareDescription>버전 비교 기능을 활성화합니다. 버전은 About/Manifest.xml 또는 About.xml의 modVersion에서 읽어옵니다.</useVersionCompareDescription>
<EnableWritingCustomVersionHeader>세이브 파일에 모드 메타데이터 기록하기</EnableWritingCustomVersionHeader>
<writeMetaDescription>세이브 파일에 모드에 대한 메타데이터를 기록합니다. 사용하는 것을 권장합니다.</writeMetaDescription>
<ModMismatchFormatter_Added>추가됨: {0}</ModMismatchFormatter_Added>
<ModMismatchFormatter_Removed>제거됨: {0}</ModMismatchFormatter_Removed>
<ModMismatchFormatter_Moved>로드 순서 변경: {0}</ModMismatchFormatter_Moved>
<ModMismatchFormatter_VersionChanged>버전 변경: {0}</ModMismatchFormatter_VersionChanged>
<ModMismatchFormatter_ShowUnchanged>변경 없는 모드 표시</ModMismatchFormatter_ShowUnchanged>
<ModMismatchFormatter_ShowOrderChanges>로드 순서 변경 표시</ModMismatchFormatter_ShowOrderChanges>
<ModMismatchFormatter_State_Added>세이브에는 없고, 이후에 추가된 모드입니다.</ModMismatchFormatter_State_Added>
<ModMismatchFormatter_State_Removed>세이브에는 있지만, 지금은 실행 중이 아닌 모드입니다.</ModMismatchFormatter_State_Removed>
<ModMismatchFormatter_State_MovedFrom>세이브에 기록된 위치입니다. 이 모드는 지금도 실행 중이지만 로드 순서가 바뀌었습니다.</ModMismatchFormatter_State_MovedFrom>
<ModMismatchFormatter_State_MovedTo>현재 위치입니다. 세이브에서는 로드 순서상 다른 위치에 있었습니다.</ModMismatchFormatter_State_MovedTo>
<ModMismatchFormatter_State_VersionChanged>세이브에 기록된 버전과 다릅니다.</ModMismatchFormatter_State_VersionChanged>
</LanguageData>
8 changes: 8 additions & 0 deletions LoadFolders.xml
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,12 @@
<li>/</li>
<li>1.5</li>
</v1.5>
<v1.6>
<li>/</li>
<li>1.6</li>
</v1.6>
<v1.7>
<li>/</li>
<li>1.7</li>
</v1.7>
</loadFolders>
57 changes: 55 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,61 @@
![RW_Ver](https://img.shields.io/badge/Rimworld-1.0%201.1%201.2-brightgreen)
![RW_Ver](https://img.shields.io/badge/Rimworld-1.0%20~%201.7-brightgreen)
![GitHub tag (latest SemVer)](https://img.shields.io/github/v/tag/zzzz465/ModMisMatchFormatter?label=mod%20version)
![downloadCounter](https://raster.shields.io/steam/downloads/1872244972.png?color=blue&label=Workshop&logo=steam)
![SteamSub](https://img.shields.io/steam/subscriptions/1872244972)
[![build](https://github.com/zzzz465/ModMisMatchFormatter/actions/workflows/build.yml/badge.svg)](https://github.com/zzzz465/ModMisMatchFormatter/actions/workflows/build.yml)

simple mod for comparing two mismatching mod list

https://steamcommunity.com/sharedfiles/filedetails/?id=1872244972
https://steamcommunity.com/sharedfiles/filedetails/?id=1872244972

## Building

Requires the .NET 10 SDK.

```
dotnet build Source/ModMismatchFormatter.slnx -c Release
dotnet test --project Source/ModMismatchFormatter.Tests/ModMismatchFormatter.Tests.csproj
```

| Target | RimWorld | Output | References |
| --- | --- | --- | --- |
| `net472` | 1.6 (Unity) | `1.6/Assemblies` | `Krafs.Rimworld.Ref`, `Lib.Harmony.Ref` 2.4 |
| `net10.0` | 1.7 (Waylith) | `1.7/Assemblies` | local game files, `Lib.Harmony.Ref` 3.0 |

There is no reference package for RimWorld 1.7 yet, so the 1.7 target is only built when
`RimWorld17Dir` points at a folder containing the 1.7 `Assembly-CSharp.dll` and `UnityStubs.dll`.
It is picked up automatically from the default Steam path when that install is on the unstable
branch; otherwise set it in `Source/Local.props` (git-ignored):

```xml
<Project>
<PropertyGroup>
<RimWorld17Dir>D:\Games\RimWorld-1.7</RimWorld17Dir>
</PropertyGroup>
</Project>
```

## Releasing

The assemblies are built locally and committed; CI never downloads the game.

1. Bump the version in `About/Manifest.xml` and `<Version>` in `Source/ModMismatchFormatter/ModMismatchFormatter.csproj`.
2. Build both targets (`dotnet build Source/ModMismatchFormatter.slnx -c Release`, with RimWorld 1.7
available) and commit `1.6/Assemblies` and `1.7/Assemblies`.
3. Push a tag: `git tag v1.2.0 && git push origin v1.2.0`. Tags with a suffix (`v1.2.0-beta.1`) become pre-releases.

`.github/workflows/release.yml` then checks that the tag matches both versions, that the committed
assemblies exist (and warns if they predate the last source change), validates the XML, runs the
tests, creates a GitHub release with `ModMismatchFormatter-<tag>.zip` (mod files only), and updates
the Steam Workshop item from `About/PublishedFileId.txt` with the same files. Pre-release tags skip
the Workshop.

The Workshop upload uses steamcmd with a cached login, from two repository secrets:

- `STEAM_USERNAME`: the account that owns the Workshop item.
- `STEAM_CONFIG_VDF`: base64 of `config/config.vdf` from a steamcmd that has logged in to that account.
When it expires, log in with steamcmd again and update the secret.

Running the workflow manually (Actions → release → Run workflow) only checks that this login still works.

The assemblies for 1.0–1.5 are prebuilt and no longer rebuilt from source.
Loading