From 828fc60c833cb088c8cd05e852ae0a96e8857896 Mon Sep 17 00:00:00 2001 From: yonggamer <24843208+yonggamer@users.noreply.github.com> Date: Tue, 22 Sep 2026 23:13:57 +0200 Subject: [PATCH] add code for gui --- gui/README.md | 30 ++++ gui/build.sh | 12 ++ .../com/apkeditor/gui/ArgumentParser.class | Bin 0 -> 1090 bytes .../com/apkeditor/gui/BuildCommand.class | Bin 0 -> 626 bytes .../com/apkeditor/gui/CliCommand.class | Bin 0 -> 198 bytes .../apkeditor/gui/CliProcessRunner$1.class | Bin 0 -> 952 bytes .../gui/CliProcessRunner$Listener.class | Bin 0 -> 311 bytes .../com/apkeditor/gui/CliProcessRunner.class | Bin 0 -> 2389 bytes .../com/apkeditor/gui/CommandCatalog.class | Bin 0 -> 654 bytes .../com/apkeditor/gui/DecompileCommand.class | Bin 0 -> 638 bytes .../apkeditor/gui/FileChooserService.class | Bin 0 -> 642 bytes .../com/apkeditor/gui/InfoCommand.class | Bin 0 -> 626 bytes .../classes/com/apkeditor/gui/Main.class | Bin 0 -> 1565 bytes .../com/apkeditor/gui/MainFrame$1.class | Bin 0 -> 2292 bytes .../classes/com/apkeditor/gui/MainFrame.class | Bin 0 -> 7687 bytes .../com/apkeditor/gui/MergeCommand.class | Bin 0 -> 626 bytes .../com/apkeditor/gui/ProtectCommand.class | Bin 0 -> 632 bytes .../com/apkeditor/gui/RefactorCommand.class | Bin 0 -> 635 bytes gui/build/manifest.mf | 1 + gui/src/com/apkeditor/gui/ArgumentParser.java | 23 +++ gui/src/com/apkeditor/gui/BuildCommand.java | 7 + gui/src/com/apkeditor/gui/CliCommand.java | 10 ++ .../com/apkeditor/gui/CliProcessRunner.java | 44 +++++ gui/src/com/apkeditor/gui/CommandCatalog.java | 12 ++ .../com/apkeditor/gui/DecompileCommand.java | 7 + .../com/apkeditor/gui/FileChooserService.java | 19 ++ gui/src/com/apkeditor/gui/InfoCommand.java | 7 + gui/src/com/apkeditor/gui/Main.java | 24 +++ gui/src/com/apkeditor/gui/MainFrame.java | 170 ++++++++++++++++++ gui/src/com/apkeditor/gui/MergeCommand.java | 7 + gui/src/com/apkeditor/gui/ProtectCommand.java | 7 + .../com/apkeditor/gui/RefactorCommand.java | 7 + 32 files changed, 387 insertions(+) create mode 100644 gui/README.md create mode 100644 gui/build.sh create mode 100644 gui/build/classes/com/apkeditor/gui/ArgumentParser.class create mode 100644 gui/build/classes/com/apkeditor/gui/BuildCommand.class create mode 100644 gui/build/classes/com/apkeditor/gui/CliCommand.class create mode 100644 gui/build/classes/com/apkeditor/gui/CliProcessRunner$1.class create mode 100644 gui/build/classes/com/apkeditor/gui/CliProcessRunner$Listener.class create mode 100644 gui/build/classes/com/apkeditor/gui/CliProcessRunner.class create mode 100644 gui/build/classes/com/apkeditor/gui/CommandCatalog.class create mode 100644 gui/build/classes/com/apkeditor/gui/DecompileCommand.class create mode 100644 gui/build/classes/com/apkeditor/gui/FileChooserService.class create mode 100644 gui/build/classes/com/apkeditor/gui/InfoCommand.class create mode 100644 gui/build/classes/com/apkeditor/gui/Main.class create mode 100644 gui/build/classes/com/apkeditor/gui/MainFrame$1.class create mode 100644 gui/build/classes/com/apkeditor/gui/MainFrame.class create mode 100644 gui/build/classes/com/apkeditor/gui/MergeCommand.class create mode 100644 gui/build/classes/com/apkeditor/gui/ProtectCommand.class create mode 100644 gui/build/classes/com/apkeditor/gui/RefactorCommand.class create mode 100644 gui/build/manifest.mf create mode 100644 gui/src/com/apkeditor/gui/ArgumentParser.java create mode 100644 gui/src/com/apkeditor/gui/BuildCommand.java create mode 100644 gui/src/com/apkeditor/gui/CliCommand.java create mode 100644 gui/src/com/apkeditor/gui/CliProcessRunner.java create mode 100644 gui/src/com/apkeditor/gui/CommandCatalog.java create mode 100644 gui/src/com/apkeditor/gui/DecompileCommand.java create mode 100644 gui/src/com/apkeditor/gui/FileChooserService.java create mode 100644 gui/src/com/apkeditor/gui/InfoCommand.java create mode 100644 gui/src/com/apkeditor/gui/Main.java create mode 100644 gui/src/com/apkeditor/gui/MainFrame.java create mode 100644 gui/src/com/apkeditor/gui/MergeCommand.java create mode 100644 gui/src/com/apkeditor/gui/ProtectCommand.java create mode 100644 gui/src/com/apkeditor/gui/RefactorCommand.java diff --git a/gui/README.md b/gui/README.md new file mode 100644 index 00000000..e9adcfa9 --- /dev/null +++ b/gui/README.md @@ -0,0 +1,30 @@ +# APKEditor GUI wrapper + +This is a Java Swing wrapper for the CLI commands described in `../cli comands`. +It supports Build (`b`), Decompile (`d`), Info (`info`), Merge (`m`), Protect (`p`), +and Refactor (`x`). The CLI JAR is selected in the UI, so the wrapper does not +bundle or assume a particular APKEditor version. + +## Build + +Requires Java 8 or newer. A JDK is needed to build the project. + +```sh +./build.sh +``` + +The result is `APKEditor-GUI.jar`. + +## Run + +```sh +java -jar APKEditor-GUI.jar +``` + +Run this command on a desktop session. A remote Codespace or SSH terminal without +an X11/Wayland display cannot show a Swing window. + +The interface uses Java's system look-and-feel, which gives native controls on +each supported desktop OS while keeping the application in one OS-independent JAR. +The target CLI JAR still needs to be downloaded separately and must be run with a +compatible Java runtime. \ No newline at end of file diff --git a/gui/build.sh b/gui/build.sh new file mode 100644 index 00000000..fc6bac75 --- /dev/null +++ b/gui/build.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env sh +set -eu + +ROOT=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) +BUILD="$ROOT/build" +rm -rf "$BUILD" +mkdir -p "$BUILD/classes" + +find "$ROOT/src" -name '*.java' -print0 | xargs -0 javac -source 8 -target 8 -d "$BUILD/classes" +printf 'Main-Class: com.apkeditor.gui.Main\n' > "$BUILD/manifest.mf" +jar cfm "$ROOT/APKEditor-GUI.jar" "$BUILD/manifest.mf" -C "$BUILD/classes" . +printf 'Created %s\n' "$ROOT/APKEditor-GUI.jar" \ No newline at end of file diff --git a/gui/build/classes/com/apkeditor/gui/ArgumentParser.class b/gui/build/classes/com/apkeditor/gui/ArgumentParser.class new file mode 100644 index 0000000000000000000000000000000000000000..65ebeabf0802babe15d7bceaabf4b14b878e91da GIT binary patch literal 1090 zcma)5ZBNrs6n^er=vvAc%n7IYpwTSdb8maj^PK10bHD!h_!+Qj^VhG`FGJMg6I(TbiDePskLSNhH&Q zUJiXC&fx4HWLj|yp(mzQ@%yso<@Adfz2a=0L3 z7#A6$hbFu>5}R5f&cxBWniy2w= z+9ZoeBSW2_BWRdaonhoCW2=^vJ^0mXGB;PRP+Di+wDd){wyxVHb=@NhZ#SwY6*~M6 zLeEZ)p`+-i+Ezhr_~{(Rj}2L~5C*VgyRzDe$UKaP22H@A=;V literal 0 HcmV?d00001 diff --git a/gui/build/classes/com/apkeditor/gui/BuildCommand.class b/gui/build/classes/com/apkeditor/gui/BuildCommand.class new file mode 100644 index 0000000000000000000000000000000000000000..11a3d80b981adb29d18a1054487111e77ae1a533 GIT binary patch literal 626 zcmZWm%TB^j5Iw`IwN!bCPadN901|CX++kcu;)>`3W8$)fo91F^NmKkT-9Vy=AK*tB z=OR%9n=>QeeLx+28u zjRze%fgBwMA$dKPUSESr*!mmmMxpfGSxbZl(uf<#Agdu~B9C=Kr56k>d-NpwG73V= z9ZPG*ZU@7m?e__~E256=2#-*xH#&=}TWaZsiA`*=ID0e_YUs4SOkAFw%WCP^A;hZe zK7*o(lG3H|x9y-WbX3@4L;X@Uv9DTs6#UrIP$QI<4YWP^D?g!@5HcOH2?gK^jsH&PY3|DMm3N1U^m7zhV^nFL2Z2;(7FWsbx#*_?u2IUyV|sx}a3WCM(f zTI4u;&1)|UM75JIutZ#0Bqo`{zURc&OcYRiNBVJ!)#e0i6BP2>44m((yv#7c-r^!9 K@R(J^aPkd_q;?np literal 0 HcmV?d00001 diff --git a/gui/build/classes/com/apkeditor/gui/CliCommand.class b/gui/build/classes/com/apkeditor/gui/CliCommand.class new file mode 100644 index 0000000000000000000000000000000000000000..4a6abbfa81b801ca62fe17b572a464cd1920a290 GIT binary patch literal 198 zcmYL@F%H5o3`M_NAf*g2FmeD`$il{0iK!ApPms1zl#({6N;w<@2jEZ$S0Huqx9so# z{Qf*&08Us5M1)=CTdD3hUEAP$X$C7ZXEWcns;dQNgiWI!N;=gUc`X}V1%fa-WWLsf z?f#HY`y%wVGiO4=hFq1}aU1+^Oh^kq^p(C?$Cv8~a~u;E1VYT80WnkOSYV`!zb2!y JW63o_`UMObF2?`> literal 0 HcmV?d00001 diff --git a/gui/build/classes/com/apkeditor/gui/CliProcessRunner$1.class b/gui/build/classes/com/apkeditor/gui/CliProcessRunner$1.class new file mode 100644 index 0000000000000000000000000000000000000000..3e9ae037cbf390eedf60133b1f1a960dba3b7f57 GIT binary patch literal 952 zcmbu7U2oGc6oy|nrCGC#W?jq2_}WJXZMu;naW{mJDv*#0Ol;b{n^>K<)ImZri7QFU<#H3mOQCn5iKCSsYV zIvf`=93=8Ur;*6AuZ2=V`>z<5&v{}}CtSr0-td&4^RUQe5)NgS?;Gfni#i+z*Q6%2 zDyWuWZ@9n?24d1nkn3UvcNuE=iOl>yX0%Sl8QyjN^H5}wkH%MnglCz^Y;+lVxA*_5FAPin zT7(TUo(G0UusqK=8lXr%EF;;7OkbgQ4ePM*gl4<+U8UFxc4uJ0-VAoon_)S)MB{?i z8J^NuqX?|x8I3Cz@ZBqc2EIW{6|^W}vmED8t1R$>DUqhcox=Ws_BGHcWwg*hn}lqY ajBWT7tK`HTyr5Z=a*SB>bZ9lG)&Bw{Qtv_l literal 0 HcmV?d00001 diff --git a/gui/build/classes/com/apkeditor/gui/CliProcessRunner$Listener.class b/gui/build/classes/com/apkeditor/gui/CliProcessRunner$Listener.class new file mode 100644 index 0000000000000000000000000000000000000000..17c4d44d9aaa29f73414f0591ebc0bd157a94435 GIT binary patch literal 311 zcmah^%Sr=55Uft(PO?VL#UBt45y825^B4>S0uck=$4#?NTxY^O{5TJOfFC9H1SH_a zOILRnU0wC{{qYIl788XY;kxl}+U#C=W0McM%~sDk`w)D?F+OJJI9x7mOe~GUfH2vb zccVMwTD^MSa+3%{?^ZeOvb5*Z<$+(PU|oB6^+c$>n_Fk&3vUR6>7wkEclROqAne-b z(D2-LEE0eJ-xNi{cu~yEIuj#Dg;T=C|K7#iultHX=u32sjf`+QN%?{~8x*IpUfZv~- zgI8YaKj0cK60zs#ofrNWe}ET?&uo$=P(9)?IhmcEc|V`$eP{CPpFjQt-~{f)5yBP? zT{^;u2plXrYiXl$OPZGFxapE_rL$%0lIs*@wR+jNZRrYV@~$lUo)kEj%HK3@8)@IO z%IUmS^)mT?H!!3GB`sx!uf`EYOha79R_Fq~fwi(>m(mL>H)YWih@7@;%R4KuB{h6i zL(daJORKIlObxvPL(ky&cpTf%r+n=YFjLPEYvzCE7e&9o)?(QjuQV`>qZ_+b1bYOA zo9G4kDsYXJvTS+`))qb2vP%I>-Po(+IfYTU>KU%5VNhUC8?Gir)4o+Ur5ncp_A4(* zfgPJTQm_x;ppHWr;=W6=U1B}sx{gbi(pVE1OkEpp`=XteZY1KFY7pmR|KM@!^~SYn{8(+ z$IjZ=iY#8$Fr#A@=LJTep$l5=N8^sY;CqB$vDm?ON8P26)A3qRcg3<(l^XI4x=jf? z(7B`PHjay!*Rg<00^NpbF8Y;<%R@t&tkX{88yZ2T+pPGr{`2& zS9QFBHwEI3JD3FZvnFjtELB5MgdaC4Mkm?MLI_W zj1s|F#;Oi0VAOLOW>ku|bd(iunqvp;r@;M zU1@ubgYfAZX{<7+Zg@I;#bV5AS&ZTi<(rM2+*p?8F@l_ROjg`BRe1CM+KP0S6vw2- z^%-Dn(JI-7=et~4YT~nT1)SalPUh@?y%v}l8?$(8&Zq>AHGImZ4`A8wLx7Eyx_V3ao^&;=gY&^g}a+aRBU z&+tec+CxqR-sczz7GoSg;5-zFo&v8??j_dIU0BDq!pP`Y9ozeM*0F1TyoN+uy$4$; zxE(vO8+-WukZ&P;#2+u=hB_sUg{Fof_Wdj>$(2rAN4__WS04fKx+ttnYTP@{q|M#Tm@7@6RL8#=ppDC05x1 z>Uke)%tV1Tjb4re90!rWK58W~i38LA{zfY;Jc}#wTVSI~G zd`t@$5c>m1HH>RW^`F;p@iAhEXb5TeL_@Z*!DR>z(cckzOd9#$rn#?{3)eNQ^|bKr ujp8%xAc>0U{p3Wkq6M^xHd!sML-?HAA$)-^`RwB?%imX#82?iQpT7VKL}XC_ literal 0 HcmV?d00001 diff --git a/gui/build/classes/com/apkeditor/gui/CommandCatalog.class b/gui/build/classes/com/apkeditor/gui/CommandCatalog.class new file mode 100644 index 0000000000000000000000000000000000000000..9100c2e663925365512dd792d188852d611c81be GIT binary patch literal 654 zcmZvZ%}&BV6ot=Fpnxa{qNt#N8*pJ`;!fiNjEM>U#2S<8YM=ut_J=gZ=h794jXr=6 zWxSIR(3EM?IdkXhJ?XjMKObKJPH>Px42p_)76~L7@+1DrHJiIb?Y1`({eU5PX1P{y z&Y(0~k1A3OmA>a_eEcFzEAV`6II*;jZFM}y;jXD7!>|`6+7rt*XG{iZX}ZeLh=Lb_ zYGcb5b2uyEJVP}KU%3Ns&gCRmTc+aMa!fl(}nyu$<)NzsWDMLp0 zCVpRBQc?`X{{}~Lj5x+FO;@fMJv3iXhIFdZ&S*~@PQl(uP-OI$*Fc@u}!TMeuRkEM+y95>mfQUs_P`1NkI&b40H>%sFko6JxF}+RF!LaugcpdY@7;7CWG^L5oT*5q=LECf zxU*ps$hYATN|&-1hXWNz3ofDh-`YtN74Q z7&V?k8LKYVv^I;sZNPS&&-^2e&;{uJpX@u0`Lu6MP!h5kaIDGJYnuy+zKxgQP(?d zU(3J{@&_tX=8%xB*3KOi2rD+|w>KCm4*T^}g|d02^+3i&ILO-Hj0+dDgwjBoA4-I} zd!|e%3Cq=^*9yLgnb4@6+my14Im{E5IK?7;8FG20qhsCS#9XzN#x1&7!m>qQ>CtIl zMk$|AnV`kUcun2w>qthXQEORv#l;Ff63Sg^Qn2jUBK+WLEmfqnXKxw~)(G2y?s=kr zBRk6I*y|2e;+#Cwm+`F%tf*$9t$d^+c{1#^Wqc;umYNTeh7i8LE2{NQ6W*#I%=$(I zH^-tsK^A>Ij02gfu2}O{&7SQm_OZ$XwO@u8j}(MYeEQ6m*iP*{fbMxie1*gf6fli- zY)nEgIWfn+&A)Ii9x#1U-yNe^UmK%T-x*`!4+`}$KHSkA2W1k>DuaL-2JzH+Euh3Y W%8Z=H7xolD|FGfkAPAdD*4G#19+LC` literal 0 HcmV?d00001 diff --git a/gui/build/classes/com/apkeditor/gui/InfoCommand.class b/gui/build/classes/com/apkeditor/gui/InfoCommand.class new file mode 100644 index 0000000000000000000000000000000000000000..57e47e9d5c33b85f9b1ac56c3cf75344ea9944a5 GIT binary patch literal 626 zcmZWmT}uK%6g}6~c3ZbJv#ivz>_dfGg5HcC5`sYL!3cWX)k((IU0K$CSG_0{^aJ`) z(H)ZteL45e%(-XInfd&B`v7o;iisFB9dQc@ED+K?=h?A6$M4#=Zcnr$!os=qWpqK% zD%E=f1|cr}$H0Ke-8FYQEJE&YuoZ>U?_Lh2*AbzKBs3E#q;;%V$Y7OFYzKYY89a%O zjDpba4yCPh8bQDB_#J}L6;ac1g-6I%s?AyDy3$;;u#OzNa|Q#UhK?)q#QE8&tGaXwp)U3Fwc!A~k3WkO-zf5Vf%_Tx%~kZMX_+zk7! z2=5%%;~ldV48yj#l4_Cu7x_d*#3_4;fw0Fv6X(hw!nn_?B3I&?sEt9d+z<{J)iIzk zvVTTJ&2ydXX7raCqSEB|pCc~L5)(|}*i&MCVhSj~Bl$4KQf-9g5we*L2JW|1Ut}0z OXI4lVJY*L!9DM@@*LF4l literal 0 HcmV?d00001 diff --git a/gui/build/classes/com/apkeditor/gui/Main.class b/gui/build/classes/com/apkeditor/gui/Main.class new file mode 100644 index 0000000000000000000000000000000000000000..f2a08ae747defdb8c5dc59505dc8216871d14b4b GIT binary patch literal 1565 zcma)6?Q+{h6g?ZeQKKq#$Y;M%Lke~YQ3~bLrI5P$teu3o2}Aj{mKJAgd8LuoN&FK0 z=Lz^7ezch~!vipVCWgDRX`M-?gEiJlckey-o^!AE?|)wX1>ipFHinS1kauta!wh53 z`2qJruA1Ifc_$UF-j}JnK}6h?GRsK0`#g139l1YB6-0;WyxZpfR(;;3Bvq+8M{gpz3f!clT&Yc1A{J~+ zW5&W22Ul^8Vd9*+8LZf_2o(`4_LyY~6D_HlWuxj1+_Z4Z!TWH@$$%Vr=^^cUyX!Ts zcvFxnH=9EDbgD*CYe@wwLWGr&r|Bkd3x>&J=`6azo^|knsqS(r^gyVEJO5Rj)w6u$ zi&&Fh<1`x|IVhq;4I#P=)2Ezezm8jt(_ zqaklM0zPNL<`$`tdeljLu_6uFh$(o%=s{!tD2jBd=?kd|y&nbX7={bOcx>ZKEIW9Z zy{+0q}2KubQPJ2>uKAY5u#UX?yVP^`NHxpkBr`np^q`=S=fE zarrF20n4etr`=c#y8r*pUdngJ4vuRdxx&M+F108#IO|G@B%5!MTG(Q^b4HW%=vnxI z;nurd=~(M&fqT@1A)0p}M=KqIVECHeZqv$Wy>Ry>*fBjYd_#A0UV;w6JVxj$<3Zm& zB)i#}J%RNboIfx+KJfxm$0xXcg4@3%UwwrSffL+4ricQja0PP|VK8S24{#{;s5u|$ z8mIk9@|d7gWtvKv!M9n~Tw%?kurb>VO+T|iF+b-|EdKNopXOiSv$ypcFAzo!-_bgR a@3CZj$+|+eN2t)hMi!eao7lzlkHOm$pNSX!$G1O^nQ(~Z$KAb*+3yGA zN>wWLB{gmTNTt?^sMH@&^*2?$!!Fna%jCn(&dlC>?wK?9-u>sl{l5Xs!tFr^It_GL zID>A6k&;_U^XjfBOXYg$t(r_PbLlL4ydvV$4Bcv5`td0;%(t=3n24aqfN7x@7DIH0 z-{)zYJ6q}1jU7=^1aw_GQoYO2nMf7~h)bV^ene^Q)f|SKiFWw$=}f)>Sx}yIwlc}4 zt=l!n;TyIfA_EvSaL&ScyvQ)5z4p@n19C_g^ms=}TPi7hhFDxyQbvQia(z@7F zH$1^J2FB@B&&oDmlXh8nCdSaChbtCd!Bx5+UabnJ%rM!kLkp>ctq>*?L6U))8fn79 ztC(bnC^xWSIG1SFBLp*%#*~3+9e9S}eDf6@v8Vl3Jc?SySqraYt|8L27HPKa)jTWG zd`&6Wp=@~*Zy9*oLI&3v29Hq*Y$%K`)WSh_kvd(8d&y!1l*eP{n&;|EifvM6``e-% zdmz>pipJ*1VEfl$u4~QJ=Y{WhbHwPRVwC zs?mU-g@W!SCiRD`Td8oJoY8sLRlf3gHL&rcu=)*rY*K}NVqpa{w7REPpITVP60JVh zt1m3%>039xGQqK7pk$#8Vlm#RkDwc*^9Khcc)rO?)Ds_4qac|YtACplGjs~r+#-&) z@Z)*c-K|wyw5+BMbIfu5VO2DnKmW%dWP9J{8Vr%M+_no+i4234)2WrUx$g_#z&2@e zdL~lF9Oj~dU4|B z!OIhM#0L^I)RC&=TJZ6;t~%amknbZ`;4wz<3ljL1V5ts5*p(1A^gM(ec!wZ|>v-?^ zu)inaf50SuBy6g<5PL4f9&Jmx!85p^N9e*T@$JAGKB9LQ_4nIYr&)~fzNF_4iu44t cyGOInXtqgKLt#{!Qle}@cK2WhHXL01ABG7=kpKVy literal 0 HcmV?d00001 diff --git a/gui/build/classes/com/apkeditor/gui/MainFrame.class b/gui/build/classes/com/apkeditor/gui/MainFrame.class new file mode 100644 index 0000000000000000000000000000000000000000..b4b37f263ca537c5dea9aa1b93a8d0d3c68c4294 GIT binary patch literal 7687 zcma)B349#YdH;X9X1yBQ@(OG(KD0J6NGr>-xeQuku;c?7$rhFmWD{VtJC?>??JTpi zwgn;OFbO0O8bU~H0|o+wq@)3YRx&DSdW5!V(x!L&Yt!_;lC){lrlEy2|2H$*!`c*V z*m=kIzW06K`+vuK{`ybe_zHk4WO5#AP^+O%M?D$@%aZnZmytbcrYzTXx<>O>*MMPV zwmZhSDVXQvGd+3NwKIYX`;QpMj1yhC<5p(0Yo|Y)Q0ziF<{>Ny+j%#ecegmEAy`sL zzSlhAO(a6l1@pHI-nh-%F}7o0UlR>ng=)v`mYGh4(Tv54_Yy&ETl;<`a#Y7fSSDyprmdZZ6E>mY%JUVOJ>xL0z+w$o3z{nw^&t~N zHznVb&xEm29s9L{)m5b*bnK*=%aJl;I$>CE&BI1M?e?baoVhz|I)-a0sModiDM;NAV6n?M?sjdp#?4HQ(_!quPG!s+ z1@&!xq*Po7bnL=z9yhrT5US((@geI@y3mpUuN6FKaJA?r9eWfTjf)hu8r!R5ANJF< z9P77}o~O49 zk1^VBOc1bP+=9hn9K`KPA$skyGHHF10V89KnvN%N5-AO)j>91MqUC4XV>>C+*={5m zL?#4_l8gbaxote_PWV#_rhWX=Ug2UR-m)&Agr{>5mX0Gh%E&|YXIHNDg_;dLm5bW< zhd{Gvz5RW$om=*VU<(?09Q%0Aj40eJb`uAh;uh4^f`oUc`+MHypT^Ua?I@o0y$dK+b8nKkz~C=(nfs(%GQsYgeS7Sr&MA|$+_^L_z-ZS*{ za);PClyvNLTHzv$vv^J!`?HnNpiD|0^$U!<3d8;(gptJObi9Pm)2DrB+H1LKlQCI2 zn&8Sn-HJ3=&9};z(fntXurCLZ+e3xV(Z@vyzetenvK@C!#iCb~g!jEiX` zgfA17y*zo-31O1VhN#e(hAGAtWuNjG)JupGtyad(g;7x9y?{4#%;1-}Zj^C~@RvRf zUm*~SYx^=h0n)#TuPU4V3O&RREKFeq{F;tm$JZ!=<&hC&B?t6ZGM5+d8#;bd?b2Xm zl4G``hQ6-jx7ASHVJn?h1K-r~JNR9)KWbg=_MJfVqU|aGhJ1nYrB;;d&g|1Et>h;Oglqnnu$CX zL_u|U-_h|`_-mETJ-R`|9TTkcPjWVt?kpBb)nb>5K!3~V5b*Zuo)G??Q6?Cf#p*xe zpEUflj(@>-nTV5fD#|ixhfL>~m1JIQ@Wm2rtO~9wp)~Mi%kEPAh~@9&Up4%jjvwIP zDW!*yV3L3tHQg;EIXj(qO<(m3+5!gUE~9G0AL{r|Rd=c^tn8u2ex&2S6<3XH{IR$3 zKN|j5$2)4c$=j@q5ply(bUci=@e>{YN31}za$f#pfNE<$7?K)6OK?0f#`BnG&Y0nt zF~__kZ#iaaT}bL^+yN_>V^!-DXu=}BGP3OUg1Z-4q+UY0%o7&hi*jT3@d4IL%+^~i zzdqbl)wFZ{wrnIwTdJxNUFK`DK$nHmG@C{;G%y{+>{2#WX_fX7B!Yd)43a3XGH1(l zf1e5-2~B8v58PeF2?pMyzLsIRKrYl|u`Www=^P;x4k*NYE4RlS&8JlzpC+^}3&bh% zQ^L*)R2(dAl2FHU5s|xh%#7tcM>12ht`tkzW-gYo-I#fTS&V12oapMy95d2Zk%;~# zD|Km+7^U&Ub&$3NS5-02T!RPL%)3^)OJz&iWp!}yx!ZLwmyzMN0*nBfc&tY#)@ zmRB)-jP_owG;xt5tgcsGo_M{&ZUdz0`2;mDSfW+jtXsSSJ6R>cjXwOGmUf*`%K zi1c`DaU5ep&4;B&oeu99^HiKzH)fBUVYz{+Om^yWqx4q>kO|gCtY91IO5BUL5SMxH zl5)FO`cJu+g=80q1*{n|QI7sH5M+{!W*2pcod_ZpXL#&?&%oGto6)Hn)uw0Yd1iV5LXa%W94pmenxBMWf`z)r8jqM_s|Z7mLbk zgNg{pbvdD?!@PS0s)poF0aVTSL!x8I8qFAP-l3`+y^v7M%Ov-%*_*0kGT@fjO!uf$ z=Hh9nYIRqBe8hD2DqmDt+BK3#2aIgc*Zqu3yYt0EJ$-m}fp?=|`t*Cdz59eL+ntW1W>v3J~^?FaJ-luiB&+DC1z0KPeGp7l!MjdmZ`!h2H^W1E*(BxIYnmP8Y zW*SYtELi#flQKm4cEkqO`3v~U5kf@0XR;MhkCfh*uae%EuaMrCPkRvgAlo%^H|h9f z>y7gn*!%KTjo$^VT=`N;$`7&kZQflNukc7b(l~|r@fkD^PvOEzEG;#cmzpi5=4v+A z3{T_IDRfR^J$qMhH9NeDZeIKFuj*TowO=w*M$@S>U@FRBSmp|61c z_@dgAi)!vEU@%@+z)*Y&2VSK>B0tC9%~;6aOQ^td3cZ5AD{(1W_=*|B^;m@)u^Kny zV&0`M#TZ{#C(w@j5yyjkVSN~DX*|B5`Kr<+8kL32pTWW58QemtZk@ty{T)quldd)C z(-%A5H^*AMF-q=@ zX_0$4w%S|eZ+hQw{S;1e*8$H)AEu3Vb#k8v!CU3SBo=ml-s`OGEZ`Fd)~Yep;8&|H z;8W^LJAEejo>AYXkpI`y;OQBh9af`Lc(%JPS{HeK8ZQ>`g?LBgiv@fs&XL#DcWvZ# zeoCgkm4*VkXz=wk^alRD4A&7+Lri6(xDt6j>^y+0@hGmrDRkpSB=9odY$tIouj-re zHBx_*hw>f1-hPir`w_O{U2KzjKE>*MQLRxAYys-N%cGdb-5!`hfyXf2twpuvlQ-->GWllZNuwqO$9=$OH`hC8S5`%$fcKZt5|lla4^wouJP z>-@3$x3JDz!U?rRnL0|lC%Qw?P_*F<{E27a4t5j|Wr7+HZQF@45L5HePcsY2aRW(%Q&hOyMs}N5E*!x!chQgR2YpY)GHm3rlRARj|>(gTT-0= zIPiT6!{R7l`vWpn^a{UA3;3@Be%#OIa=$syQNX*W5njvBDJ+aPi!@7Z{j_MWcGSL! zx2L7C_DxwdEzPIB*=VyYt5vh{jq<&pMOARLlJpX~`7+vB7UE)qc`YvMPZerpd zp8vf(?fZDz_v3Lq;VC{JweO-;!`u9?aRs&KWs$#(vT^|fm@?|?GtxsLx9;j}mTl9r zqn^4A*EY+*&~RO|>>i??=j0~AIoVr-10)&_S{=ccl2e^SUzOX=3G0Up8YwCXb7_zMEhEkuUPX`=y3?@`}7BuOs?X$rcl@ literal 0 HcmV?d00001 diff --git a/gui/build/classes/com/apkeditor/gui/MergeCommand.class b/gui/build/classes/com/apkeditor/gui/MergeCommand.class new file mode 100644 index 0000000000000000000000000000000000000000..2ab34a85ac0160e65f35f6808876825d8ab42e26 GIT binary patch literal 626 zcmZWm$xZ@66s*TK<1otNg32O_3y|o<#2dzgB%V+Y7!!{JbS90nkjdb8=>-!_`~W}7 z_!@~CxYT>yU9Y+-@AK>J1Hc*T24aY7NSH_>MaT~AC);vu-?46cL(z{2sdMSe=z(aEx*aY{kH1M3={0k M3n_z#>>`GvZ=vvZBLDyZ literal 0 HcmV?d00001 diff --git a/gui/build/classes/com/apkeditor/gui/ProtectCommand.class b/gui/build/classes/com/apkeditor/gui/ProtectCommand.class new file mode 100644 index 0000000000000000000000000000000000000000..4375c86e00a927d3c3983c9b6b1ea9e277eee0b9 GIT binary patch literal 632 zcmZWmO;5r=6r4xF)=~ulMFkYa4-x^`~m(b z<69&e;WF>-?!4KV-OsPL4*=(=nutNu5x0=Q5+OZso*mnDyuN+g8HjE~Sh|p&j4laU zwf11ZAW&eyB1DI5tSzY!UA!5o|s??C!Y};Q&T|Y9m&TDR_bG{j*-i3GH}1G`Vzw! OyYoWI;1Ros;rJVgWO)Ao literal 0 HcmV?d00001 diff --git a/gui/build/classes/com/apkeditor/gui/RefactorCommand.class b/gui/build/classes/com/apkeditor/gui/RefactorCommand.class new file mode 100644 index 0000000000000000000000000000000000000000..da1819f269cd91819a9a4337126cf7dc7e463090 GIT binary patch literal 635 zcmZWmO;5r=6r81CYpH^Wq5=xyMQUKhR+@RQtyed$=kwZ$lBFrRK2cr*DS0fM;7^TDAdrAJx`pUojI!; z*kp*6=nYcHTPP@NI$hiFyTZUW1+A)EN*2oM$O!$PWI8Ggh4~B(SN_?LD-niNQ+ncR zG-!+9mbYEnFhJ8YH z3}}RuflyKNRHwTc{Ut(Fn)DBI#KkBvK@^HUBi5&;fXX|P_Y*AD##kOBo7o_seoOU5 Qf-!cYLdxI)xrpKL8{XD>2><{9 literal 0 HcmV?d00001 diff --git a/gui/build/manifest.mf b/gui/build/manifest.mf new file mode 100644 index 00000000..dde0b7a0 --- /dev/null +++ b/gui/build/manifest.mf @@ -0,0 +1 @@ +Main-Class: com.apkeditor.gui.Main diff --git a/gui/src/com/apkeditor/gui/ArgumentParser.java b/gui/src/com/apkeditor/gui/ArgumentParser.java new file mode 100644 index 00000000..3ca79fc9 --- /dev/null +++ b/gui/src/com/apkeditor/gui/ArgumentParser.java @@ -0,0 +1,23 @@ +package com.apkeditor.gui; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public final class ArgumentParser { + private static final Pattern ARGUMENT_PATTERN = Pattern + .compile("(?:\\\"([^\\\"]*)\\\"|'([^']*)'|(\\S+))"); + + private ArgumentParser() { } + + public static List split(String value) { + List arguments = new ArrayList<>(); + Matcher matcher = ARGUMENT_PATTERN.matcher(value); + while (matcher.find()) { + arguments.add(matcher.group(1) != null ? matcher.group(1) + : matcher.group(2) != null ? matcher.group(2) : matcher.group(3)); + } + return arguments; + } +} \ No newline at end of file diff --git a/gui/src/com/apkeditor/gui/BuildCommand.java b/gui/src/com/apkeditor/gui/BuildCommand.java new file mode 100644 index 00000000..c4569598 --- /dev/null +++ b/gui/src/com/apkeditor/gui/BuildCommand.java @@ -0,0 +1,7 @@ +package com.apkeditor.gui; + +public final class BuildCommand implements CliCommand { + public String getCode() { return "b"; } + public String getLabel() { return "Build"; } + public String toString() { return getLabel() + " (" + getCode() + ")"; } +} \ No newline at end of file diff --git a/gui/src/com/apkeditor/gui/CliCommand.java b/gui/src/com/apkeditor/gui/CliCommand.java new file mode 100644 index 00000000..60700c7a --- /dev/null +++ b/gui/src/com/apkeditor/gui/CliCommand.java @@ -0,0 +1,10 @@ +package com.apkeditor.gui; + +public interface CliCommand { + String getCode(); + + String getLabel(); + + @Override + String toString(); +} \ No newline at end of file diff --git a/gui/src/com/apkeditor/gui/CliProcessRunner.java b/gui/src/com/apkeditor/gui/CliProcessRunner.java new file mode 100644 index 00000000..55dd3b02 --- /dev/null +++ b/gui/src/com/apkeditor/gui/CliProcessRunner.java @@ -0,0 +1,44 @@ +package com.apkeditor.gui; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.List; + +public final class CliProcessRunner { + public interface Listener { + void onOutput(String line); + void onFinished(int exitCode); + void onError(String message); + } + + public void run(final List command, final Listener listener) { + Thread worker = new Thread(new Runnable() { + @Override + public void run() { + execute(command, listener); + } + }, "cli-process"); + worker.start(); + } + + private void execute(List command, Listener listener) { + try { + Process process = new ProcessBuilder(command).redirectErrorStream(true).start(); + try (BufferedReader reader = new BufferedReader(new InputStreamReader( + process.getInputStream(), StandardCharsets.UTF_8))) { + String line; + while ((line = reader.readLine()) != null) { + listener.onOutput(line); + } + } + listener.onFinished(process.waitFor()); + } catch (IOException exception) { + listener.onError("Could not start CLI: " + exception.getMessage()); + } catch (InterruptedException exception) { + Thread.currentThread().interrupt(); + listener.onError("Process interrupted."); + } + } +} \ No newline at end of file diff --git a/gui/src/com/apkeditor/gui/CommandCatalog.java b/gui/src/com/apkeditor/gui/CommandCatalog.java new file mode 100644 index 00000000..d86ade24 --- /dev/null +++ b/gui/src/com/apkeditor/gui/CommandCatalog.java @@ -0,0 +1,12 @@ +package com.apkeditor.gui; + +public final class CommandCatalog { + private CommandCatalog() { } + + public static CliCommand[] all() { + return new CliCommand[] { + new BuildCommand(), new DecompileCommand(), new InfoCommand(), + new MergeCommand(), new ProtectCommand(), new RefactorCommand() + }; + } +} \ No newline at end of file diff --git a/gui/src/com/apkeditor/gui/DecompileCommand.java b/gui/src/com/apkeditor/gui/DecompileCommand.java new file mode 100644 index 00000000..332995af --- /dev/null +++ b/gui/src/com/apkeditor/gui/DecompileCommand.java @@ -0,0 +1,7 @@ +package com.apkeditor.gui; + +public final class DecompileCommand implements CliCommand { + public String getCode() { return "d"; } + public String getLabel() { return "Decompile"; } + public String toString() { return getLabel() + " (" + getCode() + ")"; } +} \ No newline at end of file diff --git a/gui/src/com/apkeditor/gui/FileChooserService.java b/gui/src/com/apkeditor/gui/FileChooserService.java new file mode 100644 index 00000000..4fef272f --- /dev/null +++ b/gui/src/com/apkeditor/gui/FileChooserService.java @@ -0,0 +1,19 @@ +package com.apkeditor.gui; + +import java.awt.Component; +import java.io.File; +import javax.swing.JFileChooser; + +public final class FileChooserService { + private FileChooserService() { } + + public static File choose(Component parent, int selectionMode, String title) { + JFileChooser chooser = new JFileChooser(); + chooser.setDialogTitle(title); + chooser.setFileSelectionMode(selectionMode); + if (chooser.showOpenDialog(parent) == JFileChooser.APPROVE_OPTION) { + return chooser.getSelectedFile(); + } + return null; + } +} \ No newline at end of file diff --git a/gui/src/com/apkeditor/gui/InfoCommand.java b/gui/src/com/apkeditor/gui/InfoCommand.java new file mode 100644 index 00000000..1e7c0f59 --- /dev/null +++ b/gui/src/com/apkeditor/gui/InfoCommand.java @@ -0,0 +1,7 @@ +package com.apkeditor.gui; + +public final class InfoCommand implements CliCommand { + public String getCode() { return "info"; } + public String getLabel() { return "Info"; } + public String toString() { return getLabel() + " (" + getCode() + ")"; } +} \ No newline at end of file diff --git a/gui/src/com/apkeditor/gui/Main.java b/gui/src/com/apkeditor/gui/Main.java new file mode 100644 index 00000000..be21f5b1 --- /dev/null +++ b/gui/src/com/apkeditor/gui/Main.java @@ -0,0 +1,24 @@ +package com.apkeditor.gui; + +import javax.swing.SwingUtilities; +import javax.swing.UIManager; +import java.awt.GraphicsEnvironment; + +public final class Main { + private Main() { + } + + public static void main(String[] args) { + if (GraphicsEnvironment.isHeadless()) { + System.err.println("APKEditor GUI cannot open because this Java session has no desktop display. " + + "Copy the JAR to Windows, macOS, or Linux desktop and run it there."); + return; + } + try { + UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); + } catch (Exception ignored) { + } + + SwingUtilities.invokeLater(() -> new MainFrame().setVisible(true)); + } +} \ No newline at end of file diff --git a/gui/src/com/apkeditor/gui/MainFrame.java b/gui/src/com/apkeditor/gui/MainFrame.java new file mode 100644 index 00000000..c5fd870c --- /dev/null +++ b/gui/src/com/apkeditor/gui/MainFrame.java @@ -0,0 +1,170 @@ +package com.apkeditor.gui; + +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.FlowLayout; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import javax.swing.BorderFactory; +import javax.swing.JButton; +import javax.swing.JComboBox; +import javax.swing.JFileChooser; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTextArea; +import javax.swing.JTextField; +import javax.swing.SwingUtilities; + +public final class MainFrame extends JFrame { + private final JTextField cliJarField = new JTextField(); + private final JTextField inputField = new JTextField(); + private final JTextField outputField = new JTextField(); + private final JTextField extraArgsField = new JTextField(); + private final JComboBox commandBox = new JComboBox<>(CommandCatalog.all()); + private final JTextArea outputArea = new JTextArea(); + private final JButton runButton = new JButton("Run"); + private final CliProcessRunner processRunner = new CliProcessRunner(); + + public MainFrame() { + super("APKEditor GUI"); + setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + setMinimumSize(new Dimension(760, 520)); + setSize(900, 640); + setLocationByPlatform(true); + buildUi(); + } + + private void buildUi() { + JPanel form = new JPanel(new GridBagLayout()); + form.setBorder(BorderFactory.createEmptyBorder(12, 12, 4, 12)); + addRow(form, 0, "CLI JAR", cliJarField, "Browse", this::chooseCliJar); + addRow(form, 1, "Command", commandBox, null, null); + addRow(form, 2, "Input", inputField, "Browse", this::chooseInput); + addRow(form, 3, "Output", outputField, "Browse", this::chooseOutput); + addRow(form, 4, "Extra options", extraArgsField, null, null); + + JPanel actions = new JPanel(new FlowLayout(FlowLayout.RIGHT)); + JButton clearButton = new JButton("Clear output"); + clearButton.addActionListener(event -> outputArea.setText("")); + runButton.addActionListener(event -> runCommand()); + actions.add(clearButton); + actions.add(runButton); + outputArea.setEditable(false); + outputArea.setLineWrap(true); + outputArea.setWrapStyleWord(true); + outputArea.setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8)); + JScrollPane outputScroll = new JScrollPane(outputArea); + outputScroll.setBorder(BorderFactory.createTitledBorder("CLI output")); + + add(form, BorderLayout.NORTH); + add(outputScroll, BorderLayout.CENTER); + add(actions, BorderLayout.SOUTH); + } + + private void addRow(JPanel panel, int row, String label, java.awt.Component field, + String buttonLabel, Runnable browseAction) { + GridBagConstraints constraints = new GridBagConstraints(); + constraints.gridy = row; + constraints.insets = new Insets(4, 4, 4, 4); + constraints.anchor = GridBagConstraints.LINE_START; + constraints.fill = GridBagConstraints.HORIZONTAL; + constraints.weightx = 0; + panel.add(new JLabel(label), constraints); + + constraints.gridx = 1; + constraints.weightx = 1; + panel.add(field, constraints); + if (buttonLabel != null) { + constraints.gridx = 2; + constraints.weightx = 0; + JButton button = new JButton(buttonLabel); + button.addActionListener(event -> browseAction.run()); + panel.add(button, constraints); + } + } + + private void chooseCliJar() { + choosePath(cliJarField, JFileChooser.FILES_ONLY, "Select CLI JAR"); + } + + private void chooseInput() { + choosePath(inputField, JFileChooser.FILES_AND_DIRECTORIES, "Select input"); + } + + private void chooseOutput() { + choosePath(outputField, JFileChooser.FILES_AND_DIRECTORIES, "Select output"); + } + + private void choosePath(JTextField target, int selectionMode, String title) { + File selectedFile = FileChooserService.choose(this, selectionMode, title); + if (selectedFile != null) { + target.setText(selectedFile.getAbsolutePath()); + } + } + + private void runCommand() { + String cliJar = cliJarField.getText().trim(); + String input = inputField.getText().trim(); + if (cliJar.isEmpty() || input.isEmpty()) { + JOptionPane.showMessageDialog(this, "CLI JAR and input are required.", + "Missing input", JOptionPane.WARNING_MESSAGE); + return; + } + if (!Files.isRegularFile(Paths.get(cliJar))) { + JOptionPane.showMessageDialog(this, "The selected CLI JAR does not exist.", + "Invalid CLI JAR", JOptionPane.ERROR_MESSAGE); + return; + } + + List command = createCommand(cliJar, input); + outputArea.append("$ " + String.join(" ", command) + "\n"); + runButton.setEnabled(false); + processRunner.run(command, new CliProcessRunner.Listener() { + public void onOutput(final String line) { + SwingUtilities.invokeLater(() -> outputArea.append(line + "\n")); + } + + public void onFinished(final int exitCode) { + SwingUtilities.invokeLater(() -> { + outputArea.append("\nProcess finished with exit code " + exitCode + "\n"); + runButton.setEnabled(true); + }); + } + + public void onError(final String message) { + SwingUtilities.invokeLater(() -> { + outputArea.append(message + "\n"); + runButton.setEnabled(true); + }); + } + }); + } + + private List createCommand(String cliJar, String input) { + List command = new ArrayList<>(); + command.add(Paths.get(System.getProperty("java.home"), "bin", "java").toString()); + command.add("-jar"); + command.add(cliJar); + command.add(((CliCommand) commandBox.getSelectedItem()).getCode()); + command.add("-i"); + command.add(input); + if (!outputField.getText().trim().isEmpty()) { + command.add("-o"); + command.add(outputField.getText().trim()); + } + String extraOptions = extraArgsField.getText().trim(); + if (!extraOptions.isEmpty()) { + command.addAll(ArgumentParser.split(extraOptions)); + } + return command; + } +} \ No newline at end of file diff --git a/gui/src/com/apkeditor/gui/MergeCommand.java b/gui/src/com/apkeditor/gui/MergeCommand.java new file mode 100644 index 00000000..63dbdb2b --- /dev/null +++ b/gui/src/com/apkeditor/gui/MergeCommand.java @@ -0,0 +1,7 @@ +package com.apkeditor.gui; + +public final class MergeCommand implements CliCommand { + public String getCode() { return "m"; } + public String getLabel() { return "Merge"; } + public String toString() { return getLabel() + " (" + getCode() + ")"; } +} \ No newline at end of file diff --git a/gui/src/com/apkeditor/gui/ProtectCommand.java b/gui/src/com/apkeditor/gui/ProtectCommand.java new file mode 100644 index 00000000..9dbc49d5 --- /dev/null +++ b/gui/src/com/apkeditor/gui/ProtectCommand.java @@ -0,0 +1,7 @@ +package com.apkeditor.gui; + +public final class ProtectCommand implements CliCommand { + public String getCode() { return "p"; } + public String getLabel() { return "Protect"; } + public String toString() { return getLabel() + " (" + getCode() + ")"; } +} \ No newline at end of file diff --git a/gui/src/com/apkeditor/gui/RefactorCommand.java b/gui/src/com/apkeditor/gui/RefactorCommand.java new file mode 100644 index 00000000..047a813f --- /dev/null +++ b/gui/src/com/apkeditor/gui/RefactorCommand.java @@ -0,0 +1,7 @@ +package com.apkeditor.gui; + +public final class RefactorCommand implements CliCommand { + public String getCode() { return "x"; } + public String getLabel() { return "Refactor"; } + public String toString() { return getLabel() + " (" + getCode() + ")"; } +} \ No newline at end of file