Compare commits

...

6 Commits

34 changed files with 690 additions and 121 deletions

View File

@ -0,0 +1,175 @@
---
name: th1-base
description: TH1 project baseline engineering guide for Unity client changes: assembly boundaries, HybridCLR hotfix rules, YooAsset/AssetBundle resource rules, MemoryPack/AOT serialization safety, build packaging flow, and routing to domain skills. Use before broad TH1 Unity code/resource/build changes, especially when touching Scripts, asmdefs, HybridCLR, hotfix DLLs, StreamingAssets, BundleResources, Resources.Load replacement, YooAsset, MemoryPack, generated config, build panels, or package verification.
---
# TH1 Base Engineering
## Core Rule
Treat TH1 as a hot-update Unity project with a small AOT shell, a large `TH1.Hotfix` gameplay layer, and YooAsset-managed resources.
Most feature work may live in hotfix code, but platform bootstrap, HybridCLR/YooAsset initialization, AOT metadata loading, and build packaging must remain stable and boring. Do not solve a hotfix/AOT/resource issue with one-off special cases when a shared entrypoint or generator fix is possible.
## Skill Routing
Start here for baseline constraints, then add the domain skill that owns the behavior:
- `th1-ios-migration`: iOS/IL2CPP, HybridCLR, YooAsset, Steam platform isolation, touch/mobile, packaging.
- `th1-action-logic`: gameplay actions, AI action flow, turn state, replay, deterministic skill side effects.
- `th1-network-sync`: Steam lobby/P2P, GameStart/ForceUpdate, `MapData` network payloads, multiplayer resume.
- `th1-game-archive`: local save/load, `GameRecord`, archive files, resume, bug report archive payloads.
- `th1-multilingual`: localization export/import, `Multilingual.asset`, Workshop language mods.
- `th1-server-backend`: OSS/STS/function compute/upload/auth backend work.
- `th1-online-debug`: production logs, CrashSight/UnityLogError, obfuscated stack decode.
- `th1-crashsight-daily`: daily CrashSight triage and reports.
- `.agents/skills/th1-config-guide`: Excel config generation, `GenerateCS`/`ExcelPartial`, MemoryPack config rules.
- `.agents/skills/th1-ui-patterns`: View/Controller UI work.
- `.agents/skills/th1-ai-nodes`: NodeCanvas AI behavior tree nodes.
- `.agents/skills/th1-anim-scope`: attack animation scope-aware renderer updates.
If multiple skills apply, use this skill first, then the narrow domain skill.
## First Moves
- Run `git status --short`; preserve unrelated user/Unity changes.
- Identify whether the change touches AOT shell, hotfix code, resources/AB, serialization, build pipeline, platform services, or gameplay behavior.
- For Unity code changes, inspect nearby asmdefs and namespace ownership before moving files.
- For generated files, find and update the generator/template instead of hand-editing output, unless the user explicitly asks for a one-off generated output patch.
- Do not modify MemoryPack compatibility, obfuscation behavior, or generated config formats without explicit confirmation.
## Assembly Boundaries
Current target shape:
- AOT shell: minimal startup scene and bootstrap code. It loads AOT metadata, loads `TH1.Hotfix.dll.bytes`, initializes YooAsset, loads the game scene, then invokes hotfix entrypoints.
- `TH1.Hotfix`: gameplay logic, UI logic, renderer logic, config consumers, AI nodes, save/game/archive logic where possible.
- `TH1_Resource`: resource facade and compatibility wrappers. Gameplay code should load through these wrappers, not direct YooAsset calls scattered everywhere.
- Editor assemblies: build panels, HybridCLR/YooAsset tools, config/export windows. They must not leak into runtime/hotfix assemblies.
Rules:
- The Build Settings scene should be the empty/init AOT shell scene. The real game scene is loaded through YooAsset after hotfix and metadata are ready.
- Do not put hotfix `MonoBehaviour` references directly in the shell scene.
- If a prefab/scene contains hotfix scripts, it must be loaded only after the hotfix assembly is loaded.
- Keep startup contracts stable and explicit. Prefer direct typed calls for required runtime packages over reflection that silently skips failures.
- Hotfix code must not directly reference editor-only assemblies or platform-specific SDKs that are unavailable on the target platform.
## HybridCLR Rules
- After hotfix code, AOT dependency, or generic-heavy serialization changes, rerun the HybridCLR prepare flow before packaging.
- Required artifacts are platform-specific: hotfix DLL bytes and AOT metadata under `StreamingAssets/HybridCLR`.
- Load supplemental metadata before starting hotfix gameplay:
- `mscorlib.dll.bytes`
- `System.dll.bytes`
- `System.Core.dll.bytes`
- `MemoryPack.dll.bytes`
- `System.Runtime.CompilerServices.Unsafe.dll.bytes`
- Use `HybridCLR.RuntimeApi.LoadMetadataForAOTAssembly(..., HomologousImageMode.SuperSet)` for the required load path; do not hide missing RuntimeApi as a non-fatal reflection miss in player builds.
- Do not manually copy stale hotfix DLLs. Use the project build panel/command so generated DLL, AOT metadata, and build status stay consistent.
- If NodeCanvas/ParadoxNotion serialized task types are moved into hotfix, refresh/regenerate the relevant type metadata before packaging.
## MemoryPack And AOT Serialization
MemoryPack format compatibility is high risk. Preserve layout unless explicitly changing format.
Runtime/hotfix code should avoid direct generic MemoryPack entrypoints when the serialized type may live in hotfix:
```csharp
// Prefer project wrapper or Type API.
TH1Serialization.Serialize(value);
TH1Serialization.Deserialize<T>(bytes);
MemoryPackSerializer.Serialize(typeof(T), value);
MemoryPackSerializer.Deserialize(typeof(T), bytes);
```
Avoid adding new runtime code like:
```csharp
MemoryPackSerializer.Serialize<T>(value);
MemoryPackSerializer.Deserialize<T>(bytes);
```
Rules:
- Use `TH1_Logic.Tools.TH1Serialization` for gameplay/save/network/runtime data.
- For generators that cannot reference Unity runtime code, generate MemoryPack `Type` API calls.
- Do not reorder or remove existing `[MemoryPackInclude]` members in persisted/networked types. Append new fields.
- For old fields no longer used, keep compatibility placeholders when later fields would shift.
- Treat `MapData`, `NetData`, `GameRecord`, `GameRecordData`, config bytes, and network messages as compatibility-sensitive.
- If generated config code needs a change, update `ExcelExport/ExportTemplate_*.txt` and regenerate/sync output.
## YooAsset And AB Rules
Resources are moving to AB-first loading:
- Use `Assets/BundleResources` as the packaged resource root.
- Prefer stable logical addresses. Do not make gameplay code depend on platform-specific bundle paths.
- Replace `Resources.Load` through `TH1Resource.ResourceLoader`/resource cache wrappers, not scattered direct YooAsset calls.
- Editor may run simulate mode; PC/iOS packages should run actual built-in AB package logic.
- Rebuild YooAsset bundles after moving, adding, deleting, or renaming packaged assets.
- Keep Standalone and iOS bundles separate. Do not reuse PC bundle assumptions for iOS compression or platform output.
- Do not put invalid source files in AB roots: backups (`*.BAK`), raw local-only config, editor-only files, or unknown icon/temp files should live outside `BundleResources`.
- If an address is missing, fix the collector/address/generator path rather than adding local `Resources` fallbacks.
## Build And Packaging Flow
For migration-era PC IL2CPP smoke builds, use the project one-click flow where possible:
1. Configure target platform/build settings.
2. Configure HybridCLR.
3. Run HybridCLR `GenerateAll`.
4. Build hotfix DLL and copy hotfix/AOT metadata to `StreamingAssets`.
5. Build YooAsset built-in `DefaultPackage`.
6. Build Windows IL2CPP player.
7. Launch player and scan the log for startup red errors.
Manual Unity packaging gotchas:
- After scene list changes, use full `Build`/`Clean Build`; do not use `Build Scripts Only`.
- If you only need a runnable PC package, keep `Create Visual Studio Solution` off unless explicitly debugging the generated solution.
- Avoid `Script Debugging` for automated smoke player runs; it can wait for a managed debugger.
- If Unity exports a Visual Studio solution for IL2CPP, MSBuild may need current Windows SDK/toolset retargeting.
- Generated build artifacts such as `HybridCLRGenerate/AOTGenericReferences.cs` may change after HybridCLR GenerateAll; commit them only when intentionally refreshed.
- Obfuscator logs and local build output should not be committed unless explicitly part of the requested change.
Startup smoke success markers:
- AOT metadata logs show `OK`.
- `TH1.Hotfix` loads.
- YooAsset `DefaultPackage` initializes.
- Game scene loads from YooAsset.
- Hotfix `Main` starts.
- No `MissingMethodException`, `Type Request Error`, `NullReferenceException`, or `LogError` during startup.
## Verification
For ordinary Unity runtime C#:
```powershell
dotnet build Unity/Assembly-CSharp.csproj --no-restore
```
For editor/build/config tooling:
```powershell
dotnet build Unity/Assembly-CSharp-Editor.csproj --no-restore
```
Known caveat: this repo can hit Unity package compile errors outside changed code when building the full editor csproj from dotnet. If that happens, report it clearly and verify the narrower runtime/tool project or Unity Editor compile path instead.
For config generator changes:
```powershell
dotnet run --project ExcelExport/ExcelExport.csproj -- 1
dotnet build ExcelExport/ExcelExport.csproj --no-restore
```
Before finishing migration/build work, inspect:
```powershell
rg -n "Resources\\.Load|MemoryPackSerializer\\.Serialize<|MemoryPackSerializer\\.Deserialize<|using Steamworks|BuildScriptsOnly" Unity/Assets/Scripts
```
Only treat matches as problems after checking context; editor-only tools and intentionally platform-bound Steam implementations may be valid.

