mirror of
https://github.com/ACreTeam/ac-decomp
synced 2026-05-23 06:34:18 -04:00
almost match act_ball_b
This commit is contained in:
@@ -25370,9 +25370,28 @@ modules:
|
||||
- symbol: act_ant_v
|
||||
binary: assets/act_ant_v.bin
|
||||
header: assets/act_ant_v.inc
|
||||
header_type: none
|
||||
custom_type: vtx
|
||||
|
||||
- symbol: glider_v
|
||||
binary: assets/glider_v.bin
|
||||
header: assets/glider_v.inc
|
||||
header_type: none
|
||||
custom_type: vtx
|
||||
|
||||
- symbol: act_ball_b_pal
|
||||
binary: assets/act_ball_b_pal.bin
|
||||
header: assets/act_ball_b_pal.inc
|
||||
header_type: none
|
||||
custom_type: pal16
|
||||
|
||||
- symbol: act_ball_b_1_tex
|
||||
binary: assets/act_ball_b_1_tex.bin
|
||||
header: assets/act_ball_b_1_tex.inc
|
||||
header_type: raw
|
||||
|
||||
- symbol: act_ball_b_v
|
||||
binary: assets/act_ball_b_v.bin
|
||||
header: assets/act_ball_b_v.inc
|
||||
header_type: none
|
||||
custom_type: vtx
|
||||
@@ -3678,10 +3678,16 @@ data/scene/broker_shop.c:
|
||||
data/scene/buggy.c:
|
||||
.data start:0x0009FBC0 end:0x0009FC28
|
||||
|
||||
data/dataobject/dataobject_9FC28:
|
||||
data/model/act_ball_b.c:
|
||||
.data start:0x0009FC28 end:0x0009FFD8
|
||||
|
||||
data/dataobject/dataobject_9FFD8:
|
||||
.data start:0x0009FFD8 end:0x000A0738
|
||||
|
||||
data/dataobject/dataobject_A0738:
|
||||
.data start:0x000A0738 end:0x000A1120
|
||||
|
||||
data/dataobject/dataobject_A1120:
|
||||
.data start:0x000A1120 end:0x000A1B78
|
||||
|
||||
data/npc/model/mdl/bea_1.c:
|
||||
|
||||
@@ -2634,6 +2634,7 @@ config.libs = [
|
||||
Object(Matching, "data/model/act_ant.c"),
|
||||
Object(Matching, "data/model/act_ant_anim.c"),
|
||||
Object(Matching, "data/model/glider.c"),
|
||||
Object(Matching, "data/model/act_ball_b.c"),
|
||||
],
|
||||
),
|
||||
]
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
#include "libforest/gbi_extensions.h"
|
||||
#include "PR/gbi.h"
|
||||
#include "evw_anime.h"
|
||||
|
||||
|
||||
u16 act_ball_b_pal[] ATTRIBUTE_ALIGN(32)= {
|
||||
#include "assets/act_ball_b_pal.inc"
|
||||
};
|
||||
|
||||
u8 act_ball_b_1_tex[] ATTRIBUTE_ALIGN(32)= {
|
||||
#include "assets/act_ball_b_1_tex.inc"
|
||||
};
|
||||
|
||||
Vtx act_ball_b_v[] = {
|
||||
#include "assets/act_ball_b_v.inc"
|
||||
};
|
||||
|
||||
Gfx act_ball_b_model[] = {
|
||||
/* d7000002 ffffffff */ gsSPTexture(65535, 65535, 0, G_TX_RENDERTILE, G_ON),
|
||||
/* e7000000 00000000 */ gsDPPipeSync(),
|
||||
/* e200001c c8113078 */ gsDPSetRenderMode(G_RM_FOG_SHADE_A, G_RM_AA_ZB_TEX_EDGE2),
|
||||
/* fc127e60 fffff3f8 */ gsDPSetCombineLERP(TEXEL0, 0, SHADE, 0, 0, 0, 0, TEXEL0, PRIMITIVE, 0, COMBINED, 0, 0, 0, 0, COMBINED),
|
||||
/* e3001001 00008000 */ gsDPSetTextureLUT(G_TT_RGBA16),
|
||||
/* fd100000 00000000 */ gsDPSetTextureImage(G_IM_FMT_RGBA, G_IM_SIZ_16b, 1, act_ball_b_pal),
|
||||
/* e8000000 00000000 */ gsDPTileSync(),
|
||||
/* f50001f0 07000000 */ gsDPSetTile(G_IM_FMT_RGBA, G_IM_SIZ_4b, 0, 496, G_TX_LOADTILE, 0, G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMASK, G_TX_NOLOD, G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMASK, G_TX_NOLOD),
|
||||
/* e6000000 00000000 */ gsDPLoadSync(),
|
||||
/* f0000000 0703c000 */ gsDPLoadTLUTCmd(G_TX_LOADTILE, 15),
|
||||
/* e7000000 00000000 */ gsDPPipeSync(),
|
||||
/* fd500000 00000000 */ gsDPSetTextureImage(G_IM_FMT_CI, G_IM_SIZ_16b, 1, act_ball_b_1_tex),
|
||||
/* f5500000 07050140 */ gsDPSetTile(G_IM_FMT_CI, G_IM_SIZ_16b, 0, 0, G_TX_LOADTILE, 0, G_TX_MIRROR | G_TX_WRAP, 4, G_TX_NOLOD, G_TX_MIRROR | G_TX_WRAP, 4, G_TX_NOLOD),
|
||||
/* e6000000 00000000 */ gsDPLoadSync(),
|
||||
/* f3000000 0703f800 */ gsDPLoadBlock(G_TX_LOADTILE, 0, 0, 63, 2048),
|
||||
/* e7000000 00000000 */ gsDPPipeSync(),
|
||||
/* f5400200 00f50140 */ gsDPSetTile(G_IM_FMT_CI, G_IM_SIZ_4b, 1, 0, G_TX_RENDERTILE, 15, G_TX_MIRROR | G_TX_WRAP, 4, G_TX_NOLOD, G_TX_MIRROR | G_TX_WRAP, 4, G_TX_NOLOD),
|
||||
/* f2000000 0003c03c */ gsDPSetTileSize(G_TX_RENDERTILE, 0, 0, 60, 60),
|
||||
/* fa000080 ffffffff */ gsDPSetPrimColor(0, 128, 255, 255, 255, 255),
|
||||
/* d9000000 00230405 */ gsSPLoadGeometryMode(G_ZBUFFER | G_SHADE | G_CULL_BACK | G_FOG | G_LIGHTING | G_SHADING_SMOOTH),
|
||||
/* 0101a034 00000000 */ gsSPVertex(act_ball_b_v, 26, 0),
|
||||
/* 06000204 00020604 */ gsSP2Triangles(0, 1, 2, 0, 1, 3, 2, 0),
|
||||
/* 06020806 00020a08 */ gsSP2Triangles(1, 4, 3, 0, 1, 5, 4, 0),
|
||||
/* 06080c06 00080e0c */ gsSP2Triangles(4, 6, 3, 0, 4, 7, 6, 0),
|
||||
/* 060c0406 000c1004 */ gsSP2Triangles(6, 2, 3, 0, 6, 8, 2, 0),
|
||||
/* 06121416 00141816 */ gsSP2Triangles(9, 10, 11, 0, 10, 12, 11, 0),
|
||||
/* 06141a18 00141c1a */ gsSP2Triangles(10, 13, 12, 0, 10, 14, 13, 0),
|
||||
/* 061a1e18 001a201e */ gsSP2Triangles(13, 15, 12, 0, 13, 16, 15, 0),
|
||||
/* 061e1618 001e2216 */ gsSP2Triangles(15, 11, 12, 0, 15, 17, 11, 0),
|
||||
/* 060a0224 00022624 */ gsSP2Triangles(5, 1, 18, 0, 1, 19, 18, 0),
|
||||
/* 06022826 00020028 */ gsSP2Triangles(1, 20, 19, 0, 1, 0, 20, 0),
|
||||
/* 06281e26 0028221e */ gsSP2Triangles(20, 15, 19, 0, 20, 17, 15, 0),
|
||||
/* 061e2426 001e2024 */ gsSP2Triangles(15, 18, 19, 0, 15, 16, 18, 0),
|
||||
/* 060e082a 00082c2a */ gsSP2Triangles(7, 4, 21, 0, 4, 22, 21, 0),
|
||||
/* 0608242c 00080a24 */ gsSP2Triangles(4, 18, 22, 0, 4, 5, 18, 0),
|
||||
/* 06241a2c 0024201a */ gsSP2Triangles(18, 13, 22, 0, 18, 16, 13, 0),
|
||||
/* 061a2a2c 001a1c2a */ gsSP2Triangles(13, 21, 22, 0, 13, 14, 21, 0),
|
||||
/* 06100c2e 000c302e */ gsSP2Triangles(8, 6, 23, 0, 6, 24, 23, 0),
|
||||
/* 060c2a30 000c0e2a */ gsSP2Triangles(6, 21, 24, 0, 6, 7, 21, 0),
|
||||
/* 062a1430 002a1c14 */ gsSP2Triangles(21, 10, 24, 0, 21, 14, 10, 0),
|
||||
/* 06142e30 0014122e */ gsSP2Triangles(10, 23, 24, 0, 10, 9, 23, 0),
|
||||
/* 06000428 00043228 */ gsSP2Triangles(0, 2, 20, 0, 2, 25, 20, 0),
|
||||
/* 06042e32 0004102e */ gsSP2Triangles(2, 23, 25, 0, 2, 8, 23, 0),
|
||||
/* 062e1632 002e1216 */ gsSP2Triangles(23, 11, 25, 0, 23, 9, 11, 0),
|
||||
/* 06162832 00162228 */ gsSP2Triangles(11, 20, 25, 0, 11, 17, 20, 0),
|
||||
/* df000000 00000000 */ gsSPEndDisplayList()
|
||||
};
|
||||
|
||||
@@ -0,0 +1,308 @@
|
||||
from argparse import ArgumentParser
|
||||
from gfxdis import convert_binary_to_gfx
|
||||
from dataclasses import dataclass
|
||||
import struct
|
||||
|
||||
|
||||
@dataclass
|
||||
class EVW_REF:
|
||||
symbol_name: str
|
||||
symbol_type: str
|
||||
|
||||
|
||||
@dataclass
|
||||
class EVW_ANIME_result:
|
||||
formatted_str: str
|
||||
referenced_objects: list[EVW_REF]
|
||||
|
||||
|
||||
def anim_type_conv(anim_type: int):
|
||||
return {
|
||||
0: "EVW_ANIME_TYPE_SCROLL1",
|
||||
1: "EVW_ANIME_TYPE_SCROLL2",
|
||||
2: "EVW_ANIME_TYPE_COLREG_MANUAL",
|
||||
3: "EVW_ANIME_TYPE_COLREG_LINEAR",
|
||||
4: "EVW_ANIME_TYPE_COLREG_NONLINEAR",
|
||||
5: "EVW_ANIME_TYPE_TEXANIME",
|
||||
}.get(anim_type, str(anim_type))
|
||||
|
||||
|
||||
def parse_evw_anime_data(buff: bytes, symbols: list[str]) -> EVW_ANIME_result:
|
||||
BUFF_SIZE = 8
|
||||
assert ((len(buff) % BUFF_SIZE) == 0)
|
||||
found_syms = []
|
||||
out_data = ""
|
||||
i = 0
|
||||
while True:
|
||||
b = buff[i:i+BUFF_SIZE]
|
||||
i += BUFF_SIZE
|
||||
if len(b) < BUFF_SIZE:
|
||||
break
|
||||
(segment, anim_type) = struct.unpack(">bxhxxxx", b)
|
||||
anim_type = anim_type_conv(anim_type)
|
||||
this_symbol_name = symbols.pop(0)
|
||||
vals = (segment, anim_type, this_symbol_name)
|
||||
out_data += f"\t{{ {', '.join([str(x) for x in vals])} }},\n"
|
||||
found_syms.append(EVW_REF(this_symbol_name, anim_type))
|
||||
return EVW_ANIME_result(out_data, found_syms)
|
||||
|
||||
|
||||
def parse_evw_anime_col_prim(buff: bytes, symbols: list[str]):
|
||||
BUFF_SIZE = 5
|
||||
assert ((len(buff) % BUFF_SIZE) == 0)
|
||||
out_data = ""
|
||||
i = 0
|
||||
while True:
|
||||
b = buff[i:i+BUFF_SIZE]
|
||||
i += BUFF_SIZE
|
||||
if len(b) < BUFF_SIZE:
|
||||
break
|
||||
vals = struct.unpack(">BBBBB", b)
|
||||
out_data += f"\t{{ {', '.join([str(x) for x in vals])} }},\n"
|
||||
return EVW_ANIME_result(out_data, [])
|
||||
|
||||
|
||||
def parse_evw_anime_col_env(buff: bytes, symbols: list[str]):
|
||||
BUFF_SIZE = 4
|
||||
assert ((len(buff) % BUFF_SIZE) == 0)
|
||||
out_data = ""
|
||||
i = 0
|
||||
while True:
|
||||
b = buff[i:i+BUFF_SIZE]
|
||||
i += BUFF_SIZE
|
||||
if len(b) < BUFF_SIZE:
|
||||
break
|
||||
vals = struct.unpack(">BBBB", b)
|
||||
out_data += f"\t{{ {', '.join([str(x) for x in vals])} }},\n"
|
||||
return EVW_ANIME_result(out_data, [])
|
||||
|
||||
|
||||
def parse_evw_anime_colreg(buff: bytes, symbols: list[str]):
|
||||
BUFF_SIZE = 0x10
|
||||
assert ((len(buff) % BUFF_SIZE) == 0)
|
||||
found_syms = []
|
||||
out_data = ""
|
||||
i = 0
|
||||
while True:
|
||||
b = buff[i:i+BUFF_SIZE]
|
||||
i += BUFF_SIZE
|
||||
if len(b) < BUFF_SIZE:
|
||||
break
|
||||
(frame_count, key_count) = struct.unpack(">HHxxxxxxxxxxxx", b)
|
||||
prim_colors = symbols.pop(0)
|
||||
env_colors = symbols.pop(0)
|
||||
keyframes = symbols.pop(0)
|
||||
vals = (frame_count, key_count, prim_colors, env_colors, keyframes)
|
||||
out_data += f"\t{{ {', '.join([str(x) for x in vals])} }},\n"
|
||||
found_syms.append(EVW_REF(prim_colors, "EVW_ANIME_COL_PRIM"))
|
||||
found_syms.append(EVW_REF(env_colors, "EVW_ANIME_COL_ENV"))
|
||||
found_syms.append(EVW_REF(keyframes, "u16"))
|
||||
return EVW_ANIME_result(out_data, found_syms)
|
||||
|
||||
|
||||
def parse_evw_anime_scroll(buff: bytes, symbols: list[str]):
|
||||
BUFF_SIZE = 4
|
||||
assert ((len(buff) % BUFF_SIZE) == 0)
|
||||
found_syms = []
|
||||
out_data = ""
|
||||
i = 0
|
||||
while True:
|
||||
b = buff[i:i+BUFF_SIZE]
|
||||
i += BUFF_SIZE
|
||||
if len(b) < BUFF_SIZE:
|
||||
break
|
||||
vals = struct.unpack(">bbBB", b)
|
||||
out_data += f"\t{{ {', '.join([str(x) for x in vals])} }},\n"
|
||||
return EVW_ANIME_result(out_data, found_syms)
|
||||
|
||||
|
||||
def parse_evw_texanime(buff: bytes, symbols: list[str]):
|
||||
BUFF_SIZE = 0x10
|
||||
assert ((len(buff) % BUFF_SIZE) == 0)
|
||||
found_syms = []
|
||||
out_data = ""
|
||||
i = 0
|
||||
while True:
|
||||
b = buff[i:i+BUFF_SIZE]
|
||||
i += BUFF_SIZE
|
||||
if len(b) < BUFF_SIZE:
|
||||
break
|
||||
(frame_count, key_count) = struct.unpack(">HHxxxxxxxxxxxx", b)
|
||||
texture_tbl = symbols.pop(0)
|
||||
animation_pattern = symbols.pop(0)
|
||||
keyframes = symbols.pop(0)
|
||||
vals = (frame_count, key_count, texture_tbl,
|
||||
animation_pattern, keyframes)
|
||||
out_data += f"\t{{ {', '.join([str(x) for x in vals])} }},\n"
|
||||
found_syms.append(EVW_REF(texture_tbl, "VOID*_LIST"))
|
||||
found_syms.append(EVW_REF(animation_pattern, "u8"))
|
||||
found_syms.append(EVW_REF(keyframes, "u16"))
|
||||
return EVW_ANIME_result(out_data, found_syms)
|
||||
|
||||
|
||||
def lookup_bins_and_symbols(lines: list[str], name: str):
|
||||
out_bin = bytearray()
|
||||
out_symbols = []
|
||||
begin_ind = 0
|
||||
end_ind = 0
|
||||
for i, line in enumerate(lines):
|
||||
if line.startswith(f".obj {name}"):
|
||||
begin_ind = i
|
||||
if line.startswith(f".endobj {name}"):
|
||||
end_ind = i
|
||||
data_lines = lines[begin_ind+1:end_ind]
|
||||
|
||||
for line in data_lines:
|
||||
data = line.split(".4byte ")[1]
|
||||
try:
|
||||
out_bin.extend(int(data, 16).to_bytes(4, 'big'))
|
||||
except Exception as e:
|
||||
out_bin.extend(b'\0\0\0\0')
|
||||
out_symbols.append(data)
|
||||
return out_bin, out_symbols
|
||||
|
||||
|
||||
def convert_source_to_gfx_c_source(src_file, dest_path):
|
||||
with open(src_file) as f:
|
||||
lines = f.read().split("\n")
|
||||
|
||||
includes = ["libforest/gbi_extensions.h", "PR/gbi.h", "evw_anime.h"]
|
||||
|
||||
header = "\n".join([f'#include "{x}"' for x in includes]) + "\n"
|
||||
|
||||
# found_objs = []
|
||||
found_types = []
|
||||
all_objs = []
|
||||
for line in lines:
|
||||
if line.startswith(".obj"):
|
||||
this_obj = line.split(" ")[1].strip(",")
|
||||
all_objs.append(this_obj)
|
||||
|
||||
if this_obj.endswith("_v"):
|
||||
found_types.append((this_obj, "Vtx"))
|
||||
elif "_model" in this_obj:
|
||||
found_types.append((this_obj, "Gfx"))
|
||||
elif this_obj.endswith("evw_anime"):
|
||||
found_types.append((this_obj, "EVW_ANIME_DATA"))
|
||||
elif this_obj.endswith("_pal"):
|
||||
found_types.append((this_obj, "PAL"))
|
||||
elif this_obj.endswith("_tex"):
|
||||
found_types.append((this_obj, "TEX"))
|
||||
|
||||
# we now have a list of all objects, and we have a partial mapping of what type they could have
|
||||
# what we want to do is go through all the objects we know, and get the out-data
|
||||
# there are some objs that will add to this list, we can queue them onto the list
|
||||
|
||||
converted_types = {}
|
||||
# we now have a list of objects+type
|
||||
while len(found_types) > 0:
|
||||
obj_name, type = found_types.pop()
|
||||
if obj_name in converted_types:
|
||||
continue
|
||||
if type == "Vtx":
|
||||
data = f'#include "assets/{obj_name}.inc"'
|
||||
converted_types[obj_name] = (type, data, "")
|
||||
elif type == "Gfx":
|
||||
data = convert_binary_to_gfx(
|
||||
*lookup_bins_and_symbols(lines, obj_name))
|
||||
converted_types[obj_name] = (type, data, "")
|
||||
elif type == "PAL":
|
||||
data = f'#include "assets/{obj_name}.inc"'
|
||||
converted_types[obj_name] = ("u16", data, "ATTRIBUTE_ALIGN(32)")
|
||||
elif type == "TEX":
|
||||
data = f'#include "assets/{obj_name}.inc"'
|
||||
converted_types[obj_name] = ("u8", data, "ATTRIBUTE_ALIGN(32)")
|
||||
elif type == "EVW_ANIME_DATA":
|
||||
res = parse_evw_anime_data(
|
||||
*lookup_bins_and_symbols(lines, obj_name))
|
||||
data = res.formatted_str
|
||||
converted_types[obj_name] = (type, data, "")
|
||||
for ref in res.referenced_objects:
|
||||
found_types.insert(
|
||||
0, (ref.symbol_name, ref.symbol_type))
|
||||
|
||||
elif type in ["EVW_ANIME_TYPE_SCROLL1", "EVW_ANIME_TYPE_SCROLL2"]:
|
||||
res = parse_evw_anime_scroll(
|
||||
*lookup_bins_and_symbols(lines, obj_name))
|
||||
data = res.formatted_str
|
||||
converted_types[obj_name] = ("EVW_ANIME_SCROLL", data, "")
|
||||
for ref in res.referenced_objects:
|
||||
found_types.insert(
|
||||
0, (ref.symbol_name, ref.symbol_type))
|
||||
elif type == "EVW_ANIME_TYPE_COLREG_MANUAL":
|
||||
res = parse_evw_anime_colreg(
|
||||
*lookup_bins_and_symbols(lines, obj_name))
|
||||
data = res.formatted_str
|
||||
converted_types[obj_name] = (type, data, "")
|
||||
for ref in res.referenced_objects:
|
||||
found_types.insert(
|
||||
0, (ref.symbol_name, ref.symbol_type))
|
||||
elif type == "EVW_ANIME_TYPE_COLREG_LINEAR":
|
||||
res = parse_evw_anime_colreg(
|
||||
*lookup_bins_and_symbols(lines, obj_name))
|
||||
data = res.formatted_str
|
||||
converted_types[obj_name] = (type, data, "")
|
||||
for ref in res.referenced_objects:
|
||||
found_types.insert(
|
||||
0, (ref.symbol_name, ref.symbol_type))
|
||||
elif type == "EVW_ANIME_TYPE_COLREG_NONLINEAR":
|
||||
res = parse_evw_anime_colreg(
|
||||
*lookup_bins_and_symbols(lines, obj_name))
|
||||
data = res.formatted_str
|
||||
converted_types[obj_name] = (type, data, "")
|
||||
for ref in res.referenced_objects:
|
||||
found_types.insert(
|
||||
0, (ref.symbol_name, ref.symbol_type))
|
||||
elif type == "EVW_ANIME_TYPE_TEXANIME":
|
||||
res = parse_evw_texanime(
|
||||
*lookup_bins_and_symbols(lines, obj_name))
|
||||
data = res.formatted_str
|
||||
converted_types[obj_name] = (type, data, "")
|
||||
for ref in res.referenced_objects:
|
||||
found_types.insert(
|
||||
0, (ref.symbol_name, ref.symbol_type))
|
||||
elif type == "EVW_ANIME_COL_PRIM":
|
||||
res = parse_evw_anime_col_prim(
|
||||
*lookup_bins_and_symbols(lines, obj_name))
|
||||
data = res.formatted_str
|
||||
converted_types[obj_name] = (type, data, "")
|
||||
for ref in res.referenced_objects:
|
||||
found_types.insert(
|
||||
0, (ref.symbol_name, ref.symbol_type))
|
||||
elif type == "EVW_ANIME_COL_ENV":
|
||||
res = parse_evw_anime_col_env(
|
||||
*lookup_bins_and_symbols(lines, obj_name))
|
||||
data = res.formatted_str
|
||||
converted_types[obj_name] = (type, data, "")
|
||||
for ref in res.referenced_objects:
|
||||
found_types.insert(
|
||||
0, (ref.symbol_name, ref.symbol_type))
|
||||
elif type == "u16":
|
||||
assert (False)
|
||||
elif type == "u8":
|
||||
assert (False)
|
||||
|
||||
out = header + "\n\n"
|
||||
for obj in all_objs:
|
||||
this_type, out_data, align = converted_types.get(
|
||||
obj, (None, None, None))
|
||||
out += f"{this_type} {obj}[] {align}= {{ \n{out_data}\n}};\n\n"
|
||||
# print(out)
|
||||
with open(dest_path, "w") as f:
|
||||
f.write(out)
|
||||
|
||||
|
||||
def main():
|
||||
parser = ArgumentParser(
|
||||
description="Converts a binary file into gfx calls"
|
||||
)
|
||||
parser.add_argument("src_path", type=str, help="Binary source file path")
|
||||
parser.add_argument("dest_path", type=str,
|
||||
help="Destination C include file path")
|
||||
|
||||
args = parser.parse_args()
|
||||
convert_source_to_gfx_c_source(args.src_path, args.dest_path)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user