Files
rebar_server/game_server/decompile_beam.erl
T
2026-05-19 11:30:21 +08:00

87 lines
3.2 KiB
Erlang

#!/usr/bin/env escript
%% -*- erlang -*-
%% Decompile .beam files into .erl source files when debug_info is available.
%% Usage: escript decompile_beam.erl <beam file or directory> [more paths...]
-mode(compile).
main(Args) ->
case Args of
[] ->
usage();
Paths ->
lists:foreach(fun decompile_path/1, Paths)
end.
usage() ->
io:format("Usage: escript decompile_beam.erl <beam file or directory> [more paths...]~n", []),
io:format("Example: escript decompile_beam.erl module.beam~n", []),
io:format(" escript decompile_beam.erl ./ebin~n", []),
io:format("~nNote: readable source can only be generated when the .beam was compiled with debug_info.~n", []),
io:format(" Example: compile:file(File, [debug_info])~n", []),
halt(1).
decompile_path(Path) ->
case filelib:is_dir(Path) of
true ->
decompile_dir(Path);
false ->
case filelib:is_file(Path) of
true ->
case filename:extension(Path) of
".beam" -> decompile_beam(Path);
_ -> io:format("Skip non-.beam file: ~s~n", [Path])
end;
false ->
io:format("Path does not exist: ~s~n", [Path])
end
end.
decompile_dir(Dir) ->
case file:list_dir(Dir) of
{ok, Files} ->
Beams = [filename:join(Dir, F) || F <- Files, filename:extension(F) =:= ".beam"],
io:format("Found ~p .beam files in ~s~n", [length(Beams), Dir]),
lists:foreach(fun decompile_beam/1, Beams);
{error, Reason} ->
io:format("Failed to read directory ~s: ~p~n", [Dir, Reason])
end.
decompile_beam(BeamPath) ->
case beam_lib:chunks(BeamPath, [abstract_code]) of
{ok, {_Module, [{abstract_code, {raw_abstract_v1, Forms}}]}} ->
ErlPath = to_erl_path(BeamPath),
case format_and_write(Forms, ErlPath) of
ok ->
io:format("Generated: ~s~n", [ErlPath]);
{error, Reason} ->
io:format("Write failed ~s: ~p~n", [ErlPath, Reason])
end;
{ok, {_Module, _}} ->
io:format("Skip ~s: no abstract_code chunk (likely built without debug_info)~n", [BeamPath]);
{error, beam_lib, {missing_chunk, _, "Abst"}} ->
io:format("Skip ~s: missing abstract code chunk, rebuild with debug_info~n", [BeamPath]);
{error, beam_lib, Reason} ->
io:format("Read failed ~s: ~p~n", [BeamPath, Reason])
end.
to_erl_path(BeamPath) ->
Dir = filename:dirname(BeamPath),
Base = filename:basename(BeamPath, ".beam"),
OutDir =
case filename:basename(Dir) of
"ebin" -> filename:join(filename:dirname(Dir), "src");
_ -> Dir
end,
filename:join(OutDir, Base ++ ".erl").
format_and_write(Forms, ErlPath) ->
try
Strings = lists:map(fun(F) -> erl_pp:form(F) end, Forms),
Content = lists:flatten([S ++ ".\n\n" || S <- Strings]),
ok = filelib:ensure_dir(ErlPath),
file:write_file(ErlPath, Content, [write, {encoding, utf8}])
catch
_:Reason -> {error, Reason}
end.