View File

@ -7,6 +7,7 @@ This repository is a Unity 2022.3 LTS / ET Framework turn-based strategy game. T
- For architecture questions, read the `MD/GameMDFramework/00-*` overview document and the relevant subsystem document before editing.
- For code navigation, read `Unity/graphify-out/GRAPH_REPORT.md` for Unity code and `graphify-out/GRAPH_REPORT.md` for whole-repository context.
- If a task touches action execution, networking, localization, online errors, backend upload, or CrashSight reports, use the matching `.codex/skills/th1-*` skill first.
- For broad TH1 Unity/code/resource/build changes, use `.codex/skills/th1-base` first, then layer the narrower business skill on top.
- Claude-era context remains in `.claude/` and `Unity/Assets/Scripts/CLAUDE.md`; treat it as historical source material, not the active entrypoint.
## Project Shape
@ -36,6 +37,7 @@ This repository is a Unity 2022.3 LTS / ET Framework turn-based strategy game. T
## Codex Skills And Agents
- Prefer `.codex/skills` for current project-specific workflows:
- `th1-base`: baseline Unity engineering rules for assembly boundaries, HybridCLR hotfix, YooAsset/AB, MemoryPack/AOT serialization, generated config, and build/package verification.
- `th1-action-logic`: action execution, AI action flow, turn actions, and action-triggered skills.
- `th1-network-sync`: Steam lobby, P2P, multiplayer save/recovery, action sync, and deterministic network behavior.
- `th1-multilingual`: localization import/export, active text scanning, duplicate IDs, and translation diagnostics.

View File

@ -14,7 +14,7 @@ namespace ExcelConfig
public override void Init(byte[] data)
{
Dict = MemoryPackSerializer.Deserialize<Dictionary<int, (ConfigName)>>(data);
Dict = (Dictionary<int, (ConfigName)>)MemoryPackSerializer.Deserialize(typeof(Dictionary<int, (ConfigName)>), data)!;
}
}

View File

@ -23,7 +23,7 @@ namespace ExcelConfig
public override byte[] GetData()
{
return MemoryPackSerializer.Serialize(Dict);
return MemoryPackSerializer.Serialize(typeof(Dictionary<int, (ConfigName)>), Dict);
}
}

View File

@ -14,7 +14,7 @@ namespace ExcelConfig
public override void Init(byte[] data)
{
Dict = MemoryPackSerializer.Deserialize<Dictionary<int, AIConfig>>(data);
Dict = (Dictionary<int, AIConfig>)MemoryPackSerializer.Deserialize(typeof(Dictionary<int, AIConfig>), data)!;
}
}

View File

@ -14,7 +14,7 @@ namespace ExcelConfig
public override void Init(byte[] data)
{
Dict = MemoryPackSerializer.Deserialize<Dictionary<int, GeoDesc>>(data);
Dict = (Dictionary<int, GeoDesc>)MemoryPackSerializer.Deserialize(typeof(Dictionary<int, GeoDesc>), data)!;
}
}

View File

@ -14,7 +14,7 @@ namespace ExcelConfig
public override void Init(byte[] data)
{
Dict = MemoryPackSerializer.Deserialize<Dictionary<int, Moment>>(data);
Dict = (Dictionary<int, Moment>)MemoryPackSerializer.Deserialize(typeof(Dictionary<int, Moment>), data)!;
}
}

View File

@ -23,7 +23,7 @@ namespace ExcelConfig
public override byte[] GetData()
{
return MemoryPackSerializer.Serialize(Dict);
return MemoryPackSerializer.Serialize(typeof(Dictionary<int, AIConfig>), Dict);
}
}

View File

@ -23,7 +23,7 @@ namespace ExcelConfig
public override byte[] GetData()
{
return MemoryPackSerializer.Serialize(Dict);
return MemoryPackSerializer.Serialize(typeof(Dictionary<int, GeoDesc>), Dict);
}
}

View File

@ -23,7 +23,7 @@ namespace ExcelConfig
public override byte[] GetData()
{
return MemoryPackSerializer.Serialize(Dict);
return MemoryPackSerializer.Serialize(typeof(Dictionary<int, Moment>), Dict);
}
}

View File

