fix(game): 移除不安全的类型转换并增加存档校验 (refs #7)
在 character.ts 中使用 flatMap 结合类型缩窄(null 检查),移除了对 DccFile 的强制类型转换,保证内部逻辑的编译期安全。在 save.ts 中增加了对背包数据的严格运行时有效性验证,避免由于恶意或破损存档导致的隐式游戏损坏,并用专用的反序列化测试覆盖新校验过程。
This commit is contained in:
parent
fc201d7290
commit
4290e5feb2
|
|
@ -189,10 +189,12 @@ export async function loadCharacterSheet(
|
|||
// the priority table is the authority on what covers what.
|
||||
const order = cofLayerOrder(cof, direction, index)
|
||||
const ordered = order
|
||||
.map(layerIndex => { const sprite = sprites[layerIndex]; return { layerIndex, sprite } })
|
||||
.filter(entry => entry.sprite !== null && entry.sprite !== undefined)
|
||||
.flatMap(layerIndex => {
|
||||
const sprite = sprites[layerIndex]
|
||||
return sprite != null ? [{ layerIndex, sprite }] : []
|
||||
})
|
||||
for (const entry of ordered) {
|
||||
const sprite = entry.sprite as DccFile
|
||||
const sprite = entry.sprite
|
||||
const dccDir = dir64ToDcc(dir64, sprite.directions.length)
|
||||
const layerDirection = sprite.directions[dccDir]
|
||||
const frame = layerDirection?.frames[index]
|
||||
|
|
|
|||
|
|
@ -133,7 +133,16 @@ export function parseSnapshot(text: string): GameSnapshot {
|
|||
if (candidate.world === undefined || typeof candidate.world.tick !== 'number') throw new Error('save has no world')
|
||||
if (candidate.world.player === undefined) throw new Error('save has no player')
|
||||
if (!Array.isArray(candidate.world.monsters)) throw new Error('save has no monster list')
|
||||
if (candidate.inventory === undefined || !Array.isArray(candidate.inventory.placed)) throw new Error('save has no inventory')
|
||||
if (candidate.inventory === undefined || typeof candidate.inventory.width !== 'number' || typeof candidate.inventory.height !== 'number') {
|
||||
throw new Error('save has no inventory bounds')
|
||||
}
|
||||
if (!Array.isArray(candidate.inventory.placed)) throw new Error('save has no inventory')
|
||||
for (const [index, entry] of candidate.inventory.placed.entries()) {
|
||||
if (typeof entry !== 'object' || entry === null) throw new Error(`inventory placed item [${index}] is malformed`)
|
||||
if (typeof (entry as any).x !== 'number') throw new Error(`inventory placed item [${index}] has invalid x`)
|
||||
if (typeof (entry as any).y !== 'number') throw new Error(`inventory placed item [${index}] has invalid y`)
|
||||
if (typeof (entry as any).item !== 'object' || (entry as any).item === null) throw new Error(`inventory placed item [${index}] has no item`)
|
||||
}
|
||||
if (!Array.isArray(candidate.quests)) throw new Error('save has no quest log')
|
||||
// Older saves predate ground items; an absent list is treated as empty rather
|
||||
// than as corruption, so the version does not have to be bumped for a field
|
||||
|
|
@ -171,7 +180,7 @@ export function restoreSnapshot(
|
|||
// live world would otherwise leave the previous player list in place.
|
||||
world: rebindPlayer({ ...snapshot.world, monsters: [...snapshot.world.monsters], players: [], events: [] }),
|
||||
rngState: snapshot.rngState,
|
||||
inventory: build.inventory(snapshot.inventory.width, snapshot.inventory.height, snapshot.inventory.placed as PlacedItem[]),
|
||||
inventory: build.inventory(snapshot.inventory.width, snapshot.inventory.height, snapshot.inventory.placed),
|
||||
quests: build.quests(snapshot.quests),
|
||||
ground: snapshot.ground.map(entry => ({ x: entry.x, y: entry.y, item: entry.item })),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
import { describe, it, expect } from 'vitest'
|
||||
import { parseSnapshot } from '../src/game/save.ts'
|
||||
|
||||
describe('parseSnapshot', () => {
|
||||
it('rejects malformed placed items in inventory', () => {
|
||||
const badSnapshot = {
|
||||
version: 1,
|
||||
rngState: 0,
|
||||
world: {
|
||||
tick: 0,
|
||||
player: {},
|
||||
monsters: [],
|
||||
},
|
||||
inventory: {
|
||||
width: 10,
|
||||
height: 4,
|
||||
placed: [
|
||||
{ x: 1, y: 1 }, // Missing item
|
||||
]
|
||||
},
|
||||
quests: [],
|
||||
ground: []
|
||||
}
|
||||
|
||||
expect(() => parseSnapshot(JSON.stringify(badSnapshot))).toThrow('inventory placed item [0] has no item')
|
||||
})
|
||||
})
|
||||
Loading…
Reference in New Issue