From 73dc52ac98b5334f99853e538d4f1095b88433b3 Mon Sep 17 00:00:00 2001 From: Daniel Nylander Date: Fri, 29 Nov 2024 03:49:16 +0100 Subject: [PATCH 1/9] Adding Swedish flags to UI (#3337) * Updated Swedish translation * Updating Swedish translation Now I feel happy with it for some time. * Adding Swedish flags * Adding Swedish flags from Wikipedia Source https://sv.wikipedia.org/wiki/Fil:Sweden_flag_orb_icon.svg * Final update for Swedish translation --- .../resources/duckstation-qt.qrc | 2 + .../resources/icons/flags/sv.png | Bin 0 -> 924 bytes .../resources/icons/flags/sv@2x.png | Bin 0 -> 1704 bytes .../translations/duckstation-qt_sv.ts | 66 +++++++++--------- 4 files changed, 36 insertions(+), 32 deletions(-) create mode 100644 src/duckstation-qt/resources/icons/flags/sv.png create mode 100644 src/duckstation-qt/resources/icons/flags/sv@2x.png diff --git a/src/duckstation-qt/resources/duckstation-qt.qrc b/src/duckstation-qt/resources/duckstation-qt.qrc index c48021fe36..f19a1673c3 100644 --- a/src/duckstation-qt/resources/duckstation-qt.qrc +++ b/src/duckstation-qt/resources/duckstation-qt.qrc @@ -174,6 +174,8 @@ icons/flags/pt-PT.png icons/flags/ru@2x.png icons/flags/ru.png + icons/flags/sv@2x.png + icons/flags/sv.png icons/flags/tr@2x.png icons/flags/tr.png icons/flags/zh-CN@2x.png diff --git a/src/duckstation-qt/resources/icons/flags/sv.png b/src/duckstation-qt/resources/icons/flags/sv.png new file mode 100644 index 0000000000000000000000000000000000000000..17aad19be5995920c87776070cd71d87a6f86ffe GIT binary patch literal 924 zcmV;N17rM&P)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!TNyrl ziPWvN+4IWYEStsZ1SoJH^n;r;fn);=szOU#4X&jBwSO6#J_AtCFwT(3BY|z)c)F?W zs9P_KUZn}EL3F-wDF<99;7|W(OVNqd33WZiCaKUU1dap-?&i)TV0@p{;{GkSU6;o|7l*j;flzHL#UBk&=rIjIo7DJ*A zTQAJkLXgk^ax2Hch`-HNvMmfDL^I%`yWyD#%YaeJ)qL9?4mb#jt3B65cL3WjW^kQp zJHrUkB7A376uEhxB1(r57t;2BOY7f+`vQflKY-K&9h7q@{)qKw)&ddJ#H>LCxj4&EtqVhwH*GXvr9@qB!jfhE}w ya+1$BE}hAeKAwY*=hVc9(FO>2bzdjqZh(LJ4ImL9KPd440000h-R literal 0 HcmV?d00001 diff --git a/src/duckstation-qt/resources/icons/flags/sv@2x.png b/src/duckstation-qt/resources/icons/flags/sv@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..d6dbb19cdb99a3a667698a0bd303617958f05a6e GIT binary patch literal 1704 zcmV;Z23PrsP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!TM0DKp_N?UA=s7{ z6H`hrK(I(64FOVq5HFN)Nf1LtDv5GwS_Fzn+ubvMvuCJX+ig#`+p@doNjPWTZ+a#> z@6LS>gaJlMkUWKyGtCoHXI6hYjU$sll)_n~Ad$ei0+L!mxW)Q91TMqG1sLX8B-USq z$K$n18W;w|0C^I_yb^N+uz=-UB8>m_*4Xx``@V>?{=z0w-sF#13t=9FFW~^TFK*O1 zVI!;t$e)mE8p!2j1{eIW6}1S-fB>Wq5H;LliqUA)VDD1a{UU4-oze zKr8rm7CF1pbkH_^Gr(Q;4B$H{O?pK~A_#f}hEWT5*qx(kduD8<11=6faNra%s_3`3~q~?ha~U7L?!yyi=OU>+${EoQCU2bVAsQcdLTV z4nYjyz9Zwm;-??gfhdOJVVae})(PDkkXyNeuMcj46Lxrt1bwMR09_4OQ$CuT{pVW6 z4n&zeo6Xg#gRTa+#0Ks+iCV=LB)1^H)}@u7Km*+6lX$0rBCv$QE3QsaiV@?*CJT1SV+hL?#Gs%De(}Cj8|mt65-#B^?N1 zglYltS~`(29B_?^eB zGXQsG36BM<{b?Jx;4^Qp%r+AYuk8wj^&O#bAkl!Lj7j=Dz&vc-9R%U1x>*Lq4?E2J z)i!_HDM<5xx>*Kned8uyYy4>^AkmDbxI-$by8uJDeGE~cRVM>Va@s+1vsSSS$%ELo?7UW;3{WJcl(0pAY887(_3k=6TKNeyz%z?? zhV+S6F$h(mAoHXaegX|pB)z$lt@>W87((9S-T#MLbkNlR51H^8YE>9p=LAxIgyLmg zx_4It6vaG9kX)-(^dsfBD9YNQRRG-^peV>JH%-d zTU@{it)r2@VEPv7m2S86WPn;5+=WlR#$NoQBhiDDcX*Sx5XH+o=KSvH*#JdJRugZ4 zXLEr)78FrP*#beYqcH2Zjs&H50~CczuR$cd%mx3TBN2>jgehM|L56y@zgO%TpeRUh z#@$wK2`J^&RJQ|41aKNYdJe@|w#O~|1}I8b-9u4Ewn66G0;-Qi2-c4~h4}m4izv?c z-3r2IE<%cW=|VKU}b) zg9$HfrCbl!h#8?)UJ1f yl%XI~O{#62%iC34hs$U{;`z()cn0BTd*EM9-sT*+xA6!70000 Forcibly mutes both CD-DA and XA audio from the CD-ROM. Can be used to disable background music in some games. - + Tvingar både CD-DA och XA-ljud från cd-rom att vara tyst. Kan användas för att inaktivera bakgrundsmusik i vissa spel. @@ -1757,7 +1757,7 @@ Olästa meddelanden: {} Resets volume back to the global/inherited setting. - + Nollställer volymen tillbaka till global/ärvd inställning. @@ -1954,7 +1954,8 @@ Olästa meddelanden: {} Failed to remove updater exe after update: %1 - + Misslyckades med att ta bort uppdateringsfil efter uppdatering: +%1 @@ -4309,7 +4310,7 @@ Du kan inte ångra denna åtgärd. Step &Over - + Stega ö&ver @@ -4335,7 +4336,7 @@ Du kan inte ångra denna åtgärd. Step O&ut - + Stega &ut @@ -4452,7 +4453,7 @@ Du kan inte ångra denna åtgärd. Invalid address. It should be in hex (0x12345678 or 12345678) - + Ogiltig adress. Den bör anges i hex (0x12345678 eller 12345678) @@ -4474,12 +4475,13 @@ Du kan inte ångra denna åtgärd. Trace logging started to cpu_log.txt. This file can be several gigabytes, so be aware of SSD wear. - + Spårloggning startad till cpu_log.txt. +Denna fil kan bli flera gigabytes så tänk på diskutrymmet. Trace logging to cpu_log.txt stopped. - + Spårloggning till cpu_log.txt stoppad. @@ -4494,7 +4496,7 @@ This file can be several gigabytes, so be aware of SSD wear. Failed to add step-out breakpoint, are you in a valid function? - + Misslyckades med att stega ut ur brytpunkt. Är du i en giltig funktion? @@ -4504,13 +4506,13 @@ This file can be several gigabytes, so be aware of SSD wear. &Follow Load/Store - + &Följ läs in/Lagra Invalid search pattern. It should contain hex digits or question marks. - + Ogiltigt sökmönster. Det bör innehålla hexadecimala siffror eller frågetecken. @@ -4520,7 +4522,7 @@ This file can be several gigabytes, so be aware of SSD wear. Pattern found at 0x%1 (passed the end of memory). - + Mönster hittades vid 0x%1 (passerat minnets slut). @@ -4530,12 +4532,12 @@ This file can be several gigabytes, so be aware of SSD wear. Failed to add breakpoint. A breakpoint may already exist at this address. - + Misslyckades med att lägga till brytpunkt. En brytpunkt kanske redan finns på denna adress. Failed to remove breakpoint. This breakpoint may not exist. - + Misslyckades med att ta bort brytpunkt. Denna brytpunkt kanske inte finns. @@ -7387,7 +7389,7 @@ Felet var: Memory Card {} Type - + Minneskort {} typ @@ -8017,7 +8019,7 @@ Felet var: Select Macro {} Binds - + Välj bindning för makro {} @@ -8057,7 +8059,7 @@ Felet var: Set Input Binding - + Ställ in inmatningsbindning @@ -10302,7 +10304,7 @@ Söka igenom den rekursivt tar längre tid men identifierar filer i underkatalog Exclusive Fullscreen: - + Exklusiv helskärm: @@ -10369,7 +10371,7 @@ Söka igenom den rekursivt tar längre tid men identifierar filer i underkatalog Geometry Tolerance: - Geometritolerans + Geometritolerans: @@ -10511,7 +10513,7 @@ Söka igenom den rekursivt tar längre tid men identifierar filer i underkatalog Preload Texture Replacements - + Förinläs texturersättningar @@ -10576,7 +10578,7 @@ Söka igenom den rekursivt tar längre tid men identifierar filer i underkatalog Dump Compression Mode: - + Komprimeringsläge för dump: @@ -10754,7 +10756,7 @@ Söka igenom den rekursivt tar längre tid men identifierar filer i underkatalog Disable Shader Cache - + Inaktivera shadercache @@ -15177,12 +15179,12 @@ Du måste ta bort minneskortet manuellt om du vill spara. PGXP Depth Buffer is now enabled. - + PGXP Depth Buffer är nu aktiverad. PGXP Depth Buffer is now disabled. - + PGXP Depth Buffer är nu inaktiverad. @@ -15209,12 +15211,12 @@ Du måste ta bort minneskortet manuellt om du vill spara. CD Audio Muted. - + CD-ljud tystat. CD Audio Unmuted. - + CD-ljud aktiverat. @@ -16231,13 +16233,13 @@ Vill du skapa denna katalog? Triangles (Basic) GPULineDetectMode - + Trianglar (Grundläggande) Triangles (Aggressive) GPULineDetectMode - + Trianglar (Aggressiv) @@ -16381,7 +16383,7 @@ Vill du skapa denna katalog? Auto (Game Native) DisplayAspectRatio - + Auto (Spelets egna) @@ -16477,13 +16479,13 @@ Vill du skapa denna katalog? Bilinear (Sharp) DisplayScalingMode - + Bilinjär (Skarp) Bilinear (Integer) DisplayScalingMode - + Bilinjär (Heltal) @@ -17748,7 +17750,7 @@ Detta kan bero på att din GPU inte har stöd för vald renderare ({1}) eller p Bilinear Replacement Scaling - + Skalning för bilinjär ersättning From c4d4a7a7742fc18e00bf526481e1eb4b1eabf5c3 Mon Sep 17 00:00:00 2001 From: Anderson Cardoso <43047877+andercard0@users.noreply.github.com> Date: Thu, 28 Nov 2024 23:49:33 -0300 Subject: [PATCH 2/9] Fix Missing Flag (#3338) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Atualização Português do Brasil Atualizado para a última versão. * Flag fix Added reference for the missing flag Spanish Latin America * Update Flag As discussed in discord with @Hipnosis183 told us that is better change to Mexico flag instead for his lang option. --- src/duckstation-qt/resources/duckstation-qt.qrc | 1 + src/duckstation-qt/resources/icons/flags/es.png | Bin 0 -> 1090 bytes .../resources/icons/flags/es@2x.png | Bin 0 -> 2725 bytes 3 files changed, 1 insertion(+) create mode 100644 src/duckstation-qt/resources/icons/flags/es.png create mode 100644 src/duckstation-qt/resources/icons/flags/es@2x.png diff --git a/src/duckstation-qt/resources/duckstation-qt.qrc b/src/duckstation-qt/resources/duckstation-qt.qrc index f19a1673c3..b827a818fe 100644 --- a/src/duckstation-qt/resources/duckstation-qt.qrc +++ b/src/duckstation-qt/resources/duckstation-qt.qrc @@ -151,6 +151,7 @@ icons/flags/en@2x.png icons/flags/en.png icons/flags/es-ES@2x.png + icons/flags/es.png icons/flags/es-ES.png icons/flags/fr@2x.png icons/flags/fr.png diff --git a/src/duckstation-qt/resources/icons/flags/es.png b/src/duckstation-qt/resources/icons/flags/es.png new file mode 100644 index 0000000000000000000000000000000000000000..e1ac29b3efc4f208aa612a4d8b8a959e913d2faa GIT binary patch literal 1090 zcmV-I1ikx-P)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D1J_AJK~z{ry_aol z6h#=v|1)gb=e>b&Y_?76NttWU4JzP*TS7I3^mXT~i6Ek5TV1EB_lZ z5z?mj`;LNzOIaFYyn*M|ETqeuY49RerVRlUnx;H-^GsYfu%$W&rubqe1|P*Tp;O*3 zJMLU!-6J;F4anq<>74e-u$CirZRB+>*}(H_gLK?x=NUi>Y;DgkE4Oo7NvW6Um~H17 zL1aM-YfLim?79F=eT}VW5TUX0M}H_^LZzZqkKGg*Lm($9&9&ANJd{@4K~G%7yJI-C z`(<40Y-80OO6b*=fy#909K-k%=a20{Yr}S&IPw)<-BN>&qu+3=E0#!_io3aRjQMvQ zvM>xKZ^MPSfq8fNap1#UP)hR^jwz-Y&^|64pwV;bG?bxW{ML#g!$7Vt2df_2j@-Es z^!A;#m&PeV>%8oy=U)I$#%dfvH!42WUQ z9U!2+K2(?c(BTVW-!DBlSGo*x{p0W#`607hZYCiTl4kW1myRe|a4&uz?8ho!AEE&R z8w<1X;9Nh3{`wV*g7@=P&Y;EWwU(eWT{@>C97V-~dr-RcVJxnUB0MjINTixuokN3n zN_f!1wJT=(i!gtFIf~0FkRK>!)g82H4$~5N57J~9tYwms+f8{u;{C4&>27FZF=NpB ze-;|q$+x#&wvR%pgk%Y z8R+_n1)K2(Eb0&bKzHRz>R_+Yv_R7?J)V`z2Yzm6WsHdiEUcN3*H%!^w&nHRsaA107*qo IM6N<$f&rfdUjP6A literal 0 HcmV?d00001 diff --git a/src/duckstation-qt/resources/icons/flags/es@2x.png b/src/duckstation-qt/resources/icons/flags/es@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..98e41ccd471370a20f3eb2fe55a607de059755f7 GIT binary patch literal 2725 zcmaJ@dpwkB8=f(nB3k4a@iuaZ%z=zd4w)H-G8!@(Da_%;q&XOGj8mu>qA?wI6H=@; zot9~# zLK6mqY5Vz-f>i6qg?EX%>U*9O*RNVkAa6Rfl^+EqGK3(^i_K?(fFG9;4F-V>c2Zn3 z=mvu!tU1ASh)(gxv-n&TV*!H_aRn+i4Cdx35-?b?AOtYMXb#UEIeDWF32@l%$ZgIP z3`IZ$V>rIaLU3zxKrkygmgUMudU^nEBD_j~3qlM)#NEw{$BW#NA9eAn{lYOC34DY= zvF^x!ilS4f0Ff^Q0cRA>frZ6l0T)*k)_J{?qw`w85rcI`V_eZ#i~|OX$71k~7~s=` zRAm#gqwql_pHI0|kvlR5f&_RpIx#U3mFR@x3!~9kS65dw#u4r4=%7M4#3%6}hRA^z zZ~0k)1je(390A1P^MD0K29v)Ba!0Bv{pSi?!53Lx{HHdl8ip1z1ZXS@vv8%)Knmsm zL%G~9=y)gy{I}l!DI6c1BmmJtU_5`1kfj=2l;uJy0iGxX84zC>%;)d^+(l{(AL7Ty z@C5*o__1q%?KTdN%}#N&jmhS z8_)TAEySwv+6LR?jxm|&6>XgnR-J)_|-SM&)`Q3N#c2zv@Es=?z z)3-7@d#}i{gPbg!!Op_as8FS4s*}g7W+LUA9n{QencBqu@xS{vmxW%eej}flkKSwh zb6-mP++^)=>Tt>AUCj8Z8rn8fS#qOW{^pjUr$Zz@GHYauLND+@X@{QOfZbAt^;nKw zdH#BTGeEMu)u{VJ_EDCN*H3{ruGZ`o)XkQfC~T2=zQDVkH?OrHO3<@X7&6^uhpH23 zh-95DnATsBl(v*_N=4Z zc9O8#Qe6ujdySRXOZVk~3h@E?6ZP4pw~6_;lN&d+BW^SO_i#k%U(&>19a@p4ztRwcS${qZyW*R(bBAMw zSZXD&9(2&uAFF^GGMW{D=+uPOk5|EswuhHaK8$r~3+1g-y5`zA2j$f%dp>|0jj;y( zRetiv4Ru;dKS}>$t&XeahGAOD{y4Q%ve~oB?WWRz)o-QnIK}%*+Q!;XJ4Xx=>!(b{ z_N!0rlUP)pnbs#nG=sR4zsM#gLKi38^&=TQhO)ou%J-o9>6Sdf_y3EKjrgWs?Clv2NwT(>jG) ziUX-NE5!#lPx((E6;DsDp&dRlMA#}N&Af;z%ugu`Hrp7~7)kdZ7F;i1u_|+(dcJFY zSa>d2)xNKGqJ&)qH`TFfIC@dr=fJ3?j^THbyxXJF*uU1N+|E8CbZRT+=5)mB7@2CM zupBh&!j_5etn!v2hl0G*4IP!F(Wv0y?|_8*XO-@u`AV4USn=*94;Po+2-xq_LYtkl zmz}MKgeIqw5^ zX*E80hxGT)Bh`hw3M#Lcm7TM>X80W=qLp_sbY{)GTg`II=wCDM@>WTEtEj`qzGaz& zu2(X+p^S$*Zm(l>Dr@=KO{WjFDDK}%nF_xjnqOhfKQx*<#W1-$QKx}8g7(ralx%uN zn>F16#0?pn;X^Vqo9~R83j=RhUk@f7#?zMN(itm%{XLwH~ zMfM@=gbfJs3`!A02uvp1ng{r1wld|Cato;xcsujV;MyiH6u7qI%8Aj#T05(t{G5lkJqdY5KZC}e2`B*Tj91ee+z)c*h^bJXXxb4J&28J%0U%}L=A^(V4Zp0ssrlKmh@mFFpj)7!UgPpf}P zJvEmBZ^<1iHmM&>EqWckBPpQiRQi+r>1%zx7thEmmE6aOXr9w! z#|)z4oE)F|OQl!k(T4^II(fh!{n{|@Ma%8tw5jx$6nmvAhSHEXyK86&dWmgq<9_=O zX<-hv4hT`XqDS8|De}3CJSn@mTNgn;M8cKyJSr`}s$da@#gY{rOoBxGmqwD@s9Sl@ z;_1Ee4U=2{WE^PT3FQp!Ki)auMgkM3%V++;PsxsXIdnFokjwgJ`#J{0ZdKpvMWy-( zDm(6UL(B$ODBZj*_0;?ES}*d<$(qFXIlczg##i;W>@w7Be?bfy@9&RqYBaTAq-nGv z9#ky}tf1RjhZ~eOU^mgo{E@aN4AgJE3N;gUA9-qEbd|u`%5Fo)JyAoleTn#Nx=m5v zXr*!QvzuMV(DiSI%~rROVqHDjZ74*go1%l6PaA2V5b_3}oF@uObx~aV!qJ?0y+~Mf XYVVSsV!7JFkE5S=0O=<} Date: Fri, 29 Nov 2024 12:48:52 +1000 Subject: [PATCH 3/9] Cheats: Strip whitespace from code names --- src/core/cheats.cpp | 58 +++++++++++++++++++++++++++++++++++---------- 1 file changed, 45 insertions(+), 13 deletions(-) diff --git a/src/core/cheats.cpp b/src/core/cheats.cpp index ccba2ebac7..09b10a1d3e 100644 --- a/src/core/cheats.cpp +++ b/src/core/cheats.cpp @@ -1246,11 +1246,17 @@ void Cheats::ParseFile(CheatCodeList* dst_list, const std::string_view file_cont continue; } + const std::string_view name = StringUtil::StripWhitespace(linev.substr(1, linev.length() - 2)); + if (name.empty()) + { + WARNING_LOG("Empty cheat code name at line {}: {}", reader.GetCurrentLineNumber(), line); + continue; + } + if (!next_code_metadata.name.empty()) finish_code(); // new code. - const std::string_view name = linev.substr(1, linev.length() - 2); next_code_metadata.name = next_code_group.empty() ? std::string(name) : fmt::format("{}\\{}", next_code_group, name); continue; @@ -1514,12 +1520,26 @@ bool Cheats::ImportPCSXFile(CodeInfoList* dst, const std::string_view file_conte continue; } + std::string_view name_part = StringUtil::StripWhitespace(linev.substr(1, linev.length() - 2)); + if (!name_part.empty() && name_part.front() == '*') + name_part = name_part.substr(1); + if (name_part.empty()) + { + if (!reader.LogError(error, stop_on_error, "Empty code name at line {}: {}", reader.GetCurrentLineNumber(), + line)) + { + return false; + } + + continue; + } + // new code. if (!current_code.name.empty() && !finish_code()) return false; current_code = CodeInfo(); - current_code.name = (linev[1] == '*') ? linev.substr(2, linev.length() - 3) : linev.substr(1, linev.length() - 2); + current_code.name = name_part; current_code.file_offset_start = static_cast(reader.GetCurrentLineOffset()); current_code.file_offset_end = current_code.file_offset_start; current_code.file_offset_body_start = current_code.file_offset_start; @@ -1683,12 +1703,24 @@ bool Cheats::ImportEPSXeFile(CodeInfoList* dst, const std::string_view file_cont continue; } + const std::string_view name_part = StringUtil::StripWhitespace(linev.substr(1)); + if (name_part.empty()) + { + if (!reader.LogError(error, stop_on_error, "Empty code name at line {}: {}", reader.GetCurrentLineNumber(), + line)) + { + return false; + } + + continue; + } + if (!current_code.name.empty() && !finish_code()) return false; // new code. current_code = CodeInfo(); - current_code.name = linev.substr(1); + current_code.name = name_part; current_code.file_offset_start = static_cast(reader.GetCurrentOffset()); current_code.file_offset_end = current_code.file_offset_start; current_code.file_offset_body_start = current_code.file_offset_start; @@ -2360,7 +2392,7 @@ void Cheats::GamesharkCheatCode::Apply() const index++; } break; - + case InstructionCode::ExtConstantWriteIfMatchWithRestore8: { const u8 value = DoMemoryRead(inst.address); @@ -2372,7 +2404,7 @@ void Cheats::GamesharkCheatCode::Apply() const index++; } break; - + case InstructionCode::ExtConstantForceRange8: { const u8 value = DoMemoryRead(inst.address); @@ -3950,14 +3982,14 @@ void Cheats::GamesharkCheatCode::ApplyOnDisable() const index++; } break; - - [[unlikely]] default: - { - ERROR_LOG("Unhandled instruction code 0x{:02X} ({:08X} {:08X})", static_cast(inst.code.GetValue()), - inst.first, inst.second); - index++; - } - break; + + [[unlikely]] default: + { + ERROR_LOG("Unhandled instruction code 0x{:02X} ({:08X} {:08X})", static_cast(inst.code.GetValue()), + inst.first, inst.second); + index++; + } + break; } } } From eeee1e691ab99478eda8b12e4bc8c555304482c3 Mon Sep 17 00:00:00 2001 From: Stenzek Date: Fri, 29 Nov 2024 13:10:58 +1000 Subject: [PATCH 4/9] Cheats: Support importing native format Compared to only replacing the .cht file. --- src/core/cheats.cpp | 128 ++++++++++++++++++++++++++++++++++++-------- src/core/cheats.h | 1 + 2 files changed, 106 insertions(+), 23 deletions(-) diff --git a/src/core/cheats.cpp b/src/core/cheats.cpp index 09b10a1d3e..10a59564ad 100644 --- a/src/core/cheats.cpp +++ b/src/core/cheats.cpp @@ -198,7 +198,8 @@ using EnableCodeList = std::vector; static std::string GetChtTemplate(const std::string_view serial, std::optional hash, bool add_wildcard); static std::vector FindChtFilesOnDisk(const std::string_view serial, std::optional hash, bool cheats); -static void ExtractCodeInfo(CodeInfoList* dst, const std::string& file_data, bool from_database); +static bool ExtractCodeInfo(CodeInfoList* dst, const std::string_view file_data, bool from_database, bool stop_on_error, + Error* error); static void AppendCheatToList(CodeInfoList* dst, CodeInfo code); static std::string FormatCodeForFile(const CodeInfo& code); @@ -511,7 +512,7 @@ Cheats::CodeInfoList Cheats::GetCodeInfoList(const std::string_view serial, std: EnumerateChtFiles(serial, hash, cheats, true, true, load_from_database, [&ret](const std::string& filename, const std::string& data, bool from_database) { - ExtractCodeInfo(&ret, data, from_database); + ExtractCodeInfo(&ret, data, from_database, false, nullptr); }); if (sort_by_name) @@ -605,7 +606,7 @@ bool Cheats::UpdateCodeInFile(const char* path, const std::string_view name, con if (!file_contents.empty() && !name.empty()) { CodeInfoList existing_codes_in_file; - ExtractCodeInfo(&existing_codes_in_file, file_contents, false); + ExtractCodeInfo(&existing_codes_in_file, file_contents, false, false, nullptr); const CodeInfo* existing_code = FindCodeInInfoList(existing_codes_in_file, name); if (existing_code) @@ -664,7 +665,7 @@ bool Cheats::SaveCodesToFile(const char* path, const CodeInfoList& codes, Error* if (!file_contents.empty()) { CodeInfoList existing_codes_in_file; - ExtractCodeInfo(&existing_codes_in_file, file_contents, false); + ExtractCodeInfo(&existing_codes_in_file, file_contents, false, false, nullptr); const CodeInfo* existing_code = FindCodeInInfoList(existing_codes_in_file, code.name); if (existing_code) @@ -980,7 +981,8 @@ u32 Cheats::GetActiveCheatCount() // File Parsing ////////////////////////////////////////////////////////////////////////// -void Cheats::ExtractCodeInfo(CodeInfoList* dst, const std::string& file_data, bool from_database) +bool Cheats::ExtractCodeInfo(CodeInfoList* dst, std::string_view file_data, bool from_database, bool stop_on_error, + Error* error) { CodeInfo current_code; @@ -988,17 +990,24 @@ void Cheats::ExtractCodeInfo(CodeInfoList* dst, const std::string& file_data, bo std::optional legacy_type; std::optional legacy_activation; - const auto finish_code = [&dst, &file_data, ¤t_code]() { + CheatFileReader reader(file_data); + + const auto finish_code = [&dst, &file_data, &stop_on_error, &error, ¤t_code, &reader]() { if (current_code.file_offset_end > current_code.file_offset_body_start) { - current_code.body = std::string_view(file_data).substr( - current_code.file_offset_body_start, current_code.file_offset_end - current_code.file_offset_body_start); + current_code.body = file_data.substr(current_code.file_offset_body_start, + current_code.file_offset_end - current_code.file_offset_body_start); + } + else + { + if (!reader.LogError(error, stop_on_error, "Empty body for cheat '{}'", current_code.name)) + return false; } AppendCheatToList(dst, std::move(current_code)); + return true; }; - CheatFileReader reader(file_data); std::string_view line; while (reader.GetLine(&line)) { @@ -1016,15 +1025,23 @@ void Cheats::ExtractCodeInfo(CodeInfoList* dst, const std::string& file_data, bo { legacy_type = ParseTypeName(StringUtil::StripWhitespace(linev.substr(6))); if (!legacy_type.has_value()) [[unlikely]] - WARNING_LOG("Unknown type at line {}: {}", reader.GetCurrentLineNumber(), line); - continue; + { + if (!reader.LogError(error, stop_on_error, "Unknown type at line {}: {}", reader.GetCurrentLineNumber(), line)) + return false; + + continue; + } } else if (linev.starts_with("#activation=")) { legacy_activation = ParseActivationName(StringUtil::StripWhitespace(linev.substr(12))); if (!legacy_activation.has_value()) [[unlikely]] - WARNING_LOG("Unknown type at line {}: {}", reader.GetCurrentLineNumber(), line); - continue; + { + if (!reader.LogError(error, stop_on_error, "Unknown type at line {}: {}", reader.GetCurrentLineNumber(), line)) + return false; + + continue; + } } // skip comments @@ -1035,7 +1052,24 @@ void Cheats::ExtractCodeInfo(CodeInfoList* dst, const std::string& file_data, bo { if (linev.size() < 3 || linev.back() != ']') { - WARNING_LOG("Malformed code at line {}: {}", reader.GetCurrentLineNumber(), line); + if (!reader.LogError(error, stop_on_error, "Malformed code at line {}: {}", reader.GetCurrentLineNumber(), + line)) + { + return false; + } + + continue; + } + + const std::string_view name = StringUtil::StripWhitespace(linev.substr(1, linev.length() - 2)); + if (name.empty()) + { + if (!reader.LogError(error, stop_on_error, "Empty code name at line {}: {}", reader.GetCurrentLineNumber(), + line)) + { + return false; + } + continue; } @@ -1047,7 +1081,6 @@ void Cheats::ExtractCodeInfo(CodeInfoList* dst, const std::string& file_data, bo current_code = CodeInfo(); } - const std::string_view name = linev.substr(1, linev.length() - 2); current_code.name = legacy_group.has_value() ? fmt::format("{}\\{}", legacy_group.value(), name) : std::string(name); current_code.type = legacy_type.value_or(CodeType::Gameshark); @@ -1074,7 +1107,12 @@ void Cheats::ExtractCodeInfo(CodeInfoList* dst, const std::string& file_data, bo std::string_view key, value; if (!StringUtil::ParseAssignmentString(linev, &key, &value)) { - WARNING_LOG("Malformed code at line {}: {}", reader.GetCurrentLineNumber(), line); + if (!reader.LogError(error, stop_on_error, "Malformed code at line {}: {}", reader.GetCurrentLineNumber(), + line)) + { + return false; + } + continue; } @@ -1090,29 +1128,57 @@ void Cheats::ExtractCodeInfo(CodeInfoList* dst, const std::string& file_data, bo { const std::optional type = ParseTypeName(value); if (type.has_value()) [[unlikely]] + { current_code.type = type.value(); + } else - WARNING_LOG("Unknown code type at line {}: {}", reader.GetCurrentLineNumber(), line); + { + if (!reader.LogError(error, stop_on_error, "Unknown code type at line {}: {}", reader.GetCurrentLineNumber(), + line)) + return false; + } } else if (key == "Activation") { const std::optional activation = ParseActivationName(value); if (activation.has_value()) [[unlikely]] + { current_code.activation = activation.value(); + } else - WARNING_LOG("Unknown code activation at line {}: {}", reader.GetCurrentLineNumber(), line); + { + if (!reader.LogError(error, stop_on_error, "Unknown code activation at line {}: {}", + reader.GetCurrentLineNumber(), line)) + { + return false; + } + } } else if (key == "Option") { if (std::optional opt = ParseOption(value)) + { current_code.options.push_back(std::move(opt.value())); + } else - WARNING_LOG("Invalid option declaration at line {}: {}", reader.GetCurrentLineNumber(), line); + { + if (!reader.LogError(error, stop_on_error, "Invalid option declaration at line {}: {}", + reader.GetCurrentLineNumber(), line)) + { + return false; + } + } } else if (key == "OptionRange") { if (!ParseOptionRange(value, ¤t_code.option_range_start, ¤t_code.option_range_end)) - WARNING_LOG("Invalid option range declaration at line {}: {}", reader.GetCurrentLineNumber(), line); + { + if (!reader.LogError(error, stop_on_error, "Invalid option range declaration at line {}: {}", + reader.GetCurrentLineNumber(), line)) + { + return false; + } + } } // ignore other keys when we're only grabbing info @@ -1121,7 +1187,12 @@ void Cheats::ExtractCodeInfo(CodeInfoList* dst, const std::string& file_data, bo if (current_code.name.empty()) { - WARNING_LOG("Code data specified without name at line {}: {}", reader.GetCurrentLineNumber(), line); + if (!reader.LogError(error, stop_on_error, "Code data specified without name at line {}: {}", + reader.GetCurrentLineNumber(), line)) + { + return false; + } + continue; } @@ -1134,7 +1205,9 @@ void Cheats::ExtractCodeInfo(CodeInfoList* dst, const std::string& file_data, bo // last code. if (!current_code.name.empty()) - finish_code(); + return finish_code(); + else + return true; } void Cheats::AppendCheatToList(CodeInfoList* dst, CodeInfo code) @@ -1424,7 +1497,12 @@ bool Cheats::ImportCodesFromString(CodeInfoList* dst, const std::string_view fil if (file_format == FileFormat::Unknown) file_format = DetectFileFormat(file_contents); - if (file_format == FileFormat::PCSX) + if (file_format == FileFormat::DuckStation) + { + if (!ExtractCodeInfo(dst, file_contents, false, stop_on_error, error)) + return false; + } + else if (file_format == FileFormat::PCSX) { if (!ImportPCSXFile(dst, file_contents, stop_on_error, error)) return false; @@ -1468,6 +1546,10 @@ Cheats::FileFormat Cheats::DetectFileFormat(const std::string_view file_contents if (linev.starts_with("cheats")) return FileFormat::Libretro; + // native if we see brackets and a type string + if (linev[0] == '[' && file_contents.find("\nType =")) + return FileFormat::DuckStation; + // pcsxr if we see brackets if (linev[0] == '[') return FileFormat::PCSX; diff --git a/src/core/cheats.h b/src/core/cheats.h index 7a83e6e98b..5d7d8e3072 100644 --- a/src/core/cheats.h +++ b/src/core/cheats.h @@ -34,6 +34,7 @@ enum class CodeActivation : u8 enum class FileFormat : u8 { Unknown, + DuckStation, PCSX, Libretro, EPSXe, From 6be242449b00c75f993eaf3ed9083d7f2a6f1eee Mon Sep 17 00:00:00 2001 From: Stenzek Date: Wed, 27 Nov 2024 23:53:42 +1000 Subject: [PATCH 5/9] AnalogController: Simplify rumble config And fix some variables not being saved to state, yay determinism issues. --- src/core/analog_controller.cpp | 306 ++++++++++++++------------------- src/core/analog_controller.h | 16 +- src/core/save_state_version.h | 2 +- 3 files changed, 132 insertions(+), 192 deletions(-) diff --git a/src/core/analog_controller.cpp b/src/core/analog_controller.cpp index 09f22815d6..a290fa4eb3 100644 --- a/src/core/analog_controller.cpp +++ b/src/core/analog_controller.cpp @@ -84,28 +84,64 @@ bool AnalogController::DoState(StateWrapper& sw, bool apply_input_state) return false; const bool old_analog_mode = m_analog_mode; + MotorState motor_state = m_motor_state; - sw.Do(&m_analog_mode); - sw.Do(&m_dualshock_enabled); - sw.DoEx(&m_legacy_rumble_unlocked, 44, false); - sw.Do(&m_configuration_mode); - sw.Do(&m_command_param); - sw.DoEx(&m_status_byte, 55, static_cast(0x5A)); + if (sw.GetVersion() < 76) [[unlikely]] + { + u8 unused_command_param = 0; + bool unused_legacy_rumble_unlocked = false; - u16 button_state = m_button_state; - sw.DoEx(&button_state, 44, static_cast(0xFFFF)); - if (apply_input_state) - m_button_state = button_state; + sw.Do(&m_analog_mode); + sw.Do(&m_dualshock_enabled); + sw.DoEx(&unused_legacy_rumble_unlocked, 44, false); + sw.Do(&m_configuration_mode); + sw.Do(&unused_command_param); + sw.DoEx(&m_status_byte, 55, static_cast(0x5A)); - sw.Do(&m_command); + u16 button_state = m_button_state; + sw.DoEx(&button_state, 44, static_cast(0xFFFF)); + if (apply_input_state) + m_button_state = button_state; - sw.DoEx(&m_rumble_config, 45, {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}); - sw.DoEx(&m_rumble_config_large_motor_index, 45, -1); - sw.DoEx(&m_rumble_config_small_motor_index, 45, -1); - sw.DoEx(&m_analog_toggle_queued, 45, false); + sw.Do(&m_command); - MotorState motor_state = m_motor_state; - sw.Do(&motor_state); + int unused_rumble_config_large_motor_index = -1; + int unused_rumble_config_small_motor_index = -1; + + sw.DoEx(&m_rumble_config, 45, {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}); + sw.DoEx(&unused_rumble_config_large_motor_index, 45, -1); + sw.DoEx(&unused_rumble_config_small_motor_index, 45, -1); + sw.DoEx(&m_analog_toggle_queued, 45, false); + + sw.Do(&motor_state); + } + else + { + sw.Do(&m_command); + sw.Do(&m_command_step); + sw.Do(&m_response_length); + sw.DoBytes(m_rx_buffer.data(), m_rx_buffer.size()); + sw.DoBytes(m_tx_buffer.data(), m_tx_buffer.size()); + sw.Do(&m_analog_mode); + sw.Do(&m_analog_locked); + sw.Do(&m_dualshock_enabled); + sw.Do(&m_configuration_mode); + sw.DoBytes(m_rumble_config.data(), m_rumble_config.size()); + sw.Do(&m_status_byte); + // sw.Do(&m_digital_mode_extra_halfwords); // always zero + + auto axis_state = m_axis_state; + u16 button_state = m_button_state; + sw.DoBytes(axis_state.data(), axis_state.size()); + sw.Do(&button_state); + sw.Do(&motor_state); + + if (apply_input_state) + { + m_axis_state = axis_state; + m_button_state = button_state; + } + } if (sw.IsReading()) { @@ -368,8 +404,11 @@ void AnalogController::UpdateHostVibration() std::array hvalues; for (u32 motor = 0; motor < NUM_MOTORS; motor++) { + // Small motor is only 0/1. + const u8 state = + (motor == SmallMotor) ? (((m_motor_state[SmallMotor] & 0x01) != 0x00) ? 255 : 0) : m_motor_state[LargeMotor]; + // Curve from https://github.com/KrossX/Pokopom/blob/master/Pokopom/Input_XInput.cpp#L210 - const u8 state = m_motor_state[motor]; const double x = static_cast(std::clamp(static_cast(state) + m_vibration_bias[motor], 0, 255)); const double strength = 0.006474549734772402 * std::pow(x, 3.0) - 1.258165252213538 * std::pow(x, 2.0) + 156.82454281087692 * x + 3.637978807091713e-11; @@ -377,7 +416,8 @@ void AnalogController::UpdateHostVibration() hvalues[motor] = (state != 0) ? static_cast(strength / 65535.0) : 0.0f; } - InputManager::SetPadVibrationIntensity(m_index, hvalues[0], hvalues[1]); + WARNING_LOG("Set small to {}, large to {}", hvalues[SmallMotor], hvalues[LargeMotor]); + InputManager::SetPadVibrationIntensity(m_index, hvalues[LargeMotor], hvalues[SmallMotor]); } u8 AnalogController::GetExtraButtonMaskLSB() const @@ -402,20 +442,8 @@ u8 AnalogController::GetExtraButtonMaskLSB() const void AnalogController::ResetRumbleConfig() { m_rumble_config.fill(0xFF); - - m_rumble_config_large_motor_index = -1; - m_rumble_config_small_motor_index = -1; - - SetMotorState(LargeMotor, 0); SetMotorState(SmallMotor, 0); -} - -void AnalogController::SetMotorStateForConfigIndex(int index, u8 value) -{ - if (m_rumble_config_small_motor_index == index) - SetMotorState(SmallMotor, ((value & 0x01) != 0) ? 255 : 0); - else if (m_rumble_config_large_motor_index == index) - SetMotorState(LargeMotor, value); + SetMotorState(LargeMotor, 0); } u8 AnalogController::GetResponseNumHalfwords() const @@ -442,6 +470,29 @@ u8 AnalogController::GetIDByte() const return Truncate8((GetModeID() << 4) | GetResponseNumHalfwords()); } +void AnalogController::Poll() +{ + // m_tx_buffer = {GetIDByte(), m_status_byte, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + m_tx_buffer[0] = GetIDByte(); + m_tx_buffer[1] = m_status_byte; + m_tx_buffer[2] = Truncate8(m_button_state) & GetExtraButtonMaskLSB(); + m_tx_buffer[3] = Truncate8(m_button_state >> 8); + if (m_analog_mode || m_configuration_mode) + { + m_tx_buffer[4] = m_axis_state[static_cast(Axis::RightX)]; + m_tx_buffer[5] = m_axis_state[static_cast(Axis::RightY)]; + m_tx_buffer[6] = m_axis_state[static_cast(Axis::LeftX)]; + m_tx_buffer[7] = m_axis_state[static_cast(Axis::LeftY)]; + } + else + { + m_tx_buffer[4] = 0; + m_tx_buffer[5] = 0; + m_tx_buffer[6] = 0; + m_tx_buffer[7] = 0; + } +} + bool AnalogController::Transfer(const u8 data_in, u8* data_out) { bool ack; @@ -472,14 +523,17 @@ bool AnalogController::Transfer(const u8 data_in, u8* data_out) Assert(m_command_step == 0); m_response_length = (GetResponseNumHalfwords() + 1) * 2; m_command = Command::ReadPad; - m_tx_buffer = {GetIDByte(), m_status_byte, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + Poll(); } else if (data_in == 0x43) { Assert(m_command_step == 0); m_response_length = (GetResponseNumHalfwords() + 1) * 2; m_command = Command::ConfigModeSetMode; - m_tx_buffer = {GetIDByte(), m_status_byte, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + if (!m_configuration_mode) + Poll(); + else + m_tx_buffer = {GetIDByte(), m_status_byte, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; } else if (m_configuration_mode && data_in == 0x44) { @@ -524,9 +578,6 @@ bool AnalogController::Transfer(const u8 data_in, u8* data_out) m_response_length = (GetResponseNumHalfwords() + 1) * 2; m_command = Command::GetSetRumble; m_tx_buffer = {GetIDByte(), m_status_byte, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; - - m_rumble_config_large_motor_index = -1; - m_rumble_config_small_motor_index = -1; } else { @@ -541,136 +592,25 @@ bool AnalogController::Transfer(const u8 data_in, u8* data_out) case Command::ReadPad: { - const int rumble_index = m_command_step - 2; - - switch (m_command_step) + if (m_dualshock_enabled) { - case 2: - { - m_tx_buffer[m_command_step] = Truncate8(m_button_state) & GetExtraButtonMaskLSB(); - - if (m_dualshock_enabled) - SetMotorStateForConfigIndex(rumble_index, data_in); - } - break; - - case 3: + if (m_command_step >= 2 && m_command_step < 7) { - m_tx_buffer[m_command_step] = Truncate8(m_button_state >> 8); - - if (m_dualshock_enabled) - { - SetMotorStateForConfigIndex(rumble_index, data_in); - } - else - { - bool legacy_rumble_on = (m_rx_buffer[2] & 0xC0) == 0x40 && (m_rx_buffer[3] & 0x01) != 0; - SetMotorState(SmallMotor, legacy_rumble_on ? 255 : 0); - } + const u8 motor_to_set = m_rumble_config[m_command_step - 2]; + if (motor_to_set <= LargeMotor) + SetMotorState(motor_to_set, data_in); } - break; - - case 4: - { - if (m_configuration_mode || m_analog_mode) - m_tx_buffer[m_command_step] = m_axis_state[static_cast(Axis::RightX)]; - - if (m_dualshock_enabled) - SetMotorStateForConfigIndex(rumble_index, data_in); - } - break; - - case 5: - { - if (m_configuration_mode || m_analog_mode) - m_tx_buffer[m_command_step] = m_axis_state[static_cast(Axis::RightY)]; - - if (m_dualshock_enabled) - SetMotorStateForConfigIndex(rumble_index, data_in); - } - break; - - case 6: - { - if (m_configuration_mode || m_analog_mode) - m_tx_buffer[m_command_step] = m_axis_state[static_cast(Axis::LeftX)]; - - if (m_dualshock_enabled) - SetMotorStateForConfigIndex(rumble_index, data_in); - } - break; - - case 7: - { - if (m_configuration_mode || m_analog_mode) - m_tx_buffer[m_command_step] = m_axis_state[static_cast(Axis::LeftY)]; - - if (m_dualshock_enabled) - SetMotorStateForConfigIndex(rumble_index, data_in); - } - break; - - default: - { - } - break; + } + else if (m_command_step == 3) + { + const bool legacy_rumble_on = (m_rx_buffer[2] & 0xC0) == 0x40 && (m_rx_buffer[3] & 0x01) != 0; + SetMotorState(SmallMotor, legacy_rumble_on ? 255 : 0); } } break; case Command::ConfigModeSetMode: { - if (!m_configuration_mode) - { - switch (m_command_step) - { - case 2: - { - m_tx_buffer[m_command_step] = Truncate8(m_button_state) & GetExtraButtonMaskLSB(); - } - break; - - case 3: - { - m_tx_buffer[m_command_step] = Truncate8(m_button_state >> 8); - } - break; - - case 4: - { - if (m_configuration_mode || m_analog_mode) - m_tx_buffer[m_command_step] = m_axis_state[static_cast(Axis::RightX)]; - } - break; - - case 5: - { - if (m_configuration_mode || m_analog_mode) - m_tx_buffer[m_command_step] = m_axis_state[static_cast(Axis::RightY)]; - } - break; - - case 6: - { - if (m_configuration_mode || m_analog_mode) - m_tx_buffer[m_command_step] = m_axis_state[static_cast(Axis::LeftX)]; - } - break; - - case 7: - { - if (m_configuration_mode || m_analog_mode) - m_tx_buffer[m_command_step] = m_axis_state[static_cast(Axis::LeftY)]; - } - break; - - default: - { - } - break; - } - } - if (m_command_step == (static_cast(m_response_length) - 1)) { m_configuration_mode = (m_rx_buffer[2] == 1); @@ -759,25 +699,31 @@ bool AnalogController::Transfer(const u8 data_in, u8* data_out) case Command::GetSetRumble: { - int rumble_index = m_command_step - 2; - if (rumble_index >= 0) + if (m_command_step >= 2 && m_command_step < 7) { - m_tx_buffer[m_command_step] = m_rumble_config[rumble_index]; - m_rumble_config[rumble_index] = data_in; - - if (data_in == 0x00) - m_rumble_config_small_motor_index = rumble_index; - else if (data_in == 0x01) - m_rumble_config_large_motor_index = rumble_index; + const u8 index = m_command_step - 2; + m_tx_buffer[m_command_step] = m_rumble_config[index]; + m_rumble_config[index] = data_in; + + if (data_in == LargeMotor) + WARNING_LOG("Large mapped to byte index {}", index); + else if (data_in == SmallMotor) + WARNING_LOG("Small mapped to byte index {}", index); } - - if (m_command_step == 7) + else if (m_command_step == 7) { - if (m_rumble_config_large_motor_index == -1) - SetMotorState(LargeMotor, 0); - - if (m_rumble_config_small_motor_index == -1) + // reset motor value if we're no longer mapping it + bool has_small = false; + bool has_large = false; + for (size_t i = 0; i < m_rumble_config.size(); i++) + { + has_small |= (m_rumble_config[i] == SmallMotor); + has_large |= (m_rumble_config[i] == LargeMotor); + } + if (!has_small) SetMotorState(SmallMotor, 0); + if (!has_large) + SetMotorState(LargeMotor, 0); } } break; @@ -813,14 +759,14 @@ std::unique_ptr AnalogController::Create(u32 index) static const Controller::ControllerBindingInfo s_binding_info[] = { #define BUTTON(name, display_name, icon_name, button, genb) \ - { \ - name, display_name, icon_name, static_cast(button), InputBindingInfo::Type::Button, genb \ - } + {name, display_name, icon_name, static_cast(button), InputBindingInfo::Type::Button, genb} #define AXIS(name, display_name, icon_name, halfaxis, genb) \ - { \ - name, display_name, icon_name, static_cast(AnalogController::Button::Count) + static_cast(halfaxis), \ - InputBindingInfo::Type::HalfAxis, genb \ - } + {name, \ + display_name, \ + icon_name, \ + static_cast(AnalogController::Button::Count) + static_cast(halfaxis), \ + InputBindingInfo::Type::HalfAxis, \ + genb} // clang-format off BUTTON("Up", TRANSLATE_NOOP("AnalogController", "D-Pad Up"), ICON_PF_DPAD_UP, AnalogController::Button::Up, GenericInputBinding::DPadUp), diff --git a/src/core/analog_controller.h b/src/core/analog_controller.h index b1a9a117d4..5cbf47c45a 100644 --- a/src/core/analog_controller.h +++ b/src/core/analog_controller.h @@ -103,13 +103,13 @@ class AnalogController final : public Controller static constexpr s16 DEFAULT_LARGE_MOTOR_VIBRATION_BIAS = 8; Command m_command = Command::Idle; - int m_command_step = 0; + u8 m_command_step = 0; + u8 m_response_length = 0; // Transmit and receive buffers, not including the first Hi-Z/ack response byte static constexpr u32 MAX_RESPONSE_LENGTH = 8; std::array m_rx_buffer; std::array m_tx_buffer; - u32 m_response_length = 0; // Get number of response halfwords (excluding the initial controller info halfword) u8 GetResponseNumHalfwords() const; @@ -123,7 +123,7 @@ class AnalogController final : public Controller void UpdateHostVibration(); u8 GetExtraButtonMaskLSB() const; void ResetRumbleConfig(); - void SetMotorStateForConfigIndex(int index, u8 value); + void Poll(); float m_analog_deadzone = 0.0f; float m_analog_sensitivity = 1.33f; @@ -143,13 +143,11 @@ class AnalogController final : public Controller enum : u8 { - LargeMotor = 0, - SmallMotor = 1 + SmallMotor = 0, + LargeMotor = 1, }; std::array m_rumble_config{}; - int m_rumble_config_large_motor_index = -1; - int m_rumble_config_small_motor_index = -1; bool m_analog_toggle_queued = false; u8 m_status_byte = 0; @@ -164,8 +162,4 @@ class AnalogController final : public Controller // both directions of axis state, merged to m_axis_state std::array(HalfAxis::Count)> m_half_axis_state{}; - - // Member variables that are no longer used, but kept and serialized for compatibility with older save states - u8 m_command_param = 0; - bool m_legacy_rumble_unlocked = false; }; diff --git a/src/core/save_state_version.h b/src/core/save_state_version.h index 9c2e098dd4..c3cc9d4d30 100644 --- a/src/core/save_state_version.h +++ b/src/core/save_state_version.h @@ -6,7 +6,7 @@ #include "common/types.h" static constexpr u32 SAVE_STATE_MAGIC = 0x43435544; -static constexpr u32 SAVE_STATE_VERSION = 75; +static constexpr u32 SAVE_STATE_VERSION = 76; static constexpr u32 SAVE_STATE_MINIMUM_VERSION = 42; static_assert(SAVE_STATE_VERSION >= SAVE_STATE_MINIMUM_VERSION); From 3ed6cc2ba8c738a72ac6413cc0ba7095626a34d3 Mon Sep 17 00:00:00 2001 From: Stenzek Date: Fri, 29 Nov 2024 13:17:07 +1000 Subject: [PATCH 6/9] GameList: Fix crash loading custom language options --- src/core/game_list.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/game_list.cpp b/src/core/game_list.cpp index 773cd457f1..bffd38f266 100644 --- a/src/core/game_list.cpp +++ b/src/core/game_list.cpp @@ -634,7 +634,7 @@ void GameList::ApplyCustomAttributes(const std::string& path, Entry* entry, if (custom_language_str.has_value()) { const std::optional custom_region = - GameDatabase::ParseLanguageName(custom_region_str.value()); + GameDatabase::ParseLanguageName(custom_language_str.value()); if (custom_region.has_value()) { entry->custom_language = custom_region.value(); From 6d72a487085e6fb9da6813d10ac02e2b31be7c85 Mon Sep 17 00:00:00 2001 From: Stenzek Date: Fri, 29 Nov 2024 13:17:58 +1000 Subject: [PATCH 7/9] Qt: Disable All Enhancements -> Safe Mode in Debug menu --- src/duckstation-qt/mainwindow.cpp | 4 ++-- src/duckstation-qt/mainwindow.ui | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/duckstation-qt/mainwindow.cpp b/src/duckstation-qt/mainwindow.cpp index d01ed6eda4..237cc1f3cb 100644 --- a/src/duckstation-qt/mainwindow.cpp +++ b/src/duckstation-qt/mainwindow.cpp @@ -2036,8 +2036,8 @@ void MainWindow::connectSignals() Settings::DEFAULT_LOG_LEVEL, Log::Level::MaxCount); connect(m_ui.menuLogChannels, &QMenu::aboutToShow, this, &MainWindow::onDebugLogChannelsMenuAboutToShow); - SettingWidgetBinder::BindWidgetToBoolSetting(nullptr, m_ui.actionDisableAllEnhancements, "Main", - "DisableAllEnhancements", false); + SettingWidgetBinder::BindWidgetToBoolSetting(nullptr, m_ui.actionEnableSafeMode, "Main", "DisableAllEnhancements", + false); SettingWidgetBinder::BindWidgetToBoolSetting(nullptr, m_ui.actionDebugDumpCPUtoVRAMCopies, "Debug", "DumpCPUToVRAMCopies", false); SettingWidgetBinder::BindWidgetToBoolSetting(nullptr, m_ui.actionDebugDumpVRAMtoCPUCopies, "Debug", diff --git a/src/duckstation-qt/mainwindow.ui b/src/duckstation-qt/mainwindow.ui index 4fece997d9..6e97fba029 100644 --- a/src/duckstation-qt/mainwindow.ui +++ b/src/duckstation-qt/mainwindow.ui @@ -166,7 +166,7 @@ - + @@ -634,12 +634,12 @@ Dump VRAM to CPU Copies - + true - Disable All Enhancements + Enable Safe Mode From dac5dd562b37b1a9c7ab570770ec93cfe3bd9004 Mon Sep 17 00:00:00 2001 From: Stenzek Date: Fri, 29 Nov 2024 13:48:19 +1000 Subject: [PATCH 8/9] HTTPDownloader: Improve error reporting Give something human-readable when an error occurs. --- src/common/error.h | 2 +- src/core/achievements.cpp | 33 ++++---- src/core/game_list.cpp | 75 ++++++++++--------- src/duckstation-qt/autoupdaterdialog.cpp | 27 +++---- src/duckstation-qt/autoupdaterdialog.h | 6 +- src/duckstation-qt/qthost.cpp | 24 +++--- src/util/CMakeLists.txt | 2 - src/util/http_downloader.cpp | 10 ++- src/util/http_downloader.h | 7 +- src/util/http_downloader_curl.cpp | 49 +++++++++--- src/util/http_downloader_curl.h | 36 --------- src/util/http_downloader_winhttp.cpp | 95 +++++++++++++++++++----- src/util/http_downloader_winhttp.h | 38 ---------- src/util/util.vcxproj | 4 - src/util/util.vcxproj.filters | 2 - 15 files changed, 220 insertions(+), 190 deletions(-) delete mode 100644 src/util/http_downloader_curl.h delete mode 100644 src/util/http_downloader_winhttp.h diff --git a/src/common/error.h b/src/common/error.h index 1e9eebbfab..5712cbe473 100644 --- a/src/common/error.h +++ b/src/common/error.h @@ -141,6 +141,6 @@ class Error void AddPrefixFmtArgs(fmt::string_view fmt, fmt::format_args args); void AddSuffixFmtArgs(fmt::string_view fmt, fmt::format_args args); - Type m_type = Type::None; std::string m_description; + Type m_type = Type::None; }; diff --git a/src/core/achievements.cpp b/src/core/achievements.cpp index b8d75c51ff..54f5607012 100644 --- a/src/core/achievements.cpp +++ b/src/core/achievements.cpp @@ -354,10 +354,13 @@ std::string Achievements::GetLocalImagePath(const std::string_view image_name, i void Achievements::DownloadImage(std::string url, std::string cache_filename) { - auto callback = [cache_filename](s32 status_code, const std::string& content_type, + auto callback = [cache_filename](s32 status_code, const Error& error, const std::string& content_type, HTTPDownloader::Request::Data data) { if (status_code != HTTPDownloader::HTTP_STATUS_OK) + { + ERROR_LOG("Failed to download badge '{}': {}", Path::GetFileName(cache_filename), error.GetDescription()); return; + } if (!FileSystem::WriteBinaryFile(cache_filename.c_str(), data.data(), data.size())) { @@ -753,18 +756,22 @@ uint32_t Achievements::ClientReadMemory(uint32_t address, uint8_t* buffer, uint3 void Achievements::ClientServerCall(const rc_api_request_t* request, rc_client_server_callback_t callback, void* callback_data, rc_client_t* client) { - HTTPDownloader::Request::Callback hd_callback = - [callback, callback_data](s32 status_code, const std::string& content_type, HTTPDownloader::Request::Data data) { - rc_api_server_response_t rr; - rr.http_status_code = (status_code <= 0) ? (status_code == HTTPDownloader::HTTP_STATUS_CANCELLED ? - RC_API_SERVER_RESPONSE_CLIENT_ERROR : - RC_API_SERVER_RESPONSE_RETRYABLE_CLIENT_ERROR) : - status_code; - rr.body_length = data.size(); - rr.body = reinterpret_cast(data.data()); - - callback(&rr, callback_data); - }; + HTTPDownloader::Request::Callback hd_callback = [callback, callback_data](s32 status_code, const Error& error, + const std::string& content_type, + HTTPDownloader::Request::Data data) { + if (status_code != HTTPDownloader::HTTP_STATUS_OK) + ERROR_LOG("Server call failed: {}", error.GetDescription()); + + rc_api_server_response_t rr; + rr.http_status_code = (status_code <= 0) ? (status_code == HTTPDownloader::HTTP_STATUS_CANCELLED ? + RC_API_SERVER_RESPONSE_CLIENT_ERROR : + RC_API_SERVER_RESPONSE_RETRYABLE_CLIENT_ERROR) : + status_code; + rr.body_length = data.size(); + rr.body = reinterpret_cast(data.data()); + + callback(&rr, callback_data); + }; HTTPDownloader* http = static_cast(rc_client_get_userdata(client)); diff --git a/src/core/game_list.cpp b/src/core/game_list.cpp index bffd38f266..bf7f93877b 100644 --- a/src/core/game_list.cpp +++ b/src/core/game_list.cpp @@ -1454,10 +1454,11 @@ bool GameList::DownloadCovers(const std::vector& url_templates, boo return false; } - std::unique_ptr downloader(HTTPDownloader::Create(Host::GetHTTPUserAgent())); + Error error; + std::unique_ptr downloader(HTTPDownloader::Create(Host::GetHTTPUserAgent(), &error)); if (!downloader) { - progress->DisplayError("Failed to create HTTP downloader."); + progress->DisplayError(fmt::format("Failed to create HTTP downloader:\n{}", error.GetDescription())); return false; } @@ -1484,39 +1485,43 @@ bool GameList::DownloadCovers(const std::vector& url_templates, boo // we could actually do a few in parallel here... std::string filename = Path::URLDecode(url); - downloader->CreateRequest( - std::move(url), [use_serial, &save_callback, entry_path = std::move(entry_path), filename = std::move(filename)]( - s32 status_code, const std::string& content_type, HTTPDownloader::Request::Data data) { - if (status_code != HTTPDownloader::HTTP_STATUS_OK || data.empty()) - return; - - std::unique_lock lock(s_mutex); - const GameList::Entry* entry = GetEntryForPath(entry_path); - if (!entry || !GetCoverImagePathForEntry(entry).empty()) - return; - - // prefer the content type from the response for the extension - // otherwise, if it's missing, and the request didn't have an extension.. fall back to jpegs. - std::string template_filename; - std::string content_type_extension(HTTPDownloader::GetExtensionForContentType(content_type)); - - // don't treat the domain name as an extension.. - const std::string::size_type last_slash = filename.find('/'); - const std::string::size_type last_dot = filename.find('.'); - if (!content_type_extension.empty()) - template_filename = fmt::format("cover.{}", content_type_extension); - else if (last_slash != std::string::npos && last_dot != std::string::npos && last_dot > last_slash) - template_filename = Path::GetFileName(filename); - else - template_filename = "cover.jpg"; - - std::string write_path(GetNewCoverImagePathForEntry(entry, template_filename.c_str(), use_serial)); - if (write_path.empty()) - return; - - if (FileSystem::WriteBinaryFile(write_path.c_str(), data.data(), data.size()) && save_callback) - save_callback(entry, std::move(write_path)); - }); + downloader->CreateRequest(std::move(url), [use_serial, &save_callback, entry_path = std::move(entry_path), + filename = std::move(filename)](s32 status_code, const Error& error, + const std::string& content_type, + HTTPDownloader::Request::Data data) { + if (status_code != HTTPDownloader::HTTP_STATUS_OK || data.empty()) + { + ERROR_LOG("Download for {} failed: {}", Path::GetFileName(filename), error.GetDescription()); + return; + } + + std::unique_lock lock(s_mutex); + const GameList::Entry* entry = GetEntryForPath(entry_path); + if (!entry || !GetCoverImagePathForEntry(entry).empty()) + return; + + // prefer the content type from the response for the extension + // otherwise, if it's missing, and the request didn't have an extension.. fall back to jpegs. + std::string template_filename; + std::string content_type_extension(HTTPDownloader::GetExtensionForContentType(content_type)); + + // don't treat the domain name as an extension.. + const std::string::size_type last_slash = filename.find('/'); + const std::string::size_type last_dot = filename.find('.'); + if (!content_type_extension.empty()) + template_filename = fmt::format("cover.{}", content_type_extension); + else if (last_slash != std::string::npos && last_dot != std::string::npos && last_dot > last_slash) + template_filename = Path::GetFileName(filename); + else + template_filename = "cover.jpg"; + + std::string write_path(GetNewCoverImagePathForEntry(entry, template_filename.c_str(), use_serial)); + if (write_path.empty()) + return; + + if (FileSystem::WriteBinaryFile(write_path.c_str(), data.data(), data.size()) && save_callback) + save_callback(entry, std::move(write_path)); + }); downloader->WaitForAllRequests(); progress->IncrementProgressValue(); } diff --git a/src/duckstation-qt/autoupdaterdialog.cpp b/src/duckstation-qt/autoupdaterdialog.cpp index 26f6abbb4a..e5f7d83655 100644 --- a/src/duckstation-qt/autoupdaterdialog.cpp +++ b/src/duckstation-qt/autoupdaterdialog.cpp @@ -88,9 +88,10 @@ AutoUpdaterDialog::AutoUpdaterDialog(QWidget* parent /* = nullptr */) : QDialog( connect(m_ui.skipThisUpdate, &QPushButton::clicked, this, &AutoUpdaterDialog::skipThisUpdateClicked); connect(m_ui.remindMeLater, &QPushButton::clicked, this, &AutoUpdaterDialog::remindMeLaterClicked); - m_http = HTTPDownloader::Create(Host::GetHTTPUserAgent()); + Error error; + m_http = HTTPDownloader::Create(Host::GetHTTPUserAgent(), &error); if (!m_http) - ERROR_LOG("Failed to create HTTP downloader, auto updater will not be available."); + ERROR_LOG("Failed to create HTTP downloader, auto updater will not be available:\n{}", error.GetDescription()); } AutoUpdaterDialog::~AutoUpdaterDialog() = default; @@ -277,7 +278,7 @@ void AutoUpdaterDialog::queueUpdateCheck(bool display_message) } m_http->CreateRequest(LATEST_TAG_URL, std::bind(&AutoUpdaterDialog::getLatestTagComplete, this, std::placeholders::_1, - std::placeholders::_3)); + std::placeholders::_2, std::placeholders::_4)); #else emit updateCheckCompleted(); #endif @@ -294,11 +295,11 @@ void AutoUpdaterDialog::queueGetLatestRelease() std::string url = fmt::format(fmt::runtime(LATEST_RELEASE_URL), getCurrentUpdateTag()); m_http->CreateRequest(std::move(url), std::bind(&AutoUpdaterDialog::getLatestReleaseComplete, this, - std::placeholders::_1, std::placeholders::_3)); + std::placeholders::_1, std::placeholders::_2, std::placeholders::_4)); #endif } -void AutoUpdaterDialog::getLatestTagComplete(s32 status_code, std::vector response) +void AutoUpdaterDialog::getLatestTagComplete(s32 status_code, const Error& error, std::vector response) { #ifdef UPDATE_CHECKER_SUPPORTED const std::string selected_tag(getCurrentUpdateTag()); @@ -351,14 +352,14 @@ void AutoUpdaterDialog::getLatestTagComplete(s32 status_code, std::vector re else { if (m_display_messages) - reportError(fmt::format("Failed to download latest tag info: HTTP {}", status_code)); + reportError(fmt::format("Failed to download latest tag info: {}", error.GetDescription())); } emit updateCheckCompleted(); #endif } -void AutoUpdaterDialog::getLatestReleaseComplete(s32 status_code, std::vector response) +void AutoUpdaterDialog::getLatestReleaseComplete(s32 status_code, const Error& error, std::vector response) { #ifdef UPDATE_CHECKER_SUPPORTED if (status_code == HTTPDownloader::HTTP_STATUS_OK) @@ -415,7 +416,7 @@ void AutoUpdaterDialog::getLatestReleaseComplete(s32 status_code, std::vectorCreateRequest(std::move(url), std::bind(&AutoUpdaterDialog::getChangesComplete, this, std::placeholders::_1, - std::placeholders::_3)); + std::placeholders::_2, std::placeholders::_4)); #endif } -void AutoUpdaterDialog::getChangesComplete(s32 status_code, std::vector response) +void AutoUpdaterDialog::getChangesComplete(s32 status_code, const Error& error, std::vector response) { #ifdef UPDATE_CHECKER_SUPPORTED if (status_code == HTTPDownloader::HTTP_STATUS_OK) @@ -503,7 +504,7 @@ void AutoUpdaterDialog::getChangesComplete(s32 status_code, std::vector resp } else { - reportError(fmt::format("Failed to download change list: HTTP {}", status_code)); + reportError(fmt::format("Failed to download change list: {}", error.GetDescription())); } #endif } @@ -527,13 +528,13 @@ void AutoUpdaterDialog::downloadUpdateClicked() m_http->CreateRequest( m_download_url.toStdString(), - [this, &download_result](s32 status_code, const std::string&, std::vector response) { + [this, &download_result](s32 status_code, const Error& error, const std::string&, std::vector response) { if (status_code == HTTPDownloader::HTTP_STATUS_CANCELLED) return; if (status_code != HTTPDownloader::HTTP_STATUS_OK) { - reportError(fmt::format("Download failed: HTTP status code {}", status_code)); + reportError(fmt::format("Download failed: {}", error.GetDescription())); download_result = false; return; } diff --git a/src/duckstation-qt/autoupdaterdialog.h b/src/duckstation-qt/autoupdaterdialog.h index 713f0e84e4..f8d83a8a20 100644 --- a/src/duckstation-qt/autoupdaterdialog.h +++ b/src/duckstation-qt/autoupdaterdialog.h @@ -58,11 +58,11 @@ private Q_SLOTS: bool updateNeeded() const; std::string getCurrentUpdateTag() const; - void getLatestTagComplete(s32 status_code, std::vector response); - void getLatestReleaseComplete(s32 status_code, std::vector response); + void getLatestTagComplete(s32 status_code, const Error& error, std::vector response); + void getLatestReleaseComplete(s32 status_code, const Error& error, std::vector response); void queueGetChanges(); - void getChangesComplete(s32 status_code, std::vector response); + void getChangesComplete(s32 status_code, const Error& error, std::vector response); bool processUpdate(const std::vector& update_data); diff --git a/src/duckstation-qt/qthost.cpp b/src/duckstation-qt/qthost.cpp index 50c05715cd..9946875a24 100644 --- a/src/duckstation-qt/qthost.cpp +++ b/src/duckstation-qt/qthost.cpp @@ -261,11 +261,13 @@ std::optional QtHost::DownloadFile(QWidget* parent, const QString& title, { static constexpr u32 HTTP_POLL_INTERVAL = 10; - std::unique_ptr http = HTTPDownloader::Create(Host::GetHTTPUserAgent()); + Error error; + std::unique_ptr http = HTTPDownloader::Create(Host::GetHTTPUserAgent(), &error); if (!http) { QMessageBox::critical(parent, qApp->translate("QtHost", "Error"), - qApp->translate("QtHost", "Failed to create HTTPDownloader.")); + qApp->translate("QtHost", "Failed to create HTTPDownloader:\n%1") + .arg(QString::fromStdString(error.GetDescription()))); return false; } @@ -281,14 +283,16 @@ std::optional QtHost::DownloadFile(QWidget* parent, const QString& title, http->CreateRequest( std::move(url), - [parent, data, &download_result](s32 status_code, const std::string&, std::vector hdata) { + [parent, data, &download_result](s32 status_code, const Error& error, const std::string&, std::vector hdata) { if (status_code == HTTPDownloader::HTTP_STATUS_CANCELLED) return; if (status_code != HTTPDownloader::HTTP_STATUS_OK) { QMessageBox::critical(parent, qApp->translate("QtHost", "Error"), - qApp->translate("QtHost", "Download failed with HTTP status code %1.").arg(status_code)); + qApp->translate("QtHost", "Download failed with HTTP status code %1:\n%2") + .arg(status_code) + .arg(QString::fromStdString(error.GetDescription()))); download_result = false; return; } @@ -1913,11 +1917,13 @@ std::string Host::GetClipboardText() { // Hope this doesn't deadlock... std::string ret; - QtHost::RunOnUIThread([&ret]() { - QClipboard* clipboard = QGuiApplication::clipboard(); - if (clipboard) - ret = clipboard->text().toStdString(); - }, true); + QtHost::RunOnUIThread( + [&ret]() { + QClipboard* clipboard = QGuiApplication::clipboard(); + if (clipboard) + ret = clipboard->text().toStdString(); + }, + true); return ret; } diff --git a/src/util/CMakeLists.txt b/src/util/CMakeLists.txt index f1c6e91ec6..68fa264fab 100644 --- a/src/util/CMakeLists.txt +++ b/src/util/CMakeLists.txt @@ -224,7 +224,6 @@ if(WIN32) dinput_source.cpp dinput_source.h http_downloader_winhttp.cpp - http_downloader_winhttp.h platform_misc_win32.cpp win32_raw_input_source.cpp win32_raw_input_source.h @@ -275,7 +274,6 @@ endif() if(NOT WIN32 AND NOT ANDROID) target_sources(util PRIVATE http_downloader_curl.cpp - http_downloader_curl.h ) target_link_libraries(util PRIVATE CURL::libcurl diff --git a/src/util/http_downloader.cpp b/src/util/http_downloader.cpp index abb34a9d21..11ef826b9b 100644 --- a/src/util/http_downloader.cpp +++ b/src/util/http_downloader.cpp @@ -109,7 +109,8 @@ void HTTPDownloader::LockedPollRequests(std::unique_lock& lock) m_pending_http_requests.erase(m_pending_http_requests.begin() + index); lock.unlock(); - req->callback(HTTP_STATUS_TIMEOUT, std::string(), Request::Data()); + req->error.SetStringFmt("Request timed out after {} seconds.", m_timeout); + req->callback(HTTP_STATUS_TIMEOUT, req->error, std::string(), Request::Data()); CloseRequest(req); @@ -126,7 +127,8 @@ void HTTPDownloader::LockedPollRequests(std::unique_lock& lock) m_pending_http_requests.erase(m_pending_http_requests.begin() + index); lock.unlock(); - req->callback(HTTP_STATUS_CANCELLED, std::string(), Request::Data()); + req->error.SetStringView("Request was cancelled."); + req->callback(HTTP_STATUS_CANCELLED, req->error, std::string(), Request::Data()); CloseRequest(req); @@ -159,7 +161,9 @@ void HTTPDownloader::LockedPollRequests(std::unique_lock& lock) // run callback with lock unheld lock.unlock(); - req->callback(req->status_code, req->content_type, std::move(req->data)); + if (req->status_code != HTTP_STATUS_OK) + req->error.SetStringFmt("Request failed with HTTP status code {}", req->status_code); + req->callback(req->status_code, req->error, req->content_type, std::move(req->data)); CloseRequest(req); lock.lock(); } diff --git a/src/util/http_downloader.h b/src/util/http_downloader.h index 75149d1b88..4fa2b95f3a 100644 --- a/src/util/http_downloader.h +++ b/src/util/http_downloader.h @@ -3,6 +3,7 @@ #pragma once +#include "common/error.h" #include "common/types.h" #include @@ -29,7 +30,8 @@ class HTTPDownloader struct Request { using Data = std::vector; - using Callback = std::function; + using Callback = + std::function; enum class Type { @@ -53,6 +55,7 @@ class HTTPDownloader std::string post_data; std::string content_type; Data data; + Error error; u64 start_time; s32 status_code = 0; u32 content_length = 0; @@ -64,7 +67,7 @@ class HTTPDownloader HTTPDownloader(); virtual ~HTTPDownloader(); - static std::unique_ptr Create(std::string user_agent = DEFAULT_USER_AGENT); + static std::unique_ptr Create(std::string user_agent = DEFAULT_USER_AGENT, Error* error = nullptr); static std::string GetExtensionForContentType(const std::string& content_type); void SetTimeout(float timeout); diff --git a/src/util/http_downloader_curl.cpp b/src/util/http_downloader_curl.cpp index eca5429d7a..cece671035 100644 --- a/src/util/http_downloader_curl.cpp +++ b/src/util/http_downloader_curl.cpp @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin // SPDX-License-Identifier: CC-BY-NC-ND-4.0 -#include "http_downloader_curl.h" +#include "http_downloader.h" #include "common/assert.h" #include "common/log.h" @@ -9,12 +9,41 @@ #include "common/timer.h" #include +#include #include #include #include LOG_CHANNEL(HTTPDownloader); +namespace { +class HTTPDownloaderCurl final : public HTTPDownloader +{ +public: + HTTPDownloaderCurl(); + ~HTTPDownloaderCurl() override; + + bool Initialize(std::string user_agent, Error* error); + +protected: + Request* InternalCreateRequest() override; + void InternalPollRequests() override; + bool StartRequest(HTTPDownloader::Request* request) override; + void CloseRequest(HTTPDownloader::Request* request) override; + +private: + struct Request : HTTPDownloader::Request + { + CURL* handle = nullptr; + }; + + static size_t WriteCallback(char* ptr, size_t size, size_t nmemb, void* userdata); + + CURLM* m_multi_handle = nullptr; + std::string m_user_agent; +}; +} // namespace + HTTPDownloaderCurl::HTTPDownloaderCurl() : HTTPDownloader() { } @@ -25,11 +54,11 @@ HTTPDownloaderCurl::~HTTPDownloaderCurl() curl_multi_cleanup(m_multi_handle); } -std::unique_ptr HTTPDownloader::Create(std::string user_agent) +std::unique_ptr HTTPDownloader::Create(std::string user_agent, Error* error) { - std::unique_ptr instance(std::make_unique()); - if (!instance->Initialize(std::move(user_agent))) - return {}; + std::unique_ptr instance = std::make_unique(); + if (!instance->Initialize(std::move(user_agent), error)) + instance.reset(); return instance; } @@ -37,7 +66,7 @@ std::unique_ptr HTTPDownloader::Create(std::string user_agent) static bool s_curl_initialized = false; static std::once_flag s_curl_initialized_once_flag; -bool HTTPDownloaderCurl::Initialize(std::string user_agent) +bool HTTPDownloaderCurl::Initialize(std::string user_agent, Error* error) { if (!s_curl_initialized) { @@ -53,7 +82,7 @@ bool HTTPDownloaderCurl::Initialize(std::string user_agent) }); if (!s_curl_initialized) { - ERROR_LOG("curl_global_init() failed"); + Error::SetStringView(error, "curl_global_init() failed"); return false; } } @@ -61,7 +90,7 @@ bool HTTPDownloaderCurl::Initialize(std::string user_agent) m_multi_handle = curl_multi_init(); if (!m_multi_handle) { - ERROR_LOG("curl_multi_init() failed"); + Error::SetStringView(error, "curl_multi_init() failed"); return false; } @@ -153,6 +182,7 @@ void HTTPDownloaderCurl::InternalPollRequests() else { ERROR_LOG("Request for '{}' returned error {}", req->url, static_cast(msg->data.result)); + req->error.SetStringFmt("Request failed: {}", curl_easy_strerror(msg->data.result)); } req->state.store(Request::State::Complete, std::memory_order_release); @@ -187,7 +217,8 @@ bool HTTPDownloaderCurl::StartRequest(HTTPDownloader::Request* request) if (err != CURLM_OK) { ERROR_LOG("curl_multi_add_handle() returned {}", static_cast(err)); - req->callback(HTTP_STATUS_ERROR, std::string(), req->data); + req->error.SetStringFmt("curl_multi_add_handle() failed: {}", curl_multi_strerror(err)); + req->callback(HTTP_STATUS_ERROR, req->error, std::string(), req->data); curl_easy_cleanup(req->handle); delete req; return false; diff --git a/src/util/http_downloader_curl.h b/src/util/http_downloader_curl.h deleted file mode 100644 index e687be8cd3..0000000000 --- a/src/util/http_downloader_curl.h +++ /dev/null @@ -1,36 +0,0 @@ -// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin -// SPDX-License-Identifier: CC-BY-NC-ND-4.0 - -#pragma once -#include "http_downloader.h" - -#include -#include -#include -#include - -class HTTPDownloaderCurl final : public HTTPDownloader -{ -public: - HTTPDownloaderCurl(); - ~HTTPDownloaderCurl() override; - - bool Initialize(std::string user_agent); - -protected: - Request* InternalCreateRequest() override; - void InternalPollRequests() override; - bool StartRequest(HTTPDownloader::Request* request) override; - void CloseRequest(HTTPDownloader::Request* request) override; - -private: - struct Request : HTTPDownloader::Request - { - CURL* handle = nullptr; - }; - - static size_t WriteCallback(char* ptr, size_t size, size_t nmemb, void* userdata); - - CURLM* m_multi_handle = nullptr; - std::string m_user_agent; -}; diff --git a/src/util/http_downloader_winhttp.cpp b/src/util/http_downloader_winhttp.cpp index e2518c3302..e0b7fdc58c 100644 --- a/src/util/http_downloader_winhttp.cpp +++ b/src/util/http_downloader_winhttp.cpp @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin // SPDX-License-Identifier: CC-BY-NC-ND-4.0 -#include "http_downloader_winhttp.h" +#include "http_downloader.h" #include "common/assert.h" #include "common/log.h" @@ -10,6 +10,41 @@ #include +#include "common/windows_headers.h" + +#include + +namespace { +class HTTPDownloaderWinHttp final : public HTTPDownloader +{ +public: + HTTPDownloaderWinHttp(); + ~HTTPDownloaderWinHttp() override; + + bool Initialize(std::string user_agent, Error* error); + +protected: + Request* InternalCreateRequest() override; + void InternalPollRequests() override; + bool StartRequest(HTTPDownloader::Request* request) override; + void CloseRequest(HTTPDownloader::Request* request) override; + +private: + struct Request : HTTPDownloader::Request + { + std::wstring object_name; + HINTERNET hConnection = NULL; + HINTERNET hRequest = NULL; + u32 io_position = 0; + }; + + static void CALLBACK HTTPStatusCallback(HINTERNET hInternet, DWORD_PTR dwContext, DWORD dwInternetStatus, + LPVOID lpvStatusInformation, DWORD dwStatusInformationLength); + + HINTERNET m_hSession = NULL; +}; +} // namespace + LOG_CHANNEL(HTTPDownloader); HTTPDownloaderWinHttp::HTTPDownloaderWinHttp() : HTTPDownloader() @@ -25,16 +60,16 @@ HTTPDownloaderWinHttp::~HTTPDownloaderWinHttp() } } -std::unique_ptr HTTPDownloader::Create(std::string user_agent) +std::unique_ptr HTTPDownloader::Create(std::string user_agent, Error* error) { std::unique_ptr instance(std::make_unique()); - if (!instance->Initialize(std::move(user_agent))) - return {}; + if (!instance->Initialize(std::move(user_agent), error)) + instance.reset(); return instance; } -bool HTTPDownloaderWinHttp::Initialize(std::string user_agent) +bool HTTPDownloaderWinHttp::Initialize(std::string user_agent, Error* error) { static constexpr DWORD dwAccessType = WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY; @@ -42,7 +77,7 @@ bool HTTPDownloaderWinHttp::Initialize(std::string user_agent) WINHTTP_FLAG_ASYNC); if (m_hSession == NULL) { - ERROR_LOG("WinHttpOpen() failed: {}", GetLastError()); + Error::SetWin32(error, "WinHttpOpen() failed: ", GetLastError()); return false; } @@ -51,7 +86,7 @@ bool HTTPDownloaderWinHttp::Initialize(std::string user_agent) if (WinHttpSetStatusCallback(m_hSession, HTTPStatusCallback, notification_flags, NULL) == WINHTTP_INVALID_STATUS_CALLBACK) { - ERROR_LOG("WinHttpSetStatusCallback() failed: {}", GetLastError()); + Error::SetWin32(error, "WinHttpSetStatusCallback() failed: ", GetLastError()); return false; } @@ -91,6 +126,7 @@ void CALLBACK HTTPDownloaderWinHttp::HTTPStatusCallback(HINTERNET hRequest, DWOR const WINHTTP_ASYNC_RESULT* res = reinterpret_cast(lpvStatusInformation); ERROR_LOG("WinHttp async function {} returned error {}", res->dwResult, res->dwError); req->status_code = HTTP_STATUS_ERROR; + req->error.SetStringFmt("WinHttp async function {} returned error {}", res->dwResult, res->dwError); req->state.store(Request::State::Complete); return; } @@ -99,8 +135,10 @@ void CALLBACK HTTPDownloaderWinHttp::HTTPStatusCallback(HINTERNET hRequest, DWOR DEV_LOG("SendRequest complete"); if (!WinHttpReceiveResponse(hRequest, nullptr)) { - ERROR_LOG("WinHttpReceiveResponse() failed: {}", GetLastError()); + const DWORD err = GetLastError(); + ERROR_LOG("WinHttpReceiveResponse() failed: {}", err); req->status_code = HTTP_STATUS_ERROR; + req->error.SetWin32("WinHttpReceiveResponse() failed: ", err); req->state.store(Request::State::Complete); } @@ -114,8 +152,10 @@ void CALLBACK HTTPDownloaderWinHttp::HTTPStatusCallback(HINTERNET hRequest, DWOR if (!WinHttpQueryHeaders(hRequest, WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER, WINHTTP_HEADER_NAME_BY_INDEX, &req->status_code, &buffer_size, WINHTTP_NO_HEADER_INDEX)) { - ERROR_LOG("WinHttpQueryHeaders() for status code failed: {}", GetLastError()); + const DWORD err = GetLastError(); + ERROR_LOG("WinHttpQueryHeaders() for status code failed: {}", err); req->status_code = HTTP_STATUS_ERROR; + req->error.SetWin32("WinHttpQueryHeaders() failed: ", err); req->state.store(Request::State::Complete); return; } @@ -125,8 +165,9 @@ void CALLBACK HTTPDownloaderWinHttp::HTTPStatusCallback(HINTERNET hRequest, DWOR WINHTTP_HEADER_NAME_BY_INDEX, &req->content_length, &buffer_size, WINHTTP_NO_HEADER_INDEX)) { - if (GetLastError() != ERROR_WINHTTP_HEADER_NOT_FOUND) - WARNING_LOG("WinHttpQueryHeaders() for content length failed: {}", GetLastError()); + const DWORD err = GetLastError(); + if (err != ERROR_WINHTTP_HEADER_NOT_FOUND) + WARNING_LOG("WinHttpQueryHeaders() for content length failed: {}", err); req->content_length = 0; } @@ -152,8 +193,10 @@ void CALLBACK HTTPDownloaderWinHttp::HTTPStatusCallback(HINTERNET hRequest, DWOR // start reading if (!WinHttpQueryDataAvailable(hRequest, nullptr) && GetLastError() != ERROR_IO_PENDING) { - ERROR_LOG("WinHttpQueryDataAvailable() failed: {}", GetLastError()); + const DWORD err = GetLastError(); + ERROR_LOG("WinHttpQueryDataAvailable() failed: {}", err); req->status_code = HTTP_STATUS_ERROR; + req->error.SetWin32("WinHttpQueryDataAvailable() failed: ", err); req->state.store(Request::State::Complete); } @@ -178,8 +221,10 @@ void CALLBACK HTTPDownloaderWinHttp::HTTPStatusCallback(HINTERNET hRequest, DWOR if (!WinHttpReadData(hRequest, req->data.data() + req->io_position, bytes_available, nullptr) && GetLastError() != ERROR_IO_PENDING) { - ERROR_LOG("WinHttpReadData() failed: {}", GetLastError()); + const DWORD err = GetLastError(); + ERROR_LOG("WinHttpReadData() failed: {}", err); req->status_code = HTTP_STATUS_ERROR; + req->error.SetWin32("WinHttpReadData() failed: ", err); req->state.store(Request::State::Complete); } @@ -196,8 +241,10 @@ void CALLBACK HTTPDownloaderWinHttp::HTTPStatusCallback(HINTERNET hRequest, DWOR if (!WinHttpQueryDataAvailable(hRequest, nullptr) && GetLastError() != ERROR_IO_PENDING) { - ERROR_LOG("WinHttpQueryDataAvailable() failed: {}", GetLastError()); + const DWORD err = GetLastError(); + ERROR_LOG("WinHttpQueryDataAvailable() failed: {}", err); req->status_code = HTTP_STATUS_ERROR; + req->error.SetWin32("WinHttpQueryDataAvailable() failed: ", err); req->state.store(Request::State::Complete); } @@ -238,8 +285,10 @@ bool HTTPDownloaderWinHttp::StartRequest(HTTPDownloader::Request* request) const std::wstring url_wide(StringUtil::UTF8StringToWideString(req->url)); if (!WinHttpCrackUrl(url_wide.c_str(), static_cast(url_wide.size()), 0, &uc)) { - ERROR_LOG("WinHttpCrackUrl() failed: {}", GetLastError()); - req->callback(HTTP_STATUS_ERROR, std::string(), req->data); + const DWORD err = GetLastError(); + ERROR_LOG("WinHttpCrackUrl() failed: {}", err); + req->error.SetWin32("WinHttpCrackUrl() failed: ", err); + req->callback(HTTP_STATUS_ERROR, req->error, std::string(), req->data); delete req; return false; } @@ -250,8 +299,10 @@ bool HTTPDownloaderWinHttp::StartRequest(HTTPDownloader::Request* request) req->hConnection = WinHttpConnect(m_hSession, host_name.c_str(), uc.nPort, 0); if (!req->hConnection) { - ERROR_LOG("Failed to start HTTP request for '{}': {}", req->url, GetLastError()); - req->callback(HTTP_STATUS_ERROR, std::string(), req->data); + const DWORD err = GetLastError(); + ERROR_LOG("Failed to start HTTP request for '{}': {}", req->url, err); + req->error.SetWin32("WinHttpConnect() failed: ", err); + req->callback(HTTP_STATUS_ERROR, req->error, std::string(), req->data); delete req; return false; } @@ -262,7 +313,9 @@ bool HTTPDownloaderWinHttp::StartRequest(HTTPDownloader::Request* request) req->object_name.c_str(), NULL, NULL, NULL, request_flags); if (!req->hRequest) { - ERROR_LOG("WinHttpOpenRequest() failed: {}", GetLastError()); + const DWORD err = GetLastError(); + ERROR_LOG("WinHttpOpenRequest() failed: {}", err); + req->error.SetWin32("WinHttpSendRequest() failed: ", err); WinHttpCloseHandle(req->hConnection); return false; } @@ -283,8 +336,10 @@ bool HTTPDownloaderWinHttp::StartRequest(HTTPDownloader::Request* request) if (!result && GetLastError() != ERROR_IO_PENDING) { - ERROR_LOG("WinHttpSendRequest() failed: {}", GetLastError()); + const DWORD err = GetLastError(); + ERROR_LOG("WinHttpSendRequest() failed: {}", err); req->status_code = HTTP_STATUS_ERROR; + req->error.SetWin32("WinHttpSendRequest() failed: ", err); req->state.store(Request::State::Complete); } diff --git a/src/util/http_downloader_winhttp.h b/src/util/http_downloader_winhttp.h deleted file mode 100644 index 8540020fac..0000000000 --- a/src/util/http_downloader_winhttp.h +++ /dev/null @@ -1,38 +0,0 @@ -// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin -// SPDX-License-Identifier: CC-BY-NC-ND-4.0 - -#pragma once -#include "http_downloader.h" - -#include "common/windows_headers.h" - -#include - -class HTTPDownloaderWinHttp final : public HTTPDownloader -{ -public: - HTTPDownloaderWinHttp(); - ~HTTPDownloaderWinHttp() override; - - bool Initialize(std::string user_agent); - -protected: - Request* InternalCreateRequest() override; - void InternalPollRequests() override; - bool StartRequest(HTTPDownloader::Request* request) override; - void CloseRequest(HTTPDownloader::Request* request) override; - -private: - struct Request : HTTPDownloader::Request - { - std::wstring object_name; - HINTERNET hConnection = NULL; - HINTERNET hRequest = NULL; - u32 io_position = 0; - }; - - static void CALLBACK HTTPStatusCallback(HINTERNET hInternet, DWORD_PTR dwContext, DWORD dwInternetStatus, - LPVOID lpvStatusInformation, DWORD dwStatusInformationLength); - - HINTERNET m_hSession = NULL; -}; diff --git a/src/util/util.vcxproj b/src/util/util.vcxproj index e6b414fbdd..bd53ddbeb5 100644 --- a/src/util/util.vcxproj +++ b/src/util/util.vcxproj @@ -28,10 +28,6 @@ - - true - - diff --git a/src/util/util.vcxproj.filters b/src/util/util.vcxproj.filters index 10c0f48f20..60392b279d 100644 --- a/src/util/util.vcxproj.filters +++ b/src/util/util.vcxproj.filters @@ -57,8 +57,6 @@ - - From 2e31a40dda1da007c9ddced7d93f0ed27e4b1770 Mon Sep 17 00:00:00 2001 From: Stenzek Date: Fri, 29 Nov 2024 14:06:19 +1000 Subject: [PATCH 9/9] Qt: Zero spacer size hints in Graphics Settings Fixes the window size changing depending on which tab is selected. --- src/duckstation-qt/graphicssettingswidget.ui | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/duckstation-qt/graphicssettingswidget.ui b/src/duckstation-qt/graphicssettingswidget.ui index e5e7c2f0ea..d80818d6b9 100644 --- a/src/duckstation-qt/graphicssettingswidget.ui +++ b/src/duckstation-qt/graphicssettingswidget.ui @@ -281,7 +281,7 @@ 20 - 75 + 0 @@ -446,7 +446,7 @@ 20 - 40 + 0 @@ -583,7 +583,7 @@ 20 - 215 + 0 @@ -749,7 +749,7 @@ 20 - 164 + 0 @@ -1062,7 +1062,7 @@ 20 - 295 + 0 @@ -1246,7 +1246,7 @@ 20 - 40 + 0 @@ -1400,7 +1400,7 @@ 20 - 244 + 0