@ -103,7 +103,6 @@ public class AOTGenericReferences : UnityEngine.MonoBehaviour
// System.Action<Logic.AI.MatchResult>
// System.Action<Logic.Multilingual.TranslationEntry>
// System.Action<Logic.Multilingual.WorkshopModLoader.SubscribedModEntry>
// System.Action<MemoryPack.Internal.BufferSegment>
// System.Action<MomentImagePack>
// System.Action<RuntimeData.UnitFullType>
// System.Action<SkillViewTypeColor>
@ -240,7 +239,6 @@ public class AOTGenericReferences : UnityEngine.MonoBehaviour
// System.Buffers.ArrayPool<byte>
// System.Buffers.ConfigurableArrayPool.Bucket<byte>
// System.Buffers.ConfigurableArrayPool<byte>
// System.Buffers.IBufferWriter<byte>
// System.Buffers.MemoryManager<byte>
// System.Buffers.MemoryManager<float>
// System.Buffers.TlsOverPerCoreLockedStacksArrayPool.LockedStack<byte>
@ -262,7 +260,6 @@ public class AOTGenericReferences : UnityEngine.MonoBehaviour
// System.Collections.Generic.ArraySortHelper<Logic.AI.MatchResult>
// System.Collections.Generic.ArraySortHelper<Logic.Multilingual.TranslationEntry>
// System.Collections.Generic.ArraySortHelper<Logic.Multilingual.WorkshopModLoader.SubscribedModEntry>
// System.Collections.Generic.ArraySortHelper<MemoryPack.Internal.BufferSegment>
// System.Collections.Generic.ArraySortHelper<MomentImagePack>
// System.Collections.Generic.ArraySortHelper<RuntimeData.UnitFullType>
// System.Collections.Generic.ArraySortHelper<SkillViewTypeColor>
@ -305,7 +302,6 @@ public class AOTGenericReferences : UnityEngine.MonoBehaviour
// System.Collections.Generic.Comparer<Logic.AI.MatchResult>
// System.Collections.Generic.Comparer<Logic.Multilingual.TranslationEntry>
// System.Collections.Generic.Comparer<Logic.Multilingual.WorkshopModLoader.SubscribedModEntry>
// System.Collections.Generic.Comparer<MemoryPack.Internal.BufferSegment>
// System.Collections.Generic.Comparer<MomentImagePack>
// System.Collections.Generic.Comparer<RuntimeData.UnitFullType>
// System.Collections.Generic.Comparer<SkillViewTypeColor>
@ -349,7 +345,6 @@ public class AOTGenericReferences : UnityEngine.MonoBehaviour
// System.Collections.Generic.ComparisonComparer<Logic.AI.MatchResult>
// System.Collections.Generic.ComparisonComparer<Logic.Multilingual.TranslationEntry>
// System.Collections.Generic.ComparisonComparer<Logic.Multilingual.WorkshopModLoader.SubscribedModEntry>
// System.Collections.Generic.ComparisonComparer<MemoryPack.Internal.BufferSegment>
// System.Collections.Generic.ComparisonComparer<MomentImagePack>
// System.Collections.Generic.ComparisonComparer<RuntimeData.UnitFullType>
// System.Collections.Generic.ComparisonComparer<SkillViewTypeColor>
@ -611,7 +606,6 @@ public class AOTGenericReferences : UnityEngine.MonoBehaviour
// System.Collections.Generic.ICollection<Logic.AI.MatchResult>
// System.Collections.Generic.ICollection<Logic.Multilingual.TranslationEntry>
// System.Collections.Generic.ICollection<Logic.Multilingual.WorkshopModLoader.SubscribedModEntry>
// System.Collections.Generic.ICollection<MemoryPack.Internal.BufferSegment>
// System.Collections.Generic.ICollection<MomentImagePack>
// System.Collections.Generic.ICollection<RuntimeData.UnitFullType>
// System.Collections.Generic.ICollection<SkillViewTypeColor>
@ -682,7 +676,6 @@ public class AOTGenericReferences : UnityEngine.MonoBehaviour
// System.Collections.Generic.IComparer<Logic.AI.MatchResult>
// System.Collections.Generic.IComparer<Logic.Multilingual.TranslationEntry>
// System.Collections.Generic.IComparer<Logic.Multilingual.WorkshopModLoader.SubscribedModEntry>
// System.Collections.Generic.IComparer<MemoryPack.Internal.BufferSegment>
// System.Collections.Generic.IComparer<MomentImagePack>
// System.Collections.Generic.IComparer<RuntimeData.UnitFullType>
// System.Collections.Generic.IComparer<SkillViewTypeColor>
@ -727,7 +720,6 @@ public class AOTGenericReferences : UnityEngine.MonoBehaviour
// System.Collections.Generic.IEnumerable<Logic.AI.MatchResult>
// System.Collections.Generic.IEnumerable<Logic.Multilingual.TranslationEntry>
// System.Collections.Generic.IEnumerable<Logic.Multilingual.WorkshopModLoader.SubscribedModEntry>
// System.Collections.Generic.IEnumerable<MemoryPack.Internal.BufferSegment>
// System.Collections.Generic.IEnumerable<MomentImagePack>
// System.Collections.Generic.IEnumerable<RuntimeData.UnitFullType>
// System.Collections.Generic.IEnumerable<SkillViewTypeColor>
@ -798,7 +790,6 @@ public class AOTGenericReferences : UnityEngine.MonoBehaviour
// System.Collections.Generic.IEnumerator<Logic.AI.MatchResult>
// System.Collections.Generic.IEnumerator<Logic.Multilingual.TranslationEntry>
// System.Collections.Generic.IEnumerator<Logic.Multilingual.WorkshopModLoader.SubscribedModEntry>
// System.Collections.Generic.IEnumerator<MemoryPack.Internal.BufferSegment>
// System.Collections.Generic.IEnumerator<MomentImagePack>
// System.Collections.Generic.IEnumerator<RuntimeData.UnitFullType>
// System.Collections.Generic.IEnumerator<SkillViewTypeColor>
@ -882,7 +873,6 @@ public class AOTGenericReferences : UnityEngine.MonoBehaviour
// System.Collections.Generic.IList<Logic.AI.MatchResult>
// System.Collections.Generic.IList<Logic.Multilingual.TranslationEntry>
// System.Collections.Generic.IList<Logic.Multilingual.WorkshopModLoader.SubscribedModEntry>
// System.Collections.Generic.IList<MemoryPack.Internal.BufferSegment>
// System.Collections.Generic.IList<MomentImagePack>
// System.Collections.Generic.IList<RuntimeData.UnitFullType>
// System.Collections.Generic.IList<SkillViewTypeColor>
@ -969,7 +959,6 @@ public class AOTGenericReferences : UnityEngine.MonoBehaviour
// System.Collections.Generic.List.Enumerator<Logic.AI.MatchResult>
// System.Collections.Generic.List.Enumerator<Logic.Multilingual.TranslationEntry>
// System.Collections.Generic.List.Enumerator<Logic.Multilingual.WorkshopModLoader.SubscribedModEntry>
// System.Collections.Generic.List.Enumerator<MemoryPack.Internal.BufferSegment>
// System.Collections.Generic.List.Enumerator<MomentImagePack>
// System.Collections.Generic.List.Enumerator<RuntimeData.UnitFullType>
// System.Collections.Generic.List.Enumerator<SkillViewTypeColor>
@ -1012,7 +1001,6 @@ public class AOTGenericReferences : UnityEngine.MonoBehaviour
// System.Collections.Generic.List<Logic.AI.MatchResult>
// System.Collections.Generic.List<Logic.Multilingual.TranslationEntry>
// System.Collections.Generic.List<Logic.Multilingual.WorkshopModLoader.SubscribedModEntry>
// System.Collections.Generic.List<MemoryPack.Internal.BufferSegment>
// System.Collections.Generic.List<MomentImagePack>
// System.Collections.Generic.List<RuntimeData.UnitFullType>
// System.Collections.Generic.List<SkillViewTypeColor>
@ -1055,7 +1043,6 @@ public class AOTGenericReferences : UnityEngine.MonoBehaviour
// System.Collections.Generic.ObjectComparer<Logic.AI.MatchResult>
// System.Collections.Generic.ObjectComparer<Logic.Multilingual.TranslationEntry>
// System.Collections.Generic.ObjectComparer<Logic.Multilingual.WorkshopModLoader.SubscribedModEntry>
// System.Collections.Generic.ObjectComparer<MemoryPack.Internal.BufferSegment>
// System.Collections.Generic.ObjectComparer<MomentImagePack>
// System.Collections.Generic.ObjectComparer<RuntimeData.UnitFullType>
// System.Collections.Generic.ObjectComparer<SkillViewTypeColor>
@ -1154,7 +1141,6 @@ public class AOTGenericReferences : UnityEngine.MonoBehaviour
// System.Collections.ObjectModel.ReadOnlyCollection<Logic.AI.MatchResult>
// System.Collections.ObjectModel.ReadOnlyCollection<Logic.Multilingual.TranslationEntry>
// System.Collections.ObjectModel.ReadOnlyCollection<Logic.Multilingual.WorkshopModLoader.SubscribedModEntry>
// System.Collections.ObjectModel.ReadOnlyCollection<MemoryPack.Internal.BufferSegment>
// System.Collections.ObjectModel.ReadOnlyCollection<MomentImagePack>
// System.Collections.ObjectModel.ReadOnlyCollection<RuntimeData.UnitFullType>
// System.Collections.ObjectModel.ReadOnlyCollection<SkillViewTypeColor>
@ -1197,7 +1183,6 @@ public class AOTGenericReferences : UnityEngine.MonoBehaviour
// System.Comparison<Logic.AI.MatchResult>
// System.Comparison<Logic.Multilingual.TranslationEntry>
// System.Comparison<Logic.Multilingual.WorkshopModLoader.SubscribedModEntry>
// System.Comparison<MemoryPack.Internal.BufferSegment>
// System.Comparison<MomentImagePack>
// System.Comparison<RuntimeData.UnitFullType>
// System.Comparison<SkillViewTypeColor>
@ -1313,7 +1298,6 @@ public class AOTGenericReferences : UnityEngine.MonoBehaviour
// System.Predicate<Logic.AI.MatchResult>
// System.Predicate<Logic.Multilingual.TranslationEntry>
// System.Predicate<Logic.Multilingual.WorkshopModLoader.SubscribedModEntry>
// System.Predicate<MemoryPack.Internal.BufferSegment>
// System.Predicate<MomentImagePack>
// System.Predicate<RuntimeData.UnitFullType>
// System.Predicate<SkillViewTypeColor>
@ -1458,9 +1442,7 @@ public class AOTGenericReferences : UnityEngine.MonoBehaviour
// byte& MemoryPack.Internal.MemoryMarshalEx.GetArrayDataReference<byte>(byte[])
// int& MemoryPack.Internal.MemoryMarshalEx.GetArrayDataReference<int>(int[])
// object& MemoryPack.Internal.MemoryMarshalEx.GetArrayDataReference<object>(object[])
// MemoryPack.MemoryPackFormatter<int> MemoryPack.MemoryPackFormatterProvider.GetFormatter<int>()
// MemoryPack.MemoryPackFormatter<object> MemoryPack.MemoryPackFormatterProvider.GetFormatter<object>()
// MemoryPack.MemoryPackFormatter<uint> MemoryPack.MemoryPackFormatterProvider.GetFormatter<uint>()
// bool MemoryPack.MemoryPackFormatterProvider.IsRegistered<byte>()
// bool MemoryPack.MemoryPackFormatterProvider.IsRegistered<int>()
// bool MemoryPack.MemoryPackFormatterProvider.IsRegistered<object>()
@ -1557,21 +1539,10 @@ public class AOTGenericReferences : UnityEngine.MonoBehaviour
// int[] MemoryPack.MemoryPackReader.ReadUnmanagedArray<int>()
// System.Void MemoryPack.MemoryPackReader.ReadValue<object>(object&)
// object MemoryPack.MemoryPackReader.ReadValue<object>()
// int MemoryPack.MemoryPackSerializer.Deserialize<object>(System.ReadOnlySpan<byte>,object&,MemoryPack.MemoryPackSerializerOptions)
// object MemoryPack.MemoryPackSerializer.Deserialize<object>(System.ReadOnlySpan<byte>,MemoryPack.MemoryPackSerializerOptions)
// System.Void MemoryPack.MemoryPackSerializer.Serialize<int>(MemoryPack.MemoryPackWriter&,int&)
// System.Void MemoryPack.MemoryPackSerializer.Serialize<object>(MemoryPack.MemoryPackWriter&,object&)
// System.Void MemoryPack.MemoryPackSerializer.Serialize<object>(System.Buffers.IBufferWriter<byte>&,object&,MemoryPack.MemoryPackSerializerOptions)
// System.Void MemoryPack.MemoryPackSerializer.Serialize<uint>(MemoryPack.MemoryPackWriter&,uint&)
// byte[] MemoryPack.MemoryPackSerializer.Serialize<int>(int&,MemoryPack.MemoryPackSerializerOptions)
// byte[] MemoryPack.MemoryPackSerializer.Serialize<object>(object&,MemoryPack.MemoryPackSerializerOptions)
// byte[] MemoryPack.MemoryPackSerializer.Serialize<uint>(uint&,MemoryPack.MemoryPackSerializerOptions)
// System.Void MemoryPack.MemoryPackWriter.DangerousWriteUnmanagedArray<byte>(byte[])
// System.Void MemoryPack.MemoryPackWriter.DangerousWriteUnmanagedArray<int>(int[])
// System.Void MemoryPack.MemoryPackWriter.DangerousWriteUnmanagedArray<object>(object[])
// MemoryPack.IMemoryPackFormatter<int> MemoryPack.MemoryPackWriter.GetFormatter<int>()
// MemoryPack.IMemoryPackFormatter<object> MemoryPack.MemoryPackWriter.GetFormatter<object>()
// MemoryPack.IMemoryPackFormatter<uint> MemoryPack.MemoryPackWriter.GetFormatter<uint>()
// System.Void MemoryPack.MemoryPackWriter.WriteArray<object>(object[])
// System.Void MemoryPack.MemoryPackWriter.WritePackable<object>(object&)
// System.Void MemoryPack.MemoryPackWriter.WriteUnmanaged<byte,RuntimeData.UnitFullType,int,int,uint,int,int,int,RuntimeData.UnitFullType,RuntimeData.UnitFullType,int,int,uint,float,RuntimeData.UnitFullType>(byte&,RuntimeData.UnitFullType&,int&,int&,uint&,int&,int&,int&,RuntimeData.UnitFullType&,RuntimeData.UnitFullType&,int&,int&,uint&,float&,RuntimeData.UnitFullType&)
@ -1653,9 +1624,7 @@ public class AOTGenericReferences : UnityEngine.MonoBehaviour
// System.Void MemoryPack.MemoryPackWriter.WriteUnmanagedWithObjectHeader<ulong,uint,uint,uint,byte,int,int,byte,byte,byte>(byte,ulong&,uint&,uint&,uint&,byte&,int&,int&,byte&,byte&,byte&)
// System.Void MemoryPack.MemoryPackWriter.WriteUnmanagedWithObjectHeader<ulong,ulong>(byte,ulong&,ulong&)
// System.Void MemoryPack.MemoryPackWriter.WriteUnmanagedWithObjectHeader<ulong>(byte,ulong&)
// System.Void MemoryPack.MemoryPackWriter.WriteValue<int>(int&)
// System.Void MemoryPack.MemoryPackWriter.WriteValue<object>(object&)
// System.Void MemoryPack.MemoryPackWriter.WriteValue<uint>(uint&)
// Microsoft.ML.OnnxRuntime.Tensors.Tensor<float> Microsoft.ML.OnnxRuntime.NamedOnnxValue.AsTensor<float>()
// Microsoft.ML.OnnxRuntime.NamedOnnxValue Microsoft.ML.OnnxRuntime.NamedOnnxValue.CreateFromTensor<float>(string,Microsoft.ML.OnnxRuntime.Tensors.Tensor<float>)
// NodeCanvas.Framework.Variable<object> NodeCanvas.Framework.IBlackboardExtensions.GetVariable<object>(NodeCanvas.Framework.IBlackboard,string)
@ -1740,9 +1709,7 @@ public class AOTGenericReferences : UnityEngine.MonoBehaviour
// System.Void System.Runtime.CompilerServices.AsyncTaskMethodBuilder<System.ValueTuple<byte,object>>.Start<object>(object&)
// System.Void System.Runtime.CompilerServices.AsyncTaskMethodBuilder<byte>.Start<object>(object&)
// System.Void System.Runtime.CompilerServices.AsyncTaskMethodBuilder<object>.Start<object>(object&)
// bool System.Runtime.CompilerServices.RuntimeHelpers.IsReferenceOrContainsReferences<int>()
// bool System.Runtime.CompilerServices.RuntimeHelpers.IsReferenceOrContainsReferences<object>()
// bool System.Runtime.CompilerServices.RuntimeHelpers.IsReferenceOrContainsReferences<uint>()
// byte& System.Runtime.CompilerServices.Unsafe.Add<byte>(byte&,int)
// byte& System.Runtime.CompilerServices.Unsafe.As<byte,byte>(byte&)
// byte& System.Runtime.CompilerServices.Unsafe.As<int,byte>(int&)
@ -1750,16 +1717,13 @@ public class AOTGenericReferences : UnityEngine.MonoBehaviour
// object& System.Runtime.CompilerServices.Unsafe.As<object,object>(object&)
// object& System.Runtime.CompilerServices.Unsafe.As<object,object>(object&)
// System.Void* System.Runtime.CompilerServices.Unsafe.AsPointer<object>(object&)
// int& System.Runtime.CompilerServices.Unsafe.AsRef<int>(int&)
// object& System.Runtime.CompilerServices.Unsafe.AsRef<object>(object&)
// uint& System.Runtime.CompilerServices.Unsafe.AsRef<uint>(uint&)
// Empire System.Runtime.CompilerServices.Unsafe.ReadUnaligned<Empire>(byte&)
// RuntimeData.UnitFullType System.Runtime.CompilerServices.Unsafe.ReadUnaligned<RuntimeData.UnitFullType>(byte&)
// byte System.Runtime.CompilerServices.Unsafe.ReadUnaligned<byte>(byte&)
// float System.Runtime.CompilerServices.Unsafe.ReadUnaligned<float>(byte&)
// int System.Runtime.CompilerServices.Unsafe.ReadUnaligned<int>(byte&)
// long System.Runtime.CompilerServices.Unsafe.ReadUnaligned<long>(byte&)
// object System.Runtime.CompilerServices.Unsafe.ReadUnaligned<object>(byte&)
// uint System.Runtime.CompilerServices.Unsafe.ReadUnaligned<uint>(byte&)
// ulong System.Runtime.CompilerServices.Unsafe.ReadUnaligned<ulong>(byte&)
// int System.Runtime.CompilerServices.Unsafe.SizeOf<Empire>()
@ -1777,11 +1741,9 @@ public class AOTGenericReferences : UnityEngine.MonoBehaviour
// System.Void System.Runtime.CompilerServices.Unsafe.WriteUnaligned<float>(byte&,float)
// System.Void System.Runtime.CompilerServices.Unsafe.WriteUnaligned<int>(byte&,int)
// System.Void System.Runtime.CompilerServices.Unsafe.WriteUnaligned<long>(byte&,long)
// System.Void System.Runtime.CompilerServices.Unsafe.WriteUnaligned<object>(byte&,object)
// System.Void System.Runtime.CompilerServices.Unsafe.WriteUnaligned<uint>(byte&,uint)
// System.Void System.Runtime.CompilerServices.Unsafe.WriteUnaligned<ulong>(byte&,ulong)
// Steamworks.SteamNetworkingMessage_t System.Runtime.InteropServices.Marshal.PtrToStructure<Steamworks.SteamNetworkingMessage_t>(System.IntPtr)
// byte& System.Runtime.InteropServices.MemoryMarshal.GetReference<byte>(System.ReadOnlySpan<byte>)
// byte& System.Runtime.InteropServices.MemoryMarshal.GetReference<byte>(System.Span<byte>)
// int& System.Runtime.InteropServices.MemoryMarshal.GetReference<int>(System.Span<int>)
// object& System.Runtime.InteropServices.MemoryMarshal.GetReference<object>(System.Span<object>)

