# KCP 反编译源码（KCP.dll v1.1.1）

> **这不是作者的官方源码。** 作者 `KerbalMissile` 的 GitHub 仓库
> （[Kerbal-Civilization-Project](https://github.com/KerbalMissile/Kerbal-Civilization-Project)）
> 只有 `LICENSE` 和 `README.md` 两个文件，从来没提交过代码。本目录是用 ILSpy 从发布包里的
> `GameData/KCP/Plugins/KCP.dll` 还原出来的 C# 工程，用于学习与自行修改。

## 1. 来源

| 项目 | 值 |
|---|---|
| 原始程序集 | `KCP.dll`，196,096 字节 |
| 版本 | KCP v1.1.1（SpaceDock / CKAN 发布包） |
| 反编译工具 | `ilspycmd` 11.1.0.9782（IlSpy 命令行版） |
| 关键参数 | `-p`（生成工程）+ `-r "/d/Kerbal Space Program/KSP_x64_Data/Managed"`（挂 KSP/Unity 引用） |
| 产物 | 38 个 `.cs` 文件，12,778 行（含 355 行注释） |
| 校验 | `dotnet build -c Release` 产出 201,728 字节的 `KCP.dll`，与原版行为等价 |

挂 `-r` 是必要的：不挂引用时反编译器解不出 `[KSPAddon(...)]` 的属性参数，还会残留上千处
`//IL_xxxx: Unknown result type` 和 `(Object)(object)` 之类的噪音。

## 2. 目录

```
KCP_src/
├─ KCP.Core/            插件入口、坐标换算、难度设置、署名
├─ KCP.Data/            Overpass 请求、JSON/XML 解析、磁盘缓存、OSM 标签规则、数据模型
├─ KCP.Streaming/       TileStreamer：加载队列、LOD、建筑去重（本 mod 的心脏）
├─ KCP.Generation/      建筑/道路/停车场/地标网格、材质与贴图、夜景、合批
├─ KCP.UI/              署名面板
├─ KCP.Compatibility/   EVE 夜景兼容
├─ KCP.csproj           已补引用，可直接 build
├─ ARCHITECTURE.md      架构说明（先读这个）
└─ README.md            本文件
```

同级的 `../KCP_src_raw/` 是**未加注释的纯净反编译基线**，用来 `diff` 检查注释有没有改坏代码。

## 3. 注释体系

- 每个文件顶部有 `// ====...` 横幅 + 中文说明：这个文件干什么、关键设计、踩过的坑。
- 关键方法/分支上方有行内 `//` 注释，解释**为什么**这么写（阈值怎么来的、为什么加锁、为什么空 catch）。
- 全套共 124 处行内注释，锚点由 `../annotate.py` 维护：
  - `python ../annotate.py --check` 只校验锚点是否仍唯一命中；
  - `python ../annotate.py` 从 `KCP_src_raw` 重新生成全部注释（幂等，不会覆盖 `ARCHITECTURE.md` / `README.md`）。
  - 想加注释：往 `annotate.py` 的 `INSERTS` 里追加 `(相对路径, 锚点文本, [注释行...])`。
- 注意：第 1–7 节描述的是**原始反编译状态**（注释没有改动代码）。第 8 节之后代码被就地修改过，
  与 `KCP_src_raw` 的 diff 不再为空，见「8. 本次修复与新增」。
  因为重命名会让与 `KCP_src_raw` 的 diff 变得不可读（真要改，建议先重命名再单独维护）。

## 4. 编译

```bash
cd KCP_src
# 1) 按自己的 KSP 安装路径改 KCP.csproj 里的 HintPath（当前指向 D:\Kerbal Space Program）
# 2) UnityEngine.AnimationModule 是手工补的引用（原工程缺它，KCPAddon 里用到 Animator）
dotnet build -c Release
# 产物：bin/Release/net472/KCP.dll
```

目标框架 `net472`；`dotnet build` 需要 .NET SDK（本机 10.0.400 可用）。
编译时 `KCP_src_raw` 里的同名文件不会被带上（两个工程互不包含）。

## 5. 装回游戏

```bash
# 1) 备份原版
cp "KSP/GameData/KCP/Plugins/KCP.dll" "KSP/GameData/KCP/Plugins/KCP.dll.bak"
# 2) 覆盖（Release 产物）
cp KCP_src/bin/Release/net472/KCP.dll "KSP/GameData/KCP/Plugins/KCP.dll"
```

反编译重建的 DLL 与原版**语义等价**，但不是作者的原始工程：调试符号、编译优化细节不同，
体积也差几千字节。装之前请确认 KSP 版本匹配（v1.1.1 面向 1.12.x）。

## 6. 法律与边界

- 仓库的 Apache-2.0 只覆盖仓库自身那两三个文件，**没有覆盖 DLL**；DLL 的授权状态是模糊的。
- 自己学习、自己改着玩没问题；**不要把反编译源码或再编译的 DLL 公开分发**。
- 程序里的地标建模（埃菲尔铁塔、金字塔…）和程序化贴图是作者的作品，不是公开素材。

## 7. 已知瑕疵

1. 局部变量名是反编译器自动生成的（`num`、`num2`、`flag`、`val`），可读性依赖注释。
2. 少数 `switch` 用了 C# 8 的 switch 表达式写法，老 IDE 需要 `LangVersion` ≥ 8（csproj 已设 14）。
3. `KCP.csproj` 的 HintPath 是硬编码路径，换机器要改。
4. 反编译不还原 `#region`、XML 文档注释、原始文件顺序；文件顺序现在按命名空间排列。

## 8. 本次修复与新增（在原始反编译码上直接修改）

> ⚠️ 这一节之后代码已经**不再是纯反编译产物**。`../annotate.py` 会从 `KCP_src_raw` 重写源文件，
> 再跑一次会把下面所有改动冲掉，**不要运行它**。

### 8.1 玩法内的设置面板

游戏内工具栏按钮 → 「KCP 设置」窗口，可现场调整：

> v1.2.1 起面板底部还有「地标模型」分区：17 个地标可选、可填 obj 路径、可调缩放，改动即时存 `runtime.cfg`。

| 项 | 范围 | 说明 |
| --- | --- | --- |
| 内存缓存 | 8–512 个区块 | 原始 JSON 文本缓存（FIFO → **真 LRU**） |
| 解析缓存 | 4–256 个区块 | 已解析 `ParsedTile` 缓存（同样改 LRU） |
| 磁盘缓存上限 | 0–4096 MB，默认 1024 (1 GB) | 0 = 不限制；超限按最后写入时间删到 85% |
| 语言 | auto/en/zh/ru/es/ja/de/fr/pt | auto 跟随 KSP 语言 |
| 严格 TLS | 开/关 | 关（默认）= 保留 KSP 自带证书管家的宽松行为 |
| 外部地标模型 | 开/关 | 见 8.4 |
| 外部贴图 | 开/关 | 见 8.3 |

设置写到 `GameData/KCP/PluginData/runtime.cfg`（节点 `KCP_RUNTIME`），与难度面板用的
`settings.cfg` 分开存，互不覆盖：

```cfg
KCP_RUNTIME
{
    memoryCacheTiles = 96
    parsedCacheTiles = 40
    diskCacheMb = 1024
    language = auto
    strictTls = false
    externalModels = true
    texturePack = true
}
```

### 8.2 多语言

内置 en/zh 两套文案；其它语言（ru/es/ja/de/fr/pt）自动回落英文，可用外部文件覆盖：

```
GameData/KCP/PluginData/Localization/<lang>.cfg     # 节点 KCP_LOCALIZATION
KCP_LOCALIZATION
{
    KCP_Settings_Title = KCP-Einstellungen
    KCP_Reload = Blöcke neu laden
}
```

可用键：`KCP_Settings_Title` `KCP_Reload` `KCP_MemoryTiles` `KCP_ParsedTiles` `KCP_DiskMb`
`KCP_Language` `KCP_StrictTls` `KCP_ExternalModels` `KCP_TexturePack` `KCP_CacheHint`
`KCP_Credits_Title` `KCP_Credits_Data` `KCP_Credits_Note` `KCP_Credits_Close`
`KCP_Credits_DontShow` `KCP_Lang_Auto` `KCP_NoData`（带 `{0}` 的是格式化串）。

### 8.3 外部贴图包

`Textures/`、`Models/`、`Localization/` 三个目录会在插件首次启动时自动创建，并各带一份
`README.txt` 写清命名规则（懒得查文档就直接看那个文件）。

放 `GameData/KCP/PluginData/Textures/`，两种写法都行：

```
Textures/facade_skyscraper.png        # 直接一个文件
Textures/skyscraper/diffuse.png       # 或按名字建子目录, 认 diffuse.* / albedo.*
```

支持 `.png` `.jpg` `.dds` `.tga`。可用的 base name：

| base name | 作用 |
| --- | --- |
| `facade_<type>` / `facade` / `building_<type>` / `<type>_facade` / `<type>` | 建筑立面（type = residential/commercial/skyscraper/house/industrial/civic/religious/agricultural/transport/stadium） |
| `roof_<type>` / `roof` | 屋顶 |
| `road` / `road_asphalt` | 道路与停车场地面 |

立面贴图是 512×512、约 15 层 × 31 开间；放进来的图建议同样的排布和比例
（世界尺寸：横向 96m、竖向 48m 一次平铺）。贴图在后台线程加载，加载完自动换到已有材质上。

### 8.4 外部地标模型（OBJ）

```
GameData/KCP/PluginData/Models/<id>/model.obj      # 或 Models/<id>.obj
GameData/KCP/PluginData/Models/<id>/model.cfg      # 可选
```

`model.cfg` 键（节点 `KCP_MODEL`，也可以直接把键写在根上）：

```cfg
KCP_MODEL
{
    scale = 1          // 额外缩放倍率
    autoHeight = 0     // >0 时按包围盒把模型高度归一到这个米数
    offsetY = 0        // 竖直偏移(米, 乘 scale)
    rotationX = 0
    rotationY = 0
    rotationZ = 0
    flipNormals = false
    colliders = true
}
```

模型替换的是手搓地标，只要 id 对上就完全接管（否则回落到程序化几何）：

`beagle_2` `burj_khalifa` `calgary_tower` `space_needle` `cn_tower` `big_ben`
`pyramid_khufu` `pyramid_khafre` `pyramid_menkaure` `stonehenge` `eiffel` `liberty`
`empire_state` `sydney_opera` `taj_mahal` `christ_redeemer` `colosseum`

OBJ 只解析实际用得到的子集：`v` / `vt` / `vn` / `f` / `usemtl`（负索引、多边形扇形三角化都支持；
单文件上限 24MB）。每个 `usemtl` 分组成为一个 submesh，材质名走 `SharedMats.DetailMat`，
可用名字：`trim` `glass` `sign` `water` `road` `concrete` `metal` `brick` `stone` `gold` `wood` `leaf` `sand`。

### 8.5 已修掉的缺陷（按严重度）

| 位置 | 问题 | 修法 |
| --- | --- | --- |
| `SharedMats.WallMatFor` | 缓存键先查 `type_V_tex`、命中失败后改写成 `type_V_tex_<type>_e<N>`，每次调用新建一个 Material 再丢弃 → 近景每栋楼漏一个材质（25 格 × 2200 栋） | 键一次性算全，不再漏 |
| `SharedMats.MakeFacadeTex` 等 | 逐像素 `SetPixel`，启动约 1300 万次托管→原生调用，首格卡死 | 写 `Color[]` 缓冲 + `SetPixels` |
| `TileCache` | 内存/解析缓存是 FIFO 且 Put 时重写最老键，上限可被突破；容量不可调 | 真 LRU + `Configure()` 可配 + 磁盘上限清理 |
| `OverpassClient` | 端点数组里混着明文 `http://`；`ParseTags` 里 `IndexOf('"', -1)` 抛异常被吞 → 整栋楼标签丢失；Overpass 返回 200 + `remark` 错误被当空数据缓存；`CancelRequests` 把离线退避一起清零 | 全 https；索引越界跳块；`remark` 视为失败；退避状态保留；加卡死看门狗 |
| `BuildingMeshBuilder` | 圆柱端盖绕序都朝内（塔尖/烟囱顶部穿空）；V 起点随机把楼层线切半个 | 换绕序；V 起点归零；UV 比例按贴图布局写成注释锁住 |
| `ParkingLotMeshBuilder` | 坡差阈值/车位标线都是裸值，Stock 星系下标线比停车场还大；坡地整块被丢弃 | 统一过 `ScaleMeters`；坡地取中值不再拍平或丢弃 |
| `OsmTags.ParseHeight` | `"100 ft"` 被当 100m（英国楼虚高 3 倍）；`>500m` 直接丢弃；`float.TryParse` 用当前区域文化 | 识别 ft/英尺符号、逗号小数、InvariantCulture，上限放宽到 1000m |
| `OsmTags.ColorForBuilding` / `RoofColorFor` | 无锁读共享 `System.Random` | 加 `rngLock` |
| `DifficultySettings` | `fileHasData` 一旦置位，`settings.cfg` 永久压过难度面板，面板改完不重启不生效 | 一律先读当前存档参数对象 |
| `EveCityLightsFade` | 接管 EVE 材质后从不还原，换天体/重进大气时城市光变透明 | 记原值 + `OnDestroy` 还原 |
| `BuildingMeshBuilder` 面积/数量阈值 | 部分阈值没过 `ScaleMeters`，Stock 星系尺寸错位 | 统一乘 `scale²` |
| `LandmarkBuilder.BuildBurjKhalifa` | 原版只堆到约 432m（真实 828m），比例像根矮胖柱子 | 按真实尺寸重排 8 段收分 + 120m 尖顶 |
| 死常量 / 空方法 | `RoadMeshBuilder`、`ParkingLotMeshBuilder`、`BakedTileBatches` 里若干常量与实际写死的字面量不一致；`CityLightsBuilder.AddWindowGlow` 是空方法 | 保留未删（避免与原始码 diff 噪音），在各处注释里标注 |

## 9. 更新日志

完整历史（v1.1.1 反编译基线 → 1.2.3 的全部改动）见同目录 [`CHANGELOG.md`](CHANGELOG.md)。

### 9.1 v1.2.3（2026-11-08）

- 地标改为**主动下沉**：底面高度 = 足印内最高点 − 下沉量，`下沉量 = min(起伏 × 0.88, 足印 × 0.12)`。上坡侧真正埋进坡里（这才像地基），下坡侧那条缝缩到起伏的一小截，不再整座悬空。
- 足印采样从 5 点加到 **9 点**（中心 + 4 角 + 4 边中点）：沙丘上一条 230 m 边的中点常常比两个角低好几米。
- 沙台收成**薄唇**：外沿从 1.10 倍收到 1.06 倍；裙边只许往下垂（顶面上方夹到 0、下方 24 m），地形比底面高的那侧就让基座埋着，不在环上竖墙。
- 日志加 `relief=`、地形 `min..max` 和 `sink=`，一眼能看出下了多深。

### 9.2 v1.2.2（2026-11-08）

- 沙台改成**随地形起伏的裙边**：顶面仍是与地标底面齐平的平环，外沿 44 个顶点各自采一次真实地形高度往下垂（再沉 0.4 m），整圈严丝合缝贴地——修掉硬平底棱台在沙丘上必然悬空、板下露黑缝的问题。
- 方形底面（金字塔）用方形环而不是圆环，不会把四个角切掉；外沿为足印的 1.10 倍，露出一圈台面。
- 沙台几何挂到瓦片根而不是地标根（地标根带 `lm.scale` 缩放），尺寸按世界米算。
- PQS 未就绪时的高度偏差仍被夹在顶面上方 4 m / 下方 80 m 之间，不会出现离谱长幕帘。

### 9.3 v1.2.1（2026-11-08）

- 设置窗口新增「地标模型」分区：17 个地标滚动列表 + 状态（内置/外部 obj/文件不存在），
  选中后可填任意 obj 路径、调缩放，立刻写入 `runtime.cfg`（`model_<id>` / `modelScale_<id>`），
  并提供「应用并重载地标」不用重进游戏。
- 地标贴地改为**足印 5 点采样取最高点**（中心 + 四角，按真实底面尺寸），修掉大底面陷进沙丘。
- 所有地标底部自动生成**贴地沙台 pad**（金字塔沙色、建筑类石灰白），盖住平地面对起伏地形的缝隙。
- 新增日志 `[KCP] landmark <id> footprint= samples= terrain= alt= delta=`。

### 9.4 v1.2.0（2026-11-07）

- 设置面板（缓存 8–512 / 4–256 / 0–4096 MB、语言、TLS、外部资源开关），面板改动即时写 `runtime.cfg`。
- 真 LRU 缓存 + 磁盘自动治理（上限默认 1024 MB）。
- 8 语言、外部贴图包、外部地标模型（17 id，丢 obj 即换）。
- 17 个地标按真实尺寸重做、地标贴地对齐、材质泄漏等一批缺陷修复（明细见 `CHANGELOG.md`）。
- 运维：只替换 `Plugins/KCP.dll`，`PluginData/` 原样保留；启动日志 `build KCP-1.2.1` 可确认版本。
