实机故障:进入游戏后整屏灰紫,主角还是蓝色胶囊占位体,2D UI 与小地图正常。 根因(已实证,非推测)。`EntityStore::mut_despawn()` 在 despawn 的 vid 等于 主角 vid 时把 `m_main_vid` 清零。40250 服务器在 spawn-in 头几秒会对玩家自己的 vid 反复下发 GC_CHARACTER_DEL + GC_CHARACTER_ADD(视野重算,实测约 10 次)。 第一次 DEL 就把玩家永久去主角化:`get_main_vid()` 返回 0 → `_sync_main_character()` 直接 return → 建模代码从未跑过(main_attempts=0)→ 占位胶囊留在 y=0 → 相机跟着它钻到 C1 地形底下 → 满屏灰紫。地图其实一直是好的 (chunks_built 20/20、objects_placed 565)。 参考端怎么做(40250 ClientVS22)。主角身份属于 `CNetworkActorManager::m_dwMainVID`, 只由 `SetMainActorVID()`(GC_MAIN_CHARACTER)写,只在管理器析构时清; `RemoveActor()` 删 actor 行和角色实例,但绝不碰它;重新加入时 `__AppendCharacterManagerActor()` 用 `kCreateData.m_isMain = __IsMainActorVID(dwVID)` 重新推导,并再跑一次 `SetMainCharacterIndex(dwVID)`。 修法(entity_store.cpp): - `mut_despawn()` 只删行、只发 Despawn,不动 m_main_vid;m2dev 分支的 GC_CHARACTER_DEL 同规则。 - `mut_spawn_full()` 在 `e.vid == m_main_vid` 时把 `is_main` 置回 true, 并重新广播 MainSet,对齐参考端的重新推导。 顺带落地的渲染修复(同一次实机排查中定位): - libgr2 `sample_pose()` 增加 `root_offset` 形参;`Metin2AnimPlayer::apply_pose()` 显式传单位阵而不是模型的 InitialPlacement。Metin2 动画 gr2 的根骨轨道自带骨盆 高度,再乘一次 IP 会把整个人抬高一个骨盆高(武 99.4cm / 刺 92.6 / 巫 103.7 / 萨 95.5,正好各自的 IP.z)——即此前的「人物悬空」。`bind_pose()` 语义不变。 - `Metin2Model::_load_hair()` 改用与身体/武器同一条 `make_material()` (specular_disabled)而不是 roughness=1.0 的 StandardMaterial3D,头发不再被 PBR 高光洗成奶白色块。 取证与回归工具: - `project/net_trace.gd`(新):主角 vid 生命周期取证。挂在 AppFlow 之后逐帧读 `get_main_vid()`,归零时打印 `NETTRACE: MAIN_LOST was=.. now=0 reason=..` 加最近 24 条环形事件;`reason` 区分 despawn 与 silent(reset_for_map_change 清空 m_changes,是静默清零)。`MT_NETTRACE=1` 打开逐事件日志。 - `game_scene.gd`:诊断 tick 支持重复采样(分辨主角装配是「没跑」还是「没跑完」), 新增 main_sync_ready / main_loading / main_view_key / main_map_vid / main_retry_in_ms 字段;`MT_DIAG_SHOT=<png>` 让游戏自己存一帧实机画面 (screencapture 拍到的是终端窗口,没用)。 - `project/model_render_test.gd` + `test/rendering/model_baseline.json`(新): 八个种族的模型渲染基线(--bless 重新落盘)。 - `project/char_bench_test.gd`(新):无网络无地图的单角色实验台,五变体 × 六机位出证据图,只产图不做阈值判定。 - `project/package_render_test.gd`(新):导出包内的渲染自检入口 (导出模板不接受 --script,只能走 MT_TEST_MODE)。 - `/project/build/` 加入 .gitignore(渲染用例的落图输出)。 实机复验(真实服务器,非模拟): - 修前 `LIVE_SMOKE FAIL: 主角色 VID 有效`; `NETTRACE: MAIN_LOST was=25910 now=0 reason=despawn t=+7737ms`, 环里紧跟 `spawn vid=25910 is_main=false`。 - 修后 `LIVE_SMOKE RESULT: PASS`,main_vid=25933、entities=19; 40s 实机跑 16 次诊断采样全部 model_built=True / main_attempts=1 / main_sync_ready=True,player_pos 落在 C1 地形上而非 y=0; MT_DIAG_SHOT 存下的帧里地形、建筑、NPC、树木、真实角色模型、HUD、小地图俱全。 回归:gamescene_test / equip_model_test / gamescene_visual_test(feet_gap 0.001373)/ model_render_test(8 races)/ char_select_visual_test / 包内 MT_TEST_MODE=render 全绿;ctest 22/23,唯一失败 net.classic_session 已用 「还原本次改动 → 重跑 → diff 失败集合」确认与本次修改无关(失败集合完全相同)。 注:画面整体偏暗属于光照/色调差距,受 PARITY-GAP.md §0 门禁约束(对照帧未落盘前 不做相机/光照/色调/材质调参),本次不碰。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SJugvEJwz3FK4hw9ti3SRb
612 lines
20 KiB
C
612 lines
20 KiB
C
/* Granny "Oodle1" section decompressor (M0 T2).
|
|
*
|
|
* Ported (decode path only) from the leaked Granny 2.9.12 SDK:
|
|
* granny_oodle1_compression.cpp + radlz.c + radarith.c + arithbit.c
|
|
* Used as algorithm spec; RAD's build machinery / compressor are NOT included.
|
|
* See docs/reference/steps/M0-gr2-reader.md T2.
|
|
*/
|
|
#include "oodle1.h"
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
typedef uint8_t U8;
|
|
typedef uint16_t U16;
|
|
typedef uint32_t U32;
|
|
typedef int32_t S32;
|
|
typedef uintptr_t UINTa;
|
|
|
|
#define radassert(x) ((void)0)
|
|
|
|
/* ================================================================= */
|
|
/* arithbit.c — byte-aligned range decoder */
|
|
/* ================================================================= */
|
|
|
|
typedef struct ARITHBITS {
|
|
U32 low;
|
|
U32 range;
|
|
U8 *ptr;
|
|
U32 underflow; /* temp between Get()/Remove() to skip re-divide */
|
|
U8 *start; /* low bit used as scratch */
|
|
} ARITHBITS;
|
|
|
|
static void ArithBitsGetStart(ARITHBITS *ab, const void *ptr) {
|
|
U32 buf;
|
|
ab->ptr = (U8 *)ptr + 1;
|
|
buf = ((const U8 *)ptr)[0];
|
|
ab->start = (U8 *)((UINTa)ptr + (buf & 1));
|
|
ab->low = buf >> 1;
|
|
ab->range = 1u << 7;
|
|
}
|
|
|
|
static void ArithBitsDecRenorm(ARITHBITS *ab) {
|
|
U32 range = ab->range;
|
|
if (range <= 0x800000u) {
|
|
U32 low = ab->low;
|
|
U32 buf = (U32)((UINTa)ab->start & 1u);
|
|
U8 *ptr = ab->ptr;
|
|
do {
|
|
low = (low + low + buf) << 7;
|
|
buf = *ptr++;
|
|
low |= (buf >> 1);
|
|
buf &= 1u;
|
|
range <<= 8;
|
|
} while (range <= 0x800000u);
|
|
ab->low = low;
|
|
ab->start = (U8 *)(((UINTa)ab->start & ~(UINTa)1) + buf);
|
|
ab->range = range;
|
|
ab->ptr = ptr;
|
|
}
|
|
}
|
|
|
|
static U32 ArithBitsGet(ARITHBITS *ab, U32 scale) {
|
|
U32 tmp;
|
|
ArithBitsDecRenorm(ab);
|
|
ab->underflow = ab->range / scale;
|
|
tmp = ab->low / ab->underflow;
|
|
return (tmp >= scale) ? (scale - 1) : tmp;
|
|
}
|
|
|
|
static void ArithBitsRemove(ARITHBITS *ab, U32 start, U32 range, U32 scale) {
|
|
U32 tmp = ab->underflow * start;
|
|
ab->low -= tmp;
|
|
if ((start + range) < scale) ab->range = ab->underflow * range;
|
|
else ab->range -= tmp;
|
|
}
|
|
|
|
static U32 ArithBitsGetValue(ARITHBITS *ab, U32 scale) {
|
|
U32 tmp, div, start;
|
|
ArithBitsDecRenorm(ab);
|
|
div = ab->range / scale;
|
|
start = ab->low / div;
|
|
if (start >= scale) start = scale - 1;
|
|
tmp = div * start;
|
|
ab->low -= tmp;
|
|
if ((start + 1) < scale) ab->range = div;
|
|
else ab->range -= tmp;
|
|
return start;
|
|
}
|
|
|
|
static U32 ArithBitsGetBits(ARITHBITS *ab, U32 bits, U32 scale) {
|
|
U32 tmp;
|
|
ArithBitsDecRenorm(ab);
|
|
ab->underflow = ab->range >> bits;
|
|
tmp = ab->low / ab->underflow;
|
|
return (tmp >= scale) ? (scale - 1) : tmp;
|
|
}
|
|
|
|
static U32 ArithBitsGetBitsValue(ARITHBITS *ab, U32 bits, U32 scale) {
|
|
U32 tmp, div, start;
|
|
ArithBitsDecRenorm(ab);
|
|
div = ab->range >> bits;
|
|
start = ab->low / div;
|
|
if (start >= scale) start = scale - 1;
|
|
tmp = div * start;
|
|
ab->low -= tmp;
|
|
if ((start + 1) < scale) ab->range = div;
|
|
else ab->range -= tmp;
|
|
return start;
|
|
}
|
|
|
|
/* ================================================================= */
|
|
/* radarith.c — adaptive arithmetic modeller (decode path) */
|
|
/* ================================================================= */
|
|
|
|
#define NORM_BITS 14
|
|
#define NORM_COUNT (1 << NORM_BITS)
|
|
#define ADJ_SUMS (NORM_COUNT << 1)
|
|
#define OVERFLOW_COUNT ((NORM_BITS / 2) - 1) /* 6 */
|
|
#define COUNTTYPE U16
|
|
|
|
#define COUNTS_SIZE(v) (sizeof(COUNTTYPE) * (((v) + 1 + 1 + 3) & ~3u))
|
|
#define VALUES_SIZE(v) COUNTS_SIZE(v)
|
|
|
|
typedef struct ARITHDATA {
|
|
COUNTTYPE singles_tot;
|
|
COUNTTYPE update_tot;
|
|
COUNTTYPE update_max;
|
|
COUNTTYPE update_range;
|
|
COUNTTYPE rescale_tot;
|
|
COUNTTYPE singles_length;
|
|
COUNTTYPE summed_length;
|
|
COUNTTYPE unique_count;
|
|
COUNTTYPE *values;
|
|
COUNTTYPE *single_counts;
|
|
U32 table_walks[NORM_BITS];
|
|
COUNTTYPE summed_counts[OVERFLOW_COUNT]; /* deliberately overflows into the gap before single_counts */
|
|
} ARITHDATA;
|
|
typedef ARITHDATA *ARITH;
|
|
|
|
#define Arith_was_escaped(val) (((UINTa)(val)) > 65536u)
|
|
#define Arith_set_decompressed_symbol(val, v) (*((U16 *)(val)) = (U16)(v))
|
|
|
|
static U32 Arith_decompress_alloc_size(U32 unique_values) {
|
|
return (U32)(sizeof(ARITHDATA) + COUNTS_SIZE(unique_values)
|
|
+ COUNTS_SIZE(unique_values) + VALUES_SIZE(unique_values));
|
|
}
|
|
|
|
static ARITH Arith_open(void *ptr, void *compress_temp_buf, U32 max_value, U32 unique_values) {
|
|
ARITH a = (ARITH)ptr;
|
|
U32 u;
|
|
(void)compress_temp_buf; /* always NULL on the decode path */
|
|
if (!a) return a;
|
|
memset(a, 0, Arith_decompress_alloc_size(unique_values));
|
|
a->single_counts = (COUNTTYPE *)((char *)a + (sizeof(ARITHDATA) + COUNTS_SIZE(unique_values)));
|
|
a->values = (COUNTTYPE *)((char *)a->single_counts + COUNTS_SIZE(unique_values));
|
|
a->unique_count = (COUNTTYPE)unique_values;
|
|
a->singles_tot = 4;
|
|
a->summed_counts[0] = ADJ_SUMS;
|
|
a->summed_counts[1] = NORM_COUNT + ADJ_SUMS;
|
|
a->summed_counts[2] = NORM_COUNT + ADJ_SUMS;
|
|
a->summed_counts[3] = NORM_COUNT + ADJ_SUMS;
|
|
a->summed_counts[4] = NORM_COUNT + ADJ_SUMS;
|
|
a->summed_counts[5] = NORM_COUNT + ADJ_SUMS;
|
|
a->single_counts[0] = 4;
|
|
a->update_tot = 8;
|
|
a->update_range = 4;
|
|
u = max_value * 32;
|
|
if (u < 256) u = 256;
|
|
else if (u > 15160) u = 15160;
|
|
a->rescale_tot = (COUNTTYPE)u;
|
|
u = max_value * 2;
|
|
if (u < 128) u = 128;
|
|
else if (u >= (((U32)a->rescale_tot >> 1) - 32)) u = (U32)(((U32)a->rescale_tot >> 1) - 32);
|
|
a->update_max = (COUNTTYPE)u;
|
|
return a;
|
|
}
|
|
|
|
static void rescale_decompress(ARITH a) {
|
|
unsigned long i;
|
|
U32 max = 0, pos = (U32)-1;
|
|
a->single_counts[0] >>= 1;
|
|
a->singles_tot = a->single_counts[0];
|
|
for (i = 1; i <= a->singles_length; i++) {
|
|
while (a->single_counts[i] <= 1) {
|
|
if (i < a->singles_length) {
|
|
a->single_counts[i] = a->single_counts[a->singles_length];
|
|
a->single_counts[a->singles_length] = 0;
|
|
a->values[i] = a->values[a->singles_length];
|
|
--a->singles_length;
|
|
} else {
|
|
a->single_counts[i] = 0;
|
|
--a->singles_length;
|
|
goto done;
|
|
}
|
|
}
|
|
a->single_counts[i] >>= 1;
|
|
a->singles_tot = (COUNTTYPE)(a->singles_tot + a->single_counts[i]);
|
|
if (a->single_counts[i] > max) { max = a->single_counts[i]; pos = (U32)i; }
|
|
}
|
|
done:
|
|
if (max && a->singles_length) {
|
|
U32 j = a->singles_length;
|
|
if (pos != j) {
|
|
U32 t;
|
|
t = a->single_counts[j]; a->single_counts[j] = a->single_counts[pos]; a->single_counts[pos] = (COUNTTYPE)t;
|
|
t = a->values[j]; a->values[j] = a->values[pos]; a->values[pos] = (COUNTTYPE)t;
|
|
}
|
|
}
|
|
if ((a->singles_length != a->unique_count) && (a->single_counts[0] == 0)) {
|
|
++a->single_counts[0];
|
|
++a->singles_tot;
|
|
}
|
|
}
|
|
|
|
static void update_counts(ARITH a) {
|
|
U32 i, tot, adj;
|
|
adj = (NORM_COUNT * 8) / a->singles_tot;
|
|
tot = ((((U32)a->single_counts[0]) * adj) >> 3) + ADJ_SUMS;
|
|
a->summed_counts[0] = ADJ_SUMS;
|
|
i = 1;
|
|
for (;;) {
|
|
a->summed_counts[i] = (COUNTTYPE)tot;
|
|
if (i > a->singles_length) break;
|
|
tot += (((U32)a->single_counts[i]) * adj) >> 3;
|
|
++i;
|
|
}
|
|
tot = (U32)a->update_range << 1;
|
|
if (tot > a->update_max) {
|
|
a->update_tot = (COUNTTYPE)(a->singles_tot + a->update_max);
|
|
} else {
|
|
a->update_range = (COUNTTYPE)tot;
|
|
a->update_tot = (COUNTTYPE)(a->singles_tot + tot);
|
|
}
|
|
a->summed_length = a->singles_length;
|
|
a->summed_counts[a->summed_length + 1] = NORM_COUNT + ADJ_SUMS;
|
|
}
|
|
|
|
static void build_walk_table(U32 *wtable, U32 v) {
|
|
U32 num = 0, *m = wtable;
|
|
++v;
|
|
do {
|
|
v = (v + 1) >> 1;
|
|
++num;
|
|
*m++ = v;
|
|
} while (v > 4);
|
|
num -= 1;
|
|
if (num) {
|
|
memmove(wtable + NORM_BITS - 1 - num + 1, wtable + 1, num * sizeof(wtable[0]));
|
|
}
|
|
wtable[1] = wtable[0];
|
|
wtable[0] = num;
|
|
}
|
|
|
|
static UINTa Arith_decompress(ARITH a, ARITHBITS *ab) {
|
|
U32 offset;
|
|
S32 index;
|
|
|
|
if (a->singles_tot >= a->update_tot) {
|
|
if (a->update_tot >= a->rescale_tot) rescale_decompress(a);
|
|
update_counts(a);
|
|
a->summed_counts[a->summed_length + 2] = NORM_COUNT + ADJ_SUMS;
|
|
a->summed_counts[a->summed_length + 3] = NORM_COUNT + ADJ_SUMS;
|
|
a->summed_counts[a->summed_length + 4] = NORM_COUNT + ADJ_SUMS;
|
|
a->summed_counts[a->summed_length + 5] = NORM_COUNT + ADJ_SUMS;
|
|
a->summed_counts[a->summed_length + 6] = NORM_COUNT + ADJ_SUMS;
|
|
build_walk_table(a->table_walks, a->summed_length);
|
|
}
|
|
|
|
offset = ArithBitsGetBits(ab, NORM_BITS, NORM_COUNT) + ADJ_SUMS;
|
|
|
|
{
|
|
index = (S32)a->table_walks[1];
|
|
#define check_level(lev) \
|
|
if (a->summed_counts[index] > (COUNTTYPE)offset) \
|
|
index -= (S32)a->table_walks[NORM_BITS - 1 - (lev)]; \
|
|
else \
|
|
index += (S32)a->table_walks[NORM_BITS - 1 - (lev)];
|
|
switch (a->table_walks[0]) {
|
|
case 11: check_level(10) /* fallthrough */
|
|
case 10: check_level(9) /* fallthrough */
|
|
case 9: check_level(8) /* fallthrough */
|
|
case 8: check_level(7) /* fallthrough */
|
|
case 7: check_level(6) /* fallthrough */
|
|
case 6: check_level(5) /* fallthrough */
|
|
case 5: check_level(4) /* fallthrough */
|
|
case 4: check_level(3) /* fallthrough */
|
|
case 3: check_level(2) /* fallthrough */
|
|
case 2: check_level(1) /* fallthrough */
|
|
case 1: check_level(0) /* fallthrough */
|
|
case 0: break;
|
|
default: break;
|
|
}
|
|
#undef check_level
|
|
|
|
if (a->summed_counts[index] > (COUNTTYPE)offset) index -= 2;
|
|
else index += 2;
|
|
|
|
if (a->summed_counts[index] > (COUNTTYPE)offset) {
|
|
if (a->summed_counts[index - 1] > (COUNTTYPE)offset) index -= 2;
|
|
else --index;
|
|
} else {
|
|
if (a->summed_counts[index + 1] <= (COUNTTYPE)offset) ++index;
|
|
}
|
|
}
|
|
|
|
ArithBitsRemove(ab, a->summed_counts[index] - ADJ_SUMS,
|
|
a->summed_counts[index + 1] - a->summed_counts[index], NORM_COUNT);
|
|
|
|
++a->single_counts[index];
|
|
++a->singles_tot;
|
|
|
|
if (index <= 0) {
|
|
if (a->singles_length == a->summed_length) {
|
|
new_index:
|
|
index = ++a->singles_length;
|
|
a->single_counts[index] += 2;
|
|
a->singles_tot += 2;
|
|
if (a->singles_length == a->unique_count) {
|
|
a->singles_tot = (COUNTTYPE)(a->singles_tot - a->single_counts[0]);
|
|
a->single_counts[0] = 0;
|
|
}
|
|
return (UINTa)&a->values[index];
|
|
} else {
|
|
if (ArithBitsGetBitsValue(ab, 1, 2)) {
|
|
index = (S32)(ArithBitsGetValue(ab, a->singles_length - a->summed_length)
|
|
+ a->summed_length + 1);
|
|
a->single_counts[index] += 2;
|
|
a->singles_tot += 2;
|
|
} else {
|
|
goto new_index;
|
|
}
|
|
}
|
|
}
|
|
|
|
return a->values[index];
|
|
}
|
|
|
|
/* ================================================================= */
|
|
/* radlz.c — LZ decoder (decode path) */
|
|
/* ================================================================= */
|
|
|
|
#define LOW_BITS 2
|
|
#define MED_BITS 8
|
|
#define TOP_BITS 8
|
|
#define LARGEST_POSSIBLE_OFFSET ((1 << (LOW_BITS + MED_BITS + TOP_BITS)) - 1)
|
|
#define GRANNY_OFFSET LARGEST_POSSIBLE_OFFSET
|
|
|
|
#define MAX_LENS 64
|
|
#define LMAX (1 << LOW_BITS) /* 4 */
|
|
#define MMAX (1 << MED_BITS) /* 256 */
|
|
#define GETT(v) ((v) >> (LOW_BITS + MED_BITS))
|
|
#define ADDRESS_MASK 3
|
|
|
|
static const U32 long_lengths[4] = { MAX_LENS * 2, MAX_LENS * 3, MAX_LENS * 4, MAX_LENS * 8 };
|
|
|
|
typedef struct LZ_HEADER {
|
|
U32 max_offset_and_byte;
|
|
U32 uniq_offset_and_byte;
|
|
U32 uniq_lens;
|
|
} LZ_HEADER;
|
|
|
|
typedef struct LZDDATA {
|
|
ARITH bytes[ADDRESS_MASK + 1];
|
|
ARITH lens[MAX_LENS + 1];
|
|
ARITH offsl, offst;
|
|
ARITH offsm[256];
|
|
U32 max_bytes, max_offs, max_offsL, max_offsM, max_offsT;
|
|
U32 bytes_decompressed;
|
|
U32 last_len;
|
|
} LZDDATA;
|
|
typedef LZDDATA *LZD;
|
|
|
|
static void get_max_offsets(U32 max_offset, U32 *low_max, U32 *med_max, U32 *high_max) {
|
|
*low_max = (max_offset >= LMAX) ? LMAX : (max_offset + 1);
|
|
*med_max = ((max_offset >> LOW_BITS) >= MMAX) ? MMAX : ((max_offset >> LOW_BITS) + 1);
|
|
*high_max = GETT(max_offset) + 1;
|
|
}
|
|
|
|
static void copy_bytes(void *d, const void *s, U32 length) {
|
|
U8 *dest = (U8 *)d;
|
|
const U8 *src = (const U8 *)s;
|
|
/* LZ back-refs always have src < dest, so the fast path never triggers here;
|
|
* byte copy is required for correct RLE-style overlap. */
|
|
if (length >= 4 && (src - dest) >= 4) {
|
|
do {
|
|
length -= 4;
|
|
((U32 *)dest)[0] = ((const U32 *)src)[0];
|
|
dest += 4; src += 4;
|
|
} while (length > 4);
|
|
if (length == 0) return;
|
|
}
|
|
do { length--; *dest++ = *src++; } while (length);
|
|
}
|
|
|
|
static U32 LZ_decompress_alloc_size(U32 max_byte_value, U32 uniq_byte_values, U32 max_offset) {
|
|
U32 size, h, low_max, med_max, high_max;
|
|
(void)max_byte_value;
|
|
size = (U32)sizeof(LZDDATA);
|
|
get_max_offsets(max_offset, &low_max, &med_max, &high_max);
|
|
h = Arith_decompress_alloc_size(uniq_byte_values);
|
|
size += h * (ADDRESS_MASK + 1);
|
|
size += Arith_decompress_alloc_size(MAX_LENS + 1) * (MAX_LENS + 1);
|
|
size += Arith_decompress_alloc_size(low_max);
|
|
size += high_max * Arith_decompress_alloc_size(med_max);
|
|
size += Arith_decompress_alloc_size(high_max);
|
|
return size;
|
|
}
|
|
|
|
static LZD LZ_decompress_open_from_header(void *ptr, const LZ_HEADER *h) {
|
|
U32 i, j, size, uniq = 0;
|
|
U8 *addr;
|
|
LZD l = (LZD)ptr;
|
|
|
|
l->max_bytes = h->max_offset_and_byte & 511;
|
|
l->max_offs = h->max_offset_and_byte >> 9;
|
|
get_max_offsets(l->max_offs, &l->max_offsL, &l->max_offsM, &l->max_offsT);
|
|
l->last_len = 0;
|
|
l->bytes_decompressed = 0;
|
|
|
|
j = h->uniq_offset_and_byte & 511;
|
|
addr = (U8 *)(l + 1);
|
|
size = Arith_decompress_alloc_size(j);
|
|
for (i = 0; i <= ADDRESS_MASK; i++) {
|
|
l->bytes[i] = Arith_open(addr, 0, l->max_bytes - 1, j);
|
|
addr += size;
|
|
}
|
|
|
|
for (j = 0; j < 4; j++) {
|
|
uniq = (h->uniq_lens >> ((3 - j) * 8)) & 255;
|
|
size = Arith_decompress_alloc_size(uniq);
|
|
for (i = 0; i < (MAX_LENS / 4); i++) {
|
|
l->lens[(j * (MAX_LENS / 4)) + i] = Arith_open(addr, 0, MAX_LENS, uniq);
|
|
addr += size;
|
|
}
|
|
}
|
|
for (i = (MAX_LENS / 4) * 4; i <= MAX_LENS; i++) {
|
|
l->lens[i] = Arith_open(addr, 0, MAX_LENS, uniq); /* inherits last uniq/size */
|
|
addr += size;
|
|
}
|
|
|
|
size = Arith_decompress_alloc_size(l->max_offsM);
|
|
for (i = 0; i < l->max_offsT; i++) {
|
|
l->offsm[i] = Arith_open(addr, 0, l->max_offsM - 1, l->max_offsM);
|
|
addr += size;
|
|
}
|
|
|
|
l->offsl = Arith_open(addr, 0, l->max_offsL - 1, l->max_offsL);
|
|
l->offst = Arith_open(addr + Arith_decompress_alloc_size(l->max_offsL),
|
|
0, l->max_offsT - 1, GETT(h->uniq_offset_and_byte >> 9) + 1);
|
|
return l;
|
|
}
|
|
|
|
static U32 LZ_decompress(LZD l, ARITHBITS *ab, U8 *output) {
|
|
UINTa v;
|
|
U32 escaped;
|
|
|
|
v = Arith_decompress(l->lens[l->last_len], ab);
|
|
if (Arith_was_escaped(v)) {
|
|
escaped = ArithBitsGetValue(ab, MAX_LENS + 1);
|
|
Arith_set_decompressed_symbol(v, escaped);
|
|
v = escaped;
|
|
}
|
|
l->last_len = (U32)v;
|
|
|
|
if (v) {
|
|
U32 len, off, max_ofs;
|
|
UINTa m;
|
|
|
|
max_ofs = l->max_offs;
|
|
if (max_ofs > l->bytes_decompressed) max_ofs = l->bytes_decompressed;
|
|
|
|
len = (v >= (MAX_LENS - 3)) ? long_lengths[v - (MAX_LENS - 3)] : (U32)(v + 1);
|
|
|
|
v = Arith_decompress(l->offsl, ab);
|
|
if (Arith_was_escaped(v)) {
|
|
escaped = ArithBitsGetValue(ab, l->max_offsL);
|
|
Arith_set_decompressed_symbol(v, escaped);
|
|
v = escaped;
|
|
}
|
|
off = (U32)(v + 1);
|
|
|
|
v = Arith_decompress(l->offst, ab);
|
|
if (Arith_was_escaped(v)) {
|
|
escaped = ArithBitsGetValue(ab, GETT(max_ofs) + 1);
|
|
Arith_set_decompressed_symbol(v, escaped);
|
|
v = escaped;
|
|
}
|
|
|
|
m = Arith_decompress(l->offsm[v], ab);
|
|
if (Arith_was_escaped(m)) {
|
|
U32 offsm_used;
|
|
if (max_ofs >= (MMAX << LOW_BITS)) offsm_used = MMAX;
|
|
else offsm_used = (max_ofs >> LOW_BITS) + 1;
|
|
escaped = ArithBitsGetValue(ab, offsm_used);
|
|
Arith_set_decompressed_symbol(m, escaped);
|
|
m = escaped;
|
|
}
|
|
|
|
off = (U32)(off + ((U32)m << LOW_BITS) + ((U32)v << (LOW_BITS + MED_BITS)));
|
|
l->bytes_decompressed += len;
|
|
copy_bytes(output, output - off, len);
|
|
return len;
|
|
} else {
|
|
v = Arith_decompress(l->bytes[((UINTa)output) & ADDRESS_MASK], ab);
|
|
if (Arith_was_escaped(v)) {
|
|
escaped = ArithBitsGetValue(ab, l->max_bytes);
|
|
Arith_set_decompressed_symbol(v, escaped);
|
|
v = escaped;
|
|
}
|
|
*output = (U8)v;
|
|
++l->bytes_decompressed;
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
/* ================================================================= */
|
|
/* granny_oodle1_compression.cpp — the wrapper */
|
|
/* ================================================================= */
|
|
|
|
#define ALIGN32(x) (((x) + 3u) & ~3u)
|
|
|
|
static void reverse32(void *p, U32 nbytes) {
|
|
U8 *b = (U8 *)p;
|
|
U32 i;
|
|
for (i = 0; i + 4 <= nbytes; i += 4) {
|
|
U8 t0 = b[i], t1 = b[i + 1];
|
|
b[i] = b[i + 3];
|
|
b[i + 1] = b[i + 2];
|
|
b[i + 2] = t1;
|
|
b[i + 3] = t0;
|
|
}
|
|
}
|
|
|
|
int gr2_oodle1_decompress(int file_is_byte_reversed,
|
|
U32 comp_size, void *comp_bytes,
|
|
U32 stop0, U32 stop1, U32 stop2, void *out) {
|
|
LZ_HEADER headers[3];
|
|
ARITHBITS ab;
|
|
U32 temp_size, size, b;
|
|
U32 stops[3];
|
|
void *temp;
|
|
U8 *to;
|
|
|
|
/* hanging-bit fix: zero-pad up to 32-bit alignment (caller also over-allocates). */
|
|
{
|
|
U32 rounded = ALIGN32(comp_size) - comp_size;
|
|
while (rounded--) ((U8 *)comp_bytes)[comp_size + rounded] = 0;
|
|
}
|
|
|
|
if (comp_size < sizeof(headers)) return -1;
|
|
memcpy(headers, comp_bytes, sizeof(headers));
|
|
if (file_is_byte_reversed) reverse32(headers, sizeof(headers));
|
|
|
|
ArithBitsGetStart(&ab, (const U8 *)comp_bytes + sizeof(headers));
|
|
|
|
temp_size = LZ_decompress_alloc_size(255, 256, GRANNY_OFFSET);
|
|
temp = malloc(temp_size);
|
|
if (!temp) return -2;
|
|
|
|
size = 0;
|
|
stops[0] = stop0; stops[1] = stop1; stops[2] = stop2;
|
|
to = (U8 *)out;
|
|
for (b = 0; b < 3; ++b) {
|
|
U32 stop = stops[b];
|
|
LZD lz = LZ_decompress_open_from_header(temp, &headers[b]);
|
|
U32 guard = 0;
|
|
while (size < stop) {
|
|
U32 len = LZ_decompress(lz, &ab, to);
|
|
if (len == 0 || size + len > stop2 + 512) { free(temp); return -3; } /* corruption guard */
|
|
size += len;
|
|
to += len;
|
|
if (++guard > stop2 + 16) { free(temp); return -4; }
|
|
}
|
|
if (size != stop) { free(temp); return -5; }
|
|
}
|
|
|
|
free(temp);
|
|
return 0;
|
|
}
|
|
|
|
int gr2_oodle1_decompress_chunk(int file_is_byte_reversed,
|
|
U32 comp_size, void *comp_bytes,
|
|
U32 decompressed_size, void *out) {
|
|
LZ_HEADER header;
|
|
ARITHBITS ab;
|
|
U32 temp_size, size;
|
|
void *temp;
|
|
U8 *to;
|
|
|
|
if (comp_size < sizeof(header)) return -1;
|
|
memcpy(&header, comp_bytes, sizeof(header));
|
|
if (file_is_byte_reversed) reverse32(&header, sizeof(header));
|
|
|
|
ArithBitsGetStart(&ab, (const U8 *)comp_bytes + sizeof(header));
|
|
temp_size = LZ_decompress_alloc_size(255, 256, GRANNY_OFFSET);
|
|
temp = malloc(temp_size);
|
|
if (!temp) return -2;
|
|
|
|
size = 0;
|
|
to = (U8 *)out;
|
|
{
|
|
LZD lz = LZ_decompress_open_from_header(temp, &header);
|
|
while (size < decompressed_size) {
|
|
U32 len = LZ_decompress(lz, &ab, to);
|
|
if (len == 0) { free(temp); return -3; }
|
|
size += len;
|
|
to += len;
|
|
}
|
|
}
|
|
free(temp);
|
|
return (size == decompressed_size) ? 0 : -5;
|
|
}
|