View File

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using HybridCLR;
using UnityEngine;
namespace TH1_Logic.Hotfix
@ -85,26 +86,6 @@ namespace TH1_Logic.Hotfix
{
if (_aotMetadataLoaded) return;
var runtimeApiType = FindType("HybridCLR.RuntimeApi");
var modeType = FindType("HybridCLR.HomologousImageMode");
if (runtimeApiType == null || modeType == null)
{
return;
}
var method = runtimeApiType.GetMethod(
"LoadMetadataForAOTAssembly",
BindingFlags.Public | BindingFlags.Static,
null,
new[] { typeof(byte[]), modeType },
null);
if (method == null)
{
Debug.LogWarning("[TH1.Hotfix] HybridCLR RuntimeApi.LoadMetadataForAOTAssembly not found.");
return;
}
var mode = Enum.Parse(modeType, "SuperSet");
var aotDir = Path.Combine(root, HotfixManifest.AotMetadataFolderName);
var allLoaded = true;
@ -118,7 +99,7 @@ namespace TH1_Logic.Hotfix
continue;
}
var result = method.Invoke(null, new object[] { File.ReadAllBytes(path), mode });
var result = RuntimeApi.LoadMetadataForAOTAssembly(File.ReadAllBytes(path), HomologousImageMode.SuperSet);
Debug.Log($"[TH1.Hotfix] Load AOT metadata {fileName}: {result}");
}

View File

@ -14,7 +14,7 @@ namespace ExcelConfig
public override void Init(byte[] data)
{
Dict = MemoryPackSerializer.Deserialize<Dictionary<int, AIConfig>>(data);
Dict = (Dictionary<int, AIConfig>)MemoryPackSerializer.Deserialize(typeof(Dictionary<int, AIConfig>), data)!;
}
}

View File

@ -14,7 +14,7 @@ namespace ExcelConfig
public override void Init(byte[] data)
{
Dict = MemoryPackSerializer.Deserialize<Dictionary<int, GeoDesc>>(data);
Dict = (Dictionary<int, GeoDesc>)MemoryPackSerializer.Deserialize(typeof(Dictionary<int, GeoDesc>), data)!;
}
}

View File

@ -14,7 +14,7 @@ namespace ExcelConfig
public override void Init(byte[] data)
{
Dict = MemoryPackSerializer.Deserialize<Dictionary<int, Moment>>(data);
Dict = (Dictionary<int, Moment>)MemoryPackSerializer.Deserialize(typeof(Dictionary<int, Moment>), data)!;
}
}

View File

@ -1,4 +1,4 @@
/*
/*
* @Author:
* @Description:
* @Date: 20250403 11:04:31
@ -114,8 +114,8 @@ namespace RuntimeData
public MapConfig CreateRuntimeCopy()
{
var bytes = MemoryPackSerializer.Serialize(this);
return MemoryPackSerializer.Deserialize<MapConfig>(bytes);
var bytes = TH1Serialization.Serialize(this);
return TH1Serialization.Deserialize<MapConfig>(bytes);
}
// MemoryPack 反序列化之后的后处理
@ -968,7 +968,7 @@ namespace RuntimeData
{
if (!LobbyManager.Instance.Lobby.IsInLobby()) return;
byte[] bytes = MemoryPackSerializer.Serialize(this);
byte[] bytes = TH1Serialization.Serialize(this);
var hash128 = new Hash128();
hash128.Append(bytes);
var hash = hash128.ToString();
@ -2295,7 +2295,7 @@ namespace RuntimeData
{
try
{
byte[] bytes = MemoryPackSerializer.Serialize(config);
byte[] bytes = TH1Serialization.Serialize(config);
if (FileTools.SafeWriteFile(path, bytes)) return true;
retryCount--;
if (retryCount <= 0)
@ -2343,7 +2343,7 @@ namespace RuntimeData
try
{
byte[] bytes = File.ReadAllBytes(path);
var config = MemoryPackSerializer.Deserialize<MapConfig>(bytes);
var config = TH1Serialization.Deserialize<MapConfig>(bytes);
config.ClearMultiCivs();
return config;
@ -2376,14 +2376,14 @@ namespace RuntimeData
// 新版所有 begin / quick_continue / continue / end 都通过 GameArchiveManager 管理。
private static byte[] SerializeMapArchive(MapData map)
{
var rawBytes = MemoryPackSerializer.Serialize(map);
var rawBytes = TH1Serialization.Serialize(map);
return NetworkPayloadCodec.Encode(rawBytes);
}
private static MapData DeserializeMapArchive(byte[] bytes)
{
var rawBytes = NetworkPayloadCodec.DecodeIfNeeded(bytes);
return MemoryPackSerializer.Deserialize<MapData>(rawBytes);
return TH1Serialization.Deserialize<MapData>(rawBytes);
}
// 给新版 GameArchiveManager 使用的 MapData 存档序列化入口。
@ -2961,8 +2961,8 @@ namespace RuntimeData
{
try
{
var bytes1 = MemoryPackSerializer.Serialize(obj1);
var bytes2 = MemoryPackSerializer.Serialize(obj2);
var bytes1 = TH1Serialization.Serialize(obj1);
var bytes2 = TH1Serialization.Serialize(obj2);
return bytes1.SequenceEqual(bytes2);
}
@ -2977,8 +2977,8 @@ namespace RuntimeData
{
try
{
var bytes1 = MemoryPackSerializer.Serialize(obj1);
var bytes2 = MemoryPackSerializer.Serialize(obj2);
var bytes1 = TH1Serialization.Serialize(obj1);
var bytes2 = TH1Serialization.Serialize(obj2);
if (!bytes1.SequenceEqual(bytes2))
{

View File

@ -1,4 +1,4 @@
/*
/*
* @Author:
* @Description:
* @Date: 20250403 11:04:31
@ -15,6 +15,7 @@ using Logic.CrashSight;
using MemoryPack;
using TH1_Logic.Core;
using TH1_Logic.Net;
using TH1_Logic.Tools;
using UnityEngine;
@ -460,7 +461,7 @@ namespace RuntimeData
// mapData.Net.Actions = null;
//
// _bufferWriter.Clear();
// MemoryPackSerializer.Serialize(_bufferWriter, mapData);
// TH1Serialization.Serialize(_bufferWriter, mapData);
//
// mapData.Net.Actions = actions;
//
@ -478,7 +479,7 @@ namespace RuntimeData
try
{
_bufferWriter.Clear();
MemoryPackSerializer.Serialize(_bufferWriter, mapData);
TH1Serialization.Serialize(_bufferWriter, mapData);
var hash128 = new Hash128();
var writtenMemory = _bufferWriter.WrittenMemory;
if (MemoryMarshal.TryGetArray(writtenMemory, out ArraySegment<byte> segment) && segment.Array != null)

View File

@ -15,6 +15,7 @@ using Logic.Pool;
using MemoryPack;
using RuntimeData;
using TH1_Logic.Core;
using TH1_Logic.Tools;
using UnityEngine;
@ -409,7 +410,7 @@ namespace TH1_Logic.AITrain
if (_actionLogicIdData != null) return;
TextAsset asset = TH1Resource.ResourceLoader.Load<TextAsset>($"CommonIdData/CommonIdData");
var data = asset?.bytes ?? Array.Empty<byte>();
_actionLogicIdData = MemoryPackSerializer.Deserialize<ActionLogicIdData>(data) ?? new ActionLogicIdData();
_actionLogicIdData = TH1Serialization.Deserialize<ActionLogicIdData>(data) ?? new ActionLogicIdData();
}
}

View File

@ -1,4 +1,4 @@
/*
/*
* @Author:
* @Description:
* @Date: 20250410 11:04:44
@ -27,6 +27,7 @@ using TH1_Presentation.Sequencer.Task;
using TH1_Renderer;
using TH1_Logic.Net;
using TH1_Logic.Steam;
using TH1_Logic.Tools;
using UnityEngine;
using TH1Renderer;
@ -1831,8 +1832,8 @@ namespace Logic.Action
#if CHECK_ACTIONDEFFERENCE
if (actionParams.MapData == Main.MapData)
{
byte[] bt = MemoryPack.MemoryPackSerializer.Serialize(Main.MapData);
Main.Instance.CheckMapData = MemoryPack.MemoryPackSerializer.Deserialize<MapData>(bt);
byte[] bt = TH1Serialization.Serialize(Main.MapData);
Main.Instance.CheckMapData = TH1Serialization.Deserialize<MapData>(bt);
}
#endif
}

View File

@ -10,6 +10,7 @@ using System.Collections.Generic;
using System.IO;
using Logic.CrashSight;
using MemoryPack;
using TH1_Logic.Tools;
using UnityEngine;
using Vector2 = System.Numerics.Vector2;
@ -123,7 +124,7 @@ namespace TH1_Logic.Comic
return null;
}
ComicAsset comicConfig = MemoryPackSerializer.Deserialize<ComicAsset>(textAsset.bytes);
ComicAsset comicConfig = TH1Serialization.Deserialize<ComicAsset>(textAsset.bytes);
LogSystem.LogInfo($"LoadComicAsset: 加载成功");
return comicConfig;
}
@ -146,7 +147,7 @@ namespace TH1_Logic.Comic
return null;
}
ImageMapData imageMap = MemoryPackSerializer.Deserialize<ImageMapData>(textAsset.bytes);
ImageMapData imageMap = TH1Serialization.Deserialize<ImageMapData>(textAsset.bytes);
LogSystem.LogInfo($"LoadImageMap: 加载成功");
return imageMap;
}
@ -168,7 +169,7 @@ namespace TH1_Logic.Comic
if (!Directory.Exists(folderPath)) Directory.CreateDirectory(folderPath);
string filePath = Path.Combine(folderPath, $"ImageMap.bytes");
byte[] bytes = MemoryPackSerializer.Serialize(_imageMap);
byte[] bytes = TH1Serialization.Serialize(_imageMap);
File.WriteAllBytes(filePath, bytes);
// 刷新 AssetDatabase
@ -193,7 +194,7 @@ namespace TH1_Logic.Comic
if (!Directory.Exists(folderPath)) Directory.CreateDirectory(folderPath);
string filePath = Path.Combine(folderPath, $"ComicAsset.bytes");
byte[] bytes = MemoryPackSerializer.Serialize(_asset);
byte[] bytes = TH1Serialization.Serialize(_asset);
File.WriteAllBytes(filePath, bytes);
// 刷新 AssetDatabase

View File

@ -1,4 +1,4 @@
/*
/*
* @Author:
* @Description:
* @Date: 20250401 11:04:16
@ -27,6 +27,7 @@ using TH1_Logic.MatchConfig;
using TH1_Logic.Net;
using TH1_Logic.Oss;
using TH1_Logic.Steam;
using TH1_Logic.Tools;
using TH1_UI.Controller.Interaction;
using TH1Renderer;
using TH1Resource;
@ -284,8 +285,8 @@ namespace TH1_Logic.Core
GameArchiveManager.Instance.SaveBeginArchive(MapData);
#if CHECK_ACTIONDEFFERENCE
byte[] bt = MemoryPack.MemoryPackSerializer.Serialize(Main.MapData);
Main.Instance.CheckMapData = MemoryPack.MemoryPackSerializer.Deserialize<MapData>(bt);
byte[] bt = TH1Serialization.Serialize(Main.MapData);
Main.Instance.CheckMapData = TH1Serialization.Deserialize<MapData>(bt);
#endif
MapData.RefreshTurn();
}

View File

@ -0,0 +1,377 @@
using System;
using System.IO;
using System.Linq;
using TH1_Logic.Editor.HybridCLR;
using TH1_Logic.Editor.YooAssetTools;
using UnityEditor;
using UnityEditor.Build;
using UnityEditor.Build.Reporting;
using UnityEngine;
using DiagnosticsProcess = System.Diagnostics.Process;
using DiagnosticsProcessStartInfo = System.Diagnostics.ProcessStartInfo;
namespace TH1_Logic.Editor
{
public static class TH1MigrationCommandLine
{
private const string BuildDirArg = "-th1SmokeBuildDir";
private const string DefaultBuildSubDirectory = "TH1/CodexSmoke";
private const string ExeName = "TOHOTOPIA.exe";
[MenuItem("Tools/TH1/iOS Migration/Command Line/Prepare And Build Windows Smoke")]
public static void PrepareAndBuildWindowsSmoke()
{
RunBatchAction(() =>
{
ConfigureWindowsIl2CppSmokeSettings();
PrepareCurrentPlatform(true);
var exePath = BuildWindowsSmokePlayer();
Debug.Log($"[TH1.Migration.CLI] Windows smoke build OK: {exePath}");
});
}
[MenuItem("Tools/TH1/iOS Migration/Command Line/Prepare Current Platform")]
public static void PrepareCurrentPlatformMenu()
{
RunBatchAction(() => PrepareCurrentPlatform(true));
}
public static void PrepareCurrentPlatform(bool developmentBuild)
{
AssetDatabase.SaveAssets();
TH1HybridCLRBuildTools.ConfigureHotfixSettings();
TH1YooAssetBuildTools.ConfigureDefaultPackageCollector();
TH1HybridCLRBuildTools.GenerateAll();
if (!TH1HybridCLRBuildTools.BuildAndCopyHotfixArtifacts(developmentBuild))
{
throw new BuildFailedException("[TH1.Migration.CLI] Build hotfix dll failed.");
}
TH1YooAssetBuildTools.BuildBuiltinDefaultPackage();
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
var blockers = TH1MigrationBuildStatus.GetBuildBlockingMessages(EditorUserBuildSettings.activeBuildTarget);
if (blockers.Count > 0)
{
throw new BuildFailedException(
"[TH1.Migration.CLI] Build preparation still has blocking errors:\n" +
string.Join("\n", blockers));
}
Debug.Log("[TH1.Migration.CLI] Prepare current platform OK.");
}
private static void ConfigureWindowsIl2CppSmokeSettings()
{
const BuildTargetGroup group = BuildTargetGroup.Standalone;
const BuildTarget target = BuildTarget.StandaloneWindows64;
if (EditorUserBuildSettings.activeBuildTarget != target)
{
EditorUserBuildSettings.SwitchActiveBuildTarget(group, target);
}
PlayerSettings.SetScriptingBackend(group, ScriptingImplementation.IL2CPP);
EditorUserBuildSettings.development = true;
EditorUserBuildSettings.allowDebugging = false;
PlayerSettings.usePlayerLog = true;
PlayerSettings.SetStackTraceLogType(LogType.Log, StackTraceLogType.ScriptOnly);
PlayerSettings.SetStackTraceLogType(LogType.Warning, StackTraceLogType.ScriptOnly);
PlayerSettings.SetStackTraceLogType(LogType.Error, StackTraceLogType.ScriptOnly);
PlayerSettings.SetStackTraceLogType(LogType.Assert, StackTraceLogType.ScriptOnly);
PlayerSettings.SetStackTraceLogType(LogType.Exception, StackTraceLogType.ScriptOnly);
Debug.Log("[TH1.Migration.CLI] Configured Windows IL2CPP smoke build settings.");
}
private static string BuildWindowsSmokePlayer()
{
var outputRoot = GetSmokeBuildDirectory();
CleanSmokeBuildDirectory(outputRoot);
var outputExe = Path.Combine(outputRoot, ExeName);
var scenes = EditorBuildSettings.scenes
.Where(scene => scene.enabled)
.Select(scene => scene.path)
.ToArray();
if (scenes.Length == 0)
{
throw new BuildFailedException("[TH1.Migration.CLI] No enabled scenes in EditorBuildSettings.");
}
var options = new BuildPlayerOptions
{
scenes = scenes,
locationPathName = outputExe,
target = BuildTarget.StandaloneWindows64,
targetGroup = BuildTargetGroup.Standalone,
options = BuildOptions.Development
};
var report = BuildPipeline.BuildPlayer(options);
var summary = report.summary;
if (summary.result != BuildResult.Succeeded)
{
throw new BuildFailedException(
$"[TH1.Migration.CLI] Build failed: {summary.result}, errors={summary.totalErrors}, warnings={summary.totalWarnings}");
}
var playerExe = FindPlayerExecutable(outputRoot, outputExe);
if (string.IsNullOrEmpty(playerExe))
{
BuildExportedVisualStudioSolution(outputRoot);
playerExe = FindPlayerExecutable(outputRoot, outputExe);
}
if (string.IsNullOrEmpty(playerExe))
{
throw new BuildFailedException(
$"[TH1.Migration.CLI] Build succeeded but no runnable player exe was found under {outputRoot}.");
}
return playerExe;
}
private static string GetSmokeBuildDirectory()
{
var argValue = GetCommandLineValue(BuildDirArg);
if (!string.IsNullOrEmpty(argValue))
{
return Path.GetFullPath(argValue);
}
return Path.GetFullPath(Path.Combine(GetProjectRoot(), DefaultBuildSubDirectory));
}
private static string FindPlayerExecutable(string outputRoot, string expectedExe)
{
if (File.Exists(expectedExe)) return expectedExe;
if (!Directory.Exists(outputRoot)) return string.Empty;
var exeFiles = Directory.GetFiles(outputRoot, "*.exe", SearchOption.AllDirectories)
.Where(path =>
{
if (path.IndexOf("Il2CppOutputProject", StringComparison.OrdinalIgnoreCase) >= 0) return false;
var fileName = Path.GetFileName(path);
if (fileName.IndexOf("CrashHandler", StringComparison.OrdinalIgnoreCase) >= 0) return false;
if (fileName.Equals("il2cpp.exe", StringComparison.OrdinalIgnoreCase)) return false;
if (fileName.Equals("UnityLinker.exe", StringComparison.OrdinalIgnoreCase)) return false;
if (fileName.Equals("bee_backend.exe", StringComparison.OrdinalIgnoreCase)) return false;
if (fileName.Equals("Analytics.exe", StringComparison.OrdinalIgnoreCase)) return false;
if (fileName.Equals("createdump.exe", StringComparison.OrdinalIgnoreCase)) return false;
return true;
})
.OrderBy(path => path.Length)
.ToArray();
return exeFiles.FirstOrDefault() ?? string.Empty;
}
private static void BuildExportedVisualStudioSolution(string outputRoot)
{
var solutionPath = Directory.GetFiles(outputRoot, "*.sln", SearchOption.TopDirectoryOnly)
.OrderBy(path => path.Length)
.FirstOrDefault();
if (string.IsNullOrEmpty(solutionPath))
{
return;
}
var msBuildPath = FindMSBuildPath();
if (string.IsNullOrEmpty(msBuildPath))
{
throw new BuildFailedException(
$"[TH1.Migration.CLI] Unity exported a Visual Studio solution but MSBuild was not found: {solutionPath}");
}
Debug.Log($"[TH1.Migration.CLI] Unity exported Visual Studio solution, building it with MSBuild: {solutionPath}");
var windowsSdkVersion = FindLatestWindowsSdkVersion();
var platformToolset = FindLatestPlatformToolset(msBuildPath);
var retargetArgs = string.Empty;
if (!string.IsNullOrEmpty(windowsSdkVersion))
{
retargetArgs += $" /p:WindowsTargetPlatformVersion={windowsSdkVersion}";
}
if (!string.IsNullOrEmpty(platformToolset))
{
retargetArgs += $" /p:PlatformToolset={platformToolset}";
}
RunProcess(
msBuildPath,
$"\"{solutionPath}\" /m /p:Configuration=Debug /p:Platform=x64{retargetArgs} /verbosity:minimal",
outputRoot);
}
private static string FindMSBuildPath()
{
var programFilesX86 = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86);
var vsWherePath = Path.Combine(programFilesX86, "Microsoft Visual Studio", "Installer", "vswhere.exe");
if (File.Exists(vsWherePath))
{
var output = RunProcess(vsWherePath, "-latest -products * -requires Microsoft.Component.MSBuild -find MSBuild\\**\\Bin\\MSBuild.exe", GetProjectRoot(), false);
var path = output
.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries)
.FirstOrDefault(File.Exists);
if (!string.IsNullOrEmpty(path))
{
return path;
}
}
var programFiles = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles);
var candidates = new[]
{
Path.Combine(programFiles, "Microsoft Visual Studio", "2022", "Community", "MSBuild", "Current", "Bin", "MSBuild.exe"),
Path.Combine(programFiles, "Microsoft Visual Studio", "2022", "Professional", "MSBuild", "Current", "Bin", "MSBuild.exe"),
Path.Combine(programFiles, "Microsoft Visual Studio", "2022", "Enterprise", "MSBuild", "Current", "Bin", "MSBuild.exe"),
Path.Combine(programFilesX86, "Microsoft Visual Studio", "2022", "BuildTools", "MSBuild", "Current", "Bin", "MSBuild.exe")
};
return candidates.FirstOrDefault(File.Exists) ?? string.Empty;
}
private static string FindLatestWindowsSdkVersion()
{
var programFilesX86 = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86);
var sdkLibRoot = Path.Combine(programFilesX86, "Windows Kits", "10", "Lib");
if (!Directory.Exists(sdkLibRoot))
{
return string.Empty;
}
return Directory.GetDirectories(sdkLibRoot)
.Select(Path.GetFileName)
.Where(name => Version.TryParse(name?.TrimEnd('.'), out _))
.OrderByDescending(name => Version.Parse(name.TrimEnd('.')))
.FirstOrDefault() ?? string.Empty;
}
private static string FindLatestPlatformToolset(string msBuildPath)
{
var current = new FileInfo(msBuildPath).Directory;
while (current != null)
{
var toolsetRoot = Path.Combine(current.FullName, "Microsoft", "VC", "v170", "Platforms", "x64", "PlatformToolsets");
if (Directory.Exists(toolsetRoot))
{
return Directory.GetDirectories(toolsetRoot)
.Select(Path.GetFileName)
.Where(name => name != null && name.StartsWith("v", StringComparison.OrdinalIgnoreCase))
.OrderByDescending(name => name)
.FirstOrDefault() ?? string.Empty;
}
current = current.Parent;
}
return string.Empty;
}
private static string RunProcess(string fileName, string arguments, string workingDirectory, bool throwOnError = true)
{
var process = new DiagnosticsProcess
{
StartInfo = new DiagnosticsProcessStartInfo
{
FileName = fileName,
Arguments = arguments,
WorkingDirectory = workingDirectory,
UseShellExecute = false,
CreateNoWindow = true,
RedirectStandardOutput = true,
RedirectStandardError = true
}
};
process.Start();
var output = process.StandardOutput.ReadToEnd();
var error = process.StandardError.ReadToEnd();
process.WaitForExit();
if (!string.IsNullOrWhiteSpace(output))
{
Debug.Log(output);
}
if (!string.IsNullOrWhiteSpace(error))
{
Debug.LogWarning(error);
}
if (throwOnError && process.ExitCode != 0)
{
throw new BuildFailedException(
$"[TH1.Migration.CLI] Process failed ({process.ExitCode}): {fileName} {arguments}\n{output}\n{error}");
}
return output;
}
private static string GetCommandLineValue(string name)
{
var args = Environment.GetCommandLineArgs();
for (var i = 0; i < args.Length - 1; i++)
{
if (string.Equals(args[i], name, StringComparison.OrdinalIgnoreCase))
{
return args[i + 1];
}
}
return string.Empty;
}
private static string GetProjectRoot()
{
return Directory.GetParent(Application.dataPath)?.FullName ?? Application.dataPath;
}
private static void CleanSmokeBuildDirectory(string outputRoot)
{
var fullOutputRoot = Path.GetFullPath(outputRoot);
var defaultSafeRoot = Path.GetFullPath(Path.Combine(GetProjectRoot(), "TH1"));
if (!fullOutputRoot.StartsWith(defaultSafeRoot, StringComparison.OrdinalIgnoreCase) &&
string.IsNullOrEmpty(GetCommandLineValue(BuildDirArg)))
{
throw new BuildFailedException($"[TH1.Migration.CLI] Refuse to clean unexpected smoke build dir: {fullOutputRoot}");
}
if (Directory.Exists(fullOutputRoot))
{
Directory.Delete(fullOutputRoot, true);
}
Directory.CreateDirectory(fullOutputRoot);
}
private static void RunBatchAction(System.Action action)
{
try
{
action();
if (Application.isBatchMode)
{
EditorApplication.Exit(0);
}
}
catch (Exception e)
{
Debug.LogError($"[TH1.Migration.CLI] Failed:\n{e}");
if (Application.isBatchMode)
{
EditorApplication.Exit(1);
return;
}
throw;
}
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 3ef088115a5f4492b90b43c79291c551
timeCreated: 1781111040

View File

@ -1,4 +1,4 @@
/*
/*
* @Author:
* @Description:
* @Date: 20250522 11:05:24
@ -237,7 +237,7 @@ namespace RuntimeData
// 统一保存 GameRecordData避免 Add/Remove/Upsert 到处重复序列化逻辑。
private void SaveGameRecordData()
{
byte[] bytes = MemoryPackSerializer.Serialize(_gameRecord);
byte[] bytes = TH1Serialization.Serialize(_gameRecord);
FileTools.SafeWriteFile(Application.persistentDataPath + "/../Config/game_record.dat", bytes);
}
@ -262,7 +262,7 @@ namespace RuntimeData
LogSystem.LogInfo("[GameRecord] 从备份恢复成功");
try
{
byte[] bytes = MemoryPackSerializer.Serialize(_gameRecord);
byte[] bytes = TH1Serialization.Serialize(_gameRecord);
FileTools.SafeWriteFile(path, bytes);
}
catch (Exception e)

View File

@ -1,4 +1,4 @@
/*
/*
* @Author:
* @Description:
* @Date: 20250905 15:09:36
@ -90,7 +90,7 @@ namespace Logic
LogSystem.LogInfo("[InputConfig] 从备份恢复成功");
try
{
byte[] bytes = MemoryPackSerializer.Serialize(_config);
byte[] bytes = TH1Serialization.Serialize(_config);
FileTools.SafeWriteFile(path, bytes);
}
catch (System.Exception e)
@ -111,7 +111,7 @@ namespace Logic
public void SaveInputConfig()
{
if (_config == null) return;
byte[] bytes = MemoryPackSerializer.Serialize(_config);
byte[] bytes = TH1Serialization.Serialize(_config);
FileTools.SafeWriteFile(Application.persistentDataPath + "/../Config/input_config.dat", bytes);
}

View File

@ -12,6 +12,7 @@ using Logic.CrashSight;
using MemoryPack;
using RuntimeData;
using TH1_Logic.MatchConfig;
using TH1_Logic.Tools;
using UnityEngine;
@ -49,7 +50,7 @@ namespace Logic
string filePath = Path.Combine(folderPath, $"{name}.bytes");
var record = new MapRecordData(map, name);
byte[] bytes = MemoryPackSerializer.Serialize(record);
byte[] bytes = TH1Serialization.Serialize(record);
File.WriteAllBytes(filePath, bytes);
// 刷新 AssetDatabase
@ -81,7 +82,7 @@ namespace Logic
return null;
}
MapRecordData map = MemoryPackSerializer.Deserialize<MapRecordData>(textAsset.bytes);
MapRecordData map = TH1Serialization.Deserialize<MapRecordData>(textAsset.bytes);
LogSystem.LogInfo($"LoadMapRecord: 地图 {name} 加载成功");
return map;
}
@ -92,4 +93,4 @@ namespace Logic
}
}
}
}
}

View File

@ -11,6 +11,7 @@ using System.IO;
using Logic.CrashSight;
using MemoryPack;
using RuntimeData;
using TH1_Logic.Tools;
using UnityEngine;
@ -59,7 +60,7 @@ namespace TH1_Logic.MatchConfig
return null;
}
MatchLevelData matchConfig = MemoryPackSerializer.Deserialize<MatchLevelData>(textAsset.bytes);
MatchLevelData matchConfig = TH1Serialization.Deserialize<MatchLevelData>(textAsset.bytes);
LogSystem.LogInfo($"LoadMatchLevelData: 关卡配置加载成功");
return matchConfig;
}
@ -89,7 +90,7 @@ namespace TH1_Logic.MatchConfig
if (!Directory.Exists(folderPath)) Directory.CreateDirectory(folderPath);
string filePath = Path.Combine(folderPath, $"LevelData.bytes");
byte[] bytes = MemoryPackSerializer.Serialize(LevelData);
byte[] bytes = TH1Serialization.Serialize(LevelData);
File.WriteAllBytes(filePath, bytes);
// 刷新 AssetDatabase

View File

@ -1,4 +1,4 @@
using System;
using System;
using System.Threading.Tasks;
using Logic.CrashSight;
using MemoryPack;
@ -9,6 +9,7 @@ using TH1_Logic.Config;
using TH1_Logic.Core;
using TH1_Logic.GameArchive;
using TH1_Logic.Net;
using TH1_Logic.Tools;
using UnityEngine;
namespace TH1_Logic.Oss
@ -55,7 +56,7 @@ namespace TH1_Logic.Oss
ossData.Actions = endMap.Net.Actions;
ossData.CollectData = CollectManager.Instance.CollectData;
ossData.CollectData.MemberId = LobbyManager.Instance.Lobby.GetSelfMemberId();
byte[] bytes = MemoryPackSerializer.Serialize(ossData);
byte[] bytes = TH1Serialization.Serialize(ossData);
_ = UploadGameDataAsync(steamId, bytes);
}
@ -110,7 +111,7 @@ namespace TH1_Logic.Oss
LogSystem.LogInfo($"Collect STS token obtained, expires at {_collectCredentialsExpireTime}");
}
byte[] data = MemoryPackSerializer.Serialize(collectData);
byte[] data = TH1Serialization.Serialize(collectData);
var result = await _uploadService.UploadFileAsync(_cachedCollectCredentials, data);
if (result)
{

View File

@ -1,4 +1,4 @@
using Logic;
using Logic;
using System;
using System.Collections.Generic;
using System.Linq;
@ -12,6 +12,7 @@ using TH1_Core.Managers;
using TH1_Logic.Config;
using TH1_Logic.Core;
using TH1_Logic.Net;
using TH1_Logic.Tools;
using UnityEngine;
namespace TH1_Logic.Steam
@ -25,7 +26,7 @@ namespace TH1_Logic.Steam
{
try
{
var message = MemoryPack.MemoryPackSerializer.Deserialize<BaseMessage>(data);
var message = TH1Serialization.Deserialize<BaseMessage>(data);
if (message == null) return;
if (message.MessageType == P2PMsgType.NetworkStress) return;

View File

@ -1,4 +1,4 @@
/*
/*
* @Author:
* @Description:
* @Date: 20250908 17:09:18
@ -17,6 +17,7 @@ using TH1_Logic.Chat;
using TH1_Logic.Config;
using TH1_Logic.Core;
using TH1_Logic.Net;
using TH1_Logic.Tools;
namespace TH1_Logic.Steam
@ -67,7 +68,7 @@ namespace TH1_Logic.Steam
LobbyVersion = lobbyInfo.Version,
};
byte[] messageBytes = NetworkPayloadCodec.Encode(MemoryPackSerializer.Serialize<BaseMessage>(data));
byte[] messageBytes = NetworkPayloadCodec.Encode(TH1Serialization.Serialize<BaseMessage>(data));
if (SimpleP2P.Instance.SendToWithOutConnect(targetId, messageBytes)) return true;
LogSystem.LogError($"InviteVersionMismatchMessage: 发送给房主失败 owner={lobbyInfo.OwnerId}, lobby={lobbyInfo.LobbyId}");
@ -97,7 +98,7 @@ namespace TH1_Logic.Steam
private static byte[] SerializeForNetwork(BaseMessage message)
{
var rawBytes = MemoryPack.MemoryPackSerializer.Serialize<BaseMessage>(message);
var rawBytes = TH1Serialization.Serialize<BaseMessage>(message);
return NetworkPayloadCodec.Encode(rawBytes);
}

View File

@ -1,4 +1,4 @@

using System;
using System.Collections.Generic;
using System.IO;
@ -17,6 +17,7 @@ using TH1_Logic.Chat;
using TH1_Logic.Config;
using TH1_Logic.Core;
using TH1_Logic.Net;
using TH1_Logic.Tools;
using UnityEngine;
@ -1036,7 +1037,7 @@ namespace TH1_Logic.Steam
};
var data = new InviteMessage();
data.LobbyInfo = lobbyInfo;
byte[] bytes = MemoryPackSerializer.Serialize<BaseMessage>(data);
byte[] bytes = TH1Serialization.Serialize<BaseMessage>(data);
// 优先从缓存获取,否则直接构造 CSteamID
var targetId = _onlineFriendsId.TryGetValue(targetSteamId, out var cachedId) ? cachedId : new CSteamID(targetSteamId);
@ -1103,7 +1104,7 @@ namespace TH1_Logic.Steam
ReporterId = selfId,
Version = ConfigManager.Instance.VersionCfg.CurVersionInfo.Version,
};
var bytes = MemoryPackSerializer.Serialize<BaseMessage>(data);
var bytes = TH1Serialization.Serialize<BaseMessage>(data);
if (!SimpleP2P.Instance.SendToWithOutConnect(targetId, bytes))
{
LogSystem.LogError($"Report lobby failed: send to owner failed, lobby={lobbyInfo.LobbyId}, owner={lobbyInfo.OwnerId}");

View File

@ -9,7 +9,6 @@
using System;
using System.IO;
using Logic.CrashSight;
using MemoryPack;
namespace TH1_Logic.Tools
@ -29,7 +28,7 @@ namespace TH1_Logic.Tools
return null;
}
var data = MemoryPackSerializer.Deserialize<T>(bytes);
var data = TH1Serialization.Deserialize<T>(bytes);
if (data == null)
{
LogSystem.LogError($"反序列化结果无效: {path}");
@ -101,4 +100,4 @@ namespace TH1_Logic.Tools
return true;
}
}
}
}

View File

@ -0,0 +1,48 @@
/*
* @Author: Codex
* @Description: AOT-safe MemoryPack entrypoints for hotfix types
* @Date: 20260611 00:00:00
* @Modify:
*/
using System;
using System.Buffers;
using MemoryPack;
namespace TH1_Logic.Tools
{
public static class TH1Serialization
{
public static byte[] Serialize<T>(T value)
{
return MemoryPackSerializer.Serialize(typeof(T), value);
}
public static byte[] Serialize(Type type, object value)
{
if (type == null) throw new ArgumentNullException(nameof(type));
return MemoryPackSerializer.Serialize(type, value);
}
public static void Serialize<T>(IBufferWriter<byte> writer, T value)
{
if (writer == null) throw new ArgumentNullException(nameof(writer));
MemoryPackSerializer.Serialize(typeof(T), writer, value);
}
public static T Deserialize<T>(byte[] bytes)
{
if (bytes == null) throw new ArgumentNullException(nameof(bytes));
object value = MemoryPackSerializer.Deserialize(typeof(T), bytes);
return value is T data ? data : default;
}
public static object Deserialize(Type type, byte[] bytes)
{
if (type == null) throw new ArgumentNullException(nameof(type));
if (bytes == null) throw new ArgumentNullException(nameof(bytes));
return MemoryPackSerializer.Deserialize(type, bytes);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8f579489cbe74efc80aab7da3c1aa1f